diff --git a/Dockerfile b/Dockerfile
index 864331d..0a82b68 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,6 +10,4 @@ RUN if [ ! -d "node_modules" ]; then npm i; fi && \
# Use Nginx to serve
FROM nginx:1.23-alpine
-RUN npm i fast-xml-parser
-
COPY --from=build /app/docs /usr/share/nginx/html
diff --git a/src/xml.ts b/src/xml.ts
index 2f3ef02..d415fdf 100644
--- a/src/xml.ts
+++ b/src/xml.ts
@@ -3,11 +3,6 @@
* @param {string} xml - The XML string to parse
* @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) {
xml = xml.trim();
let pos = 0;
@@ -33,7 +28,7 @@ export function fromXml(xml: string) {
if(xml[pos] === '/' && xml[pos + 1] === '>') {
pos += 2; // skip />
- return { [tagName]: Object.keys(attributes).length > 0 ? { '@_': attributes } : '' };
+ return { tag: tagName, attributes, children: [] };
}
pos++; // skip >
@@ -50,54 +45,17 @@ export function fromXml(xml: string) {
const child = parseNode();
if(child) children.push(child);
}
-
- // 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 : '' };
+ return { tag: tagName, attributes, children };
}
+ /** Parses and returns the tag name at the current position */
function parseTagName() {
let name = '';
while (pos < xml.length && /[a-zA-Z0-9_:-]/.test(xml[pos])) name += xml[pos++];
return name;
}
+ /** Parses and returns an object containing all attributes at the current position */
function parseAttributes() {
const attrs: any = {};
while (pos < xml.length) {
@@ -118,6 +76,7 @@ export function fromXml(xml: string) {
return attrs;
}
+ /** Parses and returns text content, or null if empty */
function parseText() {
let text = '';
while (pos < xml.length && xml[pos] !== '<') text += xml[pos++];
@@ -125,16 +84,19 @@ export function fromXml(xml: string) {
return text ? escapeXml(text, true) : null;
}
+ /** Skips over XML declaration () */
function parseDeclaration() {
while (xml[pos] !== '>') pos++;
pos++;
}
+ /** Skips over XML comments () */
function parseComment() {
while (!(xml[pos] === '-' && xml[pos + 1] === '-' && xml[pos + 2] === '>')) pos++;
pos += 3;
}
+ /** Advances position past any whitespace characters */
function skipWhitespace() {
while (pos < xml.length && /\s/.test(xml[pos])) pos++;
}
diff --git a/tests/xml.spec.ts b/tests/xml.spec.ts
index ef95851..e78e353 100644
--- a/tests/xml.spec.ts
+++ b/tests/xml.spec.ts
@@ -5,20 +5,22 @@ describe('XML Parser', () => {
it('should parse simple tag', () => {
const xml = '';
const result = fromXml(xml);
- expect(result).toEqual({ root: '' });
+ expect(result).toEqual({ tag: 'root', attributes: {}, children: [] });
});
it('should parse self-closing tag', () => {
const xml = ' ';
const result = fromXml(xml);
- expect(result).toEqual({ item: '' });
+ expect(result).toEqual({ tag: 'item', attributes: {}, children: [] });
});
it('should parse tag with attributes', () => {
const xml = '';
const result = fromXml(xml);
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 = 'someone@example.com';
const result = fromXml(xml);
expect(result).toEqual({
- email: 'someone@example.com'
+ tag: 'email',
+ attributes: {},
+ children: ['someone@example.com']
});
});
@@ -34,75 +38,54 @@ describe('XML Parser', () => {
const xml = 'text';
const result = fromXml(xml);
expect(result).toEqual({
- root: {
- child: 'text'
- }
+ tag: 'root',
+ attributes: {},
+ children: [
+ { tag: 'child', attributes: {}, children: ['text'] }
+ ]
});
});
it('should parse multiple children', () => {
const xml = '';
const result = fromXml(xml);
- expect(result.root.a).toBe('');
- expect(result.root.b).toBe('');
- expect(result.root.c).toBe('');
- });
-
- it('should parse repeated tags as arrays', () => {
- const xml = '- 1
- 2
- 3
';
- const result = fromXml(xml);
- expect(result).toEqual({
- root: {
- item: ['1', '2', '3']
- }
- });
+ expect(result.children.length).toBe(3);
+ expect(result.children[0]).toEqual({ tag: 'a', attributes: {}, children: [] });
});
it('should skip XML declaration', () => {
const xml = '';
const result = fromXml(xml);
- expect(result.root).toBe('');
+ expect(result.tag).toBe('root');
});
it('should skip comments', () => {
const xml = '';
const result = fromXml(xml);
- expect(result).toEqual({
- root: { child: '' }
- });
+ expect(result.children.length).toBe(1);
+ expect(result.children[0].tag).toBe('child');
});
it('should handle escaped characters', () => {
const xml = '<hello> & "world"';
const result = fromXml(xml);
- expect(result.text).toBe(' & "world"');
- });
-
- it('should parse tag with attributes and text', () => {
- const xml = 'John';
- const result = fromXml(xml);
- expect(result).toEqual({
- user: {
- '@_id': '1',
- '#text': 'John'
- }
- });
+ expect(result.children[0]).toBe(' & "world"');
});
it('should parse complex nested structure', () => {
const xml = `
-
-
- someone@example.com
-
-
-
- `;
+
+
+ someone@example.com
+
+
+
+ `;
const result = fromXml(xml);
- expect(result.root.user['@_id']).toBe('1');
- expect(result.root.user['@_name']).toBe('someone');
- expect(result.root.user.email).toBe('someone@example.com');
- expect(result.root.user.active).toBe('');
+ expect(result.tag).toBe('root');
+ expect(result.children[0].tag).toBe('user');
+ expect(result.children[0].attributes.name).toBe('someone');
+ expect(result.children[0].children.length).toBe(2);
});
});