backend v4 half

This commit is contained in:
2025-07-18 09:20:40 +02:00
parent aba7a506ad
commit 725516ad6c
4183 changed files with 217684 additions and 75056 deletions
+77
View File
@@ -1,5 +1,82 @@
<small>Note: If you find missing information about particular minor version, that version must have been changed without any functional change in this library.</small>
**5.2.5 / 2025-06-08**
- Inform user to use [fxp-cli](https://github.com/NaturalIntelligence/fxp-cli) instead of in-built CLI feature
- Export typings for direct use
**5.2.4 / 2025-06-06**
- fix (#747): fix EMPTY and ANY with ELEMENT in DOCTYPE
**5.2.3 / 2025-05-11**
- fix (#747): support EMPTY and ANY with ELEMENT in DOCTYPE
**5.2.2 / 2025-05-05**
- fix (#746): update strnum to fix parsing issues related to enotations
**5.2.1 / 2025-04-22**
- fix: read DOCTYPE entity value correctly
- read DOCTYPE NOTATION, ELEMENT exp but not using read values
**5.2.0 / 2025-04-03**
- feat: support metadata on nodes (#593) (By [Steven R. Loomis](https://github.com/srl295))
**5.1.0 / 2025-04-02**
- feat: declare package as side-effect free (#738) (By [Thomas Bouffard](https://github.com/tbouffard))
- fix cjs build mode
- fix builder return type to string
-
**5.0.9 / 2025-03-14**
- fix: support numeric entities with values over 0xFFFF (#726) (By [Marc Durdin](https://github.com/mcdurdin))
- fix: update strnum to fix parsing 0 if skiplike option is used
**5.0.8 / 2025-02-27**
- fix parsing 0 if skiplike option is used.
- updating strnum dependency
**5.0.7 / 2025-02-25**
- fix (#724) typings for cjs.
**5.0.6 / 2025-02-20**
- fix cli output (By [Angel Delgado](https://github.com/angeld7))
- remove multiple JSON parsing
**5.0.5 / 2025-02-20**
- fix parsing of string starting with 'e' or 'E' by updating strnum
**5.0.4 / 2025-02-20**
- fix CLI to support all the versions of node js when displaying library version.
- fix CJS import in v5
- by fixing webpack config
**5.0.3 / 2025-02-20**
- Using strnum ESM module
- new fixes in strum may break your experience
**5.0.2 / 2025-02-20**
- fix: include CommonJS resources in the npm package #714 (By [Thomas Bouffard](https://github.com/tbouffard))
- fix: move babel deps to dev deps
**5.0.1 / 2025-02-19**
- fix syntax error for CLI command
**5.0.0 / 2025-02-19**
- ESM support
- no change in the functionality, syntax, APIs, options, or documentation.
**4.5.2 / 2025-02-18**
- Fix null CDATA to comply with undefined behavior (#701) (By [Matthieu BOHEAS](https://github.com/Kelgors))
- Fix(performance): Update check for leaf node in saveTextToParentTag function in OrderedObjParser.js (#707) (By [...](https://github.com/tomingtoming))
- Fix: emit full JSON string from CLI when no output filename specified (#710) (By [Matt Benson](https://github.com/mbenson))
**4.5.1 / 2024-12-15**
- Fix empty tag key name for v5 (#697). no impact on v4
- Fixes entity parsing when used in strict mode (#699)
**4.5.0 / 2024-09-03**
- feat #666: ignoreAttributes support function, and array of string or regex (By [ArtemM](https://github.com/mav-rik))
**4.4.1 / 2024-07-28**
- v5 fix: maximum length limit to currency value
- fix #634: build attributes with oneListGroup and attributesGroupName (#653)(By [Andreas Naziris](https://github.com/a-rasin))
+40 -53
View File
@@ -1,17 +1,29 @@
# [fast-xml-parser](https://www.npmjs.com/package/fast-xml-parser)
[![NPM quality][quality-image]][quality-url]
[![Coverage Status](https://coveralls.io/repos/github/NaturalIntelligence/fast-xml-parser/badge.svg?branch=master)](https://coveralls.io/github/NaturalIntelligence/fast-xml-parser?branch=master)
[<img src="https://img.shields.io/badge/Try-me-blue.svg?colorA=FFA500&colorB=0000FF" alt="Try me"/>](https://naturalintelligence.github.io/fast-xml-parser/)
[![NPM total downloads](https://img.shields.io/npm/dt/fast-xml-parser.svg)](https://npm.im/fast-xml-parser)
[![NPM total downloads](https://img.shields.io/npm/dt/fast-xml-parser.svg)](https://npm.im/fast-xml-parser)
Validate XML, Parse XML to JS Object, or Build XML from JS Object without C/C++ based libraries and no callback.
---
<img align="right" src="static/img/fxp_logo.png" width="180px" alt="FXP logo"/>
<a href="https://www.amazon.in/Power-Glasses-world-imagination-Perspective-ebook/dp/B0CW1CJGNK/"><img align="left" src="https://github.com/NaturalIntelligence/fast-xml-parser/assets/7692328/e7a42bcc-5186-45e3-bfee-de8d8b9a69d4" alt="ads-thePowerGlassesBook"/></a>
I had recently published a book, The Power Glasses. Please have a look. Your feedback would be helpful. You can [mail](githubissues@proton.me) me for a free copy.
<br>
* Validate XML data syntactically. Use [detailed-xml-validator](https://github.com/NaturalIntelligence/detailed-xml-validator/) to verify business rules.
* Parse XML to JS Objectand vice versa
* Common JS, ESM, and browser compatible
* Faster than any other pure JS implementation.
It can handle big files (tested up to 100mb). XML Entities, HTML entities, and DOCTYPE entites are supported. Unpaired tags (Eg `<br>` in HTML), stop nodes (Eg `<script>` in HTML) are supported. It can also preserve Order of tags in JS object
---
# Your Support, Our Motivation
## Try out our New Thoughts
We've recently launched **Flowgger**
<a href="https://github.com/solothought/flowgger"> <img src="static/img/flowgger_h.webp" alt="Flowgger Logging Framework" /></a>
Don't forget to check our new library [Text2Chart](https://solothought.com/text2chart/flow) that constructs flow chart out of simple text. Very helpful in creating or alayzing an algorithm, and documentation purpose.
## Financial Support
Sponsor this project
@@ -21,7 +33,7 @@ Sponsor this project
<a href="https://opencollective.com/fast-xml-parser/donate" target="_blank">
<img src="https://opencollective.com/fast-xml-parser/donate/button@2x.png?color=blue" width=180 />
</a>
<a href="https://paypal.me/naturalintelligence"> <img src="static/img/support_paypal.svg" alt="Stubmatic donate button" width="180"/></a>
<a href="https://paypal.me/naturalintelligence"> <img src="static/img/support_paypal.svg" alt="donate button" width="180"/></a>
<br>
<br>
<br>
@@ -50,7 +62,9 @@ Through OpenCollective
<a href="https://opencollective.com/fast-xml-parser/sponsor/9/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/9/avatar.svg"></a>
-->
![fxp_sponsors](https://github.com/NaturalIntelligence/fast-xml-parser/assets/7692328/c9367497-d67e-410a-90a6-66e3808be929)
![fxp_sponsors](https://raw.githubusercontent.com/NaturalIntelligence/ThankYouBackers/main/assets/NI_sponsors.jpg)
> This is a donation. No goods or services are expected in return. Any requests for refunds for those purposes will be rejected.
## Users
@@ -78,30 +92,7 @@ Through OpenCollective
---
## Main Features
<img align="right" src="static/img/fxp_logo.png" width="180px" alt="FXP logo"/>
* Validate XML data syntactically
* Parse XML to JS Object
* Build XML from JS Object
* Compatible to node js packages, in browser, and in CLI (click try me button above for demo)
* Faster than any other pure JS implementation.
* It can handle big files (tested up to 100mb).
* Controlled parsing using various options
* XML Entities, HTML entities, and DOCTYPE entites are supported.
* unpaired tags (Eg `<br>` in HTML), stop nodes (Eg `<script>` in HTML) are supported.
* You can restore almost same XML from JSON
* Supports comments
* It can preserve Order of tags in JS object
* You can control if a single tag should be parsed into array.
* Supports parsing of PI (Processing Instruction) tags with XML declaration tags
* And many more other features.
## v5
I developed v5 in Apr 2023. And I didn't get the chance to complete all the features. I've ensured that new features don't impact performance. With v5, you have more control on parsing output. Check [docs](./docs/v5) for syntax help and basic understanding.
Please leave a comment in discussion forum for your suggestions and if you really need v5.
# More about this library
## How to use
@@ -152,9 +143,9 @@ Bundle size
| fxp.min.js | 26K |
| fxvalidator.min.js | 5.7K |
### Documents
## Documents
<table>
<tr><td>v3</td><td>v4</td><td>v5</td></tr>
<tr><td>v3</td><td>v4 and v5</td><td>v6</td></tr>
<tr>
<td>
<a href="./docs/v3/docs.md">documents</a>
@@ -169,16 +160,18 @@ Bundle size
<li><a href="./docs/v4/7.PITags.md">PI Tag processing</a></li>
</ol></td>
<td><ol>
<li></li><a href="./docs/v5/1.GettingStarted.md">Getting Started</a></li>
<li><a href="./docs/v5/2.Features.md">Features</a></li>
<li><a href="./docs/v5/3.Options.md">Options</a></li>
<li><a href="./docs/v5/4.OutputBuilders.md">Output Builders</a></li>
<li><a href="./docs/v5/5.ValueParsers.md">Value Parsers</a></li>
<li></li><a href="./docs/v6/1.GettingStarted.md">Getting Started</a></li>
<li><a href="./docs/v6/2.Features.md">Features</a></li>
<li><a href="./docs/v6/3.Options.md">Options</a></li>
<li><a href="./docs/v6/4.OutputBuilders.md">Output Builders</a></li>
<li><a href="./docs/v6/5.ValueParsers.md">Value Parsers</a></li>
</ol></td>
</tr>
</table>
**note**: version 5 is released with version 4 tfor experimental use. Based on it's demand, it'll be developed and the features can be different in final release.
**note**:
- Version 6 is released with version 4 for experimental use. Based on it's demand, it'll be developed and the features can be different in final release.
- Version 5 has the same functionalities as version 4.
## Performance
<small>negative means error</small>
@@ -196,10 +189,9 @@ Bundle size
<img src="./docs/imgs/XMLBuilder_v4.png" width="50%" />
* Y-axis: requests per second
<!-- [![](static/img/ni_ads_ads.gif)](https://github.com/NaturalIntelligence/ads/) -->
---
## Usage Trend
@@ -209,20 +201,15 @@ Bundle size
<img src="https://npm-compare.com/img/npm-trend/THREE_YEARS/fast-xml-parser.png" width="50%" alt="NPM Usage Trend of fast-xml-parser" />
</a>
## Supporters
### Contributors
# Supporters
#### Contributors
This project exists thanks to [all](graphs/contributors) the people who contribute. [[Contribute](docs/CONTRIBUTING.md)].
<!-- <a href="graphs/contributors"><img src="https://opencollective.com/fast-xml-parser/contributors.svg?width=890&button=false" /></a> -->
<!--
### Lead Maintainers
![Amit Gupta](https://avatars1.githubusercontent.com/u/7692328?s=100&v=4)
[![Vohmyanin Sergey Vasilevich](https://avatars3.githubusercontent.com/u/783335?s=100&v=4)](https://github.com/Delagen)
### All Contributors -->
<a href="graphs/contributors"><img src="https://opencollective.com/fast-xml-parser/contributors.svg?width=890&button=false" /></a>
### Backers
#### Backers from Open collective
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/fast-xml-parser#backer)]
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+458
View File
@@ -0,0 +1,458 @@
type X2jOptions = {
/**
* Preserve the order of tags in resulting JS object
*
* Defaults to `false`
*/
preserveOrder?: boolean;
/**
* Give a prefix to the attribute name in the resulting JS object
*
* Defaults to '@_'
*/
attributeNamePrefix?: string;
/**
* A name to group all attributes of a tag under, or `false` to disable
*
* Defaults to `false`
*/
attributesGroupName?: false | string;
/**
* The name of the next node in the resulting JS
*
* Defaults to `#text`
*/
textNodeName?: string;
/**
* Whether to ignore attributes when parsing
*
* When `true` - ignores all the attributes
*
* When `false` - parses all the attributes
*
* When `Array<string | RegExp>` - filters out attributes that match provided patterns
*
* When `Function` - calls the function for each attribute and filters out those for which the function returned `true`
*
* Defaults to `true`
*/
ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPath: string) => boolean);
/**
* Whether to remove namespace string from tag and attribute names
*
* Defaults to `false`
*/
removeNSPrefix?: boolean;
/**
* Whether to allow attributes without value
*
* Defaults to `false`
*/
allowBooleanAttributes?: boolean;
/**
* Whether to parse tag value with `strnum` package
*
* Defaults to `true`
*/
parseTagValue?: boolean;
/**
* Whether to parse tag value with `strnum` package
*
* Defaults to `false`
*/
parseAttributeValue?: boolean;
/**
* Whether to remove surrounding whitespace from tag or attribute value
*
* Defaults to `true`
*/
trimValues?: boolean;
/**
* Give a property name to set CDATA values to instead of merging to tag's text value
*
* Defaults to `false`
*/
cdataPropName?: false | string;
/**
* If set, parse comments and set as this property
*
* Defaults to `false`
*/
commentPropName?: false | string;
/**
* Control how tag value should be parsed. Called only if tag value is not empty
*
* @returns {undefined|null} `undefined` or `null` to set original value.
* @returns {unknown}
*
* 1. Different value or value with different data type to set new value.
* 2. Same value to set parsed value if `parseTagValue: true`.
*
* Defaults to `(tagName, val, jPath, hasAttributes, isLeafNode) => val`
*/
tagValueProcessor?: (tagName: string, tagValue: string, jPath: string, hasAttributes: boolean, isLeafNode: boolean) => unknown;
/**
* Control how attribute value should be parsed
*
* @param attrName
* @param attrValue
* @param jPath
* @returns {undefined|null} `undefined` or `null` to set original value
* @returns {unknown}
*
* Defaults to `(attrName, val, jPath) => val`
*/
attributeValueProcessor?: (attrName: string, attrValue: string, jPath: string) => unknown;
/**
* Options to pass to `strnum` for parsing numbers
*
* Defaults to `{ hex: true, leadingZeros: true, eNotation: true }`
*/
numberParseOptions?: strnumOptions;
/**
* Nodes to stop parsing at
*
* Defaults to `[]`
*/
stopNodes?: string[];
/**
* List of tags without closing tags
*
* Defaults to `[]`
*/
unpairedTags?: string[];
/**
* Whether to always create a text node
*
* Defaults to `false`
*/
alwaysCreateTextNode?: boolean;
/**
* Determine whether a tag should be parsed as an array
*
* @param tagName
* @param jPath
* @param isLeafNode
* @param isAttribute
* @returns {boolean}
*
* Defaults to `() => false`
*/
isArray?: (tagName: string, jPath: string, isLeafNode: boolean, isAttribute: boolean) => boolean;
/**
* Whether to process default and DOCTYPE entities
*
* Defaults to `true`
*/
processEntities?: boolean;
/**
* Whether to process HTML entities
*
* Defaults to `false`
*/
htmlEntities?: boolean;
/**
* Whether to ignore the declaration tag from output
*
* Defaults to `false`
*/
ignoreDeclaration?: boolean;
/**
* Whether to ignore Pi tags
*
* Defaults to `false`
*/
ignorePiTags?: boolean;
/**
* Transform tag names
*
* Defaults to `false`
*/
transformTagName?: ((tagName: string) => string) | false;
/**
* Transform attribute names
*
* Defaults to `false`
*/
transformAttributeName?: ((attributeName: string) => string) | false;
/**
* Change the tag name when a different name is returned. Skip the tag from parsed result when false is returned.
* Modify `attrs` object to control attributes for the given tag.
*
* @returns {string} new tag name.
* @returns false to skip the tag
*
* Defaults to `(tagName, jPath, attrs) => tagName`
*/
updateTag?: (tagName: string, jPath: string, attrs: {[k: string]: string}) => string | boolean;
/**
* If true, adds a Symbol to all object nodes, accessible by {@link XMLParser.getMetaDataSymbol} with
* metadata about each the node in the XML file.
*/
captureMetaData?: boolean;
};
type strnumOptions = {
hex: boolean;
leadingZeros: boolean,
skipLike?: RegExp,
eNotation?: boolean
}
type validationOptions = {
/**
* Whether to allow attributes without value
*
* Defaults to `false`
*/
allowBooleanAttributes?: boolean;
/**
* List of tags without closing tags
*
* Defaults to `[]`
*/
unpairedTags?: string[];
};
type XmlBuilderOptions = {
/**
* Give a prefix to the attribute name in the resulting JS object
*
* Defaults to '@_'
*/
attributeNamePrefix?: string;
/**
* A name to group all attributes of a tag under, or `false` to disable
*
* Defaults to `false`
*/
attributesGroupName?: false | string;
/**
* The name of the next node in the resulting JS
*
* Defaults to `#text`
*/
textNodeName?: string;
/**
* Whether to ignore attributes when building
*
* When `true` - ignores all the attributes
*
* When `false` - builds all the attributes
*
* When `Array<string | RegExp>` - filters out attributes that match provided patterns
*
* When `Function` - calls the function for each attribute and filters out those for which the function returned `true`
*
* Defaults to `true`
*/
ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPath: string) => boolean);
/**
* Give a property name to set CDATA values to instead of merging to tag's text value
*
* Defaults to `false`
*/
cdataPropName?: false | string;
/**
* If set, parse comments and set as this property
*
* Defaults to `false`
*/
commentPropName?: false | string;
/**
* Whether to make output pretty instead of single line
*
* Defaults to `false`
*/
format?: boolean;
/**
* If `format` is set to `true`, sets the indent string
*
* Defaults to ` `
*/
indentBy?: string;
/**
* Give a name to a top-level array
*
* Defaults to `undefined`
*/
arrayNodeName?: string;
/**
* Create empty tags for tags with no text value
*
* Defaults to `false`
*/
suppressEmptyNode?: boolean;
/**
* Suppress an unpaired tag
*
* Defaults to `true`
*/
suppressUnpairedNode?: boolean;
/**
* Don't put a value for boolean attributes
*
* Defaults to `true`
*/
suppressBooleanAttributes?: boolean;
/**
* Preserve the order of tags in resulting JS object
*
* Defaults to `false`
*/
preserveOrder?: boolean;
/**
* List of tags without closing tags
*
* Defaults to `[]`
*/
unpairedTags?: string[];
/**
* Nodes to stop parsing at
*
* Defaults to `[]`
*/
stopNodes?: string[];
/**
* Control how tag value should be parsed. Called only if tag value is not empty
*
* @returns {undefined|null} `undefined` or `null` to set original value.
* @returns {unknown}
*
* 1. Different value or value with different data type to set new value.
* 2. Same value to set parsed value if `parseTagValue: true`.
*
* Defaults to `(tagName, val, jPath, hasAttributes, isLeafNode) => val`
*/
tagValueProcessor?: (name: string, value: unknown) => unknown;
/**
* Control how attribute value should be parsed
*
* @param attrName
* @param attrValue
* @param jPath
* @returns {undefined|null} `undefined` or `null` to set original value
* @returns {unknown}
*
* Defaults to `(attrName, val, jPath) => val`
*/
attributeValueProcessor?: (name: string, value: unknown) => unknown;
/**
* Whether to process default and DOCTYPE entities
*
* Defaults to `true`
*/
processEntities?: boolean;
oneListGroup?: boolean;
};
type ESchema = string | object | Array<string|object>;
type ValidationError = {
err: {
code: string;
msg: string,
line: number,
col: number
};
};
declare class XMLParser {
constructor(options?: X2jOptions);
parse(xmlData: string | Buffer ,validationOptions?: validationOptions | boolean): any;
/**
* Add Entity which is not by default supported by this library
* @param entityIdentifier {string} Eg: 'ent' for &ent;
* @param entityValue {string} Eg: '\r'
*/
addEntity(entityIdentifier: string, entityValue: string): void;
/**
* Returns a Symbol that can be used to access the {@link XMLMetaData}
* property on a node.
*
* If Symbol is not available in the environment, an ordinary property is used
* and the name of the property is here returned.
*
* The XMLMetaData property is only present when {@link X2jOptions.captureMetaData}
* is true in the options.
*/
static getMetaDataSymbol() : Symbol;
}
declare class XMLValidator{
static validate(xmlData: string, options?: validationOptions): true | ValidationError;
}
declare class XMLBuilder {
constructor(options?: XmlBuilderOptions);
build(jObj: any): string;
}
/**
* This object is available on nodes via the symbol {@link XMLParser.getMetaDataSymbol}
* when {@link X2jOptions.captureMetaData} is true.
*/
declare interface XMLMetaData {
/** The index, if available, of the character where the XML node began in the input stream. */
startIndex?: number;
}
declare namespace fxp {
export {
XMLParser,
XMLValidator,
XMLBuilder,
XMLMetaData
}
}
export = fxp;
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+36 -20
View File
@@ -1,23 +1,43 @@
{
"name": "fast-xml-parser",
"version": "4.4.1",
"version": "5.2.5",
"description": "Validate XML, Parse XML, Build XML without C/C++ based libraries",
"main": "./src/fxp.js",
"main": "./lib/fxp.cjs",
"type": "module",
"sideEffects": false,
"module": "./src/fxp.js",
"types": "./src/fxp.d.ts",
"exports": {
".": {
"import": {
"types": "./src/fxp.d.ts",
"default": "./src/fxp.js"
},
"require": {
"types": "./lib/fxp.d.cts",
"default": "./lib/fxp.cjs"
}
}
},
"scripts": {
"test": "nyc --reporter=lcov --reporter=text jasmine spec/*spec.js",
"test": "c8 --reporter=lcov --reporter=text jasmine spec/*spec.js",
"test-types": "tsc --noEmit spec/typings/typings-test.ts",
"unit": "jasmine",
"coverage": "nyc report --reporter html --reporter text -t .nyc_output --report-dir .nyc_output/summary",
"perf": "node ./benchmark/perfTest3.js",
"lint": "eslint src/*.js spec/*.js",
"bundle": "webpack --config webpack-prod.config.js",
"lint": "eslint src/**/*.js spec/**/*.js benchmark/**/*.js",
"bundle": "webpack --config webpack.cjs.config.js",
"prettier": "prettier --write src/**/*.js",
"publish-please": "publish-please",
"checkReadiness": "publish-please --dry-run"
},
"bin": {
"fxparser": "./src/cli/cli.js"
},
"files": [
"lib",
"src",
"CHANGELOG.md"
],
"repository": {
"type": "git",
"url": "https://github.com/NaturalIntelligence/fast-xml-parser"
@@ -31,7 +51,6 @@
"x2js",
"xml2json",
"js",
"cli",
"validator",
"validate",
"transformer",
@@ -49,26 +68,23 @@
"@babel/register": "^7.13.8",
"@types/node": "20",
"babel-loader": "^8.2.2",
"cytorus": "^0.2.9",
"c8": "^10.1.3",
"eslint": "^8.3.0",
"he": "^1.2.0",
"jasmine": "^3.6.4",
"nyc": "^15.1.0",
"prettier": "^1.19.1",
"jasmine": "^5.6.0",
"prettier": "^3.5.1",
"publish-please": "^5.5.2",
"typescript": "5",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1"
},
"typings": "src/fxp.d.ts",
"funding": [{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
},{
"type": "paypal",
"url": "https://paypal.me/naturalintelligence"
}],
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"dependencies": {
"strnum": "^1.0.5"
"strnum": "^2.1.0"
}
}
+16 -12
View File
@@ -1,15 +1,21 @@
#!/usr/bin/env node
'use strict';
/*eslint-disable no-console*/
const fs = require('fs');
const path = require('path');
const {XMLParser, XMLValidator} = require("../fxp");
const readToEnd = require('./read').readToEnd;
import fs from 'fs';
import { resolve } from 'path';
import {XMLParser, XMLValidator} from "../fxp.js";
import ReadToEnd from './read.js';
import cmdDetail from "./man.js"
console.warn("\x1b[33m%s\x1b[0m", "⚠️ Warning: The built-in CLI interface is now deprecated.");
console.warn("Please install the dedicated CLI package instead:");
console.warn(" npm install -g fxp-cli");
const version = require('./../../package.json').version;
if (process.argv[2] === '--help' || process.argv[2] === '-h') {
console.log(require("./man"));
console.log(cmdDetail);
} else if (process.argv[2] === '--version') {
const packageJsonPath = resolve(process.cwd(), 'package.json');
const version = JSON.parse(fs.readFileSync(packageJsonPath).toString()).version;
console.log(version);
} else {
const options = {
@@ -44,13 +50,10 @@ if (process.argv[2] === '--help' || process.argv[2] === '-h') {
const callback = function(xmlData) {
let output = '';
if (validate) {
const parser = new XMLParser(options);
output = parser.parse(xmlData,validate);
} else if (validateOnly) {
if (validateOnly) {
output = XMLValidator.validate(xmlData);
process.exitCode = output === true ? 0 : 1;
} else {
} else {
const parser = new XMLParser(options);
output = JSON.stringify(parser.parse(xmlData,validate), null, 4);
}
@@ -61,10 +64,11 @@ if (process.argv[2] === '--help' || process.argv[2] === '-h') {
}
};
try {
if (!fileName) {
readToEnd(process.stdin, function(err, data) {
ReadToEnd.readToEnd(process.stdin, function(err, data) {
if (err) {
throw err;
}
+6 -1
View File
@@ -1,4 +1,9 @@
module.exports = `Fast XML Parser 4.0.0
import fs from 'fs';
import { resolve } from 'path';
const packageJsonPath = resolve(process.cwd(), 'package.json');
const version = JSON.parse(fs.readFileSync(packageJsonPath).toString()).version;
export default `Fast XML Parser ${version}
----------------
$ fxparser [-ns|-a|-c|-v|-V] <filename> [-o outputfile.json]
$ cat xmlfile.xml | fxparser [-ns|-a|-c|-v|-V] [-o outputfile.json]
+31 -80
View File
@@ -1,92 +1,43 @@
'use strict';
// Copyright 2013 Timothy J Fontaine <tjfontaine@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the 'Software'), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE
import { Transform } from 'stream';
/*
Read any stream all the way to the end and trigger a single cb
const http = require('http');
const rte = require('readtoend');
http.get('http://nodejs.org', function(response) {
rte.readToEnd(response, function(err, body) {
console.log(body);
});
});
*/
let stream = require('stream');
const util = require('util');
if (!stream.Transform) {
stream = require('readable-stream');
}
function ReadToEnd(opts) {
if (!(this instanceof ReadToEnd)) {
return new ReadToEnd(opts);
export default class ReadToEnd extends Transform {
constructor(options = {}) {
super(options);
this._encoding = options.encoding || 'utf8';
this._buffer = '';
}
stream.Transform.call(this, opts);
this._rte_encoding = opts.encoding || 'utf8';
this._buff = '';
}
module.exports = ReadToEnd;
util.inherits(ReadToEnd, stream.Transform);
ReadToEnd.prototype._transform = function(chunk, encoding, done) {
this._buff += chunk.toString(this._rte_encoding);
this.push(chunk);
done();
};
ReadToEnd.prototype._flush = function(done) {
this.emit('complete', undefined, this._buff);
done();
};
ReadToEnd.readToEnd = function(stream, options, cb) {
if (!cb) {
cb = options;
options = {};
_transform(chunk, encoding, done) {
this._buffer += chunk.toString(this._encoding);
this.push(chunk);
done();
}
const dest = new ReadToEnd(options);
_flush(done) {
this.emit('complete', null, this._buffer);
done();
}
stream.pipe(dest);
static readToEnd(stream, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
stream.on('error', function(err) {
stream.unpipe(dest);
cb(err);
});
const dest = new ReadToEnd(options);
dest.on('complete', cb);
stream.pipe(dest);
dest.resume();
stream.on('error', (err) => {
stream.unpipe(dest);
callback(err);
});
return dest;
};
dest.on('complete', callback);
dest.resume();
return dest;
}
}
+52 -9
View File
@@ -1,4 +1,4 @@
type X2jOptions = {
export type X2jOptions = {
/**
* Preserve the order of tags in resulting JS object
*
@@ -30,9 +30,17 @@ type X2jOptions = {
/**
* Whether to ignore attributes when parsing
*
* When `true` - ignores all the attributes
*
* When `false` - parses all the attributes
*
* When `Array<string | RegExp>` - filters out attributes that match provided patterns
*
* When `Function` - calls the function for each attribute and filters out those for which the function returned `true`
*
* Defaults to `true`
*/
ignoreAttributes?: boolean;
ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPath: string) => boolean);
/**
* Whether to remove namespace string from tag and attribute names
@@ -202,16 +210,22 @@ type X2jOptions = {
* Defaults to `(tagName, jPath, attrs) => tagName`
*/
updateTag?: (tagName: string, jPath: string, attrs: {[k: string]: string}) => string | boolean;
/**
* If true, adds a Symbol to all object nodes, accessible by {@link XMLParser.getMetaDataSymbol} with
* metadata about each the node in the XML file.
*/
captureMetaData?: boolean;
};
type strnumOptions = {
export type strnumOptions = {
hex: boolean;
leadingZeros: boolean,
skipLike?: RegExp,
eNotation?: boolean
}
type validationOptions = {
export type validationOptions = {
/**
* Whether to allow attributes without value
*
@@ -227,7 +241,7 @@ type validationOptions = {
unpairedTags?: string[];
};
type XmlBuilderOptions = {
export type XmlBuilderOptions = {
/**
* Give a prefix to the attribute name in the resulting JS object
*
@@ -250,11 +264,19 @@ type XmlBuilderOptions = {
textNodeName?: string;
/**
* Whether to ignore attributes when parsing
* Whether to ignore attributes when building
*
* When `true` - ignores all the attributes
*
* When `false` - builds all the attributes
*
* When `Array<string | RegExp>` - filters out attributes that match provided patterns
*
* When `Function` - calls the function for each attribute and filters out those for which the function returned `true`
*
* Defaults to `true`
*/
ignoreAttributes?: boolean;
ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPath: string) => boolean);
/**
* Give a property name to set CDATA values to instead of merging to tag's text value
@@ -373,7 +395,7 @@ type XmlBuilderOptions = {
type ESchema = string | object | Array<string|object>;
type ValidationError = {
export type ValidationError = {
err: {
code: string;
msg: string,
@@ -391,6 +413,18 @@ export class XMLParser {
* @param entityValue {string} Eg: '\r'
*/
addEntity(entityIdentifier: string, entityValue: string): void;
/**
* Returns a Symbol that can be used to access the {@link XMLMetaData}
* property on a node.
*
* If Symbol is not available in the environment, an ordinary property is used
* and the name of the property is here returned.
*
* The XMLMetaData property is only present when {@link X2jOptions.captureMetaData}
* is true in the options.
*/
static getMetaDataSymbol() : Symbol;
}
export class XMLValidator{
@@ -398,5 +432,14 @@ export class XMLValidator{
}
export class XMLBuilder {
constructor(options?: XmlBuilderOptions);
build(jObj: any): any;
build(jObj: any): string;
}
/**
* This object is available on nodes via the symbol {@link XMLParser.getMetaDataSymbol}
* when {@link X2jOptions.captureMetaData} is true.
*/
export interface XMLMetaData {
/** The index, if available, of the character where the XML node began in the input stream. */
startIndex?: number;
}
+11 -8
View File
@@ -1,11 +1,14 @@
'use strict';
const validator = require('./validator');
const XMLParser = require('./xmlparser/XMLParser');
const XMLBuilder = require('./xmlbuilder/json2xml');
import {validate} from './validator.js';
import XMLParser from './xmlparser/XMLParser.js';
import XMLBuilder from './xmlbuilder/json2xml.js';
module.exports = {
XMLParser: XMLParser,
XMLValidator: validator,
XMLBuilder: XMLBuilder
}
const XMLValidator = {
validate: validate
}
export {
XMLParser,
XMLValidator,
XMLBuilder
};
@@ -0,0 +1,18 @@
export default function getIgnoreAttributesFn(ignoreAttributes) {
if (typeof ignoreAttributes === 'function') {
return ignoreAttributes
}
if (Array.isArray(ignoreAttributes)) {
return (attrName) => {
for (const pattern of ignoreAttributes) {
if (typeof pattern === 'string' && attrName === pattern) {
return true
}
if (pattern instanceof RegExp && pattern.test(attrName)) {
return true
}
}
}
}
return () => false
}
+14 -18
View File
@@ -2,10 +2,10 @@
const nameStartChar = ':A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD';
const nameChar = nameStartChar + '\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040';
const nameRegexp = '[' + nameStartChar + '][' + nameChar + ']*'
export const nameRegexp = '[' + nameStartChar + '][' + nameChar + ']*';
const regexName = new RegExp('^' + nameRegexp + '$');
const getAllMatches = function(string, regex) {
export function getAllMatches(string, regex) {
const matches = [];
let match = regex.exec(string);
while (match) {
@@ -19,27 +19,27 @@ const getAllMatches = function(string, regex) {
match = regex.exec(string);
}
return matches;
};
}
const isName = function(string) {
export const isName = function(string) {
const match = regexName.exec(string);
return !(match === null || typeof match === 'undefined');
};
}
exports.isExist = function(v) {
export function isExist(v) {
return typeof v !== 'undefined';
};
}
exports.isEmptyObject = function(obj) {
export function isEmptyObject(obj) {
return Object.keys(obj).length === 0;
};
}
/**
* Copy all the properties of a into b.
* @param {*} target
* @param {*} a
*/
exports.merge = function(target, a, arrayMode) {
export function merge(target, a, arrayMode) {
if (a) {
const keys = Object.keys(a); // will return an array of own properties
const len = keys.length; //don't make it inline
@@ -51,22 +51,18 @@ exports.merge = function(target, a, arrayMode) {
}
}
}
};
}
/* exports.merge =function (b,a){
return Object.assign(b,a);
} */
exports.getValue = function(v) {
export function getValue(v) {
if (exports.isExist(v)) {
return v;
} else {
return '';
}
};
}
// const fakeCall = function(a) {return a;};
// const fakeCallNoReturn = function() {};
exports.isName = isName;
exports.getAllMatches = getAllMatches;
exports.nameRegexp = nameRegexp;
// const fakeCallNoReturn = function() {};
@@ -1,4 +1,4 @@
modules.export = {
export default {
"<" : "<", //tag start
">" : ">", //tag end
"/" : "/", //close tag
@@ -13,11 +13,10 @@ const htmlEntities = {
"copyright" : { regex: /&(copy|#169);/g, val: "©" },
"reg" : { regex: /&(reg|#174);/g, val: "®" },
"inr" : { regex: /&(inr|#8377);/g, val: "₹" },
"num_dec": { regex: /&#([0-9]{1,7});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 10)) },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 16)) },
"num_dec": { regex: /&#([0-9]{1,7});/g, val : (_, str) => String.fromCodePoint(Number.parseInt(str, 10)) },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCodePoint(Number.parseInt(str, 16)) },
};
class EntitiesParser{
export default class EntitiesParser{
constructor(replaceHtmlEntities) {
this.replaceHtmlEntities = replaceHtmlEntities;
this.docTypeEntities = {};
@@ -103,5 +102,3 @@ function validateEntityName(name){
}
return name;
}
module.exports = EntitiesParser;
@@ -1,7 +1,7 @@
const JsObjOutputBuilder = require("./OutputBuilders/JsObjBuilder");
import {JsObjOutputBuilder} from './OutputBuilders/JsObjBuilder.js';
const defaultOptions = {
export const defaultOptions = {
preserveOrder: false,
removeNSPrefix: false, // remove NS from tag name or attribute name if true
//ignoreRootElement : false,
@@ -35,7 +35,7 @@ const defaultOptions = {
OutputBuilder: new JsObjOutputBuilder(),
};
const buildOptions = function(options) {
export const buildOptions = function(options) {
const finalOptions = { ... defaultOptions};
copyProperties(finalOptions,options)
return finalOptions;
@@ -59,6 +59,3 @@ function copyProperties(target, source) {
}
}
}
exports.buildOptions = buildOptions;
exports.defaultOptions = defaultOptions;
@@ -1,4 +1,4 @@
class BaseOutputBuilder{
export default class BaseOutputBuilder{
constructor(){
// this.attributes = {};
}
@@ -67,5 +67,3 @@ class BaseOutputBuilder{
this.attributes = {}
}
}
module.exports = BaseOutputBuilder;
@@ -1,6 +1,6 @@
const {buildOptions,registerCommonValueParsers} = require("./ParserOptionsBuilder");
import {buildOptions,registerCommonValueParsers} from './ParserOptionsBuilder.js';
class OutputBuilder{
export default class OutputBuilder{
constructor(options){
this.options = buildOptions(options);
this.registeredParsers = registerCommonValueParsers(this.options);
@@ -16,7 +16,7 @@ class OutputBuilder{
}
const rootName = '!js_arr';
const BaseOutputBuilder = require("./BaseOutputBuilder");
import BaseOutputBuilder from './BaseOutputBuilder.js';
class JsArrBuilder extends BaseOutputBuilder{
@@ -1,6 +1,6 @@
const {buildOptions,registerCommonValueParsers} = require("./ParserOptionsBuilder");
import {buildOptions,registerCommonValueParsers} from"./ParserOptionsBuilder";
class OutputBuilder{
export default class OutputBuilder{
constructor(options){
this.options = buildOptions(options);
this.registeredParsers = registerCommonValueParsers(this.options);
@@ -15,7 +15,7 @@ class OutputBuilder{
}
}
const BaseOutputBuilder = require("./BaseOutputBuilder");
import BaseOutputBuilder from "./BaseOutputBuilder.js";
const rootName = '^';
class JsMinArrBuilder extends BaseOutputBuilder{
@@ -98,5 +98,3 @@ class JsMinArrBuilder extends BaseOutputBuilder{
return this.root[rootName];
}
}
module.exports = OutputBuilder;
@@ -1,8 +1,8 @@
const {buildOptions,registerCommonValueParsers} = require("./ParserOptionsBuilder");
import {buildOptions,registerCommonValueParsers} from './ParserOptionsBuilder.js';
class OutputBuilder{
export default class OutputBuilder{
constructor(builderOptions){
this.options = buildOptions(builderOptions);
this.registeredParsers = registerCommonValueParsers(this.options);
@@ -17,7 +17,7 @@ class OutputBuilder{
}
}
const BaseOutputBuilder = require("./BaseOutputBuilder");
import BaseOutputBuilder from './BaseOutputBuilder.js';
const rootName = '^';
class JsObjBuilder extends BaseOutputBuilder{
@@ -152,5 +152,3 @@ class JsObjBuilder extends BaseOutputBuilder{
function isEmpty(obj) {
return Object.keys(obj).length === 0;
}
module.exports = OutputBuilder;
@@ -1,7 +1,7 @@
const trimParser = require("../valueParsers/trim")
const booleanParser = require("../valueParsers/booleanParser")
const currencyParser = require("../valueParsers/currency")
const numberParser = require("../valueParsers/number")
import trimParser from "../valueParsers/trim";
import booleanParser from "../valueParsers/booleanParser";
import currencyParser from "../valueParsers/currency";
import numberParser from "../valueParsers/number";
const defaultOptions={
nameFor:{
@@ -44,7 +44,7 @@ const defaultOptions={
const withJoin = ["trim","join", /*"entities",*/"number","boolean","currency"/*, "date"*/]
const withoutJoin = ["trim", /*"entities",*/"number","boolean","currency"/*, "date"*/]
function buildOptions(options){
export function buildOptions(options){
//clone
const finalOptions = { ... defaultOptions};
@@ -78,7 +78,7 @@ function copyProperties(target, source) {
}
}
function registerCommonValueParsers(options){
export function registerCommonValueParsers(options){
return {
"trim": new trimParser(),
// "join": this.entityParser.parse,
@@ -92,8 +92,3 @@ function registerCommonValueParsers(options){
// "date": this.entityParser.parse,
}
}
module.exports = {
buildOptions : buildOptions,
registerCommonValueParsers: registerCommonValueParsers
}
@@ -1,4 +1,4 @@
class TagPath{
export default class TagPath{
constructor(pathStr){
let text = "";
let tName = "";
@@ -1,6 +1,6 @@
const TagPath = require("./TagPath");
import {TagPath} from './TagPath.js';
class TagPathMatcher{
export default class TagPathMatcher{
constructor(stack,node){
this.stack = stack;
this.node= node;
@@ -10,6 +10,4 @@ class TagPathMatcher{
const tagPath = new TagPath(path);
return tagPath.match(this.stack, this.node);
}
}
module.exports = TagPathMatcher;
}
@@ -1,7 +1,7 @@
const { buildOptions} = require("./OptionsBuilder");
const Xml2JsParser = require("./Xml2JsParser");
import { buildOptions} from './OptionsBuilder.js';
import Xml2JsParser from './Xml2JsParser.js';
class XMLParser{
export default class XMLParser{
constructor(options){
this.externalEntities = {};
@@ -81,5 +81,3 @@ function isStream(stream){
if(stream && typeof stream.read === "function" && typeof stream.on === "function" && typeof stream.readableEnded === "boolean") return true;
return false;
}
module.exports = XMLParser;
@@ -1,10 +1,10 @@
const StringSource = require("./inputSource/StringSource");
const BufferSource = require("./inputSource/BufferSource");
const {readTagExp,readClosingTagName} = require("./XmlPartReader");
const {readComment, readCdata,readDocType,readPiTag} = require("./XmlSpecialTagsReader");
const TagPath = require("./TagPath");
const TagPathMatcher = require("./TagPathMatcher");
const EntitiesParser = require('./EntitiesParser');
import StringSource from './inputSource/StringSource.js';
import BufferSource from './inputSource/BufferSource.js';
import {readTagExp,readClosingTagName} from './XmlPartReader.js';
import {readComment, readCdata,readDocType,readPiTag} from './XmlSpecialTagsReader.js';
import TagPath from './TagPath.js';
import TagPathMatcher from './TagPathMatcher.js';
import EntitiesParser from './EntitiesParser.js';
//To hold the data of current tag
//This is usually used to compare jpath expression against current tag
@@ -16,7 +16,7 @@ class TagDetail{
}
}
class Xml2JsParser {
export default class Xml2JsParser {
constructor(options) {
this.options = options;
@@ -233,5 +233,3 @@ function resolveNameSpace(name, removeNSPrefix) {
}
return name;
}
module.exports = Xml2JsParser;
@@ -2,17 +2,17 @@
/**
* find paired tag for a stop node
* @param {string} xmlDoc
* @param {string} tagName
* @param {string} xmlDoc
* @param {string} tagName
* @param {number} i : start index
*/
function readStopNode(xmlDoc, tagName, i){
export function readStopNode(xmlDoc, tagName, i){
const startIndex = i;
// Starting at 1 since we already have an open tag
let openTagCount = 1;
for (; i < xmlDoc.length; i++) {
if( xmlDoc[i] === "<"){
if( xmlDoc[i] === "<"){
if (xmlDoc[i+1] === "/") {//close tag
const closeIndex = findSubStrIndex(xmlDoc, ">", i, `${tagName} is not closed`);
let closeTagName = xmlDoc.substring(i+2,closeIndex).trim();
@@ -26,18 +26,18 @@ function readStopNode(xmlDoc, tagName, i){
}
}
i=closeIndex;
} else if(xmlDoc[i+1] === '?') {
} else if(xmlDoc[i+1] === '?') {
const closeIndex = findSubStrIndex(xmlDoc, "?>", i+1, "StopNode is not closed.")
i=closeIndex;
} else if(xmlDoc.substr(i + 1, 3) === '!--') {
} else if(xmlDoc.substr(i + 1, 3) === '!--') {
const closeIndex = findSubStrIndex(xmlDoc, "-->", i+3, "StopNode is not closed.")
i=closeIndex;
} else if(xmlDoc.substr(i + 1, 2) === '![') {
} else if(xmlDoc.substr(i + 1, 2) === '![') {
const closeIndex = findSubStrIndex(xmlDoc, "]]>", i, "StopNode is not closed.") - 2;
i=closeIndex;
} else {
const tagData = readTagExp(xmlDoc, i, '>')
if (tagData) {
const openTagName = tagData && tagData.tagName;
if (openTagName === tagName && tagData.tagExp[tagData.tagExp.length-1] !== "/") {
@@ -52,10 +52,10 @@ function readStopNode(xmlDoc, tagName, i){
/**
* Read closing tag name
* @param {Source} source
* @param {Source} source
* @returns tag name
*/
function readClosingTagName(source){
export function readClosingTagName(source){
let text = ""; //temporary data
while(source.canRead()){
let ch = source.readCh();
@@ -73,11 +73,11 @@ function readClosingTagName(source){
* This function can be used to read normal tag, pi tag.
* This function can't be used to read comment, CDATA, DOCTYPE.
* Eg <tag attr = ' some"' attr= ">" bool>
* @param {string} xmlDoc
* @param {string} xmlDoc
* @param {number} startIndex starting index
* @returns tag expression includes tag name & attribute string
* @returns tag expression includes tag name & attribute string
*/
function readTagExp(parser) {
export function readTagExp(parser) {
let inSingleQuotes = false;
let inDoubleQuotes = false;
let i;
@@ -100,14 +100,14 @@ function readTagExp(parser) {
if(inSingleQuotes || inDoubleQuotes){
throw new Error("Invalid attribute expression. Quote is not properly closed");
}else if(!EOE) throw new Error("Unexpected closing of source. Waiting for '>'");
const exp = parser.source.readStr(i);
parser.source.updateBufferBoundary(i + 1);
return buildTagExpObj(exp, parser)
}
function readPiExp(parser) {
export function readPiExp(parser) {
let inSingleQuotes = false;
let inDoubleQuotes = false;
let i;
@@ -133,7 +133,7 @@ function readPiExp(parser) {
if(inSingleQuotes || inDoubleQuotes){
throw new Error("Invalid attribute expression. Quote is not properly closed in PI tag expression");
}else if(!EOE) throw new Error("Unexpected closing of source. Waiting for '?>'");
if(!parser.options.attributes.ignore){
//TODO: use regex to verify attributes if not set to ignore
}
@@ -150,7 +150,11 @@ function buildTagExpObj(exp, parser){
};
let attrsExp = "";
if(exp[exp.length -1] === "/") tagExp.selfClosing = true;
// Check for self-closing tag before setting the name
if(exp[exp.length -1] === "/") {
tagExp.selfClosing = true;
exp = exp.slice(0, -1); // Remove the trailing slash
}
//separate tag name
let i = 0;
@@ -182,7 +186,7 @@ function parseAttributesExp(attrStr, parser) {
for (let i = 0; i < len; i++) {
let attrName = parser.processAttrName(matches[i][1]);
let attrVal = parser.replaceEntities(matches[i][4] || true);
parser.outputBuilder.addAttribute(attrName, attrVal);
}
}
@@ -204,9 +208,3 @@ const getAllMatches = function(string, regex) {
return matches;
};
module.exports = {
readStopNode: readStopNode,
readClosingTagName: readClosingTagName,
readTagExp: readTagExp,
readPiExp: readPiExp,
}
@@ -1,6 +1,6 @@
const {readPiExp} = require("./XmlPartReader");
import {readPiExp} from './XmlPartReader.js';
function readCdata(parser){
export function readCdata(parser){
//<![ are already read till this point
let str = parser.source.readStr(6); //CDATA[
parser.source.updateBufferBoundary(6);
@@ -10,7 +10,7 @@ function readCdata(parser){
let text = parser.source.readUpto("]]>");
parser.outputBuilder.addCdata(text);
}
function readPiTag(parser){
export function readPiTag(parser){
//<? are already read till this point
let tagExp = readPiExp(parser, "?>");
if(!tagExp) throw new Error("Invalid Pi Tag expression.");
@@ -22,7 +22,7 @@ function readPiTag(parser){
}
}
function readComment(parser){
export function readComment(parser){
//<!- are already read till this point
let ch = parser.source.readCh();
if(ch !== "-") throw new Error(`Invalid comment expression at ${parser.source.line}:${parser.source.cols}`);
@@ -36,7 +36,7 @@ const DOCTYPE_tags = {
"AT":/^TLIST\s+[^\s]+\s+[^\s]+\s+[^\s]+\s+[^\s]+\s+$/m,
"NO":/^TATION.+$/m
}
function readDocType(parser){
export function readDocType(parser){
//<!D are already read till this point
let str = parser.source.readStr(6); //OCTYPE
parser.source.updateBufferBoundary(6);
@@ -109,10 +109,3 @@ function registerEntity(parser){
}
}
}
module.exports = {
readCdata: readCdata,
readComment:readComment,
readDocType:readDocType,
readPiTag:readPiTag
}
@@ -2,7 +2,7 @@ const Constants = {
space: 32,
tab: 9
}
class BufferSource{
export default class BufferSource{
constructor(bytesArr){
this.line = 1;
this.cols = 0;
@@ -114,5 +114,3 @@ readUptoCloseTag(stopStr) { //stopStr: "</tagname"
}
}
module.exports = BufferSource;
@@ -1,7 +1,7 @@
const whiteSpaces = [" ", "\n", "\t"];
class StringSource{
export default class StringSource{
constructor(str){
this.line = 1;
this.cols = 0;
@@ -119,5 +119,3 @@ class StringSource{
}
}
module.exports = StringSource;
@@ -13,11 +13,11 @@ const htmlEntities = {
"copyright" : { regex: /&(copy|#169);/g, val: "©" },
"reg" : { regex: /&(reg|#174);/g, val: "®" },
"inr" : { regex: /&(inr|#8377);/g, val: "₹" },
"num_dec": { regex: /&#([0-9]{1,7});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 10)) },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 16)) },
"num_dec": { regex: /&#([0-9]{1,7});/g, val : (_, str) => String.fromCodePoint(Number.parseInt(str, 10)) },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCodePoint(Number.parseInt(str, 16)) },
};
class EntitiesParser{
export default class EntitiesParser{
constructor(replaceHtmlEntities) {
this.replaceHtmlEntities = replaceHtmlEntities;
this.docTypeEntities = {};
@@ -103,5 +103,3 @@ function validateEntityName(name){
}
return name;
}
module.exports = EntitiesParser;
@@ -1,4 +1,4 @@
class boolParser{
export default class boolParser{
constructor(trueList, falseList){
if(trueList)
this.trueList = trueList;
@@ -20,4 +20,3 @@ class boolParser{
return val;
}
}
module.exports = boolParser;
@@ -1,4 +1,4 @@
function boolParserExt(val){
export default function boolParserExt(val){
if(isArray(val)){
for (let i = 0; i < val.length; i++) {
val[i] = parse(val[i])
@@ -17,4 +17,3 @@ function parse(val){
}
return val;
}
module.exports = boolParserExt;
@@ -16,7 +16,7 @@ const symbol = "(?:\$|€|¥|₹)?";
const currencyCheckRegex = /^\s*(?:-|\+)?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d{1,2})?\s*(?:\$|€|¥|₹)?\s*$/u;
class CurrencyParser{
export default class CurrencyParser{
constructor(options){
this.options = options || defaultOptions;
}
@@ -36,5 +36,3 @@ class CurrencyParser{
}
}
CurrencyParser.defaultOptions = defaultOptions;
module.exports = CurrencyParser;
@@ -4,11 +4,10 @@
* @param {string} by
* @returns
*/
function join(val, by=" "){
export default function join(val, by=" "){
if(isArray(val)){
val.join(by)
}
return val;
}
module.exports = join;
@@ -1,7 +1,7 @@
const toNumber = require("strnum");
import toNumber from "strnum";
class numParser{
export default class numParser{
constructor(options){
this.options = options;
}
@@ -12,5 +12,3 @@ class numParser{
return val;
}
}
module.exports = numParser;
@@ -1,8 +1,6 @@
class trimmer{
export default class trimmer{
parse(val){
if(typeof val === "string") return val.trim();
else return val;
}
}
module.exports = trimmer;
+5 -5
View File
@@ -1,6 +1,6 @@
'use strict';
const util = require('./util');
import {getAllMatches, isName} from './util.js';
const defaultOptions = {
allowBooleanAttributes: false, //A tag can have attributes without any value
@@ -8,7 +8,7 @@ const defaultOptions = {
};
//const tagsPattern = new RegExp("<\\/?([\\w:\\-_\.]+)\\s*\/?>","g");
exports.validate = function (xmlData, options) {
export function validate(xmlData, options) {
options = Object.assign({}, defaultOptions, options);
//xmlData = xmlData.replace(/(\r\n|\n|\r)/gm,"");//make it single line
@@ -321,7 +321,7 @@ function validateAttributeString(attrStr, options) {
//if(attrStr.trim().length === 0) return true; //empty string
const matches = util.getAllMatches(attrStr, validAttrStrRegxp);
const matches = getAllMatches(attrStr, validAttrStrRegxp);
const attrNames = {};
for (let i = 0; i < matches.length; i++) {
@@ -399,13 +399,13 @@ function getErrorObject(code, message, lineNumber) {
}
function validateAttrName(attrName) {
return util.isName(attrName);
return isName(attrName);
}
// const startsWithXML = /^xml/i;
function validateTagName(tagname) {
return util.isName(tagname) /* && !tagname.match(startsWithXML) */;
return isName(tagname) /* && !tagname.match(startsWithXML) */;
}
//this function returns the line number for the character at the given index
+17 -13
View File
@@ -1,6 +1,7 @@
'use strict';
//parse Empty Node as self closing node
const buildFromOrderedJs = require('./orderedJs2Xml');
import buildFromOrderedJs from './orderedJs2Xml.js';
import getIgnoreAttributesFn from "../ignoreAttributes.js";
const defaultOptions = {
attributeNamePrefix: '@_',
@@ -36,13 +37,14 @@ const defaultOptions = {
oneListGroup: false
};
function Builder(options) {
export default function Builder(options) {
this.options = Object.assign({}, defaultOptions, options);
if (this.options.ignoreAttributes || this.options.attributesGroupName) {
if (this.options.ignoreAttributes === true || this.options.attributesGroupName) {
this.isAttribute = function(/*a*/) {
return false;
};
} else {
this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
this.attrPrefixLen = this.options.attributeNamePrefix.length;
this.isAttribute = isAttribute;
}
@@ -71,13 +73,14 @@ Builder.prototype.build = function(jObj) {
[this.options.arrayNodeName] : jObj
}
}
return this.j2x(jObj, 0).val;
return this.j2x(jObj, 0, []).val;
}
};
Builder.prototype.j2x = function(jObj, level) {
Builder.prototype.j2x = function(jObj, level, ajPath) {
let attrStr = '';
let val = '';
const jPath = ajPath.join('.')
for (let key in jObj) {
if(!Object.prototype.hasOwnProperty.call(jObj, key)) continue;
if (typeof jObj[key] === 'undefined') {
@@ -89,6 +92,8 @@ Builder.prototype.j2x = function(jObj, level) {
// null attribute should be ignored by the attribute list, but should not cause the tag closing
if (this.isAttribute(key)) {
val += '';
} else if (key === this.options.cdataPropName) {
val += '';
} else if (key[0] === '?') {
val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
} else {
@@ -100,9 +105,9 @@ Builder.prototype.j2x = function(jObj, level) {
} else if (typeof jObj[key] !== 'object') {
//premitive type
const attr = this.isAttribute(key);
if (attr) {
if (attr && !this.ignoreAttributesFn(attr, jPath)) {
attrStr += this.buildAttrPairStr(attr, '' + jObj[key]);
}else {
} else if (!attr) {
//tag value
if (key === this.options.textNodeName) {
let newval = this.options.tagValueProcessor(key, '' + jObj[key]);
@@ -126,13 +131,13 @@ Builder.prototype.j2x = function(jObj, level) {
// val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
} else if (typeof item === 'object') {
if(this.options.oneListGroup){
const result = this.j2x(item, level + 1);
const result = this.j2x(item, level + 1, ajPath.concat(key));
listTagVal += result.val;
if (this.options.attributesGroupName && item.hasOwnProperty(this.options.attributesGroupName)) {
listTagAttr += result.attrStr
}
}else{
listTagVal += this.processTextOrObjNode(item, key, level)
listTagVal += this.processTextOrObjNode(item, key, level, ajPath)
}
} else {
if (this.options.oneListGroup) {
@@ -157,7 +162,7 @@ Builder.prototype.j2x = function(jObj, level) {
attrStr += this.buildAttrPairStr(Ks[j], '' + jObj[key][Ks[j]]);
}
} else {
val += this.processTextOrObjNode(jObj[key], key, level)
val += this.processTextOrObjNode(jObj[key], key, level, ajPath)
}
}
}
@@ -172,8 +177,8 @@ Builder.prototype.buildAttrPairStr = function(attrName, val){
} else return ' ' + attrName + '="' + val + '"';
}
function processTextOrObjNode (object, key, level) {
const result = this.j2x(object, level + 1);
function processTextOrObjNode (object, key, level, ajPath) {
const result = this.j2x(object, level + 1, ajPath.concat(key));
if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) {
return this.buildTextValNode(object[this.options.textNodeName], key, result.attrStr, level);
} else {
@@ -278,4 +283,3 @@ function isAttribute(name /*, options*/) {
}
}
module.exports = Builder;
@@ -6,7 +6,7 @@ const EOL = "\n";
* @param {any} options
* @returns
*/
function toXml(jArray, options) {
export default function toXml(jArray, options) {
let indentation = "";
if (options.format && options.indentBy.length > 0) {
indentation = EOL;
@@ -132,4 +132,3 @@ function replaceEntitiesValue(textValue, options) {
}
return textValue;
}
module.exports = toXml;
@@ -1,7 +1,7 @@
const util = require('../util');
import {isName} from '../util.js';
//TODO: handle comments
function readDocType(xmlData, i){
export default function readDocType(xmlData, i){
const entities = {};
if( xmlData[i + 3] === 'O' &&
@@ -17,20 +17,30 @@ function readDocType(xmlData, i){
let exp = "";
for(;i<xmlData.length;i++){
if (xmlData[i] === '<' && !comment) { //Determine the tag type
if( hasBody && isEntity(xmlData, i)){
if( hasBody && hasSeq(xmlData, "!ENTITY",i)){
i += 7;
let entityName, val;
[entityName, val,i] = readEntityExp(xmlData,i+1);
if(val.indexOf("&") === -1) //Parameter entities are not supported
entities[ validateEntityName(entityName) ] = {
entities[ entityName ] = {
regx : RegExp( `&${entityName};`,"g"),
val: val
};
}
else if( hasBody && isElement(xmlData, i)) i += 8;//Not supported
else if( hasBody && isAttlist(xmlData, i)) i += 8;//Not supported
else if( hasBody && isNotation(xmlData, i)) i += 9;//Not supported
else if( isComment) comment = true;
else throw new Error("Invalid DOCTYPE");
else if( hasBody && hasSeq(xmlData, "!ELEMENT",i)) {
i += 8;//Not supported
const {index} = readElementExp(xmlData,i+1);
i = index;
}else if( hasBody && hasSeq(xmlData, "!ATTLIST",i)){
i += 8;//Not supported
// const {index} = readAttlistExp(xmlData,i+1);
// i = index;
}else if( hasBody && hasSeq(xmlData, "!NOTATION",i)) {
i += 9;//Not supported
const {index} = readNotationExp(xmlData,i+1);
i = index;
}else if( hasSeq(xmlData, "!--",i) ) comment = true;
else throw new Error(`Invalid DOCTYPE`);
angleBracketsCount++;
exp = "";
@@ -61,7 +71,14 @@ function readDocType(xmlData, i){
return {entities, i};
}
function readEntityExp(xmlData,i){
const skipWhitespace = (data, index) => {
while (index < data.length && /\s/.test(data[index])) {
index++;
}
return index;
};
function readEntityExp(xmlData, i) {
//External entities are not supported
// <!ENTITY ext SYSTEM "http://normal-website.com" >
@@ -70,83 +87,283 @@ function readEntityExp(xmlData,i){
//Internal entities are supported
// <!ENTITY entityname "replacement text">
//read EntityName
// Skip leading whitespace after <!ENTITY
i = skipWhitespace(xmlData, i);
// Read entity name
let entityName = "";
for (; i < xmlData.length && (xmlData[i] !== "'" && xmlData[i] !== '"' ); i++) {
// if(xmlData[i] === " ") continue;
// else
while (i < xmlData.length && !/\s/.test(xmlData[i]) && xmlData[i] !== '"' && xmlData[i] !== "'") {
entityName += xmlData[i];
i++;
}
entityName = entityName.trim();
if(entityName.indexOf(" ") !== -1) throw new Error("External entites are not supported");
validateEntityName(entityName);
//read Entity Value
const startChar = xmlData[i++];
let val = ""
for (; i < xmlData.length && xmlData[i] !== startChar ; i++) {
val += xmlData[i];
// Skip whitespace after entity name
i = skipWhitespace(xmlData, i);
// Check for unsupported constructs (external entities or parameter entities)
if (xmlData.substring(i, i + 6).toUpperCase() === "SYSTEM") {
throw new Error("External entities are not supported");
}else if (xmlData[i] === "%") {
throw new Error("Parameter entities are not supported");
}
return [entityName, val, i];
// Read entity value (internal entity)
let entityValue = "";
[i, entityValue] = readIdentifierVal(xmlData, i, "entity");
i--;
return [entityName, entityValue, i ];
}
function isComment(xmlData, i){
if(xmlData[i+1] === '!' &&
xmlData[i+2] === '-' &&
xmlData[i+3] === '-') return true
return false
}
function isEntity(xmlData, i){
if(xmlData[i+1] === '!' &&
xmlData[i+2] === 'E' &&
xmlData[i+3] === 'N' &&
xmlData[i+4] === 'T' &&
xmlData[i+5] === 'I' &&
xmlData[i+6] === 'T' &&
xmlData[i+7] === 'Y') return true
return false
}
function isElement(xmlData, i){
if(xmlData[i+1] === '!' &&
xmlData[i+2] === 'E' &&
xmlData[i+3] === 'L' &&
xmlData[i+4] === 'E' &&
xmlData[i+5] === 'M' &&
xmlData[i+6] === 'E' &&
xmlData[i+7] === 'N' &&
xmlData[i+8] === 'T') return true
return false
function readNotationExp(xmlData, i) {
// Skip leading whitespace after <!NOTATION
i = skipWhitespace(xmlData, i);
// Read notation name
let notationName = "";
while (i < xmlData.length && !/\s/.test(xmlData[i])) {
notationName += xmlData[i];
i++;
}
validateEntityName(notationName);
// Skip whitespace after notation name
i = skipWhitespace(xmlData, i);
// Check identifier type (SYSTEM or PUBLIC)
const identifierType = xmlData.substring(i, i + 6).toUpperCase();
if (identifierType !== "SYSTEM" && identifierType !== "PUBLIC") {
throw new Error(`Expected SYSTEM or PUBLIC, found "${identifierType}"`);
}
i += identifierType.length;
// Skip whitespace after identifier type
i = skipWhitespace(xmlData, i);
// Read public identifier (if PUBLIC)
let publicIdentifier = null;
let systemIdentifier = null;
if (identifierType === "PUBLIC") {
[i, publicIdentifier ] = readIdentifierVal(xmlData, i, "publicIdentifier");
// Skip whitespace after public identifier
i = skipWhitespace(xmlData, i);
// Optionally read system identifier
if (xmlData[i] === '"' || xmlData[i] === "'") {
[i, systemIdentifier ] = readIdentifierVal(xmlData, i,"systemIdentifier");
}
} else if (identifierType === "SYSTEM") {
// Read system identifier (mandatory for SYSTEM)
[i, systemIdentifier ] = readIdentifierVal(xmlData, i, "systemIdentifier");
if (!systemIdentifier) {
throw new Error("Missing mandatory system identifier for SYSTEM notation");
}
}
return {notationName, publicIdentifier, systemIdentifier, index: --i};
}
function isAttlist(xmlData, i){
if(xmlData[i+1] === '!' &&
xmlData[i+2] === 'A' &&
xmlData[i+3] === 'T' &&
xmlData[i+4] === 'T' &&
xmlData[i+5] === 'L' &&
xmlData[i+6] === 'I' &&
xmlData[i+7] === 'S' &&
xmlData[i+8] === 'T') return true
return false
function readIdentifierVal(xmlData, i, type) {
let identifierVal = "";
const startChar = xmlData[i];
if (startChar !== '"' && startChar !== "'") {
throw new Error(`Expected quoted string, found "${startChar}"`);
}
i++;
while (i < xmlData.length && xmlData[i] !== startChar) {
identifierVal += xmlData[i];
i++;
}
if (xmlData[i] !== startChar) {
throw new Error(`Unterminated ${type} value`);
}
i++;
return [i, identifierVal];
}
function isNotation(xmlData, i){
if(xmlData[i+1] === '!' &&
xmlData[i+2] === 'N' &&
xmlData[i+3] === 'O' &&
xmlData[i+4] === 'T' &&
xmlData[i+5] === 'A' &&
xmlData[i+6] === 'T' &&
xmlData[i+7] === 'I' &&
xmlData[i+8] === 'O' &&
xmlData[i+9] === 'N') return true
return false
function readElementExp(xmlData, i) {
// <!ELEMENT br EMPTY>
// <!ELEMENT div ANY>
// <!ELEMENT title (#PCDATA)>
// <!ELEMENT book (title, author+)>
// <!ELEMENT name (content-model)>
// Skip leading whitespace after <!ELEMENT
i = skipWhitespace(xmlData, i);
// Read element name
let elementName = "";
while (i < xmlData.length && !/\s/.test(xmlData[i])) {
elementName += xmlData[i];
i++;
}
// Validate element name
if (!validateEntityName(elementName)) {
throw new Error(`Invalid element name: "${elementName}"`);
}
// Skip whitespace after element name
i = skipWhitespace(xmlData, i);
let contentModel = "";
// Expect '(' to start content model
if(xmlData[i] === "E" && hasSeq(xmlData, "MPTY",i)) i+=4;
else if(xmlData[i] === "A" && hasSeq(xmlData, "NY",i)) i+=2;
else if (xmlData[i] === "(") {
i++; // Move past '('
// Read content model
while (i < xmlData.length && xmlData[i] !== ")") {
contentModel += xmlData[i];
i++;
}
if (xmlData[i] !== ")") {
throw new Error("Unterminated content model");
}
}else{
throw new Error(`Invalid Element Expression, found "${xmlData[i]}"`);
}
return {
elementName,
contentModel: contentModel.trim(),
index: i
};
}
function readAttlistExp(xmlData, i) {
// Skip leading whitespace after <!ATTLIST
i = skipWhitespace(xmlData, i);
// Read element name
let elementName = "";
while (i < xmlData.length && !/\s/.test(xmlData[i])) {
elementName += xmlData[i];
i++;
}
// Validate element name
validateEntityName(elementName)
// Skip whitespace after element name
i = skipWhitespace(xmlData, i);
// Read attribute name
let attributeName = "";
while (i < xmlData.length && !/\s/.test(xmlData[i])) {
attributeName += xmlData[i];
i++;
}
// Validate attribute name
if (!validateEntityName(attributeName)) {
throw new Error(`Invalid attribute name: "${attributeName}"`);
}
// Skip whitespace after attribute name
i = skipWhitespace(xmlData, i);
// Read attribute type
let attributeType = "";
if (xmlData.substring(i, i + 8).toUpperCase() === "NOTATION") {
attributeType = "NOTATION";
i += 8; // Move past "NOTATION"
// Skip whitespace after "NOTATION"
i = skipWhitespace(xmlData, i);
// Expect '(' to start the list of notations
if (xmlData[i] !== "(") {
throw new Error(`Expected '(', found "${xmlData[i]}"`);
}
i++; // Move past '('
// Read the list of allowed notations
let allowedNotations = [];
while (i < xmlData.length && xmlData[i] !== ")") {
let notation = "";
while (i < xmlData.length && xmlData[i] !== "|" && xmlData[i] !== ")") {
notation += xmlData[i];
i++;
}
// Validate notation name
notation = notation.trim();
if (!validateEntityName(notation)) {
throw new Error(`Invalid notation name: "${notation}"`);
}
allowedNotations.push(notation);
// Skip '|' separator or exit loop
if (xmlData[i] === "|") {
i++; // Move past '|'
i = skipWhitespace(xmlData, i); // Skip optional whitespace after '|'
}
}
if (xmlData[i] !== ")") {
throw new Error("Unterminated list of notations");
}
i++; // Move past ')'
// Store the allowed notations as part of the attribute type
attributeType += " (" + allowedNotations.join("|") + ")";
} else {
// Handle simple types (e.g., CDATA, ID, IDREF, etc.)
while (i < xmlData.length && !/\s/.test(xmlData[i])) {
attributeType += xmlData[i];
i++;
}
// Validate simple attribute type
const validTypes = ["CDATA", "ID", "IDREF", "IDREFS", "ENTITY", "ENTITIES", "NMTOKEN", "NMTOKENS"];
if (!validTypes.includes(attributeType.toUpperCase())) {
throw new Error(`Invalid attribute type: "${attributeType}"`);
}
}
// Skip whitespace after attribute type
i = skipWhitespace(xmlData, i);
// Read default value
let defaultValue = "";
if (xmlData.substring(i, i + 8).toUpperCase() === "#REQUIRED") {
defaultValue = "#REQUIRED";
i += 8;
} else if (xmlData.substring(i, i + 7).toUpperCase() === "#IMPLIED") {
defaultValue = "#IMPLIED";
i += 7;
} else {
[i, defaultValue] = readIdentifierVal(xmlData, i, "ATTLIST");
}
return {
elementName,
attributeName,
attributeType,
defaultValue,
index: i
}
}
function hasSeq(data, seq,i){
for(let j=0;j<seq.length;j++){
if(seq[j]!==data[i+j+1]) return false;
}
return true;
}
function validateEntityName(name){
if (util.isName(name))
if (isName(name))
return name;
else
throw new Error(`Invalid entity name ${name}`);
}
module.exports = readDocType;
@@ -1,5 +1,5 @@
const defaultOptions = {
export const defaultOptions = {
preserveOrder: false,
attributeNamePrefix: '@_',
attributesGroupName: false,
@@ -38,11 +38,9 @@ const defaultOptions = {
return tagName
},
// skipEmptyListItem: false
captureMetaData: false,
};
const buildOptions = function(options) {
export const buildOptions = function(options) {
return Object.assign({}, defaultOptions, options);
};
exports.buildOptions = buildOptions;
exports.defaultOptions = defaultOptions;
@@ -1,10 +1,11 @@
'use strict';
///@ts-check
const util = require('../util');
const xmlNode = require('./xmlNode');
const readDocType = require("./DocTypeReader");
const toNumber = require("strnum");
import {getAllMatches, isExist} from '../util.js';
import xmlNode from './xmlNode.js';
import readDocType from './DocTypeReader.js';
import toNumber from "strnum";
import getIgnoreAttributesFn from "../ignoreAttributes.js";
// const regx =
// '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
@@ -13,7 +14,7 @@ const toNumber = require("strnum");
//const tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>(\\s*"+cdataRegx+")*([^<]+)?","g");
//const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g");
class OrderedObjParser{
export default class OrderedObjParser{
constructor(options){
this.options = options;
this.currentNode = null;
@@ -40,8 +41,8 @@ class OrderedObjParser{
"copyright" : { regex: /&(copy|#169);/g, val: "©" },
"reg" : { regex: /&(reg|#174);/g, val: "®" },
"inr" : { regex: /&(inr|#8377);/g, val: "₹" },
"num_dec": { regex: /&#([0-9]{1,7});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 10)) },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 16)) },
"num_dec": { regex: /&#([0-9]{1,7});/g, val : (_, str) => String.fromCodePoint(Number.parseInt(str, 10)) },
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCodePoint(Number.parseInt(str, 16)) },
};
this.addExternalEntities = addExternalEntities;
this.parseXml = parseXml;
@@ -53,6 +54,7 @@ class OrderedObjParser{
this.readStopNodeData = readStopNodeData;
this.saveTextToParentTag = saveTextToParentTag;
this.addChild = addChild;
this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
}
}
@@ -125,15 +127,18 @@ function resolveNameSpace(tagname) {
const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
function buildAttributesMap(attrStr, jPath, tagName) {
if (!this.options.ignoreAttributes && typeof attrStr === 'string') {
if (this.options.ignoreAttributes !== true && typeof attrStr === 'string') {
// attrStr = attrStr.replace(/\r?\n/g, ' ');
//attrStr = attrStr || attrStr.trim();
const matches = util.getAllMatches(attrStr, attrsRegx);
const matches = getAllMatches(attrStr, attrsRegx);
const len = matches.length; //don't make it inline
const attrs = {};
for (let i = 0; i < len; i++) {
const attrName = this.resolveNameSpace(matches[i][1]);
if (this.ignoreAttributesFn(attrName, jPath)) {
continue
}
let oldVal = matches[i][4];
let aName = this.options.attributeNamePrefix + attrName;
if (attrName.length) {
@@ -241,8 +246,7 @@ const parseXml = function(xmlData) {
if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){
childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath, tagData.tagName);
}
this.addChild(currentNode, childNode, jPath)
this.addChild(currentNode, childNode, jPath, i);
}
@@ -307,6 +311,7 @@ const parseXml = function(xmlData) {
if(tagName !== xmlObj.tagname){
jPath += jPath ? "." + tagName : tagName;
}
const startIndex = i;
if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) {
let tagContent = "";
//self-closing tag
@@ -335,6 +340,7 @@ const parseXml = function(xmlData) {
}
const childNode = new xmlNode(tagName);
if(tagName !== tagExp && attrExpPresent){
childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
}
@@ -345,7 +351,7 @@ const parseXml = function(xmlData) {
jPath = jPath.substr(0, jPath.lastIndexOf("."));
childNode.add(this.options.textNodeName, tagContent);
this.addChild(currentNode, childNode, jPath)
this.addChild(currentNode, childNode, jPath, startIndex);
}else{
//selfClosing tag
if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
@@ -365,7 +371,7 @@ const parseXml = function(xmlData) {
if(tagName !== tagExp && attrExpPresent){
childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
}
this.addChild(currentNode, childNode, jPath)
this.addChild(currentNode, childNode, jPath, startIndex);
jPath = jPath.substr(0, jPath.lastIndexOf("."));
}
//opening tag
@@ -376,7 +382,7 @@ const parseXml = function(xmlData) {
if(tagName !== tagExp && attrExpPresent){
childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
}
this.addChild(currentNode, childNode, jPath)
this.addChild(currentNode, childNode, jPath, startIndex);
currentNode = childNode;
}
textData = "";
@@ -390,14 +396,16 @@ const parseXml = function(xmlData) {
return xmlObj.child;
}
function addChild(currentNode, childNode, jPath){
function addChild(currentNode, childNode, jPath, startIndex){
// unset startIndex if not requested
if (!this.options.captureMetaData) startIndex = undefined;
const result = this.options.updateTag(childNode.tagname, jPath, childNode[":@"])
if(result === false){
}else if(typeof result === "string"){
} else if(typeof result === "string"){
childNode.tagname = result
currentNode.addChild(childNode);
currentNode.addChild(childNode, startIndex);
}else{
currentNode.addChild(childNode);
currentNode.addChild(childNode, startIndex);
}
}
@@ -424,7 +432,7 @@ const replaceEntitiesValue = function(val){
}
function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
if (textData) { //store previously collected data as textNode
if(isLeafNode === undefined) isLeafNode = Object.keys(currentNode.child).length === 0
if(isLeafNode === undefined) isLeafNode = currentNode.child.length === 0
textData = this.parseTextData(textData,
currentNode.tagname,
@@ -589,13 +597,10 @@ function parseValue(val, shouldParse, options) {
else if(newval === 'false' ) return false;
else return toNumber(val, options);
} else {
if (util.isExist(val)) {
if (isExist(val)) {
return val;
} else {
return '';
}
}
}
module.exports = OrderedObjParser;
@@ -1,9 +1,10 @@
const { buildOptions} = require("./OptionsBuilder");
const OrderedObjParser = require("./OrderedObjParser");
const { prettify} = require("./node2json");
const validator = require('../validator');
import { buildOptions} from './OptionsBuilder.js';
import OrderedObjParser from './OrderedObjParser.js';
import prettify from './node2json.js';
import {validate} from "../validator.js";
import XmlNode from './xmlNode.js';
class XMLParser{
export default class XMLParser{
constructor(options){
this.externalEntities = {};
@@ -25,7 +26,7 @@ class XMLParser{
if( validationOption){
if(validationOption === true) validationOption = {}; //validate with default options
const result = validator.validate(xmlData, validationOption);
const result = validate(xmlData, validationOption);
if (result !== true) {
throw Error( `${result.err.msg}:${result.err.line}:${result.err.col}` )
}
@@ -53,6 +54,18 @@ class XMLParser{
this.externalEntities[key] = value;
}
}
}
module.exports = XMLParser;
/**
* Returns a Symbol that can be used to access the metadata
* property on a node.
*
* If Symbol is not available in the environment, an ordinary property is used
* and the name of the property is here returned.
*
* The XMLMetaData property is only present when `captureMetaData`
* is true in the options.
*/
static getMetaDataSymbol() {
return XmlNode.getMetaDataSymbol();
}
}
@@ -1,12 +1,16 @@
'use strict';
import XmlNode from './xmlNode.js';
const METADATA_SYMBOL = XmlNode.getMetaDataSymbol();
/**
*
* @param {array} node
* @param {any} options
* @returns
*/
function prettify(node, options){
export default function prettify(node, options){
return compress( node, options);
}
@@ -36,6 +40,9 @@ function compress(arr, options, jPath){
let val = compress(tagObj[property], options, newJpath);
const isLeaf = isLeafTag(val, options);
if (tagObj[METADATA_SYMBOL] !== undefined) {
val[METADATA_SYMBOL] = tagObj[METADATA_SYMBOL]; // copy over metadata
}
if(tagObj[":@"]){
assignAttributes( val, tagObj[":@"], newJpath, options);
@@ -110,4 +117,3 @@ function isLeafTag(obj, options){
return false;
}
exports.prettify = prettify;
+22 -7
View File
@@ -1,6 +1,14 @@
'use strict';
class XmlNode{
let METADATA_SYMBOL;
if (typeof Symbol !== "function") {
METADATA_SYMBOL = "@@xmlMetadata";
} else {
METADATA_SYMBOL = Symbol("XML Node Metadata");
}
export default class XmlNode{
constructor(tagname) {
this.tagname = tagname;
this.child = []; //nested tags, text, cdata, comments in order
@@ -11,15 +19,22 @@ class XmlNode{
if(key === "__proto__") key = "#__proto__";
this.child.push( {[key]: val });
}
addChild(node) {
addChild(node, startIndex) {
if(node.tagname === "__proto__") node.tagname = "#__proto__";
if(node[":@"] && Object.keys(node[":@"]).length > 0){
this.child.push( { [node.tagname]: node.child, [":@"]: node[":@"] });
}else{
this.child.push( { [node.tagname]: node.child });
}
};
};
module.exports = XmlNode;
// if requested, add the startIndex
if (startIndex !== undefined) {
// Note: for now we just overwrite the metadata. If we had more complex metadata,
// we might need to do an object append here: metadata = { ...metadata, startIndex }
this.child[this.child.length - 1][METADATA_SYMBOL] = { startIndex };
}
}
/** symbol used for metadata */
static getMetaDataSymbol() {
return METADATA_SYMBOL;
}
}