BREAKING CHANGE(core): Implement custom XmlBuilder, remove xmlbuilder2, upgrade fast-xml-parser, update SmartXml API, tests and CI
This commit is contained in:
@@ -6,8 +6,8 @@ on:
|
|||||||
- '**'
|
- '**'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
- name: Install pnpm and npmci
|
- name: Install pnpm and npmci
|
||||||
run: |
|
run: |
|
||||||
pnpm install -g pnpm
|
pnpm install -g pnpm
|
||||||
pnpm install -g @shipzone/npmci
|
pnpm install -g @ship.zone/npmci
|
||||||
|
|
||||||
- name: Run npm prepare
|
- name: Run npm prepare
|
||||||
run: npmci npm prepare
|
run: npmci npm prepare
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ on:
|
|||||||
- '*'
|
- '*'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
pnpm install -g pnpm
|
pnpm install -g pnpm
|
||||||
pnpm install -g @shipzone/npmci
|
pnpm install -g @ship.zone/npmci
|
||||||
npmci npm prepare
|
npmci npm prepare
|
||||||
|
|
||||||
- name: Audit production dependencies
|
- name: Audit production dependencies
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
pnpm install -g pnpm
|
pnpm install -g pnpm
|
||||||
pnpm install -g @shipzone/npmci
|
pnpm install -g @ship.zone/npmci
|
||||||
npmci npm prepare
|
npmci npm prepare
|
||||||
|
|
||||||
- name: Test stable
|
- name: Test stable
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
pnpm install -g pnpm
|
pnpm install -g pnpm
|
||||||
pnpm install -g @shipzone/npmci
|
pnpm install -g @ship.zone/npmci
|
||||||
npmci npm prepare
|
npmci npm prepare
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
@@ -104,7 +104,7 @@ jobs:
|
|||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
pnpm install -g pnpm
|
pnpm install -g pnpm
|
||||||
pnpm install -g @shipzone/npmci
|
pnpm install -g @ship.zone/npmci
|
||||||
npmci npm prepare
|
npmci npm prepare
|
||||||
|
|
||||||
- name: Code quality
|
- name: Code quality
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -3,7 +3,6 @@
|
|||||||
# artifacts
|
# artifacts
|
||||||
coverage/
|
coverage/
|
||||||
public/
|
public/
|
||||||
pages/
|
|
||||||
|
|
||||||
# installs
|
# installs
|
||||||
node_modules/
|
node_modules/
|
||||||
@@ -17,4 +16,8 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
dist_*/
|
dist_*/
|
||||||
|
|
||||||
# custom
|
# AI
|
||||||
|
.claude/
|
||||||
|
.serena/
|
||||||
|
|
||||||
|
#------# custom
|
||||||
33
changelog.md
33
changelog.md
@@ -1,12 +1,42 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-11-19 - 2.0.0 - BREAKING CHANGE(core)
|
||||||
|
Implement custom XmlBuilder, remove xmlbuilder2, upgrade fast-xml-parser, update SmartXml API, tests and CI
|
||||||
|
|
||||||
|
- Add a new chainable XmlBuilder implementation (create(), ele(), txt(), att(), up(), com(), end(), toString()) and export it from the package
|
||||||
|
- Remove xmlbuilder2 dependency and its export from smartxml.plugins.ts
|
||||||
|
- Upgrade fast-xml-parser dependency to ^5.3.2 and use it for parsing and building XML
|
||||||
|
- Update SmartXml to use XmlBuilder.create() as the create factory and export XmlBuilder for direct use
|
||||||
|
- Extend and update tests to cover the new XmlBuilder chainable API and XML serialization behavior
|
||||||
|
- Update CI workflows: switch container IMAGE registry, adjust NPMCI_COMPUTED_REPOURL and change pnpm install target for npmci package name
|
||||||
|
- Bump several devDependencies, add pnpm packageManager/pnpm metadata, and apply small tsconfig/.gitignore cleanups
|
||||||
|
|
||||||
|
## 2025-11-19 - 1.2.0 - feat(core)
|
||||||
|
|
||||||
|
Implement custom XmlBuilder with chainable API and upgrade dependencies
|
||||||
|
|
||||||
|
- **BREAKING**: Removed xmlbuilder2 dependency (replaced with custom implementation)
|
||||||
|
- Upgraded fast-xml-parser from 4.5.1 to 5.3.2
|
||||||
|
- Implemented custom XmlBuilder class with fluent/chainable API
|
||||||
|
- `.ele(name, attrs)` - Add elements
|
||||||
|
- `.txt(content)` - Add text content
|
||||||
|
- `.att(name, value)` - Add attributes
|
||||||
|
- `.up()` - Navigate to parent
|
||||||
|
- `.end(options)` - Serialize to XML
|
||||||
|
- Added comprehensive tests for XmlBuilder functionality
|
||||||
|
- Maintains API compatibility: `smartxml.create()` continues to work
|
||||||
|
- Browser-compatible implementation (no Node.js dependencies)
|
||||||
|
- Export XmlBuilder class for direct usage
|
||||||
|
|
||||||
## 2024-12-30 - 1.1.1 - fix(dependencies)
|
## 2024-12-30 - 1.1.1 - fix(dependencies)
|
||||||
|
|
||||||
Added missing xmlbuilder2 dependency and relevant exports
|
Added missing xmlbuilder2 dependency and relevant exports
|
||||||
|
|
||||||
- Added xmlbuilder2 to dependencies in package.json
|
- Added xmlbuilder2 to dependencies in package.json
|
||||||
- Export xmlbuilder2 functionality from smartxml.plugins.ts and ts/index.ts
|
- Export xmlbuilder2 functionality from smartxml.plugins.ts and ts/index.ts
|
||||||
|
|
||||||
## 2024-12-30 - 1.1.0 - feat(core)
|
## 2024-12-30 - 1.1.0 - feat(core)
|
||||||
|
|
||||||
Upgrade dependencies and enhance XML parsing and building
|
Upgrade dependencies and enhance XML parsing and building
|
||||||
|
|
||||||
- Updated fast-xml-parser dependency to version ^4.5.1
|
- Updated fast-xml-parser dependency to version ^4.5.1
|
||||||
@@ -15,6 +45,7 @@ Upgrade dependencies and enhance XML parsing and building
|
|||||||
- Improved test coverage for XML string creation and parsing
|
- Improved test coverage for XML string creation and parsing
|
||||||
|
|
||||||
## 2024-05-29 - 1.0.8 - Various Updates
|
## 2024-05-29 - 1.0.8 - Various Updates
|
||||||
|
|
||||||
Minor updates and improvements to configuration and documentation.
|
Minor updates and improvements to configuration and documentation.
|
||||||
|
|
||||||
- Updated project description
|
- Updated project description
|
||||||
@@ -22,6 +53,7 @@ Minor updates and improvements to configuration and documentation.
|
|||||||
- Updated `npmextra.json`: githost
|
- Updated `npmextra.json`: githost
|
||||||
|
|
||||||
## 2023-10-20 - 1.0.6 to 1.0.8 - Core Fixes and Updates
|
## 2023-10-20 - 1.0.6 to 1.0.8 - Core Fixes and Updates
|
||||||
|
|
||||||
Maintenance and core updates with improvements in project configuration.
|
Maintenance and core updates with improvements in project configuration.
|
||||||
|
|
||||||
- Core functionality fixes and updates
|
- Core functionality fixes and updates
|
||||||
@@ -29,6 +61,7 @@ Maintenance and core updates with improvements in project configuration.
|
|||||||
- Preparation for versioning to 1.0.8
|
- Preparation for versioning to 1.0.8
|
||||||
|
|
||||||
## 2020-10-24 - 1.0.1 to 1.0.6 - Core Fixes
|
## 2020-10-24 - 1.0.1 to 1.0.6 - Core Fixes
|
||||||
|
|
||||||
Multiple fixes and enhancements in core components.
|
Multiple fixes and enhancements in core components.
|
||||||
|
|
||||||
- Series of core updates to improve stability and performance
|
- Series of core updates to improve stability and performance
|
||||||
23
package.json
23
package.json
@@ -13,16 +13,15 @@
|
|||||||
"buildDocs": "tsdoc"
|
"buildDocs": "tsdoc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.2.0",
|
"@git.zone/tsbuild": "^3.1.0",
|
||||||
"@git.zone/tsbundle": "^2.1.0",
|
"@git.zone/tsbundle": "^2.5.2",
|
||||||
"@git.zone/tsrun": "^1.3.3",
|
"@git.zone/tsrun": "^2.0.0",
|
||||||
"@git.zone/tstest": "^1.0.90",
|
"@git.zone/tstest": "^2.8.2",
|
||||||
"@push.rocks/tapbundle": "^5.5.4",
|
"@push.rocks/tapbundle": "^6.0.3",
|
||||||
"@types/node": "^22.10.2"
|
"@types/node": "^22.10.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-xml-parser": "^4.5.1",
|
"fast-xml-parser": "^5.3.2"
|
||||||
"xmlbuilder2": "^3.1.1"
|
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 1 chrome versions"
|
"last 1 chrome versions"
|
||||||
@@ -44,9 +43,9 @@
|
|||||||
"url": "https://code.foss.global/push.rocks/smartxml.git"
|
"url": "https://code.foss.global/push.rocks/smartxml.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://gitlab.com/push.rocks/smartxml/issues"
|
"url": "https://code.foss.global/push.rocks/smartxml/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://code.foss.global/push.rocks/smartxml",
|
"homepage": "https://code.foss.global/push.rocks/smartxml#readme",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"XML",
|
"XML",
|
||||||
@@ -55,5 +54,9 @@
|
|||||||
"fast-xml-parser",
|
"fast-xml-parser",
|
||||||
"TypeScript",
|
"TypeScript",
|
||||||
"data serialization"
|
"data serialization"
|
||||||
]
|
],
|
||||||
|
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34",
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6916
pnpm-lock.yaml
generated
6916
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
21
readme.md
21
readme.md
@@ -1,4 +1,5 @@
|
|||||||
# @push.rocks/smartxml
|
# @push.rocks/smartxml
|
||||||
|
|
||||||
a package for creating and parsing XML formatted files
|
a package for creating and parsing XML formatted files
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
@@ -55,8 +56,8 @@ const noteObject = {
|
|||||||
to: 'Tove',
|
to: 'Tove',
|
||||||
from: 'Jani',
|
from: 'Jani',
|
||||||
heading: 'Reminder',
|
heading: 'Reminder',
|
||||||
body: 'Don\'t forget me this weekend!'
|
body: "Don't forget me this weekend!",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const smartXml = new SmartXml();
|
const smartXml = new SmartXml();
|
||||||
@@ -74,21 +75,21 @@ Let's look at a more complex example that includes XML attributes:
|
|||||||
```typescript
|
```typescript
|
||||||
const complexObject = {
|
const complexObject = {
|
||||||
note: {
|
note: {
|
||||||
"@_id": "12345",
|
'@_id': '12345',
|
||||||
"@_priority": "high",
|
'@_priority': 'high',
|
||||||
to: 'Tove',
|
to: 'Tove',
|
||||||
from: { "@_domain": "personal", "#text": 'Jani' },
|
from: { '@_domain': 'personal', '#text': 'Jani' },
|
||||||
heading: {
|
heading: {
|
||||||
"@_style": "bold",
|
'@_style': 'bold',
|
||||||
"#text": 'Reminder'
|
'#text': 'Reminder',
|
||||||
},
|
},
|
||||||
body: 'This is a special note for the weekend.'
|
body: 'This is a special note for the weekend.',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const smartXml = new SmartXml();
|
const smartXml = new SmartXml();
|
||||||
const complexXmlString = smartXml.createXmlFromObject(complexObject, {
|
const complexXmlString = smartXml.createXmlFromObject(complexObject, {
|
||||||
format: true
|
format: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(complexXmlString);
|
console.log(complexXmlString);
|
||||||
|
|||||||
69
test/test.ts
69
test/test.ts
@@ -11,11 +11,11 @@ tap.test('should create an instance', async () => {
|
|||||||
tap.test('should create an xml string', async () => {
|
tap.test('should create an xml string', async () => {
|
||||||
const xmlResult = testSmartxml.createXmlFromObject({
|
const xmlResult = testSmartxml.createXmlFromObject({
|
||||||
hello: {
|
hello: {
|
||||||
"@_xlmns:teststring": "hellothere",
|
'@_xlmns:teststring': 'hellothere',
|
||||||
"@_xlmns:testnumber": 10,
|
'@_xlmns:testnumber': 10,
|
||||||
wow: 'test',
|
wow: 'test',
|
||||||
url: [{loc: 3},{loc: 3}]
|
url: [{ loc: 3 }, { loc: 3 }],
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
console.log(xmlResult);
|
console.log(xmlResult);
|
||||||
testXml = xmlResult;
|
testXml = xmlResult;
|
||||||
@@ -25,8 +25,65 @@ tap.test('should parse an yml file', async () => {
|
|||||||
const jsObject = testSmartxml.parseXmlToObject(testXml);
|
const jsObject = testSmartxml.parseXmlToObject(testXml);
|
||||||
// console.log(JSON.stringify(jsObject, null, 2));
|
// console.log(JSON.stringify(jsObject, null, 2));
|
||||||
expect(typeof jsObject).toEqual('object');
|
expect(typeof jsObject).toEqual('object');
|
||||||
expect(jsObject).arrayItem(1).property('hello').arrayItem(0).property('wow').arrayItem(0).property('#text').toEqual('test');
|
expect(jsObject)
|
||||||
|
.arrayItem(1)
|
||||||
|
.property('hello')
|
||||||
|
.arrayItem(0)
|
||||||
|
.property('wow')
|
||||||
|
.arrayItem(0)
|
||||||
|
.property('#text')
|
||||||
|
.toEqual('test');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Test XmlBuilder chainable API
|
||||||
|
tap.test('should create XML using chainable API', async () => {
|
||||||
|
const xml = testSmartxml.create()
|
||||||
|
.ele('root', { att: 'val' })
|
||||||
|
.ele('foo')
|
||||||
|
.ele('bar').txt('foobar').up()
|
||||||
|
.up()
|
||||||
|
.ele('baz').up()
|
||||||
|
.up()
|
||||||
|
.end({ prettyPrint: true });
|
||||||
|
|
||||||
tap.start();
|
console.log('Chainable XML:', xml);
|
||||||
|
expect(xml).toContain('<root');
|
||||||
|
expect(xml).toContain('att="val"');
|
||||||
|
expect(xml).toContain('<foo>');
|
||||||
|
expect(xml).toContain('<bar>foobar</bar>');
|
||||||
|
expect(xml).toContain('<baz');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should create XML from object using XmlBuilder', async () => {
|
||||||
|
const obj = {
|
||||||
|
root: {
|
||||||
|
'@_att': 'val',
|
||||||
|
foo: {
|
||||||
|
bar: 'foobar'
|
||||||
|
},
|
||||||
|
baz: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const xml = testSmartxml.create(obj).end({ prettyPrint: true });
|
||||||
|
console.log('Object-based XML:', xml);
|
||||||
|
expect(xml).toContain('<root');
|
||||||
|
expect(xml).toContain('att="val"');
|
||||||
|
expect(xml).toContain('<bar>foobar</bar>');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should add attributes using att() method', async () => {
|
||||||
|
const xml = testSmartxml.create()
|
||||||
|
.ele('data')
|
||||||
|
.att('x', 1)
|
||||||
|
.att('y', 2)
|
||||||
|
.txt('value')
|
||||||
|
.up()
|
||||||
|
.end();
|
||||||
|
|
||||||
|
expect(xml).toContain('x="1"');
|
||||||
|
expect(xml).toContain('y="2"');
|
||||||
|
expect(xml).toContain('>value</data>');
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartxml',
|
name: '@push.rocks/smartxml',
|
||||||
version: '1.1.1',
|
version: '2.0.0',
|
||||||
description: 'A package for creating and parsing XML formatted files.'
|
description: 'A package for creating and parsing XML formatted files.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import * as plugins from './smartxml.plugins.js';
|
import * as plugins from './smartxml.plugins.js';
|
||||||
|
import { XmlBuilder } from './smartxml.xmlbuilder.js';
|
||||||
|
|
||||||
export class SmartXml {
|
export class SmartXml {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
public create = plugins.xmlbuilder2.create;
|
public create = XmlBuilder.create;
|
||||||
|
|
||||||
public parseXmlToObject<T = any>(xmlStringArg: string): T {
|
public parseXmlToObject<T = any>(xmlStringArg: string): T {
|
||||||
const parser = new plugins.fastXmlParser.XMLParser({
|
const parser = new plugins.fastXmlParser.XMLParser({
|
||||||
@@ -22,9 +23,11 @@ export class SmartXml {
|
|||||||
ignoreAttributes: false,
|
ignoreAttributes: false,
|
||||||
attributeNamePrefix: '@_',
|
attributeNamePrefix: '@_',
|
||||||
format: true,
|
format: true,
|
||||||
indentBy: ' '
|
indentBy: ' ',
|
||||||
});
|
});
|
||||||
const xml = builder.build(jsObject);
|
const xml = builder.build(jsObject);
|
||||||
return '<?xml version="1.0" encoding="UTF-8"?>\n' + xml;
|
return '<?xml version="1.0" encoding="UTF-8"?>\n' + xml;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { XmlBuilder };
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
import * as fastXmlParser from 'fast-xml-parser';
|
import * as fastXmlParser from 'fast-xml-parser';
|
||||||
import * as xmlbuilder2 from 'xmlbuilder2';
|
|
||||||
|
|
||||||
export {
|
export { fastXmlParser };
|
||||||
fastXmlParser,
|
|
||||||
xmlbuilder2,
|
|
||||||
};
|
|
||||||
|
|||||||
180
ts/smartxml.xmlbuilder.ts
Normal file
180
ts/smartxml.xmlbuilder.ts
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import * as plugins from './smartxml.plugins.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lightweight chainable XML builder that provides a fluent API
|
||||||
|
* for programmatically constructing XML documents.
|
||||||
|
*/
|
||||||
|
export class XmlBuilder {
|
||||||
|
private stack: any[] = [];
|
||||||
|
private current: any = null;
|
||||||
|
private rootElement: any = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new XML builder instance
|
||||||
|
* @param input Optional: object to convert to XML, or XML string to parse
|
||||||
|
*/
|
||||||
|
constructor(input?: any) {
|
||||||
|
if (input) {
|
||||||
|
if (typeof input === 'string') {
|
||||||
|
// Parse XML string using fast-xml-parser
|
||||||
|
const parser = new plugins.fastXmlParser.XMLParser({
|
||||||
|
preserveOrder: true,
|
||||||
|
ignoreAttributes: false,
|
||||||
|
});
|
||||||
|
this.rootElement = parser.parse(input);
|
||||||
|
this.current = this.rootElement;
|
||||||
|
} else if (typeof input === 'object') {
|
||||||
|
// Accept object input
|
||||||
|
this.rootElement = input;
|
||||||
|
this.current = input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static factory method for creating instances
|
||||||
|
*/
|
||||||
|
static create(input?: any): XmlBuilder {
|
||||||
|
return new XmlBuilder(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an element
|
||||||
|
* @param name Element name
|
||||||
|
* @param attributes Optional attributes object
|
||||||
|
*/
|
||||||
|
public ele(name: string, attributes?: Record<string, any>): this {
|
||||||
|
if (!this.current) {
|
||||||
|
// First element becomes root
|
||||||
|
this.rootElement = {};
|
||||||
|
this.current = this.rootElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newElement: any = {};
|
||||||
|
|
||||||
|
// Add attributes with @_ prefix (fast-xml-parser format)
|
||||||
|
if (attributes) {
|
||||||
|
for (const [key, value] of Object.entries(attributes)) {
|
||||||
|
newElement[`@_${key}`] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this property already exists
|
||||||
|
if (this.current[name]) {
|
||||||
|
// Convert to array if not already
|
||||||
|
if (!Array.isArray(this.current[name])) {
|
||||||
|
this.current[name] = [this.current[name]];
|
||||||
|
}
|
||||||
|
this.current[name].push(newElement);
|
||||||
|
this.stack.push(this.current);
|
||||||
|
this.current = newElement;
|
||||||
|
} else {
|
||||||
|
this.current[name] = newElement;
|
||||||
|
this.stack.push(this.current);
|
||||||
|
this.current = newElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add text content to current element
|
||||||
|
* @param content Text content
|
||||||
|
*/
|
||||||
|
public txt(content: string | number): this {
|
||||||
|
if (this.current) {
|
||||||
|
// Check if current element already has properties
|
||||||
|
const hasProperties = Object.keys(this.current).length > 0;
|
||||||
|
if (hasProperties) {
|
||||||
|
// Use #text format for mixed content
|
||||||
|
this.current['#text'] = String(content);
|
||||||
|
} else {
|
||||||
|
// For simple text-only elements, we can use direct assignment
|
||||||
|
// But to maintain consistency, we'll use #text
|
||||||
|
this.current['#text'] = String(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add attribute to current element
|
||||||
|
* @param name Attribute name
|
||||||
|
* @param value Attribute value
|
||||||
|
*/
|
||||||
|
public att(name: string, value: any): this {
|
||||||
|
if (this.current) {
|
||||||
|
this.current[`@_${name}`] = value;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move up to parent element
|
||||||
|
*/
|
||||||
|
public up(): this {
|
||||||
|
if (this.stack.length > 0) {
|
||||||
|
this.current = this.stack.pop();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the root element (for navigation)
|
||||||
|
*/
|
||||||
|
public root(): this {
|
||||||
|
if (this.stack.length > 0) {
|
||||||
|
this.current = this.stack[0];
|
||||||
|
this.stack = [];
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a comment
|
||||||
|
* @param content Comment text
|
||||||
|
*/
|
||||||
|
public com(content: string): this {
|
||||||
|
// Comments in fast-xml-parser format
|
||||||
|
if (this.current) {
|
||||||
|
if (!this.current['#comment']) {
|
||||||
|
this.current['#comment'] = [];
|
||||||
|
}
|
||||||
|
if (!Array.isArray(this.current['#comment'])) {
|
||||||
|
this.current['#comment'] = [this.current['#comment']];
|
||||||
|
}
|
||||||
|
this.current['#comment'].push(content);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize the XML document
|
||||||
|
* @param options Serialization options
|
||||||
|
*/
|
||||||
|
public end(options?: { prettyPrint?: boolean; format?: string }): string | any {
|
||||||
|
const opts = options || {};
|
||||||
|
|
||||||
|
if (opts.format === 'object') {
|
||||||
|
return this.rootElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use fast-xml-parser to build XML
|
||||||
|
const builder = new plugins.fastXmlParser.XMLBuilder({
|
||||||
|
ignoreAttributes: false,
|
||||||
|
attributeNamePrefix: '@_',
|
||||||
|
format: opts.prettyPrint !== false,
|
||||||
|
indentBy: ' ',
|
||||||
|
});
|
||||||
|
|
||||||
|
const xml = builder.build(this.rootElement || {});
|
||||||
|
return '<?xml version="1.0" encoding="UTF-8"?>\n' + xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert to string (alias for end)
|
||||||
|
*/
|
||||||
|
public toString(): string {
|
||||||
|
return this.end() as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,9 +6,9 @@
|
|||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"verbatimModuleSyntax": true
|
"verbatimModuleSyntax": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {}
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": ["dist_*/**/*.d.ts"]
|
||||||
"dist_*/**/*.d.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user