From cfebc962f092a6582e4c01facf66bf4fdf00d4d1 Mon Sep 17 00:00:00 2001 From: Dror Gluska Date: Fri, 31 May 2019 11:27:48 +0300 Subject: [PATCH] typescriptify and cleanup --- .gitignore | 1 + .vscode/launch.json | 51 + .vscode/settings.json | 5 + package-lock.json | 614 ++++++++++++ package.json | 32 +- parser.js | 281 ------ parserState.js | 13 - src/parser.ts | 300 ++++++ src/parserState.ts | 11 + test/basic.spec.ts | 146 +++ test/cdata_and_comments.spec.ts | 30 + test/emit.spec.ts | 136 +++ test/error.spec.ts | 45 + test/explicit_array.spec.ts | 250 +++++ test/interested.spec.ts | 321 +++++++ test/options.spec.ts | 117 +++ test/parse.spec.ts | 97 ++ test/pause_and_resume.spec.ts | 82 ++ test/performance.spec.ts | 103 +++ test/read.spec.ts | 105 +++ test/same_name.spec.ts | 111 +++ test/test.js | 1545 ------------------------------- test/uncompressed.spec.ts | 64 ++ test/wrong_resourcepath.spec.ts | 99 ++ tsconfig.json | 33 + tslint.json | 22 + typings/node-expat/index.d.ts | 91 ++ typings/node-expat/project.json | 3 + 28 files changed, 2863 insertions(+), 1845 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 package-lock.json delete mode 100644 parser.js delete mode 100644 parserState.js create mode 100644 src/parser.ts create mode 100644 src/parserState.ts create mode 100644 test/basic.spec.ts create mode 100644 test/cdata_and_comments.spec.ts create mode 100644 test/emit.spec.ts create mode 100644 test/error.spec.ts create mode 100644 test/explicit_array.spec.ts create mode 100644 test/interested.spec.ts create mode 100644 test/options.spec.ts create mode 100644 test/parse.spec.ts create mode 100644 test/pause_and_resume.spec.ts create mode 100644 test/performance.spec.ts create mode 100644 test/read.spec.ts create mode 100644 test/same_name.spec.ts delete mode 100644 test/test.js create mode 100644 test/uncompressed.spec.ts create mode 100644 test/wrong_resourcepath.spec.ts create mode 100644 tsconfig.json create mode 100644 tslint.json create mode 100644 typings/node-expat/index.d.ts create mode 100644 typings/node-expat/project.json diff --git a/.gitignore b/.gitignore index 5148e52..fa962f1 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ jspm_packages # Optional REPL history .node_repl_history +/dist diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..da0bf16 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,51 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceFolder}\\node_stream_zip.js", + "preLaunchTask": "tsc: build - tsconfig.json", + "outFiles": [ + "${workspaceFolder}/dist/**/*.js" + ] + }, + { + "type": "node", + "request": "launch", + "name": "Mocha All", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "--timeout", + "999999", + "--colors", + "-r", + "ts-node/register", + "test/**/*.spec.ts" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "Mocha Current File", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "--timeout", + "999999", + "--colors", + "-r", + "ts-node/register", + "${file}" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7f720b1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..573caf5 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,614 @@ +{ + "name": "xml-streamer", + "version": "0.2.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@types/lodash": { + "version": "4.14.132", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.132.tgz", + "integrity": "sha512-RNUU1rrh85NgUJcjOOr96YXr+RHwInGbaQCZmlitqOaCKXffj8bh+Zxwuq5rjDy5OgzFldDVoqk4pyLEDiwxIw==", + "dev": true + }, + "@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true + }, + "@types/node": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.4.tgz", + "integrity": "sha512-j8YL2C0fXq7IONwl/Ud5Kt0PeXw22zGERt+HSSnwbKOJVsAGkEz3sFCYwaF9IOuoG1HOtE0vKCj6sXF7Q0+Vaw==", + "dev": true + }, + "@types/should": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/should/-/should-13.0.0.tgz", + "integrity": "sha512-Mi6YZ2ABnnGGFMuiBDP0a8s1ZDCDNHqP97UH8TyDmCWuGGavpsFMfJnAMYaaqmDlSCOCNbVLHBrSDEOpx/oLhw==", + "dev": true, + "requires": { + "should": "*" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.0.0.tgz", + "integrity": "sha1-ib2d9nMrUSVrxnBTQrugLtEhMe8=", + "dev": true, + "requires": { + "ms": "0.6.2" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", + "dev": true + }, + "growl": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.8.1.tgz", + "integrity": "sha1-Sy3sjZB+k9szZiTc7AGDUC+MlCg=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "dev": true + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.21.5.tgz", + "integrity": "sha1-fFiwkXTfl25DSiOx6NY5hz/FKek=", + "dev": true, + "requires": { + "commander": "2.3.0", + "debug": "2.0.0", + "diff": "1.0.8", + "escape-string-regexp": "1.0.2", + "glob": "3.2.3", + "growl": "1.8.1", + "jade": "0.26.3", + "mkdirp": "0.5.0" + }, + "dependencies": { + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "dev": true + }, + "diff": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.8.tgz", + "integrity": "sha1-NDJ2MI7Jkbe8giZ+1VvBQR+XFmY=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "dev": true + }, + "glob": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", + "dev": true, + "requires": { + "graceful-fs": "~2.0.0", + "inherits": "2", + "minimatch": "~0.2.11" + } + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "mkdirp": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + } + } + }, + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=", + "dev": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "node-expat": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/node-expat/-/node-expat-2.3.15.tgz", + "integrity": "sha1-lqU6C8AGSdr3j0/AdKI+ZEwiO0w=", + "requires": { + "bindings": "^1.2.1", + "nan": "^2.3.5" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "resolve": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", + "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dev": true, + "requires": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, + "requires": { + "should-type": "^1.4.0" + } + }, + "should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "dev": true + }, + "should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "should-util": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz", + "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "ts-node": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.2.0.tgz", + "integrity": "sha512-m8XQwUurkbYqXrKqr3WHCW310utRNvV5OnRVeISeea7LoCWVcdfeB/Ntl8JYWFh+WRoUAdBgESrzKochQt7sMw==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + }, + "dependencies": { + "diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "dev": true + } + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tslint": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.17.0.tgz", + "integrity": "sha512-pflx87WfVoYepTet3xLfDOLDm9Jqi61UXIKePOuca0qoAZyrGWonDG9VTbji58Fy+8gciUn8Bt7y69+KEVjc/w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "typescript": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz", + "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz", + "integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 0998fed..566f65f 100644 --- a/package.json +++ b/package.json @@ -24,19 +24,39 @@ "url": "https://github.com/Sai1919/xml-streamer" }, "dependencies": { - "node-expat": "2.3.15", - "lodash": "4.17.5" + "lodash": "4.17.5", + "node-expat": "2.3.15" }, "devDependencies": { "mocha": "^1.21.4", - "should": "11.1.1", - "standard": "8.5.0" + "should": "^13.2.3", + "@types/lodash": "^4.14.132", + "@types/mocha": "^5.2.7", + "@types/node": "^12.0.4", + "@types/should": "^13.0.0", + "ts-node": "^8.2.0", + "tslint": "^5.17.0", + "typescript": "^3.5.1" }, "optionalDependencies": {}, - "main": "./parser", + "main": "dist/parser.js", + "types": "dist/parser.d.ts", "scripts": { - "test": "mocha && standard" + "performance-test": "node --prof node_modules/mocha/bin/_mocha -r ts-node/register test/**/*.spec.ts", + "test-one": "mocha -r ts-node/register", + "test": "mocha -r ts-node/register test/**/*.spec.ts", + "lint": "tslint --project .", + "dryrun": "tsc -noEmit", + "build": "tsc", + "prepublish": "npm run lint && npm run dryrun && npm run test", + "install": "npm run build" }, + "contributors": [ + { + "name": "Dror Gluska", + "email": "drorgl@gmail.com" + } + ], "maintainers": [ { "name": "Sai Teja", diff --git a/parser.js b/parser.js deleted file mode 100644 index 302248f..0000000 --- a/parser.js +++ /dev/null @@ -1,281 +0,0 @@ -var expat = require('node-expat') -var _ = require('lodash') -var util = require('util') -var stream = require('stream') - -var ParserState = require('./parserState') -var defaults = { - resourcePath: '', - emitOnNodeName: false, - attrsKey: '$', - textKey: '_', - explicitArray: true, - verbatimText: false -} - -function XmlParser (opts) { - this.opts = _.defaults(opts, defaults) - this.parserState = new ParserState() - this.parser = new expat.Parser('UTF-8') - stream.Transform.call(this) - this._readableState.objectMode = true -} -util.inherits(XmlParser, stream.Transform) - -XmlParser.prototype.checkForInterestedNodeListeners = function () { - var ignore = [ 'end', 'prefinish', 'data', 'error' ] - var eventNames = Object.keys(this._events) - - for (var i = 0; i < eventNames.length; i++) { - if (_.includes(ignore, eventNames[i], 0)) continue - this.parserState.interestedNodes.push(eventNames[i]) - } -} - -XmlParser.prototype._transform = function (chunk, encoding, callback) { - if (encoding !== 'buffer') this.emit('error', new Error('unsupported encoding')) - - this.processChunk(chunk) - callback() -} - -XmlParser.prototype.processChunk = function (chunk) { - var parser = this.parser - var state = this.parserState - - if (state.isRootNode) { - this.checkForInterestedNodeListeners() - registerEvents.call(this) - } - - if (typeof chunk === 'string') { - if (!parser.parse('', true)) processError.call(this) - } else { - if (!parser.parse(chunk.toString())) processError.call(this) - } -} - -XmlParser.prototype.parse = function (chunk, cb) { - var parser = this.parser - var state = this.parserState - var error - - if (state.isRootNode) { - this.checkForInterestedNodeListeners() - registerEvents.call(this) - } - - if (typeof chunk === Buffer) chunk = chunk.toString() - - this.on('error', function (err) { - error = err - }) - - if (!parser.parse(chunk)) { - error = processError.call(this) - } - - if (error) return cb(error) - - return cb(null, this._readableState.buffer) -} - -function registerEvents () { - var scope = this - var parser = this.parser - var state = this.parserState - var lastIndex - var resourcePath = this.opts.resourcePath - var attrsKey = this.opts.attrsKey - var textKey = this.opts.textKey - var interestedNodes = state.interestedNodes - var explicitArray = this.opts.explicitArray - var verbatimText = this.opts.verbatimText; - - parser.on('startElement', function (name, attrs) { - if (state.isRootNode) state.isRootNode = false; - state.currentPath = state.currentPath + '/' + name - checkForResourcePath(name) - if (state.isPathfound) processStartElement(name, attrs) - }) - - parser.on('endElement', function (name) { - state.lastEndedNode = name - lastIndex = state.currentPath.lastIndexOf('/' + name) - state.currentPath = state.currentPath.substring(0, lastIndex) - if (state.isPathfound) processEndElement(name) - checkForResourcePath(name) - }) - - parser.on('text', function (text) { - if (state.isPathfound) processText(text) - }) - - parser.on('error', function (err) { - processError.call(this, err) - }) - - function processStartElement (name, attrs) { - if (!name) return - - var obj = {} - if (attrs && !_.isEmpty(attrs)) obj[attrsKey] = attrs - var tempObj = state.object - var path = getRelativePath(name) - if (!path) { - if (attrs && !_.isEmpty(attrs)) state.object[attrsKey] = attrs - return - } - var tokens = path.split('.') - - for (var i = 0; i < tokens.length; i++) { - if (tempObj[tokens[i]] && !(explicitArray === false && i === tokens.length - 1)) { - tempObj = tempObj[tokens[i]] - } else { - // if explicitArray is true then create each node as array - // irrespective of how many nodes are there with same name. - tempObj[tokens[i]] = explicitArray ? [] : obj - tempObj = tempObj[tokens[i]] - } - if (Array.isArray(tempObj) && i !== tokens.length - 1) tempObj = tempObj[tempObj.length - 1] - } - - if (Array.isArray(tempObj)) { - tempObj.push(obj) - } - } - - function processEndElement (name) { - if (resourcePath) { - var index = resourcePath.lastIndexOf('/') - var rpath = resourcePath.substring(0, index) - - if (rpath === state.currentPath) { - scope.push(state.object) - if (scope.opts.emitOnNodeName) scope.emit(name, state.object) - state.object = {} - } - } else { - if (_.includes(interestedNodes, name, 0)) { - emitInterestedNode(name) - if (state.firstFoundNode === name) { - state.object = {} - state.firstFoundNode = '' - state.isPathfound = false - } - } - } - } - - function emitInterestedNode (name) { - var index - var xpath - var pathTokens - - xpath = state.currentPath.substring(1) - pathTokens = xpath.split('/') - pathTokens.push(name) - index = pathTokens.indexOf(state.firstFoundNode) - pathTokens = _.drop(pathTokens, index + 1) - var tempObj = state.object - for (var i = 0; i < pathTokens.length; i++) { - tempObj = tempObj[pathTokens[i]] - } - if (Array.isArray(tempObj)) tempObj = tempObj[tempObj.length - 1] - scope.emit(name, tempObj) - scope.push(tempObj) - } - - function processText (text) { - if ((!text) || ((!verbatimText) && !/\S/.test(text))) { - return - } - var path = getRelativePath() - var tempObj = state.object - if (!path) { - if (!state.object[textKey]) state.object[textKey] = '' - state.object[textKey] = state.object[textKey] + text - return - } - var tokens = path.split('.') - for (var i = 0; i < tokens.length; i++) { - if (tempObj[tokens[i]]) { - tempObj = tempObj[tokens[i]] - } else { - tempObj[tokens[i]] = explicitArray ? [] : {} - tempObj = tempObj[tokens[i]] - } - if (Array.isArray(tempObj) && i !== tokens.length - 1) tempObj = tempObj[tempObj.length - 1] - } - - if (Array.isArray(tempObj)) { - var obj = tempObj[tempObj.length - 1] - if (!obj[textKey]) obj[textKey] = '' - obj[textKey] = obj[textKey] + text - } else { - if (!tempObj[textKey]) tempObj[textKey] = '' - tempObj[textKey] = tempObj[textKey] + text - } - } - - function checkForResourcePath (name) { - if (resourcePath) { - if (state.currentPath.indexOf(resourcePath) === 0) { - state.isPathfound = true - } else { - state.isPathfound = false - } - } else { - if (_.includes(interestedNodes, name, 0)) { - state.isPathfound = true - if (!state.firstFoundNode) { - state.firstFoundNode = name - } - } - } - } - - function getRelativePath () { - var tokens - var jsonPath - var index - - if (resourcePath) { - var xpath = state.currentPath.substring(resourcePath.length) - - if (!xpath) return - if (xpath[0] === '/') xpath = xpath.substring(1) - tokens = xpath.split('/') - jsonPath = tokens.join('.') - } else { - xpath = state.currentPath.substring(1) - tokens = xpath.split('/') - index = tokens.indexOf(state.firstFoundNode) - tokens = _.drop(tokens, index + 1) - jsonPath = tokens.join('.') - } - return jsonPath - } -} - -function processError (err) { - var parser = this.parser - var error = '' - - if (err) { - error = err - } else { - error = parser.getError() - } - error = new Error(error + ' at line no: ' + parser.getCurrentLineNumber()) - this.emit('error', error) - return error -} - -XmlParser.prototype._flush = function (callback) { - this.processChunk('') - callback() -} - -module.exports = XmlParser - diff --git a/parserState.js b/parserState.js deleted file mode 100644 index 2456342..0000000 --- a/parserState.js +++ /dev/null @@ -1,13 +0,0 @@ - -function ParserState () { - this.currentPath = '' - this.lastEndedNode = '' - this.isPathfound = false - this.object = {} - this.paused = false - this.isRootNode = true - this.firstFoundNode = '' - this.interestedNodes = [] -} - -module.exports = ParserState diff --git a/src/parser.ts b/src/parser.ts new file mode 100644 index 0000000..ce0a709 --- /dev/null +++ b/src/parser.ts @@ -0,0 +1,300 @@ +/// +import _ from "lodash"; +import * as expat from "node-expat"; +import stream from "stream"; +import util from "util"; + +import { ParserState } from "./parserState"; +const defaults = { + resourcePath: "", + emitOnNodeName: false, + attrsKey: "$", + textKey: "_", + explicitArray: true, + verbatimText: false +}; + +export interface IXmlParserOptions { + resourcePath?: string; + emitOnNodeName?: boolean; + attrsKey?: string; + textKey?: string; + explicitArray?: boolean; + verbatimText?: boolean; +} + +export class XmlParser extends stream.Transform { + public parserState: ParserState; + private opts: IXmlParserOptions; + private _readableState: { objectMode: true, buffer: any }; + private parser: expat.Parser; + constructor(opts?: IXmlParserOptions) { + super(); + this.opts = _.defaults(opts, defaults); + this.parserState = new ParserState(); + this.parser = new expat.Parser(); + this._readableState.objectMode = true; + } + + public checkForInterestedNodeListeners() { + const ignore = ["end", "prefinish", "data", "error"]; + const eventNames = Object.keys((this as any)._events); + + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < eventNames.length; i++) { + if (_.includes(ignore, eventNames[i], 0)) { continue; } + this.parserState.interestedNodes.push(eventNames[i]); + } + } + + public _transform(chunk: Buffer | string, encoding: string, callback: () => void) { + if (encoding !== "buffer") { this.emit("error", new Error("unsupported encoding")); } + + this.processChunk(chunk); + callback(); + } + + public processChunk(chunk: string | Buffer) { + const parser = this.parser; + const state = this.parserState; + + if (state.isRootNode) { + this.checkForInterestedNodeListeners(); + registerEvents.call(this); + } + + if (typeof chunk === "string") { + if (!parser.parse("", true)) { processError.call(this); } + } else { + if (!parser.parse(chunk.toString())) { processError.call(this); } + } + } + + public parse(chunk: Buffer | string, cb: (error: Error, data?: Buffer) => void) { + const parser = this.parser; + const state = this.parserState; + let error; + + if (state.isRootNode) { + this.checkForInterestedNodeListeners(); + registerEvents.call(this); + } + + if (chunk instanceof Buffer) { chunk = chunk.toString(); } + + this.on("error", (err) => { + error = err; + }); + + if (!parser.parse(chunk)) { + error = processError.call(this); + } + + if (error) { return cb(error); } + + const result = []; + while (this._readableState.buffer.length > 0) { + result.push(this._readableState.buffer.consume()); + } + return cb(null, result as any); + } + + public _flush(callback: () => void) { + this.processChunk(""); + callback(); + } + +} + +function registerEvents() { + const scope = this; + const parser: expat.Parser = this.parser; + const state = this.parserState; + let lastIndex; + const resourcePath = this.opts.resourcePath; + const attrsKey = this.opts.attrsKey; + const textKey = this.opts.textKey; + const interestedNodes = state.interestedNodes; + const explicitArray = this.opts.explicitArray; + const verbatimText = this.opts.verbatimText; + + parser.on("startElement", (name, attrs) => { + if (state.isRootNode) { state.isRootNode = false; } + state.currentPath = state.currentPath + "/" + name; + checkForResourcePath(name); + if (state.isPathfound) { processStartElement(name, attrs); } + }); + + parser.on("endElement", (name) => { + state.lastEndedNode = name; + lastIndex = state.currentPath.lastIndexOf("/" + name); + state.currentPath = state.currentPath.substring(0, lastIndex); + if (state.isPathfound) { processEndElement(name); } + checkForResourcePath(name); + }); + + parser.on("text", (text) => { + if (state.isPathfound) { processText(text); } + }); + + parser.on("error", function(err) { + processError.call(this, err); + }); + + function processStartElement(name: string, attrs: any) { + if (!name) { return; } + + const obj: any = {}; + if (attrs && !_.isEmpty(attrs)) { obj[attrsKey] = attrs; } + let tempObj = state.object; + const path = getRelativePath(/*name*/); + if (!path) { + if (attrs && !_.isEmpty(attrs)) { state.object[attrsKey] = attrs; } + return; + } + const tokens = path.split("."); + + for (let i = 0; i < tokens.length; i++) { + if (tempObj[tokens[i]] && !(explicitArray === false && i === tokens.length - 1)) { + tempObj = tempObj[tokens[i]]; + } else { + // if explicitArray is true then create each node as array + // irrespective of how many nodes are there with same name. + tempObj[tokens[i]] = explicitArray ? [] : obj; + tempObj = tempObj[tokens[i]]; + } + if (Array.isArray(tempObj) && i !== tokens.length - 1) { tempObj = tempObj[tempObj.length - 1]; } + } + + if (Array.isArray(tempObj)) { + tempObj.push(obj); + } + } + + function processEndElement(name: string) { + if (resourcePath) { + const index = resourcePath.lastIndexOf("/"); + const rpath = resourcePath.substring(0, index); + + if (rpath === state.currentPath) { + scope.push(state.object); + if (scope.opts.emitOnNodeName) { scope.emit(name, state.object); } + state.object = {}; + } + } else { + if (_.includes(interestedNodes, name, 0)) { + emitInterestedNode(name); + if (state.firstFoundNode === name) { + state.object = {}; + state.firstFoundNode = ""; + state.isPathfound = false; + } + } + } + } + + function emitInterestedNode(name: string) { + let index; + let xpath; + let pathTokens; + + xpath = state.currentPath.substring(1); + pathTokens = xpath.split("/"); + pathTokens.push(name); + index = pathTokens.indexOf(state.firstFoundNode); + pathTokens = _.drop(pathTokens, index + 1); + let tempObj = state.object; + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < pathTokens.length; i++) { + tempObj = tempObj[pathTokens[i] as any]; + } + if (Array.isArray(tempObj)) { tempObj = tempObj[tempObj.length - 1]; } + scope.emit(name, tempObj); + scope.push(tempObj); + } + + function processText(text: string) { + if ((!text) || ((!verbatimText) && !/\S/.test(text))) { + return; + } + const path = getRelativePath(); + let tempObj = state.object; + if (!path) { + if (!state.object[textKey]) { state.object[textKey] = ""; } + state.object[textKey] = state.object[textKey] + text; + return; + } + const tokens = path.split("."); + for (let i = 0; i < tokens.length; i++) { + if (tempObj[tokens[i]]) { + tempObj = tempObj[tokens[i]]; + } else { + tempObj[tokens[i]] = explicitArray ? [] : {}; + tempObj = tempObj[tokens[i]]; + } + if (Array.isArray(tempObj) && i !== tokens.length - 1) { tempObj = tempObj[tempObj.length - 1]; } + } + + if (Array.isArray(tempObj)) { + const obj = tempObj[tempObj.length - 1]; + if (!obj[textKey]) { obj[textKey] = ""; } + obj[textKey] = obj[textKey] + text; + } else { + if (!tempObj[textKey]) { tempObj[textKey] = ""; } + tempObj[textKey] = tempObj[textKey] + text; + } + } + + function checkForResourcePath(name: string) { + if (resourcePath) { + if (state.currentPath.indexOf(resourcePath) === 0) { + state.isPathfound = true; + } else { + state.isPathfound = false; + } + } else { + if (_.includes(interestedNodes, name, 0)) { + state.isPathfound = true; + if (!state.firstFoundNode) { + state.firstFoundNode = name; + } + } + } + } + + function getRelativePath() { + let tokens; + let jsonPath; + let index; + + if (resourcePath) { + let xpath = state.currentPath.substring(resourcePath.length); + + if (!xpath) { return; } + if (xpath[0] === "/") { xpath = xpath.substring(1); } + tokens = xpath.split("/"); + jsonPath = tokens.join("."); + } else { + const xpath = state.currentPath.substring(1); + tokens = xpath.split("/"); + index = tokens.indexOf(state.firstFoundNode); + tokens = _.drop(tokens, index + 1); + jsonPath = tokens.join("."); + } + return jsonPath; + } +} + +function processError(err: Error) { + const parser = this.parser; + let error: Error = null; + + if (err) { + error = err; + } else { + error = parser.getError(); + } + error = new Error(error + " at line no: " + parser.getCurrentLineNumber()); + this.emit("error", error); + return error; +} diff --git a/src/parserState.ts b/src/parserState.ts new file mode 100644 index 0000000..ac4856a --- /dev/null +++ b/src/parserState.ts @@ -0,0 +1,11 @@ + +export class ParserState { + public currentPath = ""; + public lastEndedNode = ""; + public isPathfound = false; + public object: any = {}; + public paused = false; + public isRootNode = true; + public firstFoundNode = ""; + public interestedNodes: string[] = []; +} diff --git a/test/basic.spec.ts b/test/basic.spec.ts new file mode 100644 index 0000000..c91ec8d --- /dev/null +++ b/test/basic.spec.ts @@ -0,0 +1,146 @@ +import fs from "fs"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; + +describe("Basic behavior", () => { + it("should properly parse a simple file.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/item.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + const expectedData = [ + { + $: { id: "1", test: "hello" }, + subitem: + [{ $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }] + }, + { + $: { id: "2" }, + subitem: [{ _: "three" }, { _: "four" }, { _: "five" }] + }]; + const actualData: string[] = []; + let dataEventCount = 0; + + parser.on("data", (data) => { + actualData.push(data); + dataEventCount++; + }); + + parser.on("error", (err) => { + should(err).not.be.ok(); + done(err); + }); + + parser.on("end", () => { + // console.log('actualData=', actualData) + // console.log('dataEventCount=', dataEventCount) + should(actualData).deepEqual(expectedData); + should(dataEventCount).equal(2); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse a medium size file.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/medium.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + + let dataEventCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + should(dataEventCount).equal(10); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse a file containing many nodes.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/manyItems.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + + let dataEventCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + should(dataEventCount).equal(296); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse a xml simple file in which nodes contain text values randomly.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/randomText.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + const expectedData = [{ + $: { id: "1", test: "hello" }, _: " item one two", + subitem: [{ $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }] + }, + { + $: { id: "2" }, _: " item one two three four", + subitem: [{ _: "three" }, { _: "four" }, { _: "five" }] + } + ]; + const actualData: string[] = []; + let dataEventCount = 0; + + parser.on("data", (data) => { + actualData.push(data); + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('actualData=', JSON.stringify(actualData, null, 1)) + // console.log('dataEventCount=', dataEventCount) + should(actualData).deepEqual(expectedData); + should(dataEventCount).equal(2); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse a huge file.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/hugeFile.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + // console.log(parser) + let dataEventCount = 0; + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + should(dataEventCount).equal(2072); + done(); + }); + xmlStream.pipe(parser); + }); +}); diff --git a/test/cdata_and_comments.spec.ts b/test/cdata_and_comments.spec.ts new file mode 100644 index 0000000..75499bd --- /dev/null +++ b/test/cdata_and_comments.spec.ts @@ -0,0 +1,30 @@ +import fs from "fs"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; +describe("CData and comments in xml", () => { + it("should properly parse a simple file.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/CData-comments.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + + let dataEventCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + should(dataEventCount).equal(296); + done(); + }); + xmlStream.pipe(parser); + }); +}); diff --git a/test/emit.spec.ts b/test/emit.spec.ts new file mode 100644 index 0000000..ee19474 --- /dev/null +++ b/test/emit.spec.ts @@ -0,0 +1,136 @@ +import fs from "fs"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; +describe("emitOnNodeName", () => { + it("should properly emit events on node names.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/item.xml"); + const parser = new XmlParser({ resourcePath: "/items/item", emitOnNodeName: true }); + const expectedData = [ + { + $: { id: "1", test: "hello" }, + subitem: + [{ $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }] + }, + { + $: { id: "2" }, + subitem: [{ _: "three" }, { _: "four" }, { _: "five" }] + }]; + const actualData: string[] = []; + const itemData : string[] = []; + let dataEventCount = 0; + let itemCount = 0; + + parser.on("data", (data) => { + actualData.push(data); + dataEventCount++; + }); + + parser.on("item", (item) => { + itemData.push(item); + itemCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('actualData=', actualData) + // console.log('dataEventCount=', dataEventCount) + should(actualData).deepEqual(expectedData); + should(dataEventCount).equal(2); + should(itemData).deepEqual(expectedData); + should(itemCount).equal(2); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly emit events on node names while parsing a medium size file.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/medium.xml"); + const parser = new XmlParser({ resourcePath: "/items/item", emitOnNodeName: true }); + + let dataEventCount = 0; + let itemCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("item", (data) => { + itemCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + should(dataEventCount).equal(10); + should(itemCount).equal(10); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse a file containing many nodes.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/manyItems.xml"); + const parser = new XmlParser({ resourcePath: "/items/item", emitOnNodeName: true }); + + let dataEventCount = 0; + let itemCount = 0; + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("item", (data) => { + itemCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + should(dataEventCount).equal(296); + should(itemCount).equal(296); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse a huge file.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/hugeFile.xml"); + const parser = new XmlParser({ resourcePath: "/items/item", emitOnNodeName: true }); + + let dataEventCount = 0; + let itemCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("item", (item) => { + itemCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + should(dataEventCount).equal(2072); + should(itemCount).equal(2072); + done(); + }); + xmlStream.pipe(parser); + }); +}); diff --git a/test/error.spec.ts b/test/error.spec.ts new file mode 100644 index 0000000..effd02b --- /dev/null +++ b/test/error.spec.ts @@ -0,0 +1,45 @@ + +import fs from "fs"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; +describe("Error Handling", () => { + it("should properly return error if the xml file is corrupted.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/corrupted.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + let dataEventCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("error", (err) => { + // console.log(err) + should(err.message).equal("mismatched tag at line no: 11"); + done(); + }); + + xmlStream.pipe(parser); + }); + + it("should properly return error if the large xml file is corrupted.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/largeCorruptedFile.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + let dataEventCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("error", (err) => { + // console.log(err) + should(err.message).equal("mismatched tag at line no: 8346"); + done(); + }); + + xmlStream.pipe(parser); + }); +}); diff --git a/test/explicit_array.spec.ts b/test/explicit_array.spec.ts new file mode 100644 index 0000000..df03009 --- /dev/null +++ b/test/explicit_array.spec.ts @@ -0,0 +1,250 @@ +import fs from "fs"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; +describe("should respect explicitArray constructor option", () => { + it("should properly parse a simple file with explicitArray set to false.", (done) => { + const xml = fs.readFileSync("./test/TestFiles/item.xml"); + const parser = new XmlParser({ resourcePath: "/items/item", explicitArray: false }); + const expectedData = [ + { + $: { id: "1", test: "hello" }, + subitem: { $: { sub: "2" }, _: "two" } + }, + { + $: { id: "2" }, + subitem: { _: "five" } + }]; + + parser.parse(xml.toString(), (err, data) => { + if (err) { done(err); } + // console.log('data=', JSON.stringify(data)) + should(data).deepEqual(expectedData); + done(); + }); + }); + + it("should properly parse a medium size file with explicitArray set to false.", (done) => { + const xml = fs.readFileSync("./test/TestFiles/medium.xml"); + const parser = new XmlParser({ resourcePath: "/items/item", explicitArray: false }); + const expectedData = [ + { + $: { + id: "1", + test: "hello" + }, + subitem: { + $: { + sub: "2" + }, + _: "two" + } + }, + { + $: { + id: "2" + }, + subitem: { + _: "five" + } + }, + { + $: { + id: "3", + test: "hello" + }, + subitem: { + $: { + sub: "2" + }, + _: "two" + } + }, + { + $: { + id: "4", + test: "hello" + }, + subitem: { + $: { + sub: "2" + }, + _: "two" + } + }, + { + $: { + id: "5", + test: "hello" + }, + subitem: { + $: { + sub: "2" + }, + _: "two" + } + }, + { + $: { + id: "6", + test: "hello" + }, + subitem: { + $: { + sub: "2" + }, + _: "two" + } + }, + { + $: { + id: "7", + test: "hello" + }, + subitem: { + $: { + sub: "2" + }, + _: "two" + } + }, + { + $: { + id: "8", + test: "hello" + }, + subitem: { + $: { + sub: "2" + }, + _: "two" + } + }, + { + $: { + id: "9", + test: "hello" + }, + subitem: { + $: { + sub: "2" + }, + _: "two" + } + }, + { + $: { + id: "10", + test: "hello" + }, + subitem: { + $: { + sub: "2" + }, + _: "two" + } + } + ]; + parser.parse(xml, (err, data) => { + if (err) { done(err); } + + should(data).deepEqual(expectedData); + should(data.length).equal(10); + done(); + }); + }); + + it("should properly parse a file containing many nodes when explicitArray set to false.", (done) => { + const xml = fs.readFileSync("./test/TestFiles/manyItems.xml"); + const parser = new XmlParser({ resourcePath: "/items/item", explicitArray: false }); + + parser.parse(xml, (err, data) => { + if (err) { done(err); } + + should(data.length).equal(296); + done(); + }); + }); + + it("should properly parse a xml simple file in which nodes contain text values randomly when explicitArray set to false.", (done) => { + const xml = fs.readFileSync("./test/TestFiles/randomText.xml"); + const parser = new XmlParser({ resourcePath: "/items/item", explicitArray: false }); + const expectedData = [{ + $: { id: "1", test: "hello" }, _: " item one two", + subitem: { $: { sub: "2" }, _: "two" } + }, + { + $: { id: "2" }, _: " item one two three four", + subitem: { _: "five" } + } + ]; + + parser.parse(xml, (err, data) => { + if (err) { done(err); } + + should(data).deepEqual(expectedData); + should(data.length).equal(2); + done(); + }); + }); + + it("should properly parse a huge file with explicitArray set to false.", (done) => { + const xml = fs.readFileSync("./test/TestFiles/hugeFile.xml"); + const parser = new XmlParser({ resourcePath: "/items/item", explicitArray: false }); + // console.log(parser) + parser.parse(xml, (err, data) => { + if (err) { done(err); } + should(data.length).equal(2072); + done(); + }); + }); + + it("should properly return error if the xml file is corrupted.", (done) => { + const xml = fs.readFileSync("./test/TestFiles/corrupted.xml"); + const parser = new XmlParser({ resourcePath: "/items/item", explicitArray: false }); + + parser.parse(xml, (err, data) => { + // console.log(err) + should(err.message).equal("mismatched tag at line no: 11"); + should(data).not.be.ok(); + done(); + }); + }); + + it("should properly generate objects when special symbols are passed as attrs and text keys and explicitArray is false in the options.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/item.xml"); + const parser = new XmlParser({ resourcePath: "/items/item", attrsKey: "!", textKey: "%", explicitArray: false }); + const expectedData = [ + { + "!": { id: "1", test: "hello" }, + "subitem": { "!": { sub: "2" }, "%": "two" } + }, + { + "!": { id: "2" }, + "subitem": { "%": "five" } + }]; + const actualData : string[] = []; + let dataEventCount = 0; + + parser.on("data", (data) => { + actualData.push(data); + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('actualData=', JSON.stringify(actualData, null, 1)) + // console.log('dataEventCount=', dataEventCount) + should(actualData).deepEqual(expectedData); + should(dataEventCount).equal(2); + done(); + }); + xmlStream.pipe(parser); + }); +}); diff --git a/test/interested.spec.ts b/test/interested.spec.ts new file mode 100644 index 0000000..0a63b91 --- /dev/null +++ b/test/interested.spec.ts @@ -0,0 +1,321 @@ +import fs from "fs"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; + +describe("interested Nodes", () => { + it("should properly parse a simple file.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/item.xml"); + const parser = new XmlParser(); + + const expectedData = + [ + { $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }, + { + $: { id: "1", test: "hello" }, + subitem: [{ $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }] + }, + { _: "three" }, + { _: "four" }, + { _: "five" }, + { + $: { id: "2" }, + subitem: [{ _: "three" }, { _: "four" }, { _: "five" }] + } + ]; + + const actualData: string[] = []; + let dataEventCount = 0; + const expectedItems = [ + { + $: { id: "1", test: "hello" }, + subitem: + [{ $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }] + }, + { + $: { id: "2" }, + subitem: [{ _: "three" }, { _: "four" }, { _: "five" }] + }]; + const actualItems: string[] = []; + const actualSubitems: string[] = []; + const expectedSubitems = [ + { $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }, + { _: "three" }, + { _: "four" }, + { _: "five" } + ]; + + parser.on("data", (data) => { + actualData.push(data); + dataEventCount++; + }); + + parser.on("error", (err) => { + should(err).not.be.ok(); + done(err); + }); + + parser.on("item", (item) => { + actualItems.push(item); + }); + + parser.on("subitem", (subitem) => { + actualSubitems.push(subitem); + }); + + parser.on("end", () => { + // console.log('actualData=', JSON.stringify(actualData, null, 1)) + // console.log('dataEventCount=', dataEventCount) + should(actualData).deepEqual(expectedData); + should(actualItems).deepEqual(expectedItems); + should(actualSubitems).deepEqual(expectedSubitems); + should(actualSubitems.length).equal(5); + should(actualItems.length).equal(2); + should(dataEventCount).equal(7); + done(); + }); + + xmlStream.pipe(parser); + }); + + it("should properly parse a medium size file.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/medium.xml"); + const parser = new XmlParser(); + + let dataEventCount = 0; + let itemEventCount = 0; + let subitemEventCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("item", (item) => { + itemEventCount++; + }); + + parser.on("subitem", (subitem) => { + subitemEventCount++; + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + // console.log('itemEventCount=', itemEventCount) + // console.log('subitemEventCount=', subitemEventCount) + should(dataEventCount).equal(31); + should(itemEventCount).equal(10); + should(subitemEventCount).equal(21); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse a file containing many nodes.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/manyItems.xml"); + const parser = new XmlParser(); + + let dataEventCount = 0; + let itemEventCount = 0; + let subitemEventCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("item", (item) => { + itemEventCount++; + }); + + parser.on("subitem", (subitem) => { + subitemEventCount++; + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + // console.log('itemEventCount=', itemEventCount) + // console.log('subitemEventCount=', subitemEventCount) + should(itemEventCount).equal(296); + should(subitemEventCount).equal(600); + should(dataEventCount).equal(896); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse a xml simple file in which nodes contain text values randomly.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/randomText.xml"); + const parser = new XmlParser(); + const expectedData = + [ + { $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }, + { + $: { id: "1", test: "hello" }, _: " item one two", + subitem: [{ $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }] + }, + { _: "three" }, + { _: "four" }, + { _: "five" }, + { + $: { id: "2" }, _: " item one two three four", + subitem: [{ _: "three" }, { _: "four" }, { _: "five" }] + } + ]; + const expectedItems = [ + { + $: { id: "1", test: "hello" }, _: " item one two", + subitem: + [{ $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }] + }, + { + $: { id: "2" }, _: " item one two three four", + subitem: [{ _: "three" }, { _: "four" }, { _: "five" }] + }]; + const actualItems: string[] = []; + const actualSubitems: string[] = []; + const expectedSubitems = [ + { $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }, + { _: "three" }, + { _: "four" }, + { _: "five" } + ]; + const actualData: string[] = []; + let dataEventCount = 0; + let itemEventCount = 0; + let subitemEventCount = 0; + + parser.on("data", (data) => { + actualData.push(data); + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("item", (item) => { + itemEventCount++; + actualItems.push(item); + }); + + parser.on("subitem", (subitem) => { + subitemEventCount++; + actualSubitems.push(subitem); + }); + + parser.on("end", () => { + // console.log('actualData=', JSON.stringify(actualData, null, 1)) + // console.log('dataEventCount=', dataEventCount) + // console.log('itemEventCount=', itemEventCount) + // console.log('subitemEventCount=', subitemEventCount) + should(actualData).deepEqual(expectedData); + should(actualItems).deepEqual(expectedItems); + should(actualSubitems).deepEqual(expectedSubitems); + should(dataEventCount).equal(7); + should(itemEventCount).equal(2); + should(subitemEventCount).equal(5); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse a huge file.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/hugeFile.xml"); + const parser = new XmlParser(); + + let dataEventCount = 0; + let itemEventCount = 0; + let subitemEventCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("item", (item) => { + itemEventCount++; + }); + + parser.on("subitem", (subitem) => { + subitemEventCount++; + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + // console.log('itemEventCount=', itemEventCount) + // console.log('subitemEventCount=', subitemEventCount) + should(dataEventCount).equal(6272); + should(itemEventCount).equal(2072); + should(subitemEventCount).equal(4200); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse a simple file and return when root element when listening on it.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/item.xml"); + const parser = new XmlParser(); + const expectedData = + [{ + item: [{ + $: { id: "1", test: "hello" }, + subitem: [{ $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }] + }, + { + $: { id: "2" }, subitem: [{ _: "three" }, { _: "four" }, + { _: "five" }] + }] + }]; + + const actualData: string[] = []; + let dataEventCount = 0; + let itemsEventCount = 0; + + parser.on("data", (data) => { + actualData.push(data); + dataEventCount++; + }); + + parser.on("error", (err) => { + should(err).not.be.ok(); + done(err); + }); + + parser.on("items", (item) => { + itemsEventCount++; + }); + + parser.on("end", () => { + // console.log('actualData=', JSON.stringify(actualData, null, 1)) + // console.log('dataEventCount=', dataEventCount) + // console.log('itemEventCount=', itemsEventCount) + should(actualData).deepEqual(expectedData); + should(itemsEventCount).equal(1); + should(dataEventCount).equal(1); + done(); + }); + xmlStream.pipe(parser); + }); +}); diff --git a/test/options.spec.ts b/test/options.spec.ts new file mode 100644 index 0000000..0ab70ab --- /dev/null +++ b/test/options.spec.ts @@ -0,0 +1,117 @@ +import fs from "fs"; +import "mocha"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; +describe("should respect the options passed", () => { + it("should properly generate objects with $ as key for attrs and _ as key for text value of node.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/item.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + const expectedData = [ + { + $: { id: "1", test: "hello" }, + subitem: + [{ $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }] + }, + { + $: { id: "2" }, + subitem: [{ _: "three" }, { _: "four" }, { _: "five" }] + }]; + const actualData: string[] = []; + let dataEventCount = 0; + + parser.on("data", (data) => { + actualData.push(data); + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('actualData=', actualData) + // console.log('dataEventCount=', dataEventCount) + should(actualData).deepEqual(expectedData); + should(dataEventCount).equal(2); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly generate objects with passed attrs and text keys in the options.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/item.xml"); + const parser = new XmlParser({ resourcePath: "/items/item", attrsKey: "attrs", textKey: "text" }); + const expectedData = [ + { + attrs: { id: "1", test: "hello" }, + subitem: + [{ attrs: { sub: "TESTING SUB" }, text: "one" }, + { attrs: { sub: "2" }, text: "two" }] + }, + { + attrs: { id: "2" }, + subitem: [{ text: "three" }, { text: "four" }, { text: "five" }] + }]; + const actualData: string[] = []; + let dataEventCount = 0; + + parser.on("data", (data) => { + actualData.push(data); + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('actualData=', JSON.stringify(actualData, null, 1)) + // console.log('dataEventCount=', dataEventCount) + should(actualData).deepEqual(expectedData); + should(dataEventCount).equal(2); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly generate objects when special symbols are passed as attrs and text keys in the options.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/item.xml"); + const parser = new XmlParser({ resourcePath: "/items/item", attrsKey: "!", textKey: "%" }); + const expectedData = [ + { + "!": { id: "1", test: "hello" }, + "subitem": + [{ "!": { sub: "TESTING SUB" }, "%": "one" }, + { "!": { sub: "2" }, "%": "two" }] + }, + { + "!": { id: "2" }, + "subitem": [{ "%": "three" }, { "%": "four" }, { "%": "five" }] + }]; + const actualData: string[] = []; + let dataEventCount = 0; + + parser.on("data", (data) => { + actualData.push(data); + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('actualData=', JSON.stringify(actualData, null, 1)) + // console.log('dataEventCount=', dataEventCount) + should(actualData).deepEqual(expectedData); + should(dataEventCount).equal(2); + done(); + }); + xmlStream.pipe(parser); + }); +}); diff --git a/test/parse.spec.ts b/test/parse.spec.ts new file mode 100644 index 0000000..5753a55 --- /dev/null +++ b/test/parse.spec.ts @@ -0,0 +1,97 @@ +import fs from "fs"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; +describe("Parse function should work properly", () => { + it("should properly parse a simple file.", (done) => { + const xml = fs.readFileSync("./test/TestFiles/item.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + const expectedData = [ + { + $: { id: "1", test: "hello" }, + subitem: + [{ $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }] + }, + { + $: { id: "2" }, + subitem: [{ _: "three" }, { _: "four" }, { _: "five" }] + }]; + + parser.parse(xml.toString(), (err, data) => { + if (err) { done(err); } + should(data).deepEqual(expectedData); + done(); + }); + }); + + it("should properly parse a medium size file.", (done) => { + const xml = fs.readFileSync("./test/TestFiles/medium.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + + parser.parse(xml, (err, data) => { + if (err) { done(err); } + should(data.length).equal(10); + done(); + }); + }); + + it("should properly parse a file containing many nodes.", (done) => { + const xml = fs.readFileSync("./test/TestFiles/manyItems.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + + parser.parse(xml, (err, data) => { + if (err) { done(err); } + should(data.length).equal(296); + done(); + }); + }); + + it("should properly parse a xml simple file in which nodes contain text values randomly.", (done) => { + const xml = fs.readFileSync("./test/TestFiles/randomText.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + const expectedData = [{ + $: { id: "1", test: "hello" }, _: " item one two", + subitem: [{ $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }] + }, + { + $: { id: "2" }, _: " item one two three four", + subitem: [{ _: "three" }, { _: "four" }, { _: "five" }] + } + ]; + + parser.parse(xml, (err, data) => { + if (err) { done(err); } + should(data).deepEqual(expectedData); + should(data.length).equal(2); + done(); + }); + }); + + it("should properly parse a huge file.", (done) => { + const xml = fs.readFileSync("./test/TestFiles/hugeFile.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + // console.log(parser) + parser.parse(xml, (err, data) => { + if (err) { done(err); } + should(data.length).equal(2072); + done(); + }); + }); + + it("should properly return error if the xml file is corrupted.", (done) => { + const xml = fs.readFileSync("./test/TestFiles/corrupted.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + + parser.parse(xml, (err, data) => { + // console.log(err) + should(err.message).equal("mismatched tag at line no: 11"); + should(data).not.be.ok(); + done(); + }); + }); +}); diff --git a/test/pause_and_resume.spec.ts b/test/pause_and_resume.spec.ts new file mode 100644 index 0000000..1934acb --- /dev/null +++ b/test/pause_and_resume.spec.ts @@ -0,0 +1,82 @@ +import fs from "fs"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; +describe("pause and resume", () => { + it("should properly parse a simple file.", function(done) { + const xmlStream = fs.createReadStream("./test/TestFiles/item.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + const expectedData = [ + { + $: { id: "1", test: "hello" }, + subitem: + [{ $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }] + }, + { + $: { id: "2" }, + subitem: [{ _: "three" }, { _: "four" }, { _: "five" }] + }]; + const actualData: string[] = []; + let dataEventCount = 0; + let isSetTimeoutHappened = true; + this.timeout(4000); + parser.on("data", (data) => { + actualData.push(data); + parser.pause(); + should(isSetTimeoutHappened).equal(true); + setTimeout(() => { + parser.resume(); + isSetTimeoutHappened = true; + }, 1000); + dataEventCount++; + isSetTimeoutHappened = false; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('actualData=', actualData) + // console.log('dataEventCount=', dataEventCount) + should(actualData).deepEqual(expectedData); + should(dataEventCount).equal(2); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should emit data events with 1sec interval between each using pause and resume.", function(done) { + const xmlStream = fs.createReadStream("./test/TestFiles/medium.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + + let dataEventCount = 0; + let isSetTimeoutHappened = true; + this.timeout(20000); + parser.on("data", (data) => { + parser.pause(); + should(isSetTimeoutHappened).equal(true); + setTimeout(() => { + parser.resume(); + isSetTimeoutHappened = true; + }, 1000); + dataEventCount++; + isSetTimeoutHappened = false; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + should(dataEventCount).equal(10); + done(); + }); + xmlStream.pipe(parser); + }); +}); diff --git a/test/performance.spec.ts b/test/performance.spec.ts new file mode 100644 index 0000000..fe714be --- /dev/null +++ b/test/performance.spec.ts @@ -0,0 +1,103 @@ + +import fs from "fs"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; +describe.skip("performance testing", () => { + it("should properly parse more than 500 MB of file.", function(done) { + const parser = new XmlParser({ resourcePath: "/items/item" }); + // var wsStream = fs.createWriteStream('./test/TestFiles/MB_and_GB_size_files/MBFile.xml') + // var rsStream = fs.createReadStream('./test/TestFiles/MB_and_GB_size_files/MBFile.xml') + let dataEventCount = 0; + // var maxRSSMemoryTaken = 0 + // var rss + const startTime = Date.now(); + const xmlStream = new stream.Readable(); + xmlStream._read = function noop() { + // nop + }; + let dataChunk; + this.timeout(900000); + const firstChunk = fs.readFileSync("./test/TestFiles/MB_and_GB_size_files/firstChunk.xml"); + xmlStream.push(firstChunk); + for (let i = 0; i < 2200; i++) { + dataChunk = fs.readFileSync("./test/TestFiles/MB_and_GB_size_files/repetitiveChunk.xml"); + xmlStream.push(dataChunk); + } + + const endingChunk = fs.readFileSync("./test/TestFiles/MB_and_GB_size_files/endingChunk.xml"); + xmlStream.push(endingChunk); + xmlStream.push(null); + parser.on("data", (data) => { + // rss = process.memoryUsage().rss + // if (rss > maxRSSMemoryTaken) maxRSSMemoryTaken = rss + dataEventCount++; + }); + + parser.on("error", (err) => { + should(err).not.be.ok(); + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + // console.log('RSS memory=', rss) + const TimeTaken = Date.now() - startTime; + // console.log('time taken=', TimeTaken) + should(TimeTaken).be.belowOrEqual(300000); + should(dataEventCount).equal(4558400); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse more than 1 GB of file.", function(done) { + const parser = new XmlParser({ resourcePath: "/items/item" }); + // var wsStream = fs.createWriteStream('./test/TestFiles/MB_and_GB_size_files/MBFile.xml') + // var rsStream = fs.createReadStream('./test/TestFiles/MB_and_GB_size_files/MBFile.xml') + let dataEventCount = 0; + // var maxRSSMemoryTaken = 0 + // var rss + const startTime = Date.now(); + const xmlStream = new stream.Readable(); + xmlStream._read = function noop() { + // nop + }; + let dataChunk; + this.timeout(900000); + const firstChunk = fs.readFileSync("./test/TestFiles/MB_and_GB_size_files/firstChunk.xml"); + xmlStream.push(firstChunk); + for (let i = 0; i < 4400; i++) { + dataChunk = fs.readFileSync("./test/TestFiles/MB_and_GB_size_files/repetitiveChunk.xml"); + xmlStream.push(dataChunk); + } + + const endingChunk = fs.readFileSync("./test/TestFiles/MB_and_GB_size_files/endingChunk.xml"); + xmlStream.push(endingChunk); + xmlStream.push(null); + parser.on("data", (data) => { + // rss = process.memoryUsage().rss + // if (rss > maxRSSMemoryTaken) maxRSSMemoryTaken = rss + dataEventCount++; + }); + + parser.on("error", (err) => { + should(err).not.be.ok(); + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + // console.log('RSS memory=', rss) + const TimeTaken = Date.now() - startTime; + // console.log('time taken=', TimeTaken) + should(TimeTaken).be.belowOrEqual(700000); + should(dataEventCount).equal(9116800); + done(); + }); + xmlStream.pipe(parser); + }); +}); diff --git a/test/read.spec.ts b/test/read.spec.ts new file mode 100644 index 0000000..3deeea8 --- /dev/null +++ b/test/read.spec.ts @@ -0,0 +1,105 @@ +import fs from "fs"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; + +describe("read method", () => { + it("should properly parse a simple file.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/item.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + const expectedData = [ + { + $: { id: "1", test: "hello" }, + subitem: + [{ $: { sub: "TESTING SUB" }, _: "one" }, + { $: { sub: "2" }, _: "two" }] + }, + { + $: { id: "2" }, + subitem: [{ _: "three" }, { _: "four" }, { _: "five" }] + }]; + const actualData: string[] = []; + let obj; + let Timeout: any; + + parser.on("readable", () => { + Timeout = setInterval(() => { + obj = parser.read(); + if (obj) { + actualData.push(obj); + } + obj = null; + }, 50); + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('actualData=', actualData) + // console.log('dataEventCount=', dataEventCount) + clearInterval(Timeout); + should(actualData).deepEqual(expectedData); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse a file containing many nodes.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/manyItems.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + let objCount = 0; + const endEventOcurred = false; + + parser.on("readable", () => { + read(); + }); + + function read() { + while (parser.read()) { objCount++; } + if (!endEventOcurred) { setTimeout(read, 50); } + } + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log(objCount) + should(objCount).deepEqual(296); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse a huge.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/hugeFile.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + let objCount = 0; + const endEventOcurred = false; + + parser.on("readable", () => { + read(); + }); + + function read() { + while (parser.read()) { objCount++; } + if (!endEventOcurred) { setTimeout(read, 50); } + } + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log(objCount) + should(objCount).deepEqual(2072); + done(); + }); + xmlStream.pipe(parser); + }); +}); diff --git a/test/same_name.spec.ts b/test/same_name.spec.ts new file mode 100644 index 0000000..cbde5ed --- /dev/null +++ b/test/same_name.spec.ts @@ -0,0 +1,111 @@ +import fs from "fs"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; + +describe("nodes with same names", () => { + it("should properly parse a simple file containing nodes with same names.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/nodesWithSameNames.xml"); + const parser = new XmlParser(); + + const actualData: string[] = []; + const actualItems: string[] = []; + let dataEventCount = 0; + + parser.on("data", (data) => { + actualData.push(data); + dataEventCount++; + }); + + parser.on("error", (err) => { + should(err).not.be.ok(); + done(err); + }); + + parser.on("item", (item) => { + actualItems.push(item); + }); + + parser.on("end", () => { + should(actualItems.length).equal(18); + should(dataEventCount).equal(18); + done(); + }); + + xmlStream.pipe(parser); + }); + + it("should properly parse a simple file containing nodes with same names and emit events on multiple nodes.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/nodesWithSameNames.xml"); + const parser = new XmlParser(); + + let dataEventCount = 0; + let itemEventCount = 0; + let subitemEventCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("error", (err) => { + should(err).not.be.ok(); + done(err); + }); + + parser.on("item", (item) => { + itemEventCount++; + }); + + parser.on("subitem", (subitem) => { + subitemEventCount++; + }); + + parser.on("end", () => { + should(itemEventCount).equal(18); + should(subitemEventCount).equal(13); + should(dataEventCount).equal(31); + done(); + }); + + xmlStream.pipe(parser); + }); + + it("should properly parse a medium size file with same names randomly.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/nodesWithSameNamesRandomly.xml"); + const parser = new XmlParser(); + + let dataEventCount = 0; + let itemEventCount = 0; + let subitemEventCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("item", (item) => { + itemEventCount++; + }); + + parser.on("subitem", (subitem) => { + subitemEventCount++; + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + // console.log('itemEventCount=', itemEventCount) + // console.log('subitemEventCount=', subitemEventCount) + should(dataEventCount).equal(32); + should(itemEventCount).equal(19); + should(subitemEventCount).equal(13); + done(); + }); + xmlStream.pipe(parser); + }); +}); diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 18528c2..0000000 --- a/test/test.js +++ /dev/null @@ -1,1545 +0,0 @@ -var should = require('should') -var fs = require('fs') -var zlib = require('zlib') -var stream = require('stream') - -var ParserFactory = require('../parser') - -describe('Tests', function () { - describe('Basic behaviour', function () { - it('should properly parse a simple file.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/item.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - var expectedData = [ - { '$': { id: '1', test: 'hello' }, - subitem: - [ { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' } ] }, - { '$': { id: '2' }, - subitem: [ { _: 'three' }, { _: 'four' }, { _: 'five' } ] } ] - var actualData = [] - var dataEventCount = 0 - - parser.on('data', function (data) { - actualData.push(data) - dataEventCount++ - }) - - parser.on('error', function (err) { - should(err).not.be.ok() - done(err) - }) - - parser.on('end', function () { - // console.log('actualData=', actualData) - // console.log('dataEventCount=', dataEventCount) - actualData.should.deepEqual(expectedData) - dataEventCount.should.equal(2) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse a medium size file.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/medium.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - - var dataEventCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - dataEventCount.should.equal(10) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse a file containing many nodes.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/manyItems.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - - var dataEventCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - dataEventCount.should.equal(296) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse a xml simple file in which nodes contain text values randomly.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/randomText.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - var expectedData = [ { '$': { 'id': '1', 'test': 'hello' }, '_': ' item one two', - 'subitem': [ { '$': { 'sub': 'TESTING SUB' }, '_': 'one' }, - { '$': { 'sub': '2' }, '_': 'two' } ] }, - { '$': { 'id': '2' }, '_': ' item one two three four', - 'subitem': [ { '_': 'three' }, { '_': 'four' }, { '_': 'five' } ] } - ] - var actualData = [] - var dataEventCount = 0 - - parser.on('data', function (data) { - actualData.push(data) - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('actualData=', JSON.stringify(actualData, null, 1)) - // console.log('dataEventCount=', dataEventCount) - actualData.should.deepEqual(expectedData) - dataEventCount.should.equal(2) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse a huge file.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/hugeFile.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - // console.log(parser) - var dataEventCount = 0 - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - dataEventCount.should.equal(2072) - done() - }) - xmlStream.pipe(parser) - }) - }) - - describe('pause and resume', function () { - it('should properly parse a simple file.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/item.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - var expectedData = [ - { '$': { id: '1', test: 'hello' }, - subitem: - [ { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' } ] }, - { '$': { id: '2' }, - subitem: [ { _: 'three' }, { _: 'four' }, { _: 'five' } ] } ] - var actualData = [] - var dataEventCount = 0 - var isSetTimeoutHappened = true - this.timeout(4000) - parser.on('data', function (data) { - actualData.push(data) - parser.pause() - isSetTimeoutHappened.should.equal(true) - setTimeout(function () { - parser.resume() - isSetTimeoutHappened = true - }, 3000) - dataEventCount++ - isSetTimeoutHappened = false - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('actualData=', actualData) - // console.log('dataEventCount=', dataEventCount) - actualData.should.deepEqual(expectedData) - dataEventCount.should.equal(2) - done() - }) - xmlStream.pipe(parser) - }) - - it('should emit data events with 1sec interval between each using pause and resume.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/medium.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - - var dataEventCount = 0 - var isSetTimeoutHappened = true - this.timeout(20000) - parser.on('data', function (data) { - parser.pause() - isSetTimeoutHappened.should.equal(true) - setTimeout(function () { - parser.resume() - isSetTimeoutHappened = true - }, 2000) - dataEventCount++ - isSetTimeoutHappened = false - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - dataEventCount.should.equal(10) - done() - }) - xmlStream.pipe(parser) - }) - }) - - describe('should respect the options passed', function () { - it('should properly generate objects with $ as key for attrs and _ as key for text value of node.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/item.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - var expectedData = [ - { '$': { id: '1', test: 'hello' }, - subitem: - [ { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' } ] }, - { '$': { id: '2' }, - subitem: [ { _: 'three' }, { _: 'four' }, { _: 'five' } ] } ] - var actualData = [] - var dataEventCount = 0 - - parser.on('data', function (data) { - actualData.push(data) - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('actualData=', actualData) - // console.log('dataEventCount=', dataEventCount) - actualData.should.deepEqual(expectedData) - dataEventCount.should.equal(2) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly generate objects with passed attrs and text keys in the options.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/item.xml') - var parser = new ParserFactory({resourcePath: '/items/item', attrsKey: 'attrs', textKey: 'text'}) - var expectedData = [ - { 'attrs': { id: '1', test: 'hello' }, - subitem: - [ { 'attrs': { sub: 'TESTING SUB' }, text: 'one' }, - { 'attrs': { sub: '2' }, text: 'two' } ] }, - { 'attrs': { id: '2' }, - subitem: [ { text: 'three' }, { text: 'four' }, { text: 'five' } ] } ] - var actualData = [] - var dataEventCount = 0 - - parser.on('data', function (data) { - actualData.push(data) - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('actualData=', JSON.stringify(actualData, null, 1)) - // console.log('dataEventCount=', dataEventCount) - actualData.should.deepEqual(expectedData) - dataEventCount.should.equal(2) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly generate objects when special symbols are passed as attrs and text keys in the options.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/item.xml') - var parser = new ParserFactory({resourcePath: '/items/item', attrsKey: '!', textKey: '%'}) - var expectedData = [ - { '!': { id: '1', test: 'hello' }, - subitem: - [ { '!': { sub: 'TESTING SUB' }, '%': 'one' }, - { '!': { sub: '2' }, '%': 'two' } ] }, - { '!': { id: '2' }, - subitem: [ { '%': 'three' }, { '%': 'four' }, { '%': 'five' } ] } ] - var actualData = [] - var dataEventCount = 0 - - parser.on('data', function (data) { - actualData.push(data) - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('actualData=', JSON.stringify(actualData, null, 1)) - // console.log('dataEventCount=', dataEventCount) - actualData.should.deepEqual(expectedData) - dataEventCount.should.equal(2) - done() - }) - xmlStream.pipe(parser) - }) - }) - - describe('should properly handle uncompressed files', function () { - it('should properly parse a uncompressed xml file.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/medium.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - var gzip = zlib.createGzip() - var gunzip = zlib.createGunzip() - var dataEventCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - dataEventCount.should.equal(10) - done() - }) - xmlStream.pipe(gzip).pipe(gunzip).pipe(parser) - }) - - it('should properly parse uncompressed file and go fine with pause and resume.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/medium.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - var gzip = zlib.createGzip() - var gunzip = zlib.createGunzip() - - var dataEventCount = 0 - var isSetTimeoutHappened = true - this.timeout(20000) - parser.on('data', function (data) { - parser.pause() - isSetTimeoutHappened.should.equal(true) - setTimeout(function () { - parser.resume() - isSetTimeoutHappened = true - }, 2000) - dataEventCount++ - isSetTimeoutHappened = false - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - dataEventCount.should.equal(10) - done() - }) - xmlStream.pipe(gzip).pipe(gunzip).pipe(parser) - }) - }) - - describe('read method', function () { - it('should properly parse a simple file.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/item.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - var expectedData = [ - { '$': { id: '1', test: 'hello' }, - subitem: - [ { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' } ] }, - { '$': { id: '2' }, - subitem: [ { _: 'three' }, { _: 'four' }, { _: 'five' } ] } ] - var actualData = [] - var obj - var Timeout - - parser.on('readable', function () { - Timeout = setInterval(function () { - if ((obj = parser.read())) actualData.push(obj) - obj = null - }, 50) - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('actualData=', actualData) - // console.log('dataEventCount=', dataEventCount) - clearInterval(Timeout) - actualData.should.deepEqual(expectedData) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse a file containing many nodes.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/manyItems.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - var objCount = 0 - var endEventOcurred = false - - parser.on('readable', function () { - read() - }) - - function read () { - while (parser.read()) objCount++ - if (!endEventOcurred) setTimeout(read, 50) - } - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log(objCount) - objCount.should.deepEqual(296) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse a huge.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/hugeFile.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - var objCount = 0 - var endEventOcurred = false - - parser.on('readable', function () { - read() - }) - - function read () { - while (parser.read()) objCount++ - if (!endEventOcurred) setTimeout(read, 50) - } - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log(objCount) - objCount.should.deepEqual(2072) - done() - }) - xmlStream.pipe(parser) - }) - }) - - describe('Error Handling', function () { - it('should properly return error if the xml file is corrupted.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/corrupted.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - var dataEventCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('error', function (err) { - // console.log(err) - err.message.should.equal('mismatched tag at line no: 11') - done() - }) - - xmlStream.pipe(parser) - }) - - it('should properly return error if the large xml file is corrupted.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/largeCorruptedFile.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - var dataEventCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('error', function (err) { - // console.log(err) - err.message.should.equal('mismatched tag at line no: 8346') - done() - }) - - xmlStream.pipe(parser) - }) - }) - - describe('CData and comments in xml', function () { - it('should properly parse a simple file.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/CData-comments.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - - var dataEventCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - dataEventCount.should.equal(296) - done() - }) - xmlStream.pipe(parser) - }) - }) - - describe('emitOnNodeName', function () { - it('should properly emit events on node names.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/item.xml') - var parser = new ParserFactory({resourcePath: '/items/item', emitOnNodeName: true}) - var expectedData = [ - { '$': { id: '1', test: 'hello' }, - subitem: - [ { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' } ] }, - { '$': { id: '2' }, - subitem: [ { _: 'three' }, { _: 'four' }, { _: 'five' } ] } ] - var actualData = [] - var itemData = [] - var dataEventCount = 0 - var itemCount = 0 - - parser.on('data', function (data) { - actualData.push(data) - dataEventCount++ - }) - - parser.on('item', function (item) { - itemData.push(item) - itemCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('actualData=', actualData) - // console.log('dataEventCount=', dataEventCount) - actualData.should.deepEqual(expectedData) - dataEventCount.should.equal(2) - itemData.should.deepEqual(expectedData) - itemCount.should.equal(2) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly emit events on node names while parsing a medium size file.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/medium.xml') - var parser = new ParserFactory({resourcePath: '/items/item', emitOnNodeName: true}) - - var dataEventCount = 0 - var itemCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('item', function (data) { - itemCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - dataEventCount.should.equal(10) - itemCount.should.equal(10) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse a file containing many nodes.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/manyItems.xml') - var parser = new ParserFactory({resourcePath: '/items/item', emitOnNodeName: true}) - - var dataEventCount = 0 - var itemCount = 0 - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('item', function (data) { - itemCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - dataEventCount.should.equal(296) - itemCount.should.equal(296) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse a huge file.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/hugeFile.xml') - var parser = new ParserFactory({resourcePath: '/items/item', emitOnNodeName: true}) - - var dataEventCount = 0 - var itemCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('item', function (item) { - itemCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - dataEventCount.should.equal(2072) - itemCount.should.equal(2072) - done() - }) - xmlStream.pipe(parser) - }) - }) - - describe('wrong resourcePath', function () { - it('should be able to detect the wrong resourcePath at root level.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/item.xml') - var parser = new ParserFactory({resourcePath: '/wrong/noNodes', emitOnNodeName: true}) - - var actualData = [] - var itemData = [] - var dataEventCount = 0 - var itemCount = 0 - - parser.on('data', function (data) { - actualData.push(data) - dataEventCount++ - }) - - parser.on('item', function (item) { - itemData.push(item) - itemCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('actualData=', actualData) - // console.log('dataEventCount=', dataEventCount) - actualData.length.should.equal(0) - dataEventCount.should.equal(0) - itemData.length.should.equal(0) - itemCount.should.equal(0) - done() - }) - xmlStream.pipe(parser) - }) - - it('should be able to detect wrong resourcePath while parsing xml', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/manyItems.xml') - var parser = new ParserFactory({resourcePath: '/wrong/noNodes', emitOnNodeName: true}) - - var dataEventCount = 0 - var itemCount = 0 - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('item', function (data) { - itemCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - dataEventCount.should.equal(0) - itemCount.should.equal(0) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse a huge file.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/hugeFile.xml') - var parser = new ParserFactory({resourcePath: '/wrong/path', emitOnNodeName: true}) - - var dataEventCount = 0 - var itemCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('item', function (item) { - itemCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - dataEventCount.should.equal(0) - itemCount.should.equal(0) - done() - }) - xmlStream.pipe(parser) - }) - }) - - describe('interested Nodes', function () { - it('should properly parse a simple file.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/item.xml') - var parser = new ParserFactory() - - var expectedData = - [ - { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' }, - { '$': { id: '1', test: 'hello' }, - subitem: [ { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' } ] - }, - { _: 'three' }, - { _: 'four' }, - { _: 'five' }, - { '$': { id: '2' }, - subitem: [ { _: 'three' }, { _: 'four' }, { _: 'five' } ] } - ] - - var actualData = [] - var dataEventCount = 0 - var expectedItems = [ - { '$': { id: '1', test: 'hello' }, - subitem: - [ { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' } ] }, - { '$': { id: '2' }, - subitem: [ { _: 'three' }, { _: 'four' }, { _: 'five' } ] } ] - var actualItems = [] - var actualSubitems = [] - var expectedSubitems = [ - { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' }, - { _: 'three' }, - { _: 'four' }, - { _: 'five' } - ] - - parser.on('data', function (data) { - actualData.push(data) - dataEventCount++ - }) - - parser.on('error', function (err) { - should(err).not.be.ok() - done(err) - }) - - parser.on('item', function (item) { - actualItems.push(item) - }) - - parser.on('subitem', function (subitem) { - actualSubitems.push(subitem) - }) - - parser.on('end', function () { - // console.log('actualData=', JSON.stringify(actualData, null, 1)) - // console.log('dataEventCount=', dataEventCount) - actualData.should.deepEqual(expectedData) - actualItems.should.deepEqual(expectedItems) - actualSubitems.should.deepEqual(expectedSubitems) - actualSubitems.length.should.equal(5) - actualItems.length.should.equal(2) - dataEventCount.should.equal(7) - done() - }) - - xmlStream.pipe(parser) - }) - - it('should properly parse a medium size file.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/medium.xml') - var parser = new ParserFactory() - - var dataEventCount = 0 - var itemEventCount = 0 - var subitemEventCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('item', function (item) { - itemEventCount++ - }) - - parser.on('subitem', function (subitem) { - subitemEventCount++ - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - // console.log('itemEventCount=', itemEventCount) - // console.log('subitemEventCount=', subitemEventCount) - dataEventCount.should.equal(31) - itemEventCount.should.equal(10) - subitemEventCount.should.equal(21) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse a file containing many nodes.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/manyItems.xml') - var parser = new ParserFactory() - - var dataEventCount = 0 - var itemEventCount = 0 - var subitemEventCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('item', function (item) { - itemEventCount++ - }) - - parser.on('subitem', function (subitem) { - subitemEventCount++ - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - // console.log('itemEventCount=', itemEventCount) - // console.log('subitemEventCount=', subitemEventCount) - itemEventCount.should.equal(296) - subitemEventCount.should.equal(600) - dataEventCount.should.equal(896) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse a xml simple file in which nodes contain text values randomly.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/randomText.xml') - var parser = new ParserFactory() - var expectedData = - [ - { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' }, - { '$': { id: '1', test: 'hello' }, _: ' item one two', - subitem: [ { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' } ] - }, - { _: 'three' }, - { _: 'four' }, - { _: 'five' }, - { '$': { id: '2' }, '_': ' item one two three four', - subitem: [ { _: 'three' }, { _: 'four' }, { _: 'five' } ] } - ] - var expectedItems = [ - { '$': { id: '1', test: 'hello' }, _: ' item one two', - subitem: - [ { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' } ] }, - { '$': { id: '2' }, '_': ' item one two three four', - subitem: [ { _: 'three' }, { _: 'four' }, { _: 'five' } ] } ] - var actualItems = [] - var actualSubitems = [] - var expectedSubitems = [ - { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' }, - { _: 'three' }, - { _: 'four' }, - { _: 'five' } - ] - var actualData = [] - var dataEventCount = 0 - var itemEventCount = 0 - var subitemEventCount = 0 - - parser.on('data', function (data) { - actualData.push(data) - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('item', function (item) { - itemEventCount++ - actualItems.push(item) - }) - - parser.on('subitem', function (subitem) { - subitemEventCount++ - actualSubitems.push(subitem) - }) - - parser.on('end', function () { - // console.log('actualData=', JSON.stringify(actualData, null, 1)) - // console.log('dataEventCount=', dataEventCount) - // console.log('itemEventCount=', itemEventCount) - // console.log('subitemEventCount=', subitemEventCount) - actualData.should.deepEqual(expectedData) - actualItems.should.deepEqual(expectedItems) - actualSubitems.should.deepEqual(expectedSubitems) - dataEventCount.should.equal(7) - itemEventCount.should.equal(2) - subitemEventCount.should.equal(5) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse a huge file.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/hugeFile.xml') - var parser = new ParserFactory() - - var dataEventCount = 0 - var itemEventCount = 0 - var subitemEventCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('item', function (item) { - itemEventCount++ - }) - - parser.on('subitem', function (subitem) { - subitemEventCount++ - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - // console.log('itemEventCount=', itemEventCount) - // console.log('subitemEventCount=', subitemEventCount) - dataEventCount.should.equal(6272) - itemEventCount.should.equal(2072) - subitemEventCount.should.equal(4200) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse a simple file and return when root element when listening on it.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/item.xml') - var parser = new ParserFactory() - var expectedData = - [{ 'item': [{ '$': { 'id': '1', 'test': 'hello' }, - 'subitem': [{ '$': { 'sub': 'TESTING SUB' }, '_': 'one' }, - { '$': { 'sub': '2' }, '_': 'two' }] - }, - { '$': { 'id': '2' }, 'subitem': [{ '_': 'three' }, { '_': 'four' }, - { '_': 'five' }] - }] - }] - - var actualData = [] - var dataEventCount = 0 - var itemsEventCount = 0 - - parser.on('data', function (data) { - actualData.push(data) - dataEventCount++ - }) - - parser.on('error', function (err) { - should(err).not.be.ok() - done(err) - }) - - parser.on('items', function (item) { - itemsEventCount++ - }) - - parser.on('end', function () { - // console.log('actualData=', JSON.stringify(actualData, null, 1)) - // console.log('dataEventCount=', dataEventCount) - // console.log('itemEventCount=', itemsEventCount) - actualData.should.deepEqual(expectedData) - itemsEventCount.should.equal(1) - dataEventCount.should.equal(1) - done() - }) - xmlStream.pipe(parser) - }) - }) - - describe.skip('performance testing', function () { - it('should properly parse more than 500 MB of file.', function (done) { - var parser = new ParserFactory({resourcePath: '/items/item'}) - // var wsStream = fs.createWriteStream('./test/TestFiles/MB_and_GB_size_files/MBFile.xml') - // var rsStream = fs.createReadStream('./test/TestFiles/MB_and_GB_size_files/MBFile.xml') - var dataEventCount = 0 - // var maxRSSMemoryTaken = 0 - // var rss - var startTime = Date.now() - var xmlStream = new stream.Readable() - xmlStream._read = function noop () {} - var dataChunk - this.timeout(900000) - var firstChunk = fs.readFileSync('./test/TestFiles/MB_and_GB_size_files/firstChunk.xml') - xmlStream.push(firstChunk) - for (var i = 0; i < 2200; i++) { - dataChunk = fs.readFileSync('./test/TestFiles/MB_and_GB_size_files/repetitiveChunk.xml') - xmlStream.push(dataChunk) - } - - var endingChunk = fs.readFileSync('./test/TestFiles/MB_and_GB_size_files/endingChunk.xml') - xmlStream.push(endingChunk) - xmlStream.push(null) - parser.on('data', function (data) { - // rss = process.memoryUsage().rss - // if (rss > maxRSSMemoryTaken) maxRSSMemoryTaken = rss - dataEventCount++ - }) - - parser.on('error', function (err) { - should(err).not.be.ok() - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - // console.log('RSS memory=', rss) - var TimeTaken = Date.now() - startTime - // console.log('time taken=', TimeTaken) - TimeTaken.should.be.belowOrEqual(300000) - dataEventCount.should.equal(4558400) - done() - }) - xmlStream.pipe(parser) - }) - - it('should properly parse more than 1 GB of file.', function (done) { - var parser = new ParserFactory({resourcePath: '/items/item'}) - // var wsStream = fs.createWriteStream('./test/TestFiles/MB_and_GB_size_files/MBFile.xml') - // var rsStream = fs.createReadStream('./test/TestFiles/MB_and_GB_size_files/MBFile.xml') - var dataEventCount = 0 - // var maxRSSMemoryTaken = 0 - // var rss - var startTime = Date.now() - var xmlStream = new stream.Readable() - xmlStream._read = function noop () {} - var dataChunk - this.timeout(900000) - var firstChunk = fs.readFileSync('./test/TestFiles/MB_and_GB_size_files/firstChunk.xml') - xmlStream.push(firstChunk) - for (var i = 0; i < 4400; i++) { - dataChunk = fs.readFileSync('./test/TestFiles/MB_and_GB_size_files/repetitiveChunk.xml') - xmlStream.push(dataChunk) - } - - var endingChunk = fs.readFileSync('./test/TestFiles/MB_and_GB_size_files/endingChunk.xml') - xmlStream.push(endingChunk) - xmlStream.push(null) - parser.on('data', function (data) { - // rss = process.memoryUsage().rss - // if (rss > maxRSSMemoryTaken) maxRSSMemoryTaken = rss - dataEventCount++ - }) - - parser.on('error', function (err) { - should(err).not.be.ok() - done(err) - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - // console.log('RSS memory=', rss) - var TimeTaken = Date.now() - startTime - // console.log('time taken=', TimeTaken) - TimeTaken.should.be.belowOrEqual(700000) - dataEventCount.should.equal(9116800) - done() - }) - xmlStream.pipe(parser) - }) - }) - - describe('nodes with same names', function () { - it('should properly parse a simple file containing nodes with same names.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/nodesWithSameNames.xml') - var parser = new ParserFactory() - - var actualData = [] - var actualItems = [] - var dataEventCount = 0 - - parser.on('data', function (data) { - actualData.push(data) - dataEventCount++ - }) - - parser.on('error', function (err) { - should(err).not.be.ok() - done(err) - }) - - parser.on('item', function (item) { - actualItems.push(item) - }) - - parser.on('end', function () { - actualItems.length.should.equal(18) - dataEventCount.should.equal(18) - done() - }) - - xmlStream.pipe(parser) - }) - - it('should properly parse a simple file containing nodes with same names and emit events on multiple nodes.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/nodesWithSameNames.xml') - var parser = new ParserFactory() - - var dataEventCount = 0 - var itemEventCount = 0 - var subitemEventCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('error', function (err) { - should(err).not.be.ok() - done(err) - }) - - parser.on('item', function (item) { - itemEventCount++ - }) - - parser.on('subitem', function (subitem) { - subitemEventCount++ - }) - - parser.on('end', function () { - itemEventCount.should.equal(18) - subitemEventCount.should.equal(13) - dataEventCount.should.equal(31) - done() - }) - - xmlStream.pipe(parser) - }) - - it.skip('should properly parse a medium size file with same names randomly.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/nodesWithSameNamesRandomly.xml') - var parser = new ParserFactory() - - var dataEventCount = 0 - var itemEventCount = 0 - var subitemEventCount = 0 - - parser.on('data', function (data) { - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('item', function (item) { - itemEventCount++ - }) - - parser.on('subitem', function (subitem) { - subitemEventCount++ - }) - - parser.on('end', function () { - // console.log('dataEventCount=', dataEventCount) - // console.log('itemEventCount=', itemEventCount) - // console.log('subitemEventCount=', subitemEventCount) - dataEventCount.should.equal(32) - itemEventCount.should.equal(19) - subitemEventCount.should.equal(13) - done() - }) - xmlStream.pipe(parser) - }) - }) - - describe('Parse funtion should work properly', function () { - it('should properly parse a simple file.', function (done) { - var xml = fs.readFileSync('./test/TestFiles/item.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - var expectedData = [ - { '$': { id: '1', test: 'hello' }, - subitem: - [ { '$': { sub: 'TESTING SUB' }, _: 'one' }, - { '$': { sub: '2' }, _: 'two' } ] }, - { '$': { id: '2' }, - subitem: [ { _: 'three' }, { _: 'four' }, { _: 'five' } ] } ] - - parser.parse(xml.toString(), function (err, data) { - if (err) done(err) - data.should.deepEqual(expectedData) - done() - }) - }) - - it('should properly parse a medium size file.', function (done) { - var xml = fs.readFileSync('./test/TestFiles/medium.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - - parser.parse(xml, function (err, data) { - if (err) done(err) - data.length.should.equal(10) - done() - }) - }) - - it('should properly parse a file containing many nodes.', function (done) { - var xml = fs.readFileSync('./test/TestFiles/manyItems.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - - parser.parse(xml, function (err, data) { - if (err) done(err) - data.length.should.equal(296) - done() - }) - }) - - it('should properly parse a xml simple file in which nodes contain text values randomly.', function (done) { - var xml = fs.readFileSync('./test/TestFiles/randomText.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - var expectedData = [ { '$': { 'id': '1', 'test': 'hello' }, '_': ' item one two', - 'subitem': [ { '$': { 'sub': 'TESTING SUB' }, '_': 'one' }, - { '$': { 'sub': '2' }, '_': 'two' } ] }, - { '$': { 'id': '2' }, '_': ' item one two three four', - 'subitem': [ { '_': 'three' }, { '_': 'four' }, { '_': 'five' } ] } - ] - - parser.parse(xml, function (err, data) { - if (err) done(err) - data.should.deepEqual(expectedData) - data.length.should.equal(2) - done() - }) - }) - - it('should properly parse a huge file.', function (done) { - var xml = fs.readFileSync('./test/TestFiles/hugeFile.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - // console.log(parser) - parser.parse(xml, function (err, data) { - if (err) done(err) - data.length.should.equal(2072) - done() - }) - }) - - it('should properly return error if the xml file is corrupted.', function (done) { - var xml = fs.readFileSync('./test/TestFiles/corrupted.xml') - var parser = new ParserFactory({resourcePath: '/items/item'}) - - parser.parse(xml, function (err, data) { - // console.log(err) - err.message.should.equal('mismatched tag at line no: 11') - should(data).not.be.ok() - done() - }) - }) - }) - - describe('should respect explicitArray constructor option', function () { - it('should properly parse a simple file with explicitArray set to false.', function (done) { - var xml = fs.readFileSync('./test/TestFiles/item.xml') - var parser = new ParserFactory({resourcePath: '/items/item', explicitArray: false}) - var expectedData = [ - { '$': { id: '1', test: 'hello' }, - subitem: { '$': { sub: '2' }, _: 'two' } }, - { '$': { id: '2' }, - subitem: { _: 'five' } } ] - - parser.parse(xml.toString(), function (err, data) { - if (err) done(err) - // console.log('data=', JSON.stringify(data)) - data.should.deepEqual(expectedData) - done() - }) - }) - - it('should properly parse a medium size file with explicitArray set to false.', function (done) { - var xml = fs.readFileSync('./test/TestFiles/medium.xml') - var parser = new ParserFactory({resourcePath: '/items/item', explicitArray: false}) - var expectedData = [ - { - "$":{ - "id":"1", - "test":"hello" - }, - "subitem":{ - "$":{ - "sub":"2" - }, - "_":"two" - } - }, - { - "$":{ - "id":"2" - }, - "subitem":{ - "_":"five" - } - }, - { - "$":{ - "id":"3", - "test":"hello" - }, - "subitem":{ - "$":{ - "sub":"2" - }, - "_":"two" - } - }, - { - "$":{ - "id":"4", - "test":"hello" - }, - "subitem":{ - "$":{ - "sub":"2" - }, - "_":"two" - } - }, - { - "$":{ - "id":"5", - "test":"hello" - }, - "subitem":{ - "$":{ - "sub":"2" - }, - "_":"two" - } - }, - { - "$":{ - "id":"6", - "test":"hello" - }, - "subitem":{ - "$":{ - "sub":"2" - }, - "_":"two" - } - }, - { - "$":{ - "id":"7", - "test":"hello" - }, - "subitem":{ - "$":{ - "sub":"2" - }, - "_":"two" - } - }, - { - "$":{ - "id":"8", - "test":"hello" - }, - "subitem":{ - "$":{ - "sub":"2" - }, - "_":"two" - } - }, - { - "$":{ - "id":"9", - "test":"hello" - }, - "subitem":{ - "$":{ - "sub":"2" - }, - "_":"two" - } - }, - { - "$":{ - "id":"10", - "test":"hello" - }, - "subitem":{ - "$":{ - "sub":"2" - }, - "_":"two" - } - } - ] - parser.parse(xml, function (err, data) { - if (err) done(err) - - data.should.deepEqual(expectedData) - data.length.should.equal(10) - done() - }) - }) - - it('should properly parse a file containing many nodes when explicitArray set to false.', function (done) { - var xml = fs.readFileSync('./test/TestFiles/manyItems.xml') - var parser = new ParserFactory({resourcePath: '/items/item', explicitArray: false}) - - parser.parse(xml, function (err, data) { - if (err) done(err) - - data.length.should.equal(296) - done() - }) - }) - - it('should properly parse a xml simple file in which nodes contain text values randomly when explicitArray set to false.', function (done) { - var xml = fs.readFileSync('./test/TestFiles/randomText.xml') - var parser = new ParserFactory({resourcePath: '/items/item', explicitArray: false}) - var expectedData = [ { '$': { 'id': '1', 'test': 'hello' }, '_': ' item one two', - 'subitem': { '$': { 'sub': '2' }, '_': 'two' } }, - { '$': { 'id': '2' }, '_': ' item one two three four', - 'subitem': { '_': 'five' } } - ] - - parser.parse(xml, function (err, data) { - if (err) done(err) - - data.should.deepEqual(expectedData) - data.length.should.equal(2) - done() - }) - }) - - it('should properly parse a huge file with explicitArray set to false.', function (done) { - var xml = fs.readFileSync('./test/TestFiles/hugeFile.xml') - var parser = new ParserFactory({resourcePath: '/items/item', explicitArray: false}) - // console.log(parser) - parser.parse(xml, function (err, data) { - if (err) done(err) - data.length.should.equal(2072) - done() - }) - }) - - it('should properly return error if the xml file is corrupted.', function (done) { - var xml = fs.readFileSync('./test/TestFiles/corrupted.xml') - var parser = new ParserFactory({resourcePath: '/items/item', explicitArray: false}) - - parser.parse(xml, function (err, data) { - // console.log(err) - err.message.should.equal('mismatched tag at line no: 11') - should(data).not.be.ok() - done() - }) - }) - - it('should properly generate objects when special symbols are passed as attrs and text keys and explicitArray is false in the options.', function (done) { - var xmlStream = fs.createReadStream('./test/TestFiles/item.xml') - var parser = new ParserFactory({resourcePath: '/items/item', attrsKey: '!', textKey: '%', explicitArray: false}) - var expectedData = [ - { '!': { id: '1', test: 'hello' }, - subitem: { '!': { sub: '2' }, '%': 'two' } }, - { '!': { id: '2' }, - subitem: { '%': 'five' } } ] - var actualData = [] - var dataEventCount = 0 - - parser.on('data', function (data) { - actualData.push(data) - dataEventCount++ - }) - - parser.on('error', function (err) { - done(err) - }) - - parser.on('end', function () { - // console.log('actualData=', JSON.stringify(actualData, null, 1)) - // console.log('dataEventCount=', dataEventCount) - actualData.should.deepEqual(expectedData) - dataEventCount.should.equal(2) - done() - }) - xmlStream.pipe(parser) - }) - }) -}) diff --git a/test/uncompressed.spec.ts b/test/uncompressed.spec.ts new file mode 100644 index 0000000..b823826 --- /dev/null +++ b/test/uncompressed.spec.ts @@ -0,0 +1,64 @@ +import fs from "fs"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; + +describe("should properly handle uncompressed files", () => { + it("should properly parse a uncompressed xml file.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/medium.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + const gzip = zlib.createGzip(); + const gunzip = zlib.createGunzip(); + let dataEventCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + should(dataEventCount).equal(10); + done(); + }); + xmlStream.pipe(gzip).pipe(gunzip).pipe(parser); + }); + + it("should properly parse uncompressed file and go fine with pause and resume.", function(done) { + const xmlStream = fs.createReadStream("./test/TestFiles/medium.xml"); + const parser = new XmlParser({ resourcePath: "/items/item" }); + const gzip = zlib.createGzip(); + const gunzip = zlib.createGunzip(); + + let dataEventCount = 0; + let isSetTimeoutHappened = true; + this.timeout(20000); + parser.on("data", (data) => { + parser.pause(); + should(isSetTimeoutHappened).equal(true); + setTimeout(() => { + parser.resume(); + isSetTimeoutHappened = true; + }, 2000); + dataEventCount++; + isSetTimeoutHappened = false; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + should(dataEventCount).equal(10); + done(); + }); + xmlStream.pipe(gzip).pipe(gunzip).pipe(parser); + }); +}); diff --git a/test/wrong_resourcepath.spec.ts b/test/wrong_resourcepath.spec.ts new file mode 100644 index 0000000..ee548b3 --- /dev/null +++ b/test/wrong_resourcepath.spec.ts @@ -0,0 +1,99 @@ +import fs from "fs"; +import "mocha"; +import should from "should"; +import stream from "stream"; +import zlib from "zlib"; + +import { XmlParser } from "../src/parser"; + +describe("wrong resourcePath", () => { + it("should be able to detect the wrong resourcePath at root level.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/item.xml"); + const parser = new XmlParser({ resourcePath: "/wrong/noNodes", emitOnNodeName: true }); + + const actualData : string[] = []; + const itemData : string[] = []; + let dataEventCount = 0; + let itemCount = 0; + + parser.on("data", (data) => { + actualData.push(data); + dataEventCount++; + }); + + parser.on("item", (item) => { + itemData.push(item); + itemCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('actualData=', actualData) + // console.log('dataEventCount=', dataEventCount) + should(actualData.length).equal(0); + should(dataEventCount).equal(0); + should(itemData.length).equal(0); + should(itemCount).equal(0); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should be able to detect wrong resourcePath while parsing xml", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/manyItems.xml"); + const parser = new XmlParser({ resourcePath: "/wrong/noNodes", emitOnNodeName: true }); + + let dataEventCount = 0; + let itemCount = 0; + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("item", (data) => { + itemCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + should(dataEventCount).equal(0); + should(itemCount).equal(0); + done(); + }); + xmlStream.pipe(parser); + }); + + it("should properly parse a huge file.", (done) => { + const xmlStream = fs.createReadStream("./test/TestFiles/hugeFile.xml"); + const parser = new XmlParser({ resourcePath: "/wrong/path", emitOnNodeName: true }); + + let dataEventCount = 0; + let itemCount = 0; + + parser.on("data", (data) => { + dataEventCount++; + }); + + parser.on("item", (item) => { + itemCount++; + }); + + parser.on("error", (err) => { + done(err); + }); + + parser.on("end", () => { + // console.log('dataEventCount=', dataEventCount) + should(dataEventCount).equal(0); + should(itemCount).equal(0); + done(); + }); + xmlStream.pipe(parser); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f783190 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "types": [ + "node" + ], + "outDir": "dist", + "moduleResolution": "node", + "module": "commonjs", + "removeComments": true, + "sourceMap": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports":true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "noImplicitAny": true, + "declaration": true, + "resolveJsonModule": true, + "target":"es2017", + "lib": ["es2017"], + "typeRoots": [ + "./node_modules/@types", + "./typings" + ] + }, + "include": [ + "./typings/**/*.d.ts", + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..cca3b93 --- /dev/null +++ b/tslint.json @@ -0,0 +1,22 @@ +{ + "rules": { + "no-console": [false], + "variable-name": false, + "radix": false, + "object-literal-sort-keys": false, + "trailing-comma":[false], + "indent": [true,"tabs"], + "max-line-length": [false], + "no-string-literal": false, + "class-name": false, + "no-namespace": [false], + "no-bitwise": false + }, + "extends": "tslint:recommended", + "linterOptions": { + "exclude":[ + "*.json", + "**/*.json" + ] + } +} \ No newline at end of file diff --git a/typings/node-expat/index.d.ts b/typings/node-expat/index.d.ts new file mode 100644 index 0000000..79f88d3 --- /dev/null +++ b/typings/node-expat/index.d.ts @@ -0,0 +1,91 @@ +// Type definitions for node-expat 2.3.x +// Project: http://nodejs.org/ +// Definitions by: Microsoft TypeScript +// Oleksandr Fedorchuk +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + + +declare module "node-expat" { + import { Stream } from "stream"; + import { EventEmitter } from "events"; + + interface NameSpace { + [key: string]: ValueType; + } + + interface TypedEmitter> extends EventEmitter { + addListener (event: Event, listener: (...args: EventMapType[Event]) => void): this; + on (event: Event, listener: (...args: EventMapType[Event]) => void): this; + once (event: Event, listener: (...args: EventMapType[Event]) => void): this; + prependListener (event: Event, listener: (...args: EventMapType[Event]) => void): this; + prependOnceListener (event: Event, listener: (...args: EventMapType[Event]) => void): this; + removeListener (event: Event, listener: (...args: EventMapType[Event]) => void): this; + emit (event: Event, ...args: EventMapType[Event]): boolean; + + addListener (event: string | symbol, listener: (...args: any[]) => void): this; + on (event: string | symbol, listener: (...args: any[]) => void): this; + + + once (event: string | symbol, listener: (...args: any[]) => void): this; + prependListener (event: string | symbol, listener: (...args: any[]) => void): this; + prependOnceListener (event: string | symbol, listener: (...args: any[]) => void): this; + removeListener (event: string | symbol, listener: (...args: any[]) => void): this; + emit (event: string | symbol, ...args: any[]): boolean; + } + + interface ParserEventsMap extends NameSpace { + startElement: [string, NameSpace]; + endElement: [string]; + text: [string]; + comment: [string]; + processingInstruction: [string, string]; + xmlDecl: [string | null, string | null, boolean]; + startCdata: []; + endCdata: []; + entityDecl: [string | null, boolean, string | null, string | null, string | null, string | null, string | null]; + end: []; + close: []; + error: [string | Error]; + } + + export class Parser extends Stream implements NodeJS.WritableStream, TypedEmitter + { + readonly writable: boolean; + stop(): this; + + pause(): this; + resume(): this; + + parse(buf: string | Buffer, isFinal?: boolean): boolean; + setEncoding(value: string): this; + /// @TODO setUnknownEncoding + getError(): string | null; + destroy(): this; + destroySoon(): this; + + write(buffer: Buffer | string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + end(cb?: Function): boolean; + end(chunk: Buffer, cb?: Function): boolean; + end(chunk: string, cb?: Function): boolean; + end(chunk: string, encding?: string, cb?: Function): boolean; + reset(this: Parser): this; + getCurrentLineNumber(this: Parser): number; + getCurrentColumnNumber(this: Parser): number; + getCurrentByteIndex(this: Parser): number; + + on (event: "startElement", listener:(name:string, attrs: any)=>void):this; + on (event: "endElement", listener:(name:string)=>void):this; + on (event: "text", listener:(text:string)=>void):this; + on (event: "processingInstruction", listener:(target:any, data:any)=>void):this; + on (event: "comment", listener:(s:string)=>void):this; + on (event: "xmlDecl", listener:(version:string, encoding: string, standalone: boolean)=>void):this; + on (event: "startCdata", listener:()=>void):this; + on (event: "endCdata", listener:()=>void):this; + on (event: "entityDecl", listener:(entityName:string, isParameterEntity:boolean, value:string, base:string, systemId:string, publicId:string, notationName:string)=>void):this; + on (event: "error", listener:(e:Error)=>void):this; + on (event: "close", listener:()=>void):this; + } + + export function createParser(callback?: (name: string, attributes: NameSpace) => void): Parser; +} diff --git a/typings/node-expat/project.json b/typings/node-expat/project.json new file mode 100644 index 0000000..a514dfe --- /dev/null +++ b/typings/node-expat/project.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file