Skip to content

Commit d7601c3

Browse files
committed
fix #593: support line numbers
- add a new symbol, accessible via XMLParser.getStartIndexSymbol() which reflects the start index of a tag - copy that start index in the compressed form as well
1 parent 292fb78 commit d7601c3

File tree

7 files changed

+69
-10
lines changed

7 files changed

+69
-10
lines changed

docs/v4/1.GettingStarted.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,21 @@ if(XMLValidator.validate()){
3333
}
3434
```
3535

36-
[> Next: XmlParser](./2.XMLparseOptions.md)
36+
## Start index
37+
38+
You can get the 'start index' which is the character offset of a particular node.
39+
This is not available for nodes that appear as strings.
40+
41+
```js
42+
const {XMLParser} = require('fast-xml-parser');
43+
44+
const parser = new XMLParser({ignoreAttributes: false});
45+
const jsonObj = parser.parse(`<root><thing name="zero"/><thing name="one"/></root>`);
46+
const START_INDEX = parser.getStartIndexSymbol();
47+
// get the char offset of the start of the tag for <thing name="zero"/>
48+
const thingZero = jsonObj.root.thing[0];
49+
const thingZeroIndex = thingZero[START_INDEX]; // 6
50+
```
51+
52+
53+
[> Next: XmlParser](./2.XMLparseOptions.md)

spec/startIndex_spec.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
const {XMLParser, XMLBuilder, XMLValidator} = require("../src/fxp");
3+
4+
describe("XMLParser", function() {
5+
6+
it("should support the START_INDEX symbol ", function(){
7+
const xmlData = `<root><foo/><bar type="quux"/><bar type="bat"/></root>`;
8+
const expected = {
9+
root: {
10+
foo: '',
11+
bar: [
12+
{ "@_type": 'quux'},
13+
{ "@_type": 'bat'},
14+
],
15+
}
16+
};
17+
18+
const parser = new XMLParser({preserveOrder:false, ignoreAttributes: false});
19+
const START_INDEX = parser.getStartIndexSymbol();
20+
const result = parser.parse(xmlData);
21+
// console.dir(result, {depth:Infinity});
22+
expect(result).toEqual(expected);
23+
expect(result.root[START_INDEX]).toEqual(0); // index of <root>
24+
// expect(result.root.foo[START_INDEX]).toEqual(6); // index of <foo/> - but there's no object, just a string.
25+
expect(result.root.bar[0][START_INDEX]).toEqual(12);
26+
expect(result.root.bar[1][START_INDEX]).toEqual(30);
27+
});
28+
});

src/fxp.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ export class XMLParser {
8888
* @param entityValue {string} Eg: '\r'
8989
*/
9090
addEntity(entityIndentifier: string, entityValue: string): void;
91+
/** Returns a Symbol that can be used to extract the node start index. */
92+
getStartIndexSymbol() : Symbol;
9193
}
9294

9395
export class XMLValidator{

src/xmlparser/OrderedObjParser.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const regx =
1414
//const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g");
1515

1616
class OrderedObjParser{
17+
static START_INDEX = Symbol("Start Index of XML Node");
1718
constructor(options){
1819
this.options = options;
1920
this.currentNode = null;
@@ -226,7 +227,7 @@ const parseXml = function(xmlData) {
226227
if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){
227228
childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath);
228229
}
229-
currentNode.addChild(childNode);
230+
currentNode.addChild(childNode, OrderedObjParser.START_INDEX, i);
230231

231232
}
232233

@@ -293,6 +294,8 @@ const parseXml = function(xmlData) {
293294
currentNode = this.tagsNodeStack.pop();
294295
}
295296

297+
const startIndex = i;
298+
296299
if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) { //TODO: namespace
297300
let tagContent = "";
298301
//self-closing tag
@@ -313,6 +316,7 @@ const parseXml = function(xmlData) {
313316
}
314317

315318
const childNode = new xmlNode(tagName);
319+
316320
if(tagName !== tagExp && attrExpPresent){
317321
childNode[":@"] = this.buildAttributesMap(tagExp, jPath);
318322
}
@@ -323,7 +327,7 @@ const parseXml = function(xmlData) {
323327
jPath = jPath.substr(0, jPath.lastIndexOf("."));
324328
childNode.add(this.options.textNodeName, tagContent);
325329

326-
currentNode.addChild(childNode);
330+
currentNode.addChild(childNode, OrderedObjParser.START_INDEX, startIndex);
327331
}else{
328332
//selfClosing tag
329333
if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
@@ -343,7 +347,7 @@ const parseXml = function(xmlData) {
343347
childNode[":@"] = this.buildAttributesMap(tagExp, jPath);
344348
}
345349
jPath = jPath.substr(0, jPath.lastIndexOf("."));
346-
currentNode.addChild(childNode);
350+
currentNode.addChild(childNode, OrderedObjParser.START_INDEX, startIndex);
347351
}
348352
//opening tag
349353
else{
@@ -353,7 +357,7 @@ const parseXml = function(xmlData) {
353357
if(tagName !== tagExp && attrExpPresent){
354358
childNode[":@"] = this.buildAttributesMap(tagExp, jPath);
355359
}
356-
currentNode.addChild(childNode);
360+
currentNode.addChild(childNode, OrderedObjParser.START_INDEX, startIndex);
357361
currentNode = childNode;
358362
}
359363
textData = "";

src/xmlparser/XMLParser.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ class XMLParser{
5353
this.externalEntities[key] = value;
5454
}
5555
}
56+
57+
/** get the symbol used for the start index on nodes */
58+
getStartIndexSymbol() {
59+
return OrderedObjParser.START_INDEX;
60+
}
5661
}
5762

58-
module.exports = XMLParser;
63+
module.exports = XMLParser;

src/xmlparser/node2json.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
const { START_INDEX } = require('./OrderedObjParser');
4+
35
/**
46
*
57
* @param {array} node
@@ -36,6 +38,7 @@ function compress(arr, options, jPath){
3638

3739
let val = compress(tagObj[property], options, newJpath);
3840
const isLeaf = isLeafTag(val, options);
41+
val[START_INDEX] = tagObj[START_INDEX]; // copy over start index
3942

4043
if(tagObj[":@"]){
4144
assignAttributes( val, tagObj[":@"], newJpath, options);

src/xmlparser/xmlNode.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ class XmlNode{
1111
if(key === "__proto__") key = "#__proto__";
1212
this.child.push( {[key]: val });
1313
}
14-
addChild(node) {
14+
addChild(node, START_INDEX, startIndex) {
1515
if(node.tagname === "__proto__") node.tagname = "#__proto__";
1616
if(node[":@"] && Object.keys(node[":@"]).length > 0){
17-
this.child.push( { [node.tagname]: node.child, [":@"]: node[":@"] });
17+
this.child.push( { [node.tagname]: node.child, [":@"]: node[":@"], [START_INDEX]: startIndex });
1818
}else{
19-
this.child.push( { [node.tagname]: node.child });
19+
this.child.push( { [node.tagname]: node.child, [START_INDEX]: startIndex });
2020
}
2121
};
2222
};
2323

2424

25-
module.exports = XmlNode;
25+
module.exports = XmlNode;

0 commit comments

Comments
 (0)