diff --git a/docs/v6/1.GettingStarted.md b/docs/v6/1.GettingStarted.md index b0054f71..f9481bfd 100644 --- a/docs/v6/1.GettingStarted.md +++ b/docs/v6/1.GettingStarted.md @@ -1,18 +1,30 @@ -Example with no configuration +Example with no configuration (ESM) ```js -const XMLParser = require("fast-xml-parser/src/v6/XMLParser") +import { XMLParser } from "fast-xml-parser/v6"; + const parser = new XMLParser(); -//read xmlData your own -let result = parser.parse(xmlData, true); +// read xmlData your own +const result = parser.parse("ok"); +``` + +CommonJS (via dynamic import) + +```js +(async () => { + const { XMLParser } = await import("fast-xml-parser/v6"); + const parser = new XMLParser(); + const result = parser.parse("ok"); + console.log(result); +})(); ``` The default response of parse is built by `JsObjOutputBuilder`. FXP v6 comes with 2 more output builders. And you can set your custom output builder too to customize the output. ```js -const JsObjOutputBuilder = require("fast-xml-parser/src/v6/OutputBuilders/JsObjBuilder"); +import { XMLParser, JsObjOutputBuilder } from "fast-xml-parser/v6"; const parser = new XMLParser({ OutputBuilder: new JsObjOutputBuilder() @@ -21,4 +33,25 @@ const parser = new XMLParser({ let result = parser.parse(xmlData, true); ``` +## Types (TypeScript) + +You can import types for v6 directly from the package subpath: + +```ts +import type { V6ParserOptions, V6BuilderOptions } from 'fast-xml-parser/v6'; +import { XMLParser, JsObjOutputBuilder } from 'fast-xml-parser/v6'; + +const builderOpts: V6BuilderOptions = { + nameFor: { text: '#text' } +}; + +const parserOpts: V6ParserOptions = { + attributes: { ignore: false, booleanType: true }, + OutputBuilder: new JsObjOutputBuilder(builderOpts) +}; + +const parser = new XMLParser(parserOpts); +const result = parser.parse('1'); +``` + diff --git a/docs/v6/3.Options.md b/docs/v6/3.Options.md index 591ae694..4948f11f 100644 --- a/docs/v6/3.Options.md +++ b/docs/v6/3.Options.md @@ -86,34 +86,68 @@ Eg. By default `JsObjOutputBuilder` output builder is used with default options. +### TypeScript usage (recommended) -Example +Import types and classes from the v6 subpath for best DX: + +```ts +import type { V6ParserOptions, V6BuilderOptions } from 'fast-xml-parser/v6'; +import { XMLParser, JsObjOutputBuilder } from 'fast-xml-parser/v6'; + +const builderOpts: V6BuilderOptions = { + // onAttribute: (name, value, tagName) => ({ name, value }) +}; + +const parserOpts: V6ParserOptions = { + attributes: { ignore: false, booleanType: true }, + OutputBuilder: new JsObjOutputBuilder(builderOpts), +}; + +const parser = new XMLParser(parserOpts); +const result = parser.parse('1'); +``` + +If you only need types, prefer `import type` so they’re erased at compile time. + +### JavaScript (ESM) ```js -const XMLParser = require("fast-xml-parse/src/v6/XMLParser"); -const JsObjOutputBuilder = require("fast-xml-parse/src/v6/OutputBuilders/JsObjBuilder"); -const JsArrBuilder = require("fast-xml-parse/src/v6/OutputBuilders/JsArrBuilder"); -const JsMinArrBuilder = require("fast-xml-parse/src/v6/OutputBuilders/JsMinArrBuilder"); +import { XMLParser, JsObjOutputBuilder, JsArrOutputBuilder, JsMinArrOutputBuilder } from 'fast-xml-parser/v6'; +import fs from 'node:fs'; -const xmlData = fs.readFileSync("sample.xml").toString(); +const xmlData = fs.readFileSync('sample.xml', 'utf8'); const outputBuilderOptions = { onAttribute: (name, value, tagName) => { - console.log(name, value, tagName) + console.log(name, value, tagName); } }; const parserOptions = { attributes: { - ignore: false, - booleanType:true + ignore: false, + booleanType: true }, - OutputBuilder: new JsObjOutputBuilder(outputBuilderOptions) }; const parser = new XMLParser(parserOptions); -let result = parser.parse(xmlData); +const result = parser.parse(xmlData); -console.log(JSON.stringify(result,null,4)); +console.log(JSON.stringify(result, null, 4)); +``` + +### CommonJS (via dynamic import) + +```js +(async () => { + const { XMLParser, JsObjOutputBuilder } = await import('fast-xml-parser/v6'); + const xmlData = require('node:fs').readFileSync('sample.xml', 'utf8'); + const parser = new XMLParser({ + attributes: { ignore: false, booleanType: true }, + OutputBuilder: new JsObjOutputBuilder() + }); + const result = parser.parse(xmlData); + console.log(JSON.stringify(result, null, 4)); +})(); ``` diff --git a/docs/v6/4.OutputBuilders.md b/docs/v6/4.OutputBuilders.md index 85088c16..659e3f95 100644 --- a/docs/v6/4.OutputBuilders.md +++ b/docs/v6/4.OutputBuilders.md @@ -6,6 +6,22 @@ All 3 available output builders are ### Output builders You can use provided output builds or your own output builder. +TypeScript usage + +```ts +import type { V6BuilderOptions } from 'fast-xml-parser/v6'; +import { JsObjOutputBuilder, JsArrOutputBuilder, JsMinArrOutputBuilder } from 'fast-xml-parser/v6'; + +const opts: V6BuilderOptions = { + nameFor: { text: '#text' }, + attributes: { prefix: '@_', suffix: '' } +}; + +const objBuilder = new JsObjOutputBuilder(opts); +const arrBuilder = new JsArrOutputBuilder(opts); +const minArrBuilder = new JsMinArrOutputBuilder(opts); +``` + JsObjOutputBuilder ```js { diff --git a/package-lock.json b/package-lock.json index e980098a..d107b64d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ ], "license": "MIT", "dependencies": { - "strnum": "^2.1.0" + "strnum": "^2.1.1" }, "bin": { "fxparser": "src/cli/cli.js" @@ -6670,9 +6670,9 @@ } }, "node_modules/strnum": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.0.tgz", - "integrity": "sha512-w0S//9BqZZGw0L0Y8uLSelFGnDJgTyyNQLmSlPnVz43zPAiqu3w4t8J8sDqqANOGeZIZ/9jWuPguYcEnsoHv4A==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", "funding": [ { "type": "github", @@ -12278,9 +12278,9 @@ "dev": true }, "strnum": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.0.tgz", - "integrity": "sha512-w0S//9BqZZGw0L0Y8uLSelFGnDJgTyyNQLmSlPnVz43zPAiqu3w4t8J8sDqqANOGeZIZ/9jWuPguYcEnsoHv4A==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==" }, "supports-color": { "version": "5.5.0", diff --git a/package.json b/package.json index 5b314eaf..fe93228b 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,13 @@ "types": "./lib/fxp.d.cts", "default": "./lib/fxp.cjs" } + }, + "./v6": { + "import": "./src/v6/index.js", + "types": "./src/v6/index.d.ts" + }, + "./v6/*": { + "import": "./src/v6/*" } }, "scripts": { @@ -28,7 +35,9 @@ "lint": "eslint src/**/*.js spec/**/*.js benchmark/**/*.js", "bundle": "webpack --config webpack.cjs.config.js", "prettier": "prettier --write src/**/*.js", - "checkReadiness": "publish-please --dry-run" + "checkReadiness": "publish-please --dry-run", + "publish-please": "publish-please", + "prepublishOnly": "publish-please guard" }, "bin": { "fxparser": "./src/cli/cli.js" @@ -85,6 +94,6 @@ } ], "dependencies": { - "strnum": "^2.1.0" + "strnum": "^2.1.1" } } diff --git a/spec/typings/typings-test.ts b/spec/typings/typings-test.ts index 80f9c39a..608f8c23 100644 --- a/spec/typings/typings-test.ts +++ b/spec/typings/typings-test.ts @@ -1,12 +1,19 @@ import { - XMLParser, XMLBuilder, + XMLParser, XMLValidator, type X2jOptions, type XmlBuilderOptions, type validationOptions, } from '../../src/fxp.js'; +import { + JsObjOutputBuilder, + XMLParser as V6XMLParser, + type V6BuilderOptions, + type V6ParserOptions, +} from '../../src/v6/index.js'; + const parseOpts: X2jOptions = {}; const XML = ` @@ -44,4 +51,21 @@ const isValid = XMLValidator.validate(built, validateOpts); console.log(!!isValid); +// v6 typings smoke +const v6Opts: V6ParserOptions = { + preserveOrder: false, + attributes: { ignore: false, booleanType: true, entities: true }, +}; +const v6Parser = new V6XMLParser(v6Opts); +const v6Parsed = v6Parser.parse('1'); +console.log(!!v6Parsed); + +const v6BuilderOpts: V6BuilderOptions = { + nameFor: { text: '#text' }, +}; +const outBuilder = new JsObjOutputBuilder(v6BuilderOpts); +// ensure builder has getInstance signature +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const _builderInstance = outBuilder.getInstance(v6Opts); + diff --git a/spec/v6/test.js b/spec/v6/test.js index d1bc0848..7acd7f77 100644 --- a/spec/v6/test.js +++ b/spec/v6/test.js @@ -1,8 +1,6 @@ -import XMLParser from "../../src/v6/XMLParser"; -import JsObjOutputBuilder from "../../src/v6/OutputBuilders/JsObjBuilder"; -import JsArrBuilder from "../../src/v6/OutputBuilders/JsArrBuilder"; -import JsMinArrBuilder from "../../src/v6/OutputBuilders/JsMinArrBuilder"; -import numberParser from "../../src/v6/valueParsers/number"; +import JsObjOutputBuilder from "../../src/v6/OutputBuilders/JsObjBuilder.js"; +import numberParser from "../../src/v6/valueParsers/number.js"; +import XMLParser from "../../src/v6/XMLParser.js"; import fs from "fs"; import path from "path"; diff --git a/spec/v6_esm_entry_spec.js b/spec/v6_esm_entry_spec.js new file mode 100644 index 00000000..7a9f4a68 --- /dev/null +++ b/spec/v6_esm_entry_spec.js @@ -0,0 +1,9 @@ +import { XMLParser } from "fast-xml-parser/v6"; + +describe("ESM v6 entry", function() { + it("should import and parse", function() { + const parser = new XMLParser(); + const result = parser.parse("1"); + expect(result).toEqual({ root: 1 }); + }); +}); diff --git a/src/v6/OptionsBuilder.js b/src/v6/OptionsBuilder.js index b245166d..8d790e6c 100755 --- a/src/v6/OptionsBuilder.js +++ b/src/v6/OptionsBuilder.js @@ -1,5 +1,5 @@ -import {JsObjOutputBuilder} from './OutputBuilders/JsObjBuilder.js'; +import JsObjOutputBuilder from './OutputBuilders/JsObjBuilder.js'; export const defaultOptions = { preserveOrder: false, diff --git a/src/v6/OutputBuilders/JsArrBuilder.js b/src/v6/OutputBuilders/JsArrBuilder.js index b9c6e65d..ac05adf5 100644 --- a/src/v6/OutputBuilders/JsArrBuilder.js +++ b/src/v6/OutputBuilders/JsArrBuilder.js @@ -1,4 +1,6 @@ -import {buildOptions,registerCommonValueParsers} from './ParserOptionsBuilder.js'; +import TagPathMatcher from '../TagPathMatcher.js'; +import BaseOutputBuilder from './BaseOutputBuilder.js'; +import { buildOptions, registerCommonValueParsers } from './ParserOptionsBuilder.js'; export default class OutputBuilder{ constructor(options){ @@ -16,7 +18,6 @@ export default class OutputBuilder{ } const rootName = '!js_arr'; -import BaseOutputBuilder from './BaseOutputBuilder.js'; class JsArrBuilder extends BaseOutputBuilder{ @@ -99,5 +100,3 @@ class Node{ this[":@"] = attributes; } } - -module.exports = OutputBuilder; \ No newline at end of file diff --git a/src/v6/OutputBuilders/JsMinArrBuilder.js b/src/v6/OutputBuilders/JsMinArrBuilder.js index be96ebfe..08814e5d 100644 --- a/src/v6/OutputBuilders/JsMinArrBuilder.js +++ b/src/v6/OutputBuilders/JsMinArrBuilder.js @@ -1,4 +1,4 @@ -import {buildOptions,registerCommonValueParsers} from"./ParserOptionsBuilder"; +import { buildOptions, registerCommonValueParsers } from "./ParserOptionsBuilder.js"; export default class OutputBuilder{ constructor(options){ @@ -15,7 +15,8 @@ export default class OutputBuilder{ } } -import BaseOutputBuilder from "./BaseOutputBuilder.js"; +import TagPathMatcher from "../TagPathMatcher.js"; +import BaseOutputBuilder from "./BaseOutputBuilder.js"; const rootName = '^'; class JsMinArrBuilder extends BaseOutputBuilder{ diff --git a/src/v6/OutputBuilders/JsObjBuilder.js b/src/v6/OutputBuilders/JsObjBuilder.js index 789a42bd..cda433aa 100644 --- a/src/v6/OutputBuilders/JsObjBuilder.js +++ b/src/v6/OutputBuilders/JsObjBuilder.js @@ -1,6 +1,6 @@ -import {buildOptions,registerCommonValueParsers} from './ParserOptionsBuilder.js'; +import { buildOptions, registerCommonValueParsers } from './ParserOptionsBuilder.js'; export default class OutputBuilder{ constructor(builderOptions){ @@ -17,6 +17,7 @@ export default class OutputBuilder{ } } +import TagPathMatcher from '../TagPathMatcher.js'; import BaseOutputBuilder from './BaseOutputBuilder.js'; const rootName = '^'; diff --git a/src/v6/OutputBuilders/ParserOptionsBuilder.js b/src/v6/OutputBuilders/ParserOptionsBuilder.js index fa0e84ef..7cdcd1a2 100644 --- a/src/v6/OutputBuilders/ParserOptionsBuilder.js +++ b/src/v6/OutputBuilders/ParserOptionsBuilder.js @@ -1,7 +1,7 @@ -import trimParser from "../valueParsers/trim"; -import booleanParser from "../valueParsers/booleanParser"; -import currencyParser from "../valueParsers/currency"; -import numberParser from "../valueParsers/number"; +import booleanParser from "../valueParsers/booleanParser.js"; +import currencyParser from "../valueParsers/currency.js"; +import numberParser from "../valueParsers/number.js"; +import trimParser from "../valueParsers/trim.js"; const defaultOptions={ nameFor:{ @@ -44,24 +44,22 @@ const defaultOptions={ const withJoin = ["trim","join", /*"entities",*/"number","boolean","currency"/*, "date"*/] const withoutJoin = ["trim", /*"entities",*/"number","boolean","currency"/*, "date"*/] -export function buildOptions(options){ - //clone - const finalOptions = { ... defaultOptions}; +export function buildOptions(options = {}){ + // clone defaults + const finalOptions = { ...defaultOptions }; - //add config missed in cloning - finalOptions.tags.valueParsers.push(...withJoin) - if(!this.preserveOrder) - finalOptions.tags.valueParsers.push(...withoutJoin); + // add defaults for value parsers + finalOptions.tags.valueParsers.push(...withJoin); + if (!options.preserveOrder) finalOptions.tags.valueParsers.push(...withoutJoin); + finalOptions.attributes.valueParsers.push(...withJoin); - //add config missed in cloning - finalOptions.attributes.valueParsers.push(...withJoin) - - //override configuration - copyProperties(finalOptions,options); + // merge user options + copyProperties(finalOptions, options); return finalOptions; } function copyProperties(target, source) { + if (!source) return; for (let key in source) { if (source.hasOwnProperty(key)) { if (typeof source[key] === 'object' && !Array.isArray(source[key])) { diff --git a/src/v6/TagPathMatcher.js b/src/v6/TagPathMatcher.js index 81104476..a4b271b9 100644 --- a/src/v6/TagPathMatcher.js +++ b/src/v6/TagPathMatcher.js @@ -1,4 +1,4 @@ -import {TagPath} from './TagPath.js'; +import TagPath from './TagPath.js'; export default class TagPathMatcher{ constructor(stack,node){ diff --git a/src/v6/Xml2JsParser.js b/src/v6/Xml2JsParser.js index bd622653..a849c194 100644 --- a/src/v6/Xml2JsParser.js +++ b/src/v6/Xml2JsParser.js @@ -1,10 +1,9 @@ -import StringSource from './inputSource/StringSource.js'; +import EntitiesParser from './EntitiesParser.js'; import BufferSource from './inputSource/BufferSource.js'; -import {readTagExp,readClosingTagName} from './XmlPartReader.js'; -import {readComment, readCdata,readDocType,readPiTag} from './XmlSpecialTagsReader.js'; +import StringSource from './inputSource/StringSource.js'; import TagPath from './TagPath.js'; -import TagPathMatcher from './TagPathMatcher.js'; -import EntitiesParser from './EntitiesParser.js'; +import { readClosingTagName, readTagExp } from './XmlPartReader.js'; +import { readCdata, readComment, readDocType, readPiTag } from './XmlSpecialTagsReader.js'; //To hold the data of current tag //This is usually used to compare jpath expression against current tag @@ -59,13 +58,13 @@ export default class Xml2JsParser { if(nextChar === "!" || nextChar === "?"){ - this.source.updateBufferBoundary(); + this.source.updateBufferReadIndex(); //previously collected text should be added to current node this.addTextNode(); this.readSpecialTag(nextChar);// Read DOCTYPE, comment, CDATA, PI tag }else if(nextChar === "/"){ - this.source.updateBufferBoundary(); + this.source.updateBufferReadIndex(); this.readClosingTag(); // console.log(this.source.buffer.length, this.source.readable); // console.log(this.tagsStack.length); @@ -76,7 +75,7 @@ export default class Xml2JsParser { this.tagTextData += ch; } }//End While loop - if(this.tagsStack.length > 0 || ( this.tagTextData !== "undefined" && this.tagTextData.trimEnd().length > 0) ) throw new Error("Unexpected data in the end of document"); + if(this.tagsStack.length > 0 || ( this.tagTextData.trim() !== "undefined" && this.tagTextData.trim().length > 0) ) throw new Error("Unexpected data in the end of document"); } /** @@ -113,6 +112,24 @@ export default class Xml2JsParser { //create new tag let tagExp = readTagExp(this, ">" ); + + // if the tag is to be skipped, then continue to read until closing tag and return + if (this.options.skip.includes(tagExp.tagName)){ + // console.log(`Skipping tag ${tagExp.tagName}`); + while (this.source.canRead()){ + let ch = this.source.readCh(); + if (ch === "") break; + if(ch === "<"){//tagStart + let nextChar = this.source.readChAt(0); + if (nextChar === "" ) throw new Error("Unexpected end of source"); + if(nextChar === "/"){ + this.source.updateBufferReadIndex(); + let closeTagName = this.processTagName(readClosingTagName(this.source)); + if (closeTagName === tagExp.tagName) return; + } + } + } + } // process and skip from tagsStack For unpaired tag, self closing tag, and stop node const tagDetail = new TagDetail(tagExp.tagName); diff --git a/src/v6/XmlPartReader.js b/src/v6/XmlPartReader.js index 61c57ec8..110762b6 100644 --- a/src/v6/XmlPartReader.js +++ b/src/v6/XmlPartReader.js @@ -103,7 +103,7 @@ export function readTagExp(parser) { const exp = parser.source.readStr(i); - parser.source.updateBufferBoundary(i + 1); + parser.source.updateBufferReadIndex(i + 1); return buildTagExpObj(exp, parser) } @@ -139,7 +139,7 @@ export function readPiExp(parser) { } const exp = parser.source.readStr(i); - parser.source.updateBufferBoundary(i + 1); + parser.source.updateBufferReadIndex(i + 1); return buildTagExpObj(exp, parser) } diff --git a/src/v6/XmlSpecialTagsReader.js b/src/v6/XmlSpecialTagsReader.js index 330e0a46..c4f93754 100644 --- a/src/v6/XmlSpecialTagsReader.js +++ b/src/v6/XmlSpecialTagsReader.js @@ -1,9 +1,9 @@ -import {readPiExp} from './XmlPartReader.js'; +import { readPiExp } from './XmlPartReader.js'; export function readCdata(parser){ //; +} + +export interface V6OutputBuilderInstance { + addTag(tag: { name: string }): void; + closeTag(): void; + addValue(text: string): void; + addPi(name: string): void; + addComment(text: string): void; + getOutput(): any; +} + +export class JsObjOutputBuilder { + constructor(options?: V6BuilderOptions); + getInstance(parserOptions: V6ParserOptions): V6OutputBuilderInstance; + registerValueParser(name: string, parserInstance: ValueParser): void; +} + +export class JsArrOutputBuilder { + constructor(options?: V6BuilderOptions); + getInstance(parserOptions: V6ParserOptions): V6OutputBuilderInstance; + registerValueParser(name: string, parserInstance: ValueParser): void; +} + +export class JsMinArrOutputBuilder { + constructor(options?: V6BuilderOptions); + getInstance(parserOptions: V6ParserOptions): V6OutputBuilderInstance; + registerValueParser(name: string, parserInstance: ValueParser): void; +} + +export interface V6ParserOptions { + preserveOrder?: boolean; + removeNSPrefix?: boolean; + stopNodes?: string[]; + htmlEntities?: boolean; + tags?: { + unpaired?: string[]; + nameFor?: { + cdata?: false | string; + comment?: false | string; + text?: string; + }; + separateTextProperty?: boolean; + }; + attributes?: { + ignore?: boolean; + booleanType?: boolean; + entities?: boolean; + }; + only?: string[]; + hierarchy?: boolean; + skip?: string[]; + select?: string[]; + stop?: string[]; + OutputBuilder?: JsObjOutputBuilder | JsArrOutputBuilder | JsMinArrOutputBuilder; +} + +export declare const v6DefaultOptions: V6ParserOptions; +export declare function buildV6Options(options?: V6ParserOptions): V6ParserOptions; + +export class XMLParser { + constructor(options?: V6ParserOptions); + parse(xmlData: string | Uint8Array): any; + parseBytesArr(xmlData: Uint8Array): any; + addEntity(key: string, value: string): void; +} diff --git a/src/v6/index.js b/src/v6/index.js new file mode 100644 index 00000000..034d6bbc --- /dev/null +++ b/src/v6/index.js @@ -0,0 +1,6 @@ +export { buildOptions as buildV6Options, defaultOptions as v6DefaultOptions } from './OptionsBuilder.js'; +export { default as JsArrOutputBuilder } from './OutputBuilders/JsArrBuilder.js'; +export { default as JsMinArrOutputBuilder } from './OutputBuilders/JsMinArrBuilder.js'; +export { default as JsObjOutputBuilder } from './OutputBuilders/JsObjBuilder.js'; +export { default as XMLParser } from './XMLParser.js'; + diff --git a/src/v6/inputSource/BufferSource.js b/src/v6/inputSource/BufferSource.js index 2c5d8b59..e4bc5a49 100644 --- a/src/v6/inputSource/BufferSource.js +++ b/src/v6/inputSource/BufferSource.js @@ -104,7 +104,7 @@ readUptoCloseTag(stopStr) { //stopStr: "