From acf8b6e4324129e8b7cec07e45c7c4897a14b341 Mon Sep 17 00:00:00 2001 From: Panayot Cankov Date: Thu, 12 Nov 2015 17:10:12 +0200 Subject: [PATCH] Add character line and column index for easysax parser events --- apps/tests/xml-parser-tests/xml.expected | 2 +- js-libs/easysax/easysax.js | 47 +++++++++++++++--------- xml/xml.d.ts | 20 ++++++++++ xml/xml.ts | 35 +++++++++++------- 4 files changed, 72 insertions(+), 32 deletions(-) diff --git a/apps/tests/xml-parser-tests/xml.expected b/apps/tests/xml-parser-tests/xml.expected index 2f0db1e67..45a144197 100644 --- a/apps/tests/xml-parser-tests/xml.expected +++ b/apps/tests/xml-parser-tests/xml.expected @@ -1 +1 @@ -{"eventType":"StartElement","elementName":"DocumentElement","attributes":{"param":"value"}}{"eventType":"StartElement","elementName":"First.Element","attributes":{"some.attr":"some.value"}}{"eventType":"Text","data":"\n ¶ Some Text ®\n "}{"eventType":"EndElement","elementName":"First.Element"}{"eventType":"StartElement","elementName":"SecondElement","attributes":{"param2":"something"}}{"eventType":"Text","data":"\n Pre-Text "}{"eventType":"StartElement","elementName":"Inline"}{"eventType":"Text","data":"Inlined text"}{"eventType":"EndElement","elementName":"Inline"}{"eventType":"Text","data":" Post-text.\n "}{"eventType":"EndElement","elementName":"SecondElement"}{"eventType":"StartElement","elementName":"entities"}{"eventType":"Text","data":"Xml tags begin with \"<\" and end with \">\" Ampersand is & and apostrophe is '"}{"eventType":"EndElement","elementName":"entities"}{"eventType":"StartElement","elementName":"script"}{"eventType":"CDATA","data":"\nfunction sum(a,b)\n{\n return a+b;\n}\n"}{"eventType":"EndElement","elementName":"script"}{"eventType":"Comment","data":"\n Hello,\n I am a multi-line XML comment.\n"}{"eventType":"EndElement","elementName":"DocumentElement"} \ No newline at end of file +{"eventType":"StartElement","position":{"line":2,"column":1},"elementName":"DocumentElement","attributes":{"param":"value"}}{"eventType":"StartElement","position":{"line":3,"column":3},"elementName":"First.Element","attributes":{"some.attr":"some.value"}}{"eventType":"Text","position":{"line":3,"column":41},"data":"\n ¶ Some Text ®\n "}{"eventType":"EndElement","position":{"line":5,"column":3},"elementName":"First.Element"}{"eventType":"StartElement","position":{"line":7,"column":3},"elementName":"SecondElement","attributes":{"param2":"something"}}{"eventType":"Text","position":{"line":7,"column":37},"data":"\n Pre-Text "}{"eventType":"StartElement","position":{"line":8,"column":14},"elementName":"Inline"}{"eventType":"Text","position":{"line":8,"column":22},"data":"Inlined text"}{"eventType":"EndElement","position":{"line":8,"column":34},"elementName":"Inline"}{"eventType":"Text","position":{"line":8,"column":43},"data":" Post-text.\n "}{"eventType":"EndElement","position":{"line":9,"column":3},"elementName":"SecondElement"}{"eventType":"StartElement","position":{"line":10,"column":3},"elementName":"entities"}{"eventType":"Text","position":{"line":10,"column":13},"data":"Xml tags begin with \"<\" and end with \">\" Ampersand is & and apostrophe is '"}{"eventType":"EndElement","position":{"line":10,"column":123},"elementName":"entities"}{"eventType":"StartElement","position":{"line":11,"column":3},"elementName":"script"}{"eventType":"CDATA","position":{"line":12,"column":5},"data":"\nfunction sum(a,b)\n{\n return a+b;\n}\n"}{"eventType":"EndElement","position":{"line":18,"column":3},"elementName":"script"}{"eventType":"Comment","position":{"line":19,"column":3},"data":"\n Hello,\n I am a multi-line XML comment.\n"}{"eventType":"EndElement","position":{"line":23,"column":1},"elementName":"DocumentElement"} \ No newline at end of file diff --git a/js-libs/easysax/easysax.js b/js-libs/easysax/easysax.js index 321eabc0a..856052f39 100644 --- a/js-libs/easysax/easysax.js +++ b/js-libs/easysax/easysax.js @@ -469,11 +469,24 @@ EasySAXParser.prototype.parse = function(xml) { , stop // используется при разборе "namespace" . если встретился неизвестное пространство то события не генерируются , _nsmatrix , ok + , pos = 0, ln = 0, lnStart = -2, lnEnd = -1 ; function getStringNode() { return xml.substring(i, j+1) }; + function findLineAndColumnFromPos() { + while (lnStart < lnEnd && lnEnd < pos) { + lnStart = lnEnd; + lnEnd = xml.indexOf("\n", lnEnd + 1); + ++ln; + } + return { line: ln, column: pos - lnStart }; + } + function position(p) { + pos = p; + return findLineAndColumnFromPos; + } while(j !== -1) { stop = stopIndex > 0; @@ -487,7 +500,7 @@ EasySAXParser.prototype.parse = function(xml) { if (i === -1) { // конец разбора if (nodestack.length) { - this.onError('end file'); + this.onError('end file', position(j)); return; }; @@ -495,7 +508,7 @@ EasySAXParser.prototype.parse = function(xml) { }; if (j !== i && !stop) { - ok = this.onTextNode(xml.substring(j, i), unEntities); + ok = this.onTextNode(xml.substring(j, i), unEntities, position(j)); if (ok === false) return; }; @@ -506,13 +519,13 @@ EasySAXParser.prototype.parse = function(xml) { if (w === 91 && xml.substr(i+3, 6) === 'CDATA[') { // 91 == "[" j = xml.indexOf(']]>', i); if (j === -1) { - this.onError('cdata'); + this.onError('cdata', position(i)); return; }; //x = xml.substring(i+9, j); if (!stop) { - ok = this.onCDATA(xml.substring(i+9, j), false); + ok = this.onCDATA(xml.substring(i+9, j), false, position(i)); if (ok === false) return; }; @@ -523,13 +536,13 @@ EasySAXParser.prototype.parse = function(xml) { if (w === 45 && xml.charCodeAt(i+3) === 45) { // 45 == "-" j = xml.indexOf('-->', i); if (j === -1) { - this.onError('expected -->'); + this.onError('expected -->', position(i)); return; }; if (this.is_onComment && !stop) { - ok = this.onComment(xml.substring(i+4, j), unEntities); + ok = this.onComment(xml.substring(i+4, j), unEntities, position(i)); if (ok === false) return; }; @@ -539,12 +552,12 @@ EasySAXParser.prototype.parse = function(xml) { j = xml.indexOf('>', i+1); if (j === -1) { - this.onError('expected ">"'); + this.onError('expected ">"', position(i + 1)); return; }; if (this.is_onAttention && !stop) { - ok = this.onAttention(xml.substring(i, j+1), unEntities); + ok = this.onAttention(xml.substring(i, j+1), unEntities, position(i)); if (ok === false) return; }; @@ -555,12 +568,12 @@ EasySAXParser.prototype.parse = function(xml) { if (w === 63) { // "?" j = xml.indexOf('?>', i); if (j === -1) { // error - this.onError('...?>'); + this.onError('...?>', position(i)); return; }; if (this.is_onQuestion) { - ok = this.onQuestion(xml.substring(i, j+2)); + ok = this.onQuestion(xml.substring(i, j+2), position(i)); if (ok === false) return; }; @@ -572,7 +585,7 @@ EasySAXParser.prototype.parse = function(xml) { j = xml.indexOf('>', i+1); if (j == -1) { // error - this.onError('...>'); + this.onError('...>', position(i + 1)); return; }; @@ -589,7 +602,7 @@ EasySAXParser.prototype.parse = function(xml) { //console.log() if (xml.substring(i+2, q) !== x) { - this.onError('close tagname'); + this.onError('close tagname', position(i + 2)); return; }; @@ -601,7 +614,7 @@ EasySAXParser.prototype.parse = function(xml) { continue; }; - this.onError('close tag'); + this.onError('close tag', position(i + 2)); return; }; @@ -619,7 +632,7 @@ EasySAXParser.prototype.parse = function(xml) { }; if ( !(w > 96 && w < 123 || w > 64 && w <91) ) { - this.onError('first char nodeName'); + this.onError('first char nodeName', position(i + 1)); return; }; @@ -636,7 +649,7 @@ EasySAXParser.prototype.parse = function(xml) { break; }; - this.onError('invalid nodeName'); + this.onError('invalid nodeName', position(i + 1)); return; }; @@ -718,7 +731,7 @@ EasySAXParser.prototype.parse = function(xml) { var that = this; ok = this.onStartNode(elem, function() { return that.getAttrs() }, unEntities, tagend - , getStringNode + , getStringNode, position(i) ); if (ok === false) { @@ -730,7 +743,7 @@ EasySAXParser.prototype.parse = function(xml) { if (tagend) { ok = this.onEndNode(elem, unEntities, tagstart - , getStringNode + , getStringNode, position(i) ); if (ok === false) { diff --git a/xml/xml.d.ts b/xml/xml.d.ts index 728128793..119cc10b5 100644 --- a/xml/xml.d.ts +++ b/xml/xml.d.ts @@ -33,6 +33,21 @@ declare module "xml" { */ static Comment: string; } + + /** + * Defines a position within string, in line and column form. + */ + interface Position { + /** + * The line number. The first line is at index 1. + */ + line: number; + + /** + * The column number. The first character is at index 1. + */ + column: number; + } /** * Provides information for a parser event. @@ -43,6 +58,11 @@ declare module "xml" { * Returns the type of the parser event. This is one of the ParserEventType static members. */ eventType: string; + + /** + * Get the position in the xml string where the event was generated. + */ + position: Position; /** * If namespace processing is enabled, returns the prefix of the element in case the eventType is ParserEventType.StartElement or ParserEventType.EndElement. diff --git a/xml/xml.ts b/xml/xml.ts index cef63acf7..0094b1172 100644 --- a/xml/xml.ts +++ b/xml/xml.ts @@ -11,14 +11,16 @@ export class ParserEventType implements definition.ParserEventType { export class ParserEvent implements definition.ParserEvent { private _eventType: string; + private _position: definition.Position; private _prefix: string; private _namespace: string; private _elementName: string; private _attributes: Object; private _data: string; - constructor(eventType: string, prefix?: string, namespace?: string, elementName?: string, attributes?: Object, data?: string) { + constructor(eventType: string, position: definition.Position, prefix?: string, namespace?: string, elementName?: string, attributes?: Object, data?: string) { this._eventType = eventType; + this._position = position; this._prefix = prefix; this._namespace = namespace; this._elementName = elementName; @@ -29,6 +31,7 @@ export class ParserEvent implements definition.ParserEvent { public toString(): string { return JSON.stringify({ eventType: this.eventType, + position: this.position, prefix: this.prefix, namespace: this.namespace, elementName: this.elementName, @@ -40,6 +43,10 @@ export class ParserEvent implements definition.ParserEvent { public get eventType(): string { return this._eventType; } + + public get position(): definition.Position { + return this._position; + } public get prefix(): string { return this._prefix; @@ -103,12 +110,12 @@ export class XmlParser implements definition.XmlParser { private _processNamespaces: boolean; private _namespaceStack: Array; - constructor(onEvent: (event: definition.ParserEvent) => void, onError?: (error: Error) => void, processNamespaces?: boolean) { + constructor(onEvent: (event: definition.ParserEvent) => void, onError?: (error: Error, position: definition.Position) => void, processNamespaces?: boolean) { this._processNamespaces = processNamespaces; this._parser = new easysax.EasySAXParser(); var that = this; - this._parser.on('startNode', function (elem, attr, uq, str, tagend) { + this._parser.on('startNode', function (elem, attr, uq, tagend, str, pos) { var attributes = attr(); if (attributes === true) {//HACK: For some reason easysax returns the true literal when an element has no attributes. @@ -139,15 +146,15 @@ export class XmlParser implements definition.XmlParser { name = resolved.name; } - onEvent(new ParserEvent(ParserEventType.StartElement, prefix, namespace, name, attributes, undefined)); + onEvent(new ParserEvent(ParserEventType.StartElement, pos(), prefix, namespace, name, attributes, undefined)); }); - this._parser.on('textNode', function (text, uq) { + this._parser.on('textNode', function (text, uq, pos) { var data = uq(XmlParser._dereferenceEntities(text));// Decode entity references such as < and > - onEvent(new ParserEvent(ParserEventType.Text, undefined, undefined, undefined, undefined, data)); + onEvent(new ParserEvent(ParserEventType.Text, pos(), undefined, undefined, undefined, undefined, data)); }); - this._parser.on('endNode', function (elem, uq, tagstart, str) { + this._parser.on('endNode', function (elem, uq, tagstart, str, pos) { var prefix = undefined; var namespace = undefined; @@ -160,24 +167,24 @@ export class XmlParser implements definition.XmlParser { name = resolved.name; } - onEvent(new ParserEvent(ParserEventType.EndElement, prefix, namespace, name, undefined, undefined)); + onEvent(new ParserEvent(ParserEventType.EndElement, pos(), prefix, namespace, name, undefined, undefined)); if (that._processNamespaces) { that._namespaceStack.pop(); } }); - this._parser.on('cdata', function (data) { - onEvent(new ParserEvent(ParserEventType.CDATA, undefined, undefined, undefined, undefined, data)); + this._parser.on('cdata', function (data, res, pos) { + onEvent(new ParserEvent(ParserEventType.CDATA, pos(), undefined, undefined, undefined, undefined, data)); }); - this._parser.on('comment', function (text) { - onEvent(new ParserEvent(ParserEventType.Comment, undefined, undefined, undefined, undefined, text)); + this._parser.on('comment', function (text, uq, pos) { + onEvent(new ParserEvent(ParserEventType.Comment, pos(), undefined, undefined, undefined, undefined, text)); }); if (onError) { - this._parser.on('error', function (msg) { - onError(new Error(msg)); + this._parser.on('error', function (msg, pos) { + onError(new Error(msg), pos()); }); } }