Revert "added fast-xml-parser for testing"
This reverts commit 15ac52b6a0.
This commit is contained in:
@@ -10,6 +10,4 @@ RUN if [ ! -d "node_modules" ]; then npm i; fi && \
|
|||||||
# Use Nginx to serve
|
# Use Nginx to serve
|
||||||
FROM nginx:1.23-alpine
|
FROM nginx:1.23-alpine
|
||||||
|
|
||||||
RUN npm i fast-xml-parser
|
|
||||||
|
|
||||||
COPY --from=build /app/docs /usr/share/nginx/html
|
COPY --from=build /app/docs /usr/share/nginx/html
|
||||||
|
|||||||
54
src/xml.ts
54
src/xml.ts
@@ -3,11 +3,6 @@
|
|||||||
* @param {string} xml - The XML string to parse
|
* @param {string} xml - The XML string to parse
|
||||||
* @returns {Object} An object with `tag`, `attributes`, and `children` properties
|
* @returns {Object} An object with `tag`, `attributes`, and `children` properties
|
||||||
*/
|
*/
|
||||||
/**
|
|
||||||
* Parses an XML string into a structured JavaScript object (fast-xml-parser style).
|
|
||||||
* @param {string} xml - The XML string to parse
|
|
||||||
* @returns {Object} An object matching fast-xml-parser output format
|
|
||||||
*/
|
|
||||||
export function fromXml(xml: string) {
|
export function fromXml(xml: string) {
|
||||||
xml = xml.trim();
|
xml = xml.trim();
|
||||||
let pos = 0;
|
let pos = 0;
|
||||||
@@ -33,7 +28,7 @@ export function fromXml(xml: string) {
|
|||||||
|
|
||||||
if(xml[pos] === '/' && xml[pos + 1] === '>') {
|
if(xml[pos] === '/' && xml[pos + 1] === '>') {
|
||||||
pos += 2; // skip />
|
pos += 2; // skip />
|
||||||
return { [tagName]: Object.keys(attributes).length > 0 ? { '@_': attributes } : '' };
|
return { tag: tagName, attributes, children: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
pos++; // skip >
|
pos++; // skip >
|
||||||
@@ -50,54 +45,17 @@ export function fromXml(xml: string) {
|
|||||||
const child = parseNode();
|
const child = parseNode();
|
||||||
if(child) children.push(child);
|
if(child) children.push(child);
|
||||||
}
|
}
|
||||||
|
return { tag: tagName, attributes, children };
|
||||||
// Build fast-xml-parser style output
|
|
||||||
const result: any = {};
|
|
||||||
|
|
||||||
// Add attributes with @_ prefix
|
|
||||||
if(Object.keys(attributes).length > 0) {
|
|
||||||
for(const [key, value] of Object.entries(attributes)) {
|
|
||||||
result[`@_${key}`] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process children
|
|
||||||
if(children.length === 1 && typeof children[0] === 'string') {
|
|
||||||
// Single text node
|
|
||||||
if(Object.keys(attributes).length > 0) {
|
|
||||||
result['#text'] = children[0];
|
|
||||||
return { [tagName]: result };
|
|
||||||
}
|
|
||||||
return { [tagName]: children[0] };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group children by tag name
|
|
||||||
for(const child of children) {
|
|
||||||
if(typeof child === 'string') {
|
|
||||||
result['#text'] = child;
|
|
||||||
} else {
|
|
||||||
for(const [childTag, childValue] of Object.entries(child)) {
|
|
||||||
if(result[childTag]) {
|
|
||||||
if(!Array.isArray(result[childTag])) {
|
|
||||||
result[childTag] = [result[childTag]];
|
|
||||||
}
|
|
||||||
result[childTag].push(childValue);
|
|
||||||
} else {
|
|
||||||
result[childTag] = childValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { [tagName]: Object.keys(result).length > 0 ? result : '' };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Parses and returns the tag name at the current position */
|
||||||
function parseTagName() {
|
function parseTagName() {
|
||||||
let name = '';
|
let name = '';
|
||||||
while (pos < xml.length && /[a-zA-Z0-9_:-]/.test(xml[pos])) name += xml[pos++];
|
while (pos < xml.length && /[a-zA-Z0-9_:-]/.test(xml[pos])) name += xml[pos++];
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Parses and returns an object containing all attributes at the current position */
|
||||||
function parseAttributes() {
|
function parseAttributes() {
|
||||||
const attrs: any = {};
|
const attrs: any = {};
|
||||||
while (pos < xml.length) {
|
while (pos < xml.length) {
|
||||||
@@ -118,6 +76,7 @@ export function fromXml(xml: string) {
|
|||||||
return attrs;
|
return attrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Parses and returns text content, or null if empty */
|
||||||
function parseText() {
|
function parseText() {
|
||||||
let text = '';
|
let text = '';
|
||||||
while (pos < xml.length && xml[pos] !== '<') text += xml[pos++];
|
while (pos < xml.length && xml[pos] !== '<') text += xml[pos++];
|
||||||
@@ -125,16 +84,19 @@ export function fromXml(xml: string) {
|
|||||||
return text ? escapeXml(text, true) : null;
|
return text ? escapeXml(text, true) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Skips over XML declaration (<?xml ... ?>) */
|
||||||
function parseDeclaration() {
|
function parseDeclaration() {
|
||||||
while (xml[pos] !== '>') pos++;
|
while (xml[pos] !== '>') pos++;
|
||||||
pos++;
|
pos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Skips over XML comments (<!-- ... -->) */
|
||||||
function parseComment() {
|
function parseComment() {
|
||||||
while (!(xml[pos] === '-' && xml[pos + 1] === '-' && xml[pos + 2] === '>')) pos++;
|
while (!(xml[pos] === '-' && xml[pos + 1] === '-' && xml[pos + 2] === '>')) pos++;
|
||||||
pos += 3;
|
pos += 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Advances position past any whitespace characters */
|
||||||
function skipWhitespace() {
|
function skipWhitespace() {
|
||||||
while (pos < xml.length && /\s/.test(xml[pos])) pos++;
|
while (pos < xml.length && /\s/.test(xml[pos])) pos++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,20 +5,22 @@ describe('XML Parser', () => {
|
|||||||
it('should parse simple tag', () => {
|
it('should parse simple tag', () => {
|
||||||
const xml = '<root></root>';
|
const xml = '<root></root>';
|
||||||
const result = fromXml(xml);
|
const result = fromXml(xml);
|
||||||
expect(result).toEqual({ root: '' });
|
expect(result).toEqual({ tag: 'root', attributes: {}, children: [] });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse self-closing tag', () => {
|
it('should parse self-closing tag', () => {
|
||||||
const xml = '<item />';
|
const xml = '<item />';
|
||||||
const result = fromXml(xml);
|
const result = fromXml(xml);
|
||||||
expect(result).toEqual({ item: '' });
|
expect(result).toEqual({ tag: 'item', attributes: {}, children: [] });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse tag with attributes', () => {
|
it('should parse tag with attributes', () => {
|
||||||
const xml = '<user id="1" name="someone" />';
|
const xml = '<user id="1" name="someone" />';
|
||||||
const result = fromXml(xml);
|
const result = fromXml(xml);
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
user: { '@_id': '1', '@_name': 'someone' }
|
tag: 'user',
|
||||||
|
attributes: { id: '1', name: 'someone' },
|
||||||
|
children: []
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -26,7 +28,9 @@ describe('XML Parser', () => {
|
|||||||
const xml = '<email>someone@example.com</email>';
|
const xml = '<email>someone@example.com</email>';
|
||||||
const result = fromXml(xml);
|
const result = fromXml(xml);
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
email: 'someone@example.com'
|
tag: 'email',
|
||||||
|
attributes: {},
|
||||||
|
children: ['someone@example.com']
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,75 +38,54 @@ describe('XML Parser', () => {
|
|||||||
const xml = '<root><child>text</child></root>';
|
const xml = '<root><child>text</child></root>';
|
||||||
const result = fromXml(xml);
|
const result = fromXml(xml);
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
root: {
|
tag: 'root',
|
||||||
child: 'text'
|
attributes: {},
|
||||||
}
|
children: [
|
||||||
|
{ tag: 'child', attributes: {}, children: ['text'] }
|
||||||
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse multiple children', () => {
|
it('should parse multiple children', () => {
|
||||||
const xml = '<root><a /><b /><c /></root>';
|
const xml = '<root><a /><b /><c /></root>';
|
||||||
const result = fromXml(xml);
|
const result = fromXml(xml);
|
||||||
expect(result.root.a).toBe('');
|
expect(result.children.length).toBe(3);
|
||||||
expect(result.root.b).toBe('');
|
expect(result.children[0]).toEqual({ tag: 'a', attributes: {}, children: [] });
|
||||||
expect(result.root.c).toBe('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse repeated tags as arrays', () => {
|
|
||||||
const xml = '<root><item>1</item><item>2</item><item>3</item></root>';
|
|
||||||
const result = fromXml(xml);
|
|
||||||
expect(result).toEqual({
|
|
||||||
root: {
|
|
||||||
item: ['1', '2', '3']
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip XML declaration', () => {
|
it('should skip XML declaration', () => {
|
||||||
const xml = '<?xml version="1.0"?><root />';
|
const xml = '<?xml version="1.0"?><root />';
|
||||||
const result = fromXml(xml);
|
const result = fromXml(xml);
|
||||||
expect(result.root).toBe('');
|
expect(result.tag).toBe('root');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip comments', () => {
|
it('should skip comments', () => {
|
||||||
const xml = '<root><!-- comment --><child /></root>';
|
const xml = '<root><!-- comment --><child /></root>';
|
||||||
const result = fromXml(xml);
|
const result = fromXml(xml);
|
||||||
expect(result).toEqual({
|
expect(result.children.length).toBe(1);
|
||||||
root: { child: '' }
|
expect(result.children[0].tag).toBe('child');
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle escaped characters', () => {
|
it('should handle escaped characters', () => {
|
||||||
const xml = '<text><hello> & "world"</text>';
|
const xml = '<text><hello> & "world"</text>';
|
||||||
const result = fromXml(xml);
|
const result = fromXml(xml);
|
||||||
expect(result.text).toBe('<hello> & "world"');
|
expect(result.children[0]).toBe('<hello> & "world"');
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse tag with attributes and text', () => {
|
|
||||||
const xml = '<user id="1">John</user>';
|
|
||||||
const result = fromXml(xml);
|
|
||||||
expect(result).toEqual({
|
|
||||||
user: {
|
|
||||||
'@_id': '1',
|
|
||||||
'#text': 'John'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse complex nested structure', () => {
|
it('should parse complex nested structure', () => {
|
||||||
const xml = `
|
const xml = `
|
||||||
<root>
|
<root>
|
||||||
<user id="1" name="someone">
|
<user id="1" name="someone">
|
||||||
<email>someone@example.com</email>
|
<email>someone@example.com</email>
|
||||||
<active />
|
<active />
|
||||||
</user>
|
</user>
|
||||||
</root>
|
</root>
|
||||||
`;
|
`;
|
||||||
const result = fromXml(xml);
|
const result = fromXml(xml);
|
||||||
expect(result.root.user['@_id']).toBe('1');
|
expect(result.tag).toBe('root');
|
||||||
expect(result.root.user['@_name']).toBe('someone');
|
expect(result.children[0].tag).toBe('user');
|
||||||
expect(result.root.user.email).toBe('someone@example.com');
|
expect(result.children[0].attributes.name).toBe('someone');
|
||||||
expect(result.root.user.active).toBe('');
|
expect(result.children[0].children.length).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user