From 4879b26683600b6d7baf492d341c910ed6bf80c6 Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Wed, 3 Jun 2015 11:36:48 +0300 Subject: [PATCH 01/12] Update gitignore. Exclude tags and allow *.js in root. --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index de42debdd..f63d0ac4f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ .sublime-grunt.cache tscommand*.tmp.txt .tscache -*.js node_modules/ dist/ @@ -20,3 +19,6 @@ CrossPlatformModules.csproj.user bin/ obj/ .vs/ + +.ctags-exclude +tags From 968c8ecadc3d7d1818fca37e334cce511a9122a8 Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Wed, 3 Jun 2015 13:54:01 +0300 Subject: [PATCH 02/12] Don't use the "fast" grunt-ts compiler. Less console spam and compile time is the same. --- gruntfile.js | 1 + 1 file changed, 1 insertion(+) diff --git a/gruntfile.js b/gruntfile.js index 853c205bb..a84eef313 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -293,6 +293,7 @@ module.exports = function(grunt) { src: localCfg.typeScriptSrc, outDir: localCfg.outModulesDir, options: { + fast: 'never', module: "commonjs", target: "es5", sourceMap: false, From 7e09183d2391ed95948179269bd66f727f1cbaa1 Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Wed, 3 Jun 2015 19:13:21 +0300 Subject: [PATCH 03/12] gitignore .baseDir.ts --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f63d0ac4f..99b4fcc42 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,6 @@ bin/ obj/ .vs/ +.baseDir.ts .ctags-exclude tags From c05efb1614606f94a679e9b5a3818499ee8b003f Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Wed, 3 Jun 2015 19:16:08 +0300 Subject: [PATCH 04/12] Start nodejs test suite to avoid testing non-UI stuff with a real device. 1. Place tests in ./node-tests 2. Add chai, mocha, grunt-simple-mocha dev dependencies 3. Run it all with the grunt node-tests task. --- gruntfile.js | 39 ++++ node-tests/definitions/chai.d.ts | 284 ++++++++++++++++++++++++++++++ node-tests/definitions/mocha.d.ts | 110 ++++++++++++ node-tests/test-xml.ts | 7 + package.json | 6 +- 5 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 node-tests/definitions/chai.d.ts create mode 100644 node-tests/definitions/mocha.d.ts create mode 100644 node-tests/test-xml.ts diff --git a/gruntfile.js b/gruntfile.js index a84eef313..1622e871f 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -142,6 +142,12 @@ module.exports = function(grunt) { ] }; + var nodeTestEnv = JSON.parse(JSON.stringify(process.env)); + nodeTestEnv['NODE_PATH'] = localCfg.outModulesDir; + + localCfg.nodeTestsDir = pathModule.join(localCfg.outModulesDir, 'node-tests'); + + localCfg.mainPackageContent = grunt.file.readJSON(localCfg.packageJsonFilePath); localCfg.packageVersion = getPackageVersion(localCfg.packageJsonFilePath); localCfg.commitSHA = getCommitSha(); @@ -180,6 +186,9 @@ module.exports = function(grunt) { ], cwd: localCfg.outModulesDir }, + nodeTests: { + src: localCfg.nodeTestsDir, + }, readyAppFiles: { src: [localCfg.outModulesDir + "/apps/**"] } @@ -302,6 +311,24 @@ module.exports = function(grunt) { compiler: "node_modules/typescript/bin/tsc", noEmitOnError: true } + }, + buildNodeTests: { + src: [ + 'js-libs/easysax/**/*.ts', + 'xml/**/*.ts', + 'node-tests/**/*.ts', + ], + outDir: localCfg.outModulesDir, + options: { + fast: 'never', + module: "commonjs", + target: "es5", + sourceMap: false, + declaration: false, + removeComments: "<%= !grunt.option('leavecomments') || '' %>", + compiler: "node_modules/typescript/bin/tsc", + noEmitOnError: true + } } }, tslint: { @@ -348,6 +375,11 @@ module.exports = function(grunt) { options: { callback: assignGitSHA } + }, + }, + simplemocha: { + node: { + src: localCfg.nodeTestsDir + '/**/*.js' } } }); @@ -359,6 +391,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks("grunt-tslint"); grunt.loadNpmTasks("grunt-multi-dest"); grunt.loadNpmTasks("grunt-shell"); + grunt.loadNpmTasks("grunt-simple-mocha"); var cloneTasks = function(originalTasks, taskNameSuffix) { @@ -487,4 +520,10 @@ module.exports = function(grunt) { "pack-definitions", "get-ready-packages" ])); + + grunt.registerTask("node-tests", [ + "clean:nodeTests", + "ts:buildNodeTests", + "simplemocha:node", + ]); }; diff --git a/node-tests/definitions/chai.d.ts b/node-tests/definitions/chai.d.ts new file mode 100644 index 000000000..b07ee43e0 --- /dev/null +++ b/node-tests/definitions/chai.d.ts @@ -0,0 +1,284 @@ + +// Type definitions for chai 1.7.2 +// Project: http://chaijs.com/ +// Definitions by: Jed Hunsaker , Bart van der Schoor +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +declare module chai { + export class AssertionError { + constructor(message: string, _props?: any, ssf?: Function); + name: string; + message: string; + showDiff: boolean; + stack: string; + } + + function expect(target: any, message?: string): Expect; + + export var assert: Assert; + export var config: Config; + + export interface Config { + includeStack: boolean; + } + + // Provides a way to extend the internals of Chai + function use(fn: (chai: any, utils: any) => void): any; + + interface ExpectStatic { + (target: any): Expect; + } + + interface Assertions { + attr(name: string, value?: string): any; + css(name: string, value?: string): any; + data(name: string, value?: string): any; + class(className: string): any; + id(id: string): any; + html(html: string): any; + text(text: string): any; + value(value: string): any; + visible: any; + hidden: any; + selected: any; + checked: any; + disabled: any; + empty: any; + exist: any; + } + + interface Expect extends LanguageChains, NumericComparison, TypeComparison, Assertions { + not: Expect; + deep: Deep; + a: TypeComparison; + an: TypeComparison; + include: Include; + contain: Include; + ok: Expect; + true: Expect; + false: Expect; + null: Expect; + undefined: Expect; + exist: Expect; + empty: Expect; + arguments: Expect; + Arguments: Expect; + equal: Equal; + equals: Equal; + eq: Equal; + eql: Equal; + eqls: Equal; + property: Property; + ownProperty: OwnProperty; + haveOwnProperty: OwnProperty; + length: Length; + lengthOf: Length; + match(RegularExpression: RegExp, message?: string): Expect; + string(string: string, message?: string): Expect; + keys: Keys; + key(string: string): Expect; + throw: Throw; + throws: Throw; + Throw: Throw; + respondTo(method: string, message?: string): Expect; + itself: Expect; + satisfy(matcher: Function, message?: string): Expect; + closeTo(expected: number, delta: number, message?: string): Expect; + members: Members; + } + + interface LanguageChains { + to: Expect; + be: Expect; + been: Expect; + is: Expect; + that: Expect; + and: Expect; + have: Expect; + with: Expect; + at: Expect; + of: Expect; + same: Expect; + } + + interface NumericComparison { + above: NumberComparer; + gt: NumberComparer; + greaterThan: NumberComparer; + least: NumberComparer; + gte: NumberComparer; + below: NumberComparer; + lt: NumberComparer; + lessThan: NumberComparer; + most: NumberComparer; + lte: NumberComparer; + within(start: number, finish: number, message?: string): Expect; + } + + interface NumberComparer { + (value: number, message?: string): Expect; + } + + interface TypeComparison { + (type: string, message?: string): Expect; + instanceof: InstanceOf; + instanceOf: InstanceOf; + } + + interface InstanceOf { + (constructor: Object, message?: string): Expect; + } + + interface Deep { + equal: Equal; + property: Property; + } + + interface Equal { + (value: any, message?: string): Expect; + } + + interface Property { + (name: string, value?: any, message?: string): Expect; + } + + interface OwnProperty { + (name: string, message?: string): Expect; + } + + interface Length extends LanguageChains, NumericComparison { + (length: number, message?: string): Expect; + } + + interface Include { + (value: Object, message?: string): Expect; + (value: string, message?: string): Expect; + (value: number, message?: string): Expect; + keys: Keys; + members: Members; + } + + interface Keys { + (...keys: string[]): Expect; + (keys: any[]): Expect; + } + + interface Members { + (set: any[], message?: string): Expect; + } + + interface Throw { + (): Expect; + (expected: string, message?: string): Expect; + (expected: RegExp, message?: string): Expect; + (constructor: Error, expected?: string, message?: string): Expect; + (constructor: Error, expected?: RegExp, message?: string): Expect; + (constructor: Function, expected?: string, message?: string): Expect; + (constructor: Function, expected?: RegExp, message?: string): Expect; + } + + export interface Assert { + (express: any, msg?: string):void; + + fail(actual?: any, expected?: any, msg?: string, operator?: string):void; + + ok(val: any, msg?: string):void; + notOk(val: any, msg?: string):void; + + equal(act: any, exp: any, msg?: string):void; + notEqual(act: any, exp: any, msg?: string):void; + + strictEqual(act: any, exp: any, msg?: string):void; + notStrictEqual(act: any, exp: any, msg?: string):void; + + deepEqual(act: any, exp: any, msg?: string):void; + notDeepEqual(act: any, exp: any, msg?: string):void; + + isTrue(val: any, msg?: string):void; + isFalse(val: any, msg?: string):void; + + isNull(val: any, msg?: string):void; + isNotNull(val: any, msg?: string):void; + + isUndefined(val: any, msg?: string):void; + isDefined(val: any, msg?: string):void; + + isFunction(val: any, msg?: string):void; + isNotFunction(val: any, msg?: string):void; + + isObject(val: any, msg?: string):void; + isNotObject(val: any, msg?: string):void; + + isArray(val: any, msg?: string):void; + isNotArray(val: any, msg?: string):void; + + isString(val: any, msg?: string):void; + isNotString(val: any, msg?: string):void; + + isNumber(val: any, msg?: string):void; + isNotNumber(val: any, msg?: string):void; + + isBoolean(val: any, msg?: string):void; + isNotBoolean(val: any, msg?: string):void; + + typeOf(val: any, type: string, msg?: string):void; + notTypeOf(val: any, type: string, msg?: string):void; + + instanceOf(val: any, type: Function, msg?: string):void; + notInstanceOf(val: any, type: Function, msg?: string):void; + + include(exp: string, inc: any, msg?: string):void; + include(exp: any[], inc: any, msg?: string):void; + + notInclude(exp: string, inc: any, msg?: string):void; + notInclude(exp: any[], inc: any, msg?: string):void; + + match(exp: any, re: RegExp, msg?: string):void; + notMatch(exp: any, re: RegExp, msg?: string):void; + + property(obj: Object, prop: string, msg?: string):void; + notProperty(obj: Object, prop: string, msg?: string):void; + deepProperty(obj: Object, prop: string, msg?: string):void; + notDeepProperty(obj: Object, prop: string, msg?: string):void; + + propertyVal(obj: Object, prop: string, val: any, msg?: string):void; + propertyNotVal(obj: Object, prop: string, val: any, msg?: string):void; + + deepPropertyVal(obj: Object, prop: string, val: any, msg?: string):void; + deepPropertyNotVal(obj: Object, prop: string, val: any, msg?: string):void; + + lengthOf(exp: any, len: number, msg?: string):void; + //alias frenzy + throw(fn: Function, msg?: string):void; + throw(fn: Function, regExp: RegExp):void; + throw(fn: Function, errType: Function, msg?: string):void; + throw(fn: Function, errType: Function, regExp: RegExp):void; + + throws(fn: Function, msg?: string):void; + throws(fn: Function, regExp: RegExp):void; + throws(fn: Function, errType: Function, msg?: string):void; + throws(fn: Function, errType: Function, regExp: RegExp):void; + + Throw(fn: Function, msg?: string):void; + Throw(fn: Function, regExp: RegExp):void; + Throw(fn: Function, errType: Function, msg?: string):void; + Throw(fn: Function, errType: Function, regExp: RegExp):void; + + doesNotThrow(fn: Function, msg?: string):void; + doesNotThrow(fn: Function, regExp: RegExp):void; + doesNotThrow(fn: Function, errType: Function, msg?: string):void; + doesNotThrow(fn: Function, errType: Function, regExp: RegExp):void; + + operator(val: any, operator: string, val2: any, msg?: string):void; + closeTo(act: number, exp: number, delta: number, msg?: string):void; + + sameMembers(set1: any[], set2: any[], msg?: string):void; + includeMembers(set1: any[], set2: any[], msg?: string):void; + + ifError(val: any, msg?: string):void; + } +} + +declare module "chai" { +export = chai; +} diff --git a/node-tests/definitions/mocha.d.ts b/node-tests/definitions/mocha.d.ts new file mode 100644 index 000000000..64255a7ba --- /dev/null +++ b/node-tests/definitions/mocha.d.ts @@ -0,0 +1,110 @@ +// Type definitions for mocha 1.17.1 +// Project: http://visionmedia.github.io/mocha/ +// Definitions by: Kazi Manzur Rashid , otiai10 +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +interface Mocha { + // Setup mocha with the given setting options. + setup(options: MochaSetupOptions): Mocha; + + //Run tests and invoke `fn()` when complete. + run(callback?: () => void): void; + + // Set reporter as function + reporter(reporter: () => void): Mocha; + + // Set reporter, defaults to "dot" + reporter(reporter: string): Mocha; + + // Enable growl support. + growl(): Mocha +} + +interface MochaSetupOptions { + //milliseconds to wait before considering a test slow + slow?: number; + + // timeout in milliseconds + timeout?: number; + + // ui name "bdd", "tdd", "exports" etc + ui?: string; + + //array of accepted globals + globals?: any[]; + + // reporter instance (function or string), defaults to `mocha.reporters.Dot` + reporter?: any; + + // bail on the first test failure + bail?: Boolean; + + // ignore global leaks + ignoreLeaks?: Boolean; + + // grep string or regexp to filter tests with + grep?: any; +} + +interface MochaDone { + (error?: Error): void; +} + +declare var mocha: Mocha; + +declare var describe : { + (description: string, spec: () => void): void; + only(description: string, spec: () => void): void; + skip(description: string, spec: () => void): void; + timeout(ms: number): void; +} + +// alias for `describe` +declare var context : { + (contextTitle: string, spec: () => void): void; + only(contextTitle: string, spec: () => void): void; + skip(contextTitle: string, spec: () => void): void; + timeout(ms: number): void; +} + +declare var it: { + (expectation: string, assertion?: () => void): void; + (expectation: string, assertion?: (done: MochaDone) => void): void; + only(expectation: string, assertion?: () => void): void; + only(expectation: string, assertion?: (done: MochaDone) => void): void; + skip(expectation: string, assertion?: () => void): void; + skip(expectation: string, assertion?: (done: MochaDone) => void): void; + timeout(ms: number): void; +}; + +declare function before(action: () => void): void; + +declare function before(action: (done: MochaDone) => void): void; + +declare function setup(action: () => void): void; + +declare function setup(action: (done: MochaDone) => void): void; + +declare function after(action: () => void): void; + +declare function after(action: (done: MochaDone) => void): void; + +declare function teardown(action: () => void): void; + +declare function teardown(action: (done: MochaDone) => void): void; + +declare function beforeEach(action: () => void): void; + +declare function beforeEach(action: (done: MochaDone) => void): void; + +declare function suiteSetup(action: () => void): void; + +declare function suiteSetup(action: (done: MochaDone) => void): void; + +declare function afterEach(action: () => void): void; + +declare function afterEach(action: (done: MochaDone) => void): void; + +declare function suiteTeardown(action: () => void): void; + +declare function suiteTeardown(action: (done: MochaDone) => void): void; diff --git a/node-tests/test-xml.ts b/node-tests/test-xml.ts new file mode 100644 index 000000000..72db5eaa4 --- /dev/null +++ b/node-tests/test-xml.ts @@ -0,0 +1,7 @@ +import {assert} from "chai"; + +describe("test", () => { + it("dummy", () => { + assert.equal(1, 2); + }); +}); diff --git a/package.json b/package.json index c970e05ab..d9373162c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ }, "files": [ "**/*.*", - "**/*" + "**/*", + "!node-tests/" ], "license": "Apache-2.0", "devDependencies": { @@ -20,6 +21,9 @@ "grunt-shell": "1.1.2", "grunt-ts": "4.0.1", "grunt-tslint": "0.4.2", + "mocha": "2.2.5", + "grunt-simple-mocha": "0.4.0", + "chai": "2.3.0", "typescript": "1.5.0-beta" } } From 30566cbc2a0095f02f7f8d652c01a212f54d8a5f Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Thu, 4 Jun 2015 14:04:51 +0300 Subject: [PATCH 05/12] Run node-tests with correct NODE_PATH. --- gruntfile.js | 20 +++++++++++++++++++- node-tests/test-xml.ts | 23 ++++++++++++++++++++--- package.json | 1 + 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/gruntfile.js b/gruntfile.js index 1622e871f..0311ffad2 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -353,6 +353,9 @@ module.exports = function(grunt) { packApp: { cmd: "npm pack", cwd: "__dummy__" + }, + mochaNode: { + cmd: "grunt simplemocha:node" } }, multidest: { @@ -381,6 +384,11 @@ module.exports = function(grunt) { node: { src: localCfg.nodeTestsDir + '/**/*.js' } + }, + env: { + nodeTests: { + NODE_PATH: localCfg.outModulesDir, + } } }); @@ -391,6 +399,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks("grunt-tslint"); grunt.loadNpmTasks("grunt-multi-dest"); grunt.loadNpmTasks("grunt-shell"); + grunt.loadNpmTasks("grunt-env"); grunt.loadNpmTasks("grunt-simple-mocha"); var cloneTasks = function(originalTasks, taskNameSuffix) @@ -521,9 +530,18 @@ module.exports = function(grunt) { "get-ready-packages" ])); + grunt.registerTask("testEnv", function() { + console.log('fafla', process.env.NODE_PATH); + //var x = require('xml') + //console.log(x); + }); + grunt.registerTask("node-tests", [ "clean:nodeTests", "ts:buildNodeTests", - "simplemocha:node", + "copy:childPackageFiles", + "copy:jsLibs", + "env:nodeTests", + "exec:mochaNode", //spawn a new process to use the new NODE_PATH ]); }; diff --git a/node-tests/test-xml.ts b/node-tests/test-xml.ts index 72db5eaa4..cdb524c4a 100644 --- a/node-tests/test-xml.ts +++ b/node-tests/test-xml.ts @@ -1,7 +1,24 @@ import {assert} from "chai"; +import {XmlParser, ParserEvent, ParserEventType} from 'xml'; +//import xml = require('xml'); -describe("test", () => { - it("dummy", () => { - assert.equal(1, 2); +describe("xml parser", () => { + it("parses simple element", () => { + let attributes = null; + let element = null; + + var parser = new XmlParser(function (event: ParserEvent) { + switch (event.eventType) { + case ParserEventType.StartElement: + element = event.elementName; + attributes = event.attributes; + break; + } + }); + + parser.parse(""); + + assert.equal('TextField', element); + assert.equal('hello', attributes['text']); }); }); diff --git a/package.json b/package.json index d9373162c..b7dd7c470 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "grunt-tslint": "0.4.2", "mocha": "2.2.5", "grunt-simple-mocha": "0.4.0", + "grunt-env": "0.4.4", "chai": "2.3.0", "typescript": "1.5.0-beta" } From b4b5b1e790fc7c4edebc62b65c02f70382f99056 Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Thu, 4 Jun 2015 14:05:10 +0300 Subject: [PATCH 06/12] Get rid of UTF-8 BOM in xml/package.json --- xml/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xml/package.json b/xml/package.json index f5006340c..c512836d5 100644 --- a/xml/package.json +++ b/xml/package.json @@ -1,2 +1,2 @@ -{ "name" : "xml", - "main" : "xml.js" } \ No newline at end of file +{ "name" : "xml", + "main" : "xml.js" } From 2a2c0e5c31580986affa50df8fcb2c36857eaf0d Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Thu, 4 Jun 2015 15:26:17 +0300 Subject: [PATCH 07/12] fix(xml parser): Handle whitespace around attribute = --- js-libs/easysax/easysax.js | 14 +++++++++++++- node-tests/test-xml.ts | 35 +++++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/js-libs/easysax/easysax.js b/js-libs/easysax/easysax.js index 9828d8333..8d6a08719 100644 --- a/js-libs/easysax/easysax.js +++ b/js-libs/easysax/easysax.js @@ -263,6 +263,10 @@ function EasySAXParser() { continue; }; + if (w===32 || (w > 8 && w<14) ) { // \f\n\r\t\v пробел + continue; + }; + if (w !== 61) { // "=" == 61 //console.log('error 2'); @@ -272,7 +276,7 @@ function EasySAXParser() { break; }; - name = s.substring(i, j); + name = s.substring(i, j).trim(); ok = true; if (name === 'xmlns:xmlns') { @@ -282,6 +286,14 @@ function EasySAXParser() { w = s.charCodeAt(j+1); + while (w = s.charCodeAt(j+1)) { + if (w===32 || (w > 8 && w<14) ) { // \f\n\r\t\v пробел + j++; + } else { + break; + } + } + if (w === 34) { // '"' j = s.indexOf('"', i = j+2 ); diff --git a/node-tests/test-xml.ts b/node-tests/test-xml.ts index cdb524c4a..e208c4f60 100644 --- a/node-tests/test-xml.ts +++ b/node-tests/test-xml.ts @@ -1,24 +1,31 @@ import {assert} from "chai"; -import {XmlParser, ParserEvent, ParserEventType} from 'xml'; -//import xml = require('xml'); +import xml = require('xml'); describe("xml parser", () => { - it("parses simple element", () => { - let attributes = null; - let element = null; + let last_element = null; + let last_attrs = null; + let parser = null; - var parser = new XmlParser(function (event: ParserEvent) { + beforeEach(() => { + parser = new xml.XmlParser(function (event: xml.ParserEvent) { switch (event.eventType) { - case ParserEventType.StartElement: - element = event.elementName; - attributes = event.attributes; + case xml.ParserEventType.StartElement: + last_element = event.elementName; + last_attrs = event.attributes; break; } }); - - parser.parse(""); - - assert.equal('TextField', element); - assert.equal('hello', attributes['text']); }); + + + it("handles whitespace around attribute =", () => { + let attributes = null; + let element = null; + + parser.parse(""); + + assert.equal('TextField', last_element); + assert.equal('hello', last_attrs['text']); + }); + }); From fdd8c9b1161caf5b5358f9144f9e24c2060fd948 Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Thu, 4 Jun 2015 17:13:18 +0300 Subject: [PATCH 08/12] feat(angular xml): Support [prop] and (tap) bindings --- js-libs/easysax/easysax.js | 10 ++++++--- node-tests/test-angular-xml.ts | 40 ++++++++++++++++++++++++++++++++++ node-tests/test-xml.ts | 1 - xml/xml.ts | 2 +- 4 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 node-tests/test-angular-xml.ts diff --git a/js-libs/easysax/easysax.js b/js-libs/easysax/easysax.js index 8d6a08719..7b6f7f10e 100644 --- a/js-libs/easysax/easysax.js +++ b/js-libs/easysax/easysax.js @@ -251,8 +251,8 @@ function EasySAXParser() { continue }; - if (w < 65 || w >122 || (w>90 && w<97) ) { // ожидаем символ - //console.log('error attr 1') + if ((w < 65 && w !== 40 && w !== 41) || // allow parens () -- used for angular syntax + w > 122 || (w === 92 || (w > 93 && w < 97)) ) { // ожидаем символ return attr_res = false; // error. invalid char }; @@ -263,10 +263,14 @@ function EasySAXParser() { continue; }; - if (w===32 || (w > 8 && w<14) ) { // \f\n\r\t\v пробел + if (w === 32 || (w > 8 && w < 14) ) { // \f\n\r\t\v пробел continue; }; + if (w === 91 || w === 93 || w === 40 || w === 41 || w === 94) { // Angular special attribute chars:[]()^ + continue; + } + if (w !== 61) { // "=" == 61 //console.log('error 2'); diff --git a/node-tests/test-angular-xml.ts b/node-tests/test-angular-xml.ts new file mode 100644 index 000000000..3c65eddd5 --- /dev/null +++ b/node-tests/test-angular-xml.ts @@ -0,0 +1,40 @@ +import {assert} from "chai"; +import xml = require('xml'); + +describe("angular xml parser", () => { + let last_element = null; + let last_attrs = null; + let parser = null; + + beforeEach(() => { + parser = new xml.XmlParser(function (event: xml.ParserEvent) { + switch (event.eventType) { + case xml.ParserEventType.StartElement: + last_element = event.elementName; + last_attrs = event.attributes; + break; + } + }); + }); + + it("parses [property] binding", () => { + parser.parse(""); + + assert.equal('TextField', last_element); + assert.equal(last_attrs['[text]'], 'somevar'); + }); + + it("parses (event) binding", () => { + parser.parse(""); + + assert.equal('TextField', last_element); + assert.equal(last_attrs['(tap)'], 'onTap(blah)'); + }); + + it("parsers (^event) binding", () => { + parser.parse(""); + + assert.equal('TextField', last_element); + assert.equal(last_attrs['(^tap)'], 'onTap(blah)'); + }); +}); diff --git a/node-tests/test-xml.ts b/node-tests/test-xml.ts index e208c4f60..6d392a958 100644 --- a/node-tests/test-xml.ts +++ b/node-tests/test-xml.ts @@ -27,5 +27,4 @@ describe("xml parser", () => { assert.equal('TextField', last_element); assert.equal('hello', last_attrs['text']); }); - }); diff --git a/xml/xml.ts b/xml/xml.ts index 2efd9efea..7cd8b2563 100644 --- a/xml/xml.ts +++ b/xml/xml.ts @@ -242,4 +242,4 @@ export class XmlParser implements definition.XmlParser { return s; } -} \ No newline at end of file +} From aa112244fd7bac268c65abc2be6178abacb0cff4 Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Fri, 5 Jun 2015 14:40:38 +0300 Subject: [PATCH 09/12] Parse out attributes without values. Treat #myattr as #myAttr=''. --- js-libs/easysax/easysax.js | 65 +++++++++++++++++++++------------- node-tests/test-angular-xml.ts | 36 ++++++++++++++++++- 2 files changed, 75 insertions(+), 26 deletions(-) diff --git a/js-libs/easysax/easysax.js b/js-libs/easysax/easysax.js index 7b6f7f10e..324627adf 100644 --- a/js-libs/easysax/easysax.js +++ b/js-libs/easysax/easysax.js @@ -237,12 +237,12 @@ function EasySAXParser() { , attr_list = hasSurmiseNS ? [] : false , name, value = '' , ok = false + , noValueAttribute = false , j, w, nn, n , hasNewMatrix , alias, newalias ; - aa: for(; i < l; i++) { w = s.charCodeAt(i); @@ -251,7 +251,7 @@ function EasySAXParser() { continue }; - if ((w < 65 && w !== 40 && w !== 41) || // allow parens () -- used for angular syntax + if ((w < 65 && w !== 40 && w !== 41 && w !== 35) || // allow parens () -- used for angular syntax w > 122 || (w === 92 || (w > 93 && w < 97)) ) { // ожидаем символ return attr_res = false; // error. invalid char }; @@ -260,21 +260,28 @@ function EasySAXParser() { w = s.charCodeAt(j); if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w === 46 /* https://github.com/telerik/xPlatCore/issues/179 */) { - continue; + if (noValueAttribute) { + j--; //Started next attribute. Get back and break out of the loop. + break; + } else { + continue; + } }; - if (w === 32 || (w > 8 && w < 14) ) { // \f\n\r\t\v пробел - continue; - }; - - if (w === 91 || w === 93 || w === 40 || w === 41 || w === 94) { // Angular special attribute chars:[]()^ + if (w === 91 || w === 93 || w === 40 || w === 41 || w === 94 || w === 35) { // Angular special attribute chars:[]()^ continue; } - - if (w !== 61) { // "=" == 61 + if (w === 32 || (w > 8 && w < 14) ) { // \f\n\r\t\v пробел + noValueAttribute = true; + continue; + } else if (w === 61) { // "=" == 61 + noValueAttribute = false; + break; + } else { //console.log('error 2'); - return attr_res = false; // error. invalid char + if (!noValueAttribute) + return attr_res = false; // error. invalid char }; break; @@ -298,18 +305,19 @@ function EasySAXParser() { } } - if (w === 34) { // '"' - j = s.indexOf('"', i = j+2 ); + if (!noValueAttribute) { + if (w === 34) { // '"' + j = s.indexOf('"', i = j+2 ); - } else { - if (w === 39) { - j = s.indexOf('\'', i = j+2 ); + } else { + if (w === 39) { + j = s.indexOf('\'', i = j+2 ); - } else { // "'" - //console.log('error 3') - return attr_res = false; // error. invalid char - }; - }; + } else { // "'" + return attr_res = false; // error. invalid char + }; + }; + } if (j === -1) { //console.log('error 4') @@ -317,10 +325,10 @@ function EasySAXParser() { }; - if (j+1 < l) { + if (j+1 < l && !noValueAttribute) { w = s.charCodeAt(j+1); - if (w > 32 || w < 9 || (w<32 && w>13)) { + if (w > 32 || w < 9 || (w < 32 && w > 13)) { // error. invalid char //console.log('error 5') return attr_res = false; @@ -328,8 +336,14 @@ function EasySAXParser() { }; - value = s.substring(i, j); - i = j + 1; // след. семвол уже проверен потому проверять нужно следуюший + if (noValueAttribute) { + value = ''; + } else { + value = s.substring(i, j); + } + + //i = j + 1; // след. семвол уже проверен потому проверять нужно следуюший + i = j; // след. семвол уже проверен потому проверять нужно следуюший if (isNamespace) { // if (hasSurmiseNS) { @@ -382,6 +396,7 @@ function EasySAXParser() { }; res[name] = value; + noValueAttribute = false; }; diff --git a/node-tests/test-angular-xml.ts b/node-tests/test-angular-xml.ts index 3c65eddd5..72bb3ff52 100644 --- a/node-tests/test-angular-xml.ts +++ b/node-tests/test-angular-xml.ts @@ -31,10 +31,44 @@ describe("angular xml parser", () => { assert.equal(last_attrs['(tap)'], 'onTap(blah)'); }); - it("parsers (^event) binding", () => { + it("parses (^event) binding", () => { parser.parse(""); assert.equal('TextField', last_element); assert.equal(last_attrs['(^tap)'], 'onTap(blah)'); }); + + it("parses #id attribute", () => { + parser.parse(""); + + assert.equal('TextField', last_element); + assert.equal(last_attrs['#firstName'], ''); + }); + + it("parses #id attribute followed by another", () => { + parser.parse(""); + + assert.equal('TextField', last_element); + assert.equal(last_attrs['#firstName'], ''); + assert.equal(last_attrs['text'], 'Name'); + }); + + return + it("detects equals without value", () => { + parser.parse(""); + + assert.isFalse(last_attrs); + }); + + it("detects no equals with quoted value", () => { + parser.parse(""); + + assert.isFalse(last_attrs); + }); + + it("detects unclosed tag after no value attribute", () => { + parser.parse(" Date: Fri, 5 Jun 2015 15:51:08 +0300 Subject: [PATCH 10/12] feat(xml parser): Only allow angular syntax extensions if configured. Configure via the public `angularSyntax` property on EasySAXParser and the XmlParser wrapper. --- js-libs/easysax/easysax.d.ts | 1 + js-libs/easysax/easysax.js | 1250 ++++++++++++++++---------------- node-tests/test-angular-xml.ts | 9 +- xml/xml.d.ts | 4 +- xml/xml.ts | 8 + 5 files changed, 650 insertions(+), 622 deletions(-) diff --git a/js-libs/easysax/easysax.d.ts b/js-libs/easysax/easysax.d.ts index e70c1d1e2..3f2d82226 100644 --- a/js-libs/easysax/easysax.d.ts +++ b/js-libs/easysax/easysax.d.ts @@ -5,6 +5,7 @@ declare module "js-libs/easysax" { parse(xml: string): void; on(name: string, cb: Function): void; ns(root: string, ns: any): void; + public angularSyntax: boolean; } } diff --git a/js-libs/easysax/easysax.js b/js-libs/easysax/easysax.js index 324627adf..3cca74f73 100644 --- a/js-libs/easysax/easysax.js +++ b/js-libs/easysax/easysax.js @@ -54,17 +54,11 @@ }; - - */ // << ------------------------------------------------------------------------ >> // - - - - if (typeof exports === 'object' /*&& this == exports*/) { module.exports.EasySAXParser = EasySAXParser; }; @@ -74,133 +68,162 @@ function EasySAXParser() { if (!this) return null; + this.angularSyntax = false; + function nullFunc() {}; - var onTextNode = nullFunc, onStartNode = nullFunc, onEndNode = nullFunc, onCDATA = nullFunc, onError = nullFunc, onComment, onQuestion, onAttention; - var is_onComment, is_onQuestion, is_onAttention; + this.onTextNode = nullFunc; + this.onStartNode = nullFunc; + this.onEndNode = nullFunc; + this.onCDATA = nullFunc; + this.onError = nullFunc; + this.onComment = null; + this.onQuestion = null; + this.onAttention = null; + this.is_onComment = this.is_onQuestion = this.is_onAttention = false; - var isNamespace = false, useNS , default_xmlns, xmlns - , nsmatrix = {xmlns: xmlns} - , hasSurmiseNS = false + this.isNamespace = false; + this.useNS = null; + this.default_xmlns = null; + this.xmlns = null; + this.nsmatrix = {xmlns: this.xmlns}; + this.hasSurmiseNS = false; ; - this.on = function(name, cb) { - if (typeof cb !== 'function') { - if (cb !== null) return; - }; + this.attr_string = ''; // строка атрибутов + this.attr_posstart = 0; // + this.attr_res; // закешированный результат разбора атрибутов , null - разбор не проводился, object - хеш атрибутов, true - нет атрибутов, false - невалидный xml +} - switch(name) { - case 'error': onError = cb || nullFunc; break; - case 'startNode': onStartNode = cb || nullFunc; break; - case 'endNode': onEndNode = cb || nullFunc; break; - case 'textNode': onTextNode = cb || nullFunc; break; - case 'cdata': onCDATA = cb || nullFunc; break; +EasySAXParser.prototype.on = function(name, cb) { + if (typeof cb !== 'function') { + if (cb !== null) return; + }; - case 'comment': onComment = cb; is_onComment = !!cb; break; - case 'question': onQuestion = cb; is_onQuestion = !!cb; break; // - case 'attention': onAttention = cb; is_onAttention = !!cb; break; // - - }; - }; + switch(name) { + case 'error': this.onError = cb || nullFunc; break; + case 'startNode': this.onStartNode = cb || nullFunc; break; + case 'endNode': this.onEndNode = cb || nullFunc; break; + case 'textNode': onTextNode = cb || nullFunc; break; + case 'cdata': this.onCDATA = cb || nullFunc; break; - this.ns = function(root, ns) { - if (!root || typeof root !== 'string' || !ns) { - return; - }; + case 'comment': this.onComment = cb; this.is_onComment = !!cb; break; + case 'question': this.onQuestion = cb; this.is_onQuestion = !!cb; break; // + case 'attention': this.onAttention = cb; this.is_onAttention = !!cb; break; // + }; +}; - var u, x = {}, ok, v, i; +EasySAXParser.prototype.ns = function(root, ns) { + if (!root || typeof root !== 'string' || !ns) { + return; + }; - for(i in ns) { - v = ns[i]; - if (typeof v === 'string') { - if (root === v) ok = true; - x[i] = v; - }; - }; - - if (ok) { - isNamespace = true; - default_xmlns = root; - useNS = x; - }; - }; + var u, x = {}, ok, v, i; - this.parse = function(xml) { - if (typeof xml !== 'string') { - return; - }; + for(i in ns) { + v = ns[i]; + if (typeof v === 'string') { + if (root === v) ok = true; + x[i] = v; + }; + }; - if (isNamespace) { - nsmatrix = {xmlns: default_xmlns}; - - parse(xml); - - nsmatrix = false; - - } else { - parse(xml); - }; - - attr_res = true; - }; - - // ----------------------------------------------------- - - var xharsQuot={constructor: false, hasOwnProperty: false, isPrototypeOf: false, propertyIsEnumerable: false, toLocaleString: false, toString: false, valueOf: false - , quot: '"' - , QUOT: '"' - , amp: '&' - , AMP: '&' - , nbsp: '\u00A0' - , apos: '\'' - , lt: '<' - , LT: '<' - , gt: '>' - , GT: '>' - , copy: '\u00A9' - , laquo: '\u00AB' - , raquo: '\u00BB' - , reg: '\u00AE' - , deg: '\u00B0' - , plusmn: '\u00B1' - , sup2: '\u00B2' - , sup3: '\u00B3' - , micro: '\u00B5' - , para: '\u00B6' - }; + if (ok) { + this.isNamespace = true; + this.default_xmlns = root; + this.useNS = x; + }; +}; - function rpEntities(s, d, x, z) { - if (z) { - return xharsQuot[z] || '\x01'; - }; +EasySAXParser.prototype.parse = function(xml) { + if (typeof xml !== 'string') { + return; + }; - if (d) { - return String.fromCharCode(d); - }; + if (this.isNamespace) { + this.nsmatrix = {xmlns: this.default_xmlns}; - return String.fromCharCode(parseInt(x, 16)); - }; + parse(xml); - function unEntities(s, i) { - s = String(s); - if (s.length > 3 && s.indexOf('&') !== -1) { - if (s.indexOf('>') !== -1) s = s.replace(/>/g, '>'); - if (s.indexOf('<') !== -1) s = s.replace(/</g, '<'); - if (s.indexOf('"') !== -1) s = s.replace(/"/g, '"'); + this.nsmatrix = false; - if (s.indexOf('&') !== -1) { - s = s.replace(/&#(\d+);|&#x([0123456789abcdef]+);|&(\w+);/ig, rpEntities); - }; - }; + } else { + parse(xml); + }; - return s; - }; + this.attr_res = true; +}; - var attr_string = ''; // строка атрибутов - var attr_posstart = 0; // - var attr_res; // закешированный результат разбора атрибутов , null - разбор не проводился, object - хеш атрибутов, true - нет атрибутов, false - невалидный xml +// ----------------------------------------------------- + +var xharsQuot={constructor: false, hasOwnProperty: false, isPrototypeOf: false, propertyIsEnumerable: false, toLocaleString: false, toString: false, valueOf: false + , quot: '"' + , QUOT: '"' + , amp: '&' + , AMP: '&' + , nbsp: '\u00A0' + , apos: '\'' + , lt: '<' + , LT: '<' + , gt: '>' + , GT: '>' + , copy: '\u00A9' + , laquo: '\u00AB' + , raquo: '\u00BB' + , reg: '\u00AE' + , deg: '\u00B0' + , plusmn: '\u00B1' + , sup2: '\u00B2' + , sup3: '\u00B3' + , micro: '\u00B5' + , para: '\u00B6' +}; + + +function rpEntities(s, d, x, z) { + if (z) { + return xharsQuot[z] || '\x01'; + }; + + if (d) { + return String.fromCharCode(d); + }; + + return String.fromCharCode(parseInt(x, 16)); +}; + +function unEntities(s, i) { + s = String(s); + if (s.length > 3 && s.indexOf('&') !== -1) { + if (s.indexOf('>') !== -1) s = s.replace(/>/g, '>'); + if (s.indexOf('<') !== -1) s = s.replace(/</g, '<'); + if (s.indexOf('"') !== -1) s = s.replace(/"/g, '"'); + + if (s.indexOf('&') !== -1) { + s = s.replace(/&#(\d+);|&#x([0123456789abcdef]+);|&(\w+);/ig, rpEntities); + }; + }; + + return s; +}; + + +EasySAXParser.prototype.allowedAngularAttributeChars = function(w) { + if (!this.angularSyntax) { + return false; + } else { + return ( + w === 40 || // ( + w === 41 || // ) + w === 91 || // [ + w === 93 || // ] + w === 94 || // ^ + w === 35 // # + ); + } +}; /* парсит атрибуты по требованию. Важно! - функция не генерирует исключения. @@ -210,530 +233,519 @@ function EasySAXParser() { если есть атрибуты то возврашается обьект(хеш) */ - var RGX_ATTR_NAME = /[^\w:-]+/g; +EasySAXParser.prototype.getAttrs = function() { + if (this.attr_res !== null) { + return this.attr_res; + }; - function getAttrs() { - if (attr_res !== null) { - return attr_res; - }; + /* + if (xxtest !== u && attr_string.indexOf(xxtest) === -1) { + / * + // для ускорения + if (getAttrs('html').type == 'html') { + ... + }; + * / + return true; + }; + */ - /* - if (xxtest !== u && attr_string.indexOf(xxtest) === -1) { - / * - // для ускорения - if (getAttrs('html').type == 'html') { - ... - }; - * / - return true; - }; - */ + var u + , res = {} + , s = this.attr_string + , i = this.attr_posstart + , l = s.length + , attr_list = this.hasSurmiseNS ? [] : false + , name, value = '' + , ok = false + , noValueAttribute = false + , j, w, nn, n + , hasNewMatrix + , alias, newalias + ; - var u - , res = {} - , s = attr_string - , i = attr_posstart - , l = s.length - , attr_list = hasSurmiseNS ? [] : false - , name, value = '' - , ok = false - , noValueAttribute = false - , j, w, nn, n - , hasNewMatrix - , alias, newalias - ; + aa: + for(; i < l; i++) { + w = s.charCodeAt(i); - aa: - for(; i < l; i++) { - w = s.charCodeAt(i); + if (w===32 || (w<14 && w > 8) ) { // \f\n\r\t\v + continue + }; - if (w===32 || (w<14 && w > 8) ) { // \f\n\r\t\v - continue - }; + // Check for valid attribute start char + if ((w < 65 && !this.allowedAngularAttributeChars(w)) || + w > 122 || (w > 90 && w < 97 && !this.allowedAngularAttributeChars(w)) ) { // ожидаем символ + return this.attr_res = false; // error. invalid char + }; - if ((w < 65 && w !== 40 && w !== 41 && w !== 35) || // allow parens () -- used for angular syntax - w > 122 || (w === 92 || (w > 93 && w < 97)) ) { // ожидаем символ - return attr_res = false; // error. invalid char - }; + for(j = i + 1; j < l; j++) { // проверяем все символы имени атрибута + w = s.charCodeAt(j); - for(j = i + 1; j < l; j++) { // проверяем все символы имени атрибута - w = s.charCodeAt(j); - - if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w === 46 /* https://github.com/telerik/xPlatCore/issues/179 */) { - if (noValueAttribute) { - j--; //Started next attribute. Get back and break out of the loop. - break; - } else { - continue; - } - }; - - if (w === 91 || w === 93 || w === 40 || w === 41 || w === 94 || w === 35) { // Angular special attribute chars:[]()^ - continue; - } - - if (w === 32 || (w > 8 && w < 14) ) { // \f\n\r\t\v пробел - noValueAttribute = true; - continue; - } else if (w === 61) { // "=" == 61 - noValueAttribute = false; + if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w === 46 /* https://github.com/telerik/xPlatCore/issues/179 */) { + if (noValueAttribute) { + j--; //Started next attribute. Get back and break out of the loop. break; } else { - //console.log('error 2'); - if (!noValueAttribute) - return attr_res = false; // error. invalid char - }; - - break; - }; - - name = s.substring(i, j).trim(); - ok = true; - - if (name === 'xmlns:xmlns') { - //console.log('error 6') - return attr_res = false; // error. invalid name - }; - - w = s.charCodeAt(j+1); - - while (w = s.charCodeAt(j+1)) { - if (w===32 || (w > 8 && w<14) ) { // \f\n\r\t\v пробел - j++; - } else { - break; + continue; } + }; + + if (this.allowedAngularAttributeChars(w)) { + continue; } - if (!noValueAttribute) { - if (w === 34) { // '"' - j = s.indexOf('"', i = j+2 ); - - } else { - if (w === 39) { - j = s.indexOf('\'', i = j+2 ); - - } else { // "'" - return attr_res = false; // error. invalid char - }; - }; - } - - if (j === -1) { - //console.log('error 4') - return attr_res = false; // error. invalid char - }; - - - if (j+1 < l && !noValueAttribute) { - w = s.charCodeAt(j+1); - - if (w > 32 || w < 9 || (w < 32 && w > 13)) { - // error. invalid char - //console.log('error 5') - return attr_res = false; - }; - }; - - - if (noValueAttribute) { - value = ''; + if (w === 32 || (w > 8 && w < 14) ) { // \f\n\r\t\v пробел + noValueAttribute = true; + continue; + } else if (w === 61) { // "=" == 61 + noValueAttribute = false; + break; } else { - value = s.substring(i, j); + //console.log('error 2'); + if (!noValueAttribute) + return this.attr_res = false; // error. invalid char + }; + + break; + }; + + name = s.substring(i, j).trim(); + ok = true; + + if (name === 'xmlns:xmlns') { + //console.log('error 6') + return this.attr_res = false; // error. invalid name + }; + + w = s.charCodeAt(j+1); + + while (w = s.charCodeAt(j+1)) { + if (w===32 || (w > 8 && w<14) ) { // \f\n\r\t\v пробел + j++; + } else { + break; } - - //i = j + 1; // след. семвол уже проверен потому проверять нужно следуюший - i = j; // след. семвол уже проверен потому проверять нужно следуюший - - if (isNamespace) { // - if (hasSurmiseNS) { - // есть подозрение что в атрибутах присутствует xmlns - - if (newalias = name === 'xmlns' ? 'xmlns' : name.charCodeAt(0) === 120 && name.substr(0, 6) === 'xmlns:' && name.substr(6) ) { - alias = useNS[unEntities(value)]; - - if (alias) { - if (nsmatrix[newalias] !== alias) { - if (!hasNewMatrix) { - hasNewMatrix = true; - nn = {}; for (n in nsmatrix) nn[n] = nsmatrix[n]; - nsmatrix = nn; - }; - - nsmatrix[newalias] = alias; - }; - } else { - if (nsmatrix[newalias]) { - if (!hasNewMatrix) { - hasNewMatrix = true; - nn = {}; for (n in nsmatrix) nn[n] = nsmatrix[n]; - nsmatrix = nn; - }; - - nsmatrix[newalias] = false; - }; - }; - - res[name] = value; - continue; - }; - - attr_list.push(name, value); - continue; - }; - - w = name.length; - while(--w) { - if (name.charCodeAt(w) === 58) { // ':' - if (w = nsmatrix[name.substring(0, w)] ) { - res[w + name.substr(w)] = value; - }; - continue aa; - - // 'xml:base' ??? - }; - }; - }; - - res[name] = value; - noValueAttribute = false; - }; - - - if (!ok) { - return attr_res = true; // атрибутов нет, ошибок тоже нет - }; - - - if (hasSurmiseNS) { - bb: - - for (i = 0, l = attr_list.length; i < l; i++) { - name = attr_list[i++]; - - w = name.length; - while(--w) { // name.indexOf(':') - if (name.charCodeAt(w) === 58) { // ':' - if (w = nsmatrix[name.substring(0, w)]) { - res[w + name.substr(w)] = attr_list[i]; - }; - continue bb; - break; - }; - }; - - res[name] = attr_list[i]; - }; - }; - - return attr_res = res; - }; - - - // xml - string - function parse(xml) { - var u - , xml = String(xml) - , nodestack = [] - , stacknsmatrix = [] - //, string_node - , elem - , tagend = false - , tagstart = false - , j = 0, i = 0 - , x, y, q, w - , xmlns - , stopIndex = 0 - , stop // используется при разборе "namespace" . если встретился неизвестное пространство то события не генерируются - , _nsmatrix - , ok - ; - - function getStringNode() { - return xml.substring(i, j+1) - }; - - while(j !== -1) { - stop = stopIndex > 0; - - if (xml.charCodeAt(j) === 60) { // "<" - i = j; - } else { - i = xml.indexOf('<', j); - }; - - if (i === -1) { // конец разбора - - if (nodestack.length) { - onError('end file'); - return; - }; - - return; - }; - - if (j !== i && !stop) { - ok = onTextNode(xml.substring(j, i), unEntities); - if (ok === false) return; - }; - - w = xml.charCodeAt(i+1); - - if (w === 33) { // "!" - w = xml.charCodeAt(i+2); - if (w === 91 && xml.substr(i+3, 6) === 'CDATA[') { // 91 == "[" - j = xml.indexOf(']]>', i); - if (j === -1) { - onError('cdata'); - return; - }; - - //x = xml.substring(i+9, j); - if (!stop) { - ok = onCDATA(xml.substring(i+9, j), false); - if (ok === false) return; - }; - - - j += 3; - continue; - }; - - - if (w === 45 && xml.charCodeAt(i+3) === 45) { // 45 == "-" - j = xml.indexOf('-->', i); - if (j === -1) { - onError('expected -->'); - return; - }; - - - if (is_onComment && !stop) { - ok = onComment(xml.substring(i+4, j), unEntities); - if (ok === false) return; - }; - - j += 3; - continue; - }; - - j = xml.indexOf('>', i+1); - if (j === -1) { - onError('expected ">"'); - return; - }; - - if (is_onAttention && !stop) { - ok = onAttention(xml.substring(i, j+1), unEntities); - if (ok === false) return; - }; - - j += 1; - continue; - - } else { - if (w === 63) { // "?" - j = xml.indexOf('?>', i); - if (j === -1) { // error - onError('...?>'); - return; - }; - - if (is_onQuestion) { - ok = onQuestion(xml.substring(i, j+2)); - if (ok === false) return; - }; - - j += 2; - continue; - }; - }; - - j = xml.indexOf('>', i+1); - - if (j == -1) { // error - onError('...>'); - return; - }; - - attr_res = true; // атрибутов нет - - //if (xml.charCodeAt(i+1) === 47) { // 8 && w<14) ) { // \f\n\r\t\v пробел - continue; - }; - - onError('close tag'); - return; - }; - - } else { - if (xml.charCodeAt(j-1) === 47) { // .../> - x = elem = xml.substring(i+1, j-1); - - tagstart = true; - tagend = true; - } else { - x = elem = xml.substring(i+1, j); - - tagstart = true; - tagend = false; - }; - - if ( !(w > 96 && w < 123 || w > 64 && w <91) ) { - onError('first char nodeName'); - return; - }; - - for(q = 1, y = x.length; q < y; q++) { - w = x.charCodeAt(q); - - if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w === 46 /* https://github.com/telerik/xPlatCore/issues/179 */) { - continue; - }; - - if (w===32 || (w<14 && w > 8)) { // \f\n\r\t\v пробел - elem = x.substring(0, q) - attr_res = null; // возможно есть атирибуты - break; - }; - - onError('invalid nodeName'); - return; - }; - - if (!tagend) { - nodestack.push(elem); - }; - }; - - - if (isNamespace) { - if (stop) { - if (tagend) { - if (!tagstart) { - if (--stopIndex === 0) { - nsmatrix = stacknsmatrix.pop(); - }; - }; - - } else { - stopIndex += 1; - }; - - - j += 1; - continue; - }; - - _nsmatrix = nsmatrix; - - if (!tagend) { - stacknsmatrix.push(nsmatrix); - - if (attr_res !== true) { - if (hasSurmiseNS = x.indexOf('xmlns', q) !== -1) { - attr_string = x; - attr_posstart = q; - - getAttrs(); - - hasSurmiseNS = false; - }; - }; - }; - - - w = elem.indexOf(':'); - if (w !== -1) { - xmlns = nsmatrix[elem.substring(0, w)]; - elem = elem.substr(w+1); - - - } else { - xmlns = nsmatrix.xmlns; - }; - - - if (!xmlns) { - if (tagend) { - if (tagstart) { - nsmatrix = _nsmatrix; - } else { - nsmatrix = stacknsmatrix.pop(); - }; - } else { - stopIndex = 1; // первый элемент для которого не определено пространство имен - attr_res = true; - }; - - j += 1; - continue; - }; - - elem = xmlns + ':' + elem; - }; - - //string_node = xml.substring(i, j+1); // текст ноды как есть - - - if (tagstart) { // is_onStartNode - attr_string = x; - attr_posstart = q; - - ok = onStartNode(elem, getAttrs, unEntities, tagend - , getStringNode - ); - - if (ok === false) { - return; - }; - - attr_res = true; - }; - - if (tagend) { - ok = onEndNode(elem, unEntities, tagstart - , getStringNode - ); - - if (ok === false) { - return; - }; - - if (isNamespace) { - if (tagstart) { - nsmatrix = _nsmatrix; - } else { - nsmatrix = stacknsmatrix.pop(); - }; - }; - }; - - j += 1; - }; - }; + } + + if (!noValueAttribute) { + if (w === 34) { // '"' + j = s.indexOf('"', i = j+2 ); + + } else { + if (w === 39) { + j = s.indexOf('\'', i = j+2 ); + + } else { // "'" + return this.attr_res = false; // error. invalid char + }; + }; + } + + if (j === -1) { + //console.log('error 4') + return this.attr_res = false; // error. invalid char + }; + + + if (j+1 < l && !noValueAttribute) { + w = s.charCodeAt(j+1); + + if (w > 32 || w < 9 || (w < 32 && w > 13)) { + // error. invalid char + //console.log('error 5') + return this.attr_res = false; + }; + }; + + + if (noValueAttribute) { + value = ''; + } else { + value = s.substring(i, j); + } + + //i = j + 1; // след. семвол уже проверен потому проверять нужно следуюший + i = j; // след. семвол уже проверен потому проверять нужно следуюший + + if (this.isNamespace) { // + if (this.hasSurmiseNS) { + // есть подозрение что в атрибутах присутствует xmlns + + if (newalias = name === 'xmlns' ? 'xmlns' : name.charCodeAt(0) === 120 && name.substr(0, 6) === 'xmlns:' && name.substr(6) ) { + alias = this.useNS[unEntities(value)]; + + if (alias) { + if (this.nsmatrix[newalias] !== alias) { + if (!hasNewMatrix) { + hasNewMatrix = true; + nn = {}; for (n in this.nsmatrix) nn[n] = this.nsmatrix[n]; + this.nsmatrix = nn; + }; + + this.nsmatrix[newalias] = alias; + }; + } else { + if (this.nsmatrix[newalias]) { + if (!hasNewMatrix) { + hasNewMatrix = true; + nn = {}; for (n in this.nsmatrix) nn[n] = this.nsmatrix[n]; + this.nsmatrix = nn; + }; + + this.nsmatrix[newalias] = false; + }; + }; + + res[name] = value; + continue; + }; + + attr_list.push(name, value); + continue; + }; + + w = name.length; + while(--w) { + if (name.charCodeAt(w) === 58) { // ':' + if (w = this.nsmatrix[name.substring(0, w)] ) { + res[w + name.substr(w)] = value; + }; + continue aa; + + // 'xml:base' ??? + }; + }; + }; + + res[name] = value; + noValueAttribute = false; + }; + + + if (!ok) { + return this.attr_res = true; // атрибутов нет, ошибок тоже нет + }; + + + if (this.hasSurmiseNS) { + bb: + + for (i = 0, l = attr_list.length; i < l; i++) { + name = attr_list[i++]; + + w = name.length; + while(--w) { // name.indexOf(':') + if (name.charCodeAt(w) === 58) { // ':' + if (w = this.nsmatrix[name.substring(0, w)]) { + res[w + name.substr(w)] = attr_list[i]; + }; + continue bb; + break; + }; + }; + + res[name] = attr_list[i]; + }; + }; + + return this.attr_res = res; }; +// xml - string +EasySAXParser.prototype.parse = function(xml) { + var u + , xml = String(xml) + , nodestack = [] + , stacknsmatrix = [] + //, string_node + , elem + , tagend = false + , tagstart = false + , j = 0, i = 0 + , x, y, q, w + , xmlns + , stopIndex = 0 + , stop // используется при разборе "namespace" . если встретился неизвестное пространство то события не генерируются + , _nsmatrix + , ok + ; + + function getStringNode() { + return xml.substring(i, j+1) + }; + + while(j !== -1) { + stop = stopIndex > 0; + + if (xml.charCodeAt(j) === 60) { // "<" + i = j; + } else { + i = xml.indexOf('<', j); + }; + + if (i === -1) { // конец разбора + + if (nodestack.length) { + this.onError('end file'); + return; + }; + + return; + }; + + if (j !== i && !stop) { + ok = this.onTextNode(xml.substring(j, i), unEntities); + if (ok === false) return; + }; + + w = xml.charCodeAt(i+1); + + if (w === 33) { // "!" + w = xml.charCodeAt(i+2); + if (w === 91 && xml.substr(i+3, 6) === 'CDATA[') { // 91 == "[" + j = xml.indexOf(']]>', i); + if (j === -1) { + this.onError('cdata'); + return; + }; + + //x = xml.substring(i+9, j); + if (!stop) { + ok = this.onCDATA(xml.substring(i+9, j), false); + if (ok === false) return; + }; + + j += 3; + continue; + }; + + if (w === 45 && xml.charCodeAt(i+3) === 45) { // 45 == "-" + j = xml.indexOf('-->', i); + if (j === -1) { + this.onError('expected -->'); + return; + }; + if (this.is_onComment && !stop) { + ok = this.onComment(xml.substring(i+4, j), unEntities); + if (ok === false) return; + }; + j += 3; + continue; + }; + + j = xml.indexOf('>', i+1); + if (j === -1) { + this.onError('expected ">"'); + return; + }; + + if (this.is_onAttention && !stop) { + ok = this.onAttention(xml.substring(i, j+1), unEntities); + if (ok === false) return; + }; + + j += 1; + continue; + + } else { + if (w === 63) { // "?" + j = xml.indexOf('?>', i); + if (j === -1) { // error + this.onError('...?>'); + return; + }; + + if (this.is_onQuestion) { + ok = this.onQuestion(xml.substring(i, j+2)); + if (ok === false) return; + }; + + j += 2; + continue; + }; + }; + + j = xml.indexOf('>', i+1); + + if (j == -1) { // error + this.onError('...>'); + return; + }; + + this.attr_res = true; // атрибутов нет + + //if (xml.charCodeAt(i+1) === 47) { // 8 && w<14) ) { // \f\n\r\t\v пробел + continue; + }; + + this.onError('close tag'); + return; + }; + + } else { + if (xml.charCodeAt(j-1) === 47) { // .../> + x = elem = xml.substring(i+1, j-1); + + tagstart = true; + tagend = true; + } else { + x = elem = xml.substring(i+1, j); + + tagstart = true; + tagend = false; + }; + + if ( !(w > 96 && w < 123 || w > 64 && w <91) ) { + this.onError('first char nodeName'); + return; + }; + + for(q = 1, y = x.length; q < y; q++) { + w = x.charCodeAt(q); + + if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w === 46 /* https://github.com/telerik/xPlatCore/issues/179 */) { + continue; + }; + + if (w===32 || (w<14 && w > 8)) { // \f\n\r\t\v пробел + elem = x.substring(0, q) + this.attr_res = null; // возможно есть атирибуты + break; + }; + + this.onError('invalid nodeName'); + return; + }; + + if (!tagend) { + nodestack.push(elem); + }; + }; + + + if (this.isNamespace) { + if (stop) { + if (tagend) { + if (!tagstart) { + if (--stopIndex === 0) { + this.nsmatrix = stacknsmatrix.pop(); + }; + }; + + } else { + stopIndex += 1; + }; + + + j += 1; + continue; + }; + + _nsmatrix = this.nsmatrix; + + if (!tagend) { + stacknsmatrix.push(this.nsmatrix); + + if (this.attr_res !== true) { + if (this.hasSurmiseNS = x.indexOf('xmlns', q) !== -1) { + this.attr_string = x; + this.attr_posstart = q; + + this.getAttrs(); + + this.hasSurmiseNS = false; + }; + }; + }; + + + w = elem.indexOf(':'); + if (w !== -1) { + xmlns = this.nsmatrix[elem.substring(0, w)]; + elem = elem.substr(w+1); + + } else { + xmlns = this.nsmatrix.xmlns; + }; + + if (!xmlns) { + if (tagend) { + if (tagstart) { + this.nsmatrix = _nsmatrix; + } else { + this.nsmatrix = stacknsmatrix.pop(); + }; + } else { + stopIndex = 1; // первый элемент для которого не определено пространство имен + this.attr_res = true; + }; + + j += 1; + continue; + }; + + elem = xmlns + ':' + elem; + }; + + //string_node = xml.substring(i, j+1); // текст ноды как есть + + if (tagstart) { // is_onStartNode + this.attr_string = x; + this.attr_posstart = q; + + var that = this; + ok = this.onStartNode(elem, function() { return that.getAttrs() }, unEntities, tagend + , getStringNode + ); + + if (ok === false) { + return; + }; + + this.attr_res = true; + }; + + if (tagend) { + ok = this.onEndNode(elem, unEntities, tagstart + , getStringNode + ); + + if (ok === false) { + return; + }; + + if (this.isNamespace) { + if (tagstart) { + this.nsmatrix = _nsmatrix; + } else { + this.nsmatrix = stacknsmatrix.pop(); + }; + }; + }; + + j += 1; + }; +}; diff --git a/node-tests/test-angular-xml.ts b/node-tests/test-angular-xml.ts index 72bb3ff52..66e1ef92c 100644 --- a/node-tests/test-angular-xml.ts +++ b/node-tests/test-angular-xml.ts @@ -15,6 +15,7 @@ describe("angular xml parser", () => { break; } }); + parser.angularSyntax = true; }); it("parses [property] binding", () => { @@ -53,7 +54,6 @@ describe("angular xml parser", () => { assert.equal(last_attrs['text'], 'Name'); }); - return it("detects equals without value", () => { parser.parse(""); @@ -71,4 +71,11 @@ describe("angular xml parser", () => { assert.isFalse(last_attrs); }); + + it("rejects angular properties if syntax disabled", () => { + parser.angularSyntax = false; + parser.parse(""); + + assert.isFalse(last_attrs); + }); }); diff --git a/xml/xml.d.ts b/xml/xml.d.ts index a71af02a0..728128793 100644 --- a/xml/xml.d.ts +++ b/xml/xml.d.ts @@ -86,7 +86,7 @@ declare module "xml" { * @param onError The callback to execute when a parser error occurs. The 'error' parameter contains the error. * @param processNamespaces Specifies whether namespaces should be processed. */ - constructor(onEvent: (event: ParserEvent) => void, onError?: (error: Error) => void, processNamespaces?: boolean); + constructor(onEvent: (event: ParserEvent) => void, onError?: (error: Error) => void, processNamespaces?: boolean, angularSyntax?: boolean); /** * Parses the supplied xml string. @@ -94,4 +94,4 @@ declare module "xml" { */ parse(xmlString: string): void; } -} \ No newline at end of file +} diff --git a/xml/xml.ts b/xml/xml.ts index 7cd8b2563..5acf9842c 100644 --- a/xml/xml.ts +++ b/xml/xml.ts @@ -148,6 +148,14 @@ export class XmlParser implements definition.XmlParser { } } + public get angularSyntax() : boolean { + return this._parser.angularSyntax; + } + + public set angularSyntax(value: boolean) { + this._parser.angularSyntax = value; + } + public parse(xmlString: string): void { if (this._processNamespaces) { this._namespaceStack = []; From 7ce98a75e41be4f8d158da36206d653983d2770b Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Fri, 5 Jun 2015 16:06:29 +0300 Subject: [PATCH 11/12] Fix tslint warnings. --- node-tests/definitions/chai.d.ts | 1 + node-tests/definitions/mocha.d.ts | 2 ++ node-tests/test-xml.ts | 1 - 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/node-tests/definitions/chai.d.ts b/node-tests/definitions/chai.d.ts index b07ee43e0..fae15cd05 100644 --- a/node-tests/definitions/chai.d.ts +++ b/node-tests/definitions/chai.d.ts @@ -1,3 +1,4 @@ +/* tslint:disable */ // Type definitions for chai 1.7.2 // Project: http://chaijs.com/ diff --git a/node-tests/definitions/mocha.d.ts b/node-tests/definitions/mocha.d.ts index 64255a7ba..7cd5abcae 100644 --- a/node-tests/definitions/mocha.d.ts +++ b/node-tests/definitions/mocha.d.ts @@ -1,3 +1,5 @@ +/* tslint:disable */ + // Type definitions for mocha 1.17.1 // Project: http://visionmedia.github.io/mocha/ // Definitions by: Kazi Manzur Rashid , otiai10 diff --git a/node-tests/test-xml.ts b/node-tests/test-xml.ts index 6d392a958..3e431829d 100644 --- a/node-tests/test-xml.ts +++ b/node-tests/test-xml.ts @@ -17,7 +17,6 @@ describe("xml parser", () => { }); }); - it("handles whitespace around attribute =", () => { let attributes = null; let element = null; From 67cfab233ae3645e24e337d225446bbf41500d85 Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Fri, 5 Jun 2015 17:25:48 +0300 Subject: [PATCH 12/12] fix(xml parser): Fix text node data event. --- js-libs/easysax/easysax.js | 2 +- node-tests/test-xml.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/js-libs/easysax/easysax.js b/js-libs/easysax/easysax.js index 3cca74f73..321eabc0a 100644 --- a/js-libs/easysax/easysax.js +++ b/js-libs/easysax/easysax.js @@ -105,7 +105,7 @@ EasySAXParser.prototype.on = function(name, cb) { case 'error': this.onError = cb || nullFunc; break; case 'startNode': this.onStartNode = cb || nullFunc; break; case 'endNode': this.onEndNode = cb || nullFunc; break; - case 'textNode': onTextNode = cb || nullFunc; break; + case 'textNode': this.onTextNode = cb || nullFunc; break; case 'cdata': this.onCDATA = cb || nullFunc; break; case 'comment': this.onComment = cb; this.is_onComment = !!cb; break; diff --git a/node-tests/test-xml.ts b/node-tests/test-xml.ts index 3e431829d..713049dd1 100644 --- a/node-tests/test-xml.ts +++ b/node-tests/test-xml.ts @@ -4,6 +4,7 @@ import xml = require('xml'); describe("xml parser", () => { let last_element = null; let last_attrs = null; + let last_data = null; let parser = null; beforeEach(() => { @@ -13,6 +14,9 @@ describe("xml parser", () => { last_element = event.elementName; last_attrs = event.attributes; break; + case xml.ParserEventType.Text: + last_data = event.data; + break; } }); }); @@ -26,4 +30,9 @@ describe("xml parser", () => { assert.equal('TextField', last_element); assert.equal('hello', last_attrs['text']); }); + + it("resolves entities", () => { + parser.parse("<>"&'"); + assert.equal("<>\"&'", last_data); + }); });