├── .editorconfig ├── .eslintrc.js ├── .github ├── renovate.json └── workflows │ └── node.js.yml ├── .gitignore ├── .mocharc.js ├── .npmrc ├── .travis.yml ├── .tslint.config.json ├── AUTHORS ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── commitlint.config.js ├── examples ├── data │ └── docbook.rng ├── dump-events.js ├── example.js ├── null-parser.js └── test.xml ├── package-lock.json ├── package.json ├── src └── saxes.ts ├── test ├── attribute-name.ts ├── attribute-no-space.ts ├── attribute-normalization.ts ├── attribute-unquoted.ts ├── bad-entities.ts ├── bom.ts ├── cdata-chunked.ts ├── cdata-end-split.ts ├── cdata-fake-end.ts ├── cdata-multiple.ts ├── cdata.ts ├── close-without-open.ts ├── comment.ts ├── conformance-candidates.ts ├── conformance.ts ├── cyrillic.ts ├── dtd.ts ├── duplicate-attribute.ts ├── emoji.ts ├── entities.ts ├── entity-nan.ts ├── eol-handling.ts ├── errors.ts ├── files │ └── entities.xml ├── fragments.ts ├── issue-23.ts ├── issue-33.ts ├── issue-35.ts ├── issue-38.ts ├── issue-47.ts ├── issue-84.ts ├── issue-86.ts ├── not-string.ts ├── opentagstart.ts ├── parser-position.ts ├── pi.ts ├── self-closing-child.ts ├── self-closing-tag.ts ├── testutil.ts ├── trailing-attribute-no-value.ts ├── trailing-non-whitespace.ts ├── tsconfig.json ├── tshook.js ├── typings.ts ├── unclosed-root.ts ├── unicode.ts ├── wrong-cdata-closure.ts ├── xml-declaration.ts ├── xml-internal-entities.ts ├── xmlns-as-tag-name.ts ├── xmlns-issue-41.ts ├── xmlns-rebinding.ts ├── xmlns-strict.ts ├── xmlns-unbound.ts ├── xmlns-xml-default-ns.ts └── xmlns-xml-default-prefix.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [**/*] 2 | root=true 3 | end_of_line = lf 4 | indent_style = space 5 | indent_size = 2 6 | insert_final_newline = true 7 | max_line_length = 80 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | overrides: [{ 5 | files: [ 6 | "**/*.js", 7 | ], 8 | extends: [ 9 | "lddubeau-base", 10 | ], 11 | env: { 12 | node: true, 13 | }, 14 | overrides: [{ 15 | files: [ 16 | "lib/**/*.js", 17 | ], 18 | rules: { 19 | "no-continue": "off", 20 | // We use constant conditions quite often, for optimization reasons. 21 | "no-constant-condition": "off", 22 | }, 23 | }, { 24 | files: [ 25 | "test/**/*.js", 26 | ], 27 | env: { 28 | mocha: true, 29 | }, 30 | rules: { 31 | "no-unused-expressions": 32 | ["off", "Lots of false positivites due to chai."], 33 | }, 34 | }, { 35 | files: [ 36 | "misc/**/*.js", 37 | ], 38 | env: { 39 | browser: true, 40 | node: false, 41 | }, 42 | }], 43 | }, { 44 | files: [ 45 | "**/*.ts", 46 | ], 47 | env: { 48 | node: true, 49 | }, 50 | extends: [ 51 | "eslint:recommended", 52 | "eslint-config-lddubeau-ts", 53 | ], 54 | rules: { 55 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 56 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", 57 | // There's a bug in this plugin. 58 | "import/extensions": ["off", "always", { 59 | pattern: { 60 | js: "never", 61 | ts: "never", 62 | }, 63 | }], 64 | // There's a bug in this plugin. 65 | "import/no-unresolved": "off", 66 | // Too useful in this code-base. 67 | "@typescript-eslint/no-non-null-assertion": "off", 68 | }, 69 | overrides: [{ 70 | files: [ 71 | "test/**/*.ts", 72 | ], 73 | env: { 74 | mocha: true, 75 | }, 76 | parserOptions: { 77 | project: "test/tsconfig.json", 78 | sourceType: "module", 79 | }, 80 | settings: { 81 | "import/parsers": { 82 | "@typescript-eslint/parser": [".ts", ".tsx"], 83 | }, 84 | "import/resolver": { 85 | // use /tsconfig.json 86 | typescript: { 87 | project: "./test", 88 | }, 89 | }, 90 | }, 91 | rules: { 92 | "no-unused-expressions": 93 | ["off", "Lots of false positivites due to chai."], 94 | "no-shadow": ["off", "We shadow test all over the place."], 95 | "@typescript-eslint/tslint/config": [ 96 | "error", 97 | { 98 | lintFile: "./.tslint.config.json", 99 | }, 100 | ], 101 | // Routinely violated by large describe blocks. 102 | "max-lines-per-function": "off", 103 | }, 104 | }], 105 | }], 106 | }; 107 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "lddubeau:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | on: 3 | push: 4 | branches: [ master, "renovate/*" ] 5 | pull_request: 6 | branches: [ master ] 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | node-version: [12.x, 14.x, 16.x] 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | cache: 'npm' 21 | - run: npm ci 22 | - run: npm test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | node_modules/* 3 | nyc_output/ 4 | .nyc_output/ 5 | coverage/ 6 | /build 7 | /misc 8 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | require: ["test/tshook"], 5 | extension: ["ts", "js"], 6 | }; 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - node 5 | - 12.22.12 6 | - 10.24.1 7 | cache: 8 | directories: 9 | - $HOME/.npm 10 | - node_modules 11 | -------------------------------------------------------------------------------- /.tslint.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-lddubeau-ts/tslint.config.json", 3 | "rules": { 4 | "mocha-no-side-effect-code": false, 5 | "chai-vague-errors": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # contributors sorted by whether or not they're me. 2 | Louis-Dominique Dubeau 3 | Isaac Z. Schlueter 4 | Stein Martin Hustad 5 | Mikeal Rogers 6 | Laurie Harper 7 | Jann Horn 8 | Elijah Insua 9 | Henry Rawas 10 | Justin Makeig 11 | Mike Schilling 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [6.0.0](https://github.com/lddubeau/saxes/compare/v6.0.0-rc.1...v6.0.0) (2021-11-07) 2 | 3 | 4 | 5 | # [6.0.0-rc.1](https://github.com/lddubeau/saxes/compare/v5.0.1...v6.0.0-rc.1) (2021-11-07) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * fixing linting errors for eslint 8 ([cd4b5c9](https://github.com/lddubeau/saxes/commit/cd4b5c9ddf166d426ece77349bbde7538fb0aaa4)) 11 | * we don't support node 10 anymore ([f2aa1a8](https://github.com/lddubeau/saxes/commit/f2aa1a8e2b379f102010b9c552490f385f7854af)) 12 | 13 | 14 | ### BREAKING CHANGES 15 | 16 | * we don't support node 10. 17 | 18 | 19 | 20 | 21 | ## [5.0.1](https://github.com/lddubeau/saxes/compare/v5.0.0...v5.0.1) (2020-04-10) 22 | 23 | 24 | ### Bug Fixes 25 | 26 | * fix corrupted attribute values when there is no text handler ([e135f11](https://github.com/lddubeau/saxes/commit/e135f11)), closes [#38](https://github.com/lddubeau/saxes/issues/38) 27 | 28 | 29 | 30 | 31 | # [5.0.0](https://github.com/lddubeau/saxes/compare/v5.0.0-rc.2...v5.0.0) (2020-02-28) 32 | 33 | 34 | 35 | 36 | # [5.0.0-rc.2](https://github.com/lddubeau/saxes/compare/v5.0.0-rc.1...v5.0.0-rc.2) (2020-02-12) 37 | 38 | 39 | 40 | 41 | # [5.0.0-rc.1](https://github.com/lddubeau/saxes/compare/v4.0.2...v5.0.0-rc.1) (2020-02-12) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * disallow BOM characters at the beginning of subsequent chunks ([66d07b6](https://github.com/lddubeau/saxes/commit/66d07b6)) 47 | * fix some typing mistakes ([f2a1d5e](https://github.com/lddubeau/saxes/commit/f2a1d5e)) 48 | * handle column computation over characters in the astral plane ([cefc8f7](https://github.com/lddubeau/saxes/commit/cefc8f7)) 49 | 50 | 51 | ### Features 52 | 53 | * add makeError method ([50fa39a](https://github.com/lddubeau/saxes/commit/50fa39a)) 54 | * add xmldecl event ([a2e677f](https://github.com/lddubeau/saxes/commit/a2e677f)) 55 | * formal method for setting event listeners ([f346150](https://github.com/lddubeau/saxes/commit/f346150)) 56 | * reinstating the attribute events ([7c80f7b](https://github.com/lddubeau/saxes/commit/7c80f7b)) 57 | * saxes is now implemented in TS ([664ba69](https://github.com/lddubeau/saxes/commit/664ba69)) 58 | 59 | 60 | ### Performance Improvements 61 | 62 | * add topNS for faster namespace processing ([1a33a57](https://github.com/lddubeau/saxes/commit/1a33a57)) 63 | 64 | 65 | ### BREAKING CHANGES 66 | 67 | * The individually named event handlers no longer exist. You now 68 | must use the methods `on` and `off` to set handlers. Upcoming features require 69 | that saxes know when handlers are added and removed, and it may be necessary in 70 | the future to qualify how to add or remove a handler. Getters/setters are too 71 | restrictives so we bite the bullet now and move to actual methods. 72 | * The fix to column number reporting changes the meaning of the 73 | ``column`` field. If you need the old behavior of ``column`` you can use the new 74 | ``columnIndex`` field which behaves like the old ``column`` and may be useful in 75 | some contexts. Ultimately you should decide whether your application needs to 76 | know column numbers by Unicode character count or by JavaScript index. (And you 77 | need to know the difference between the two. You can see [this 78 | page](https://mathiasbynens.be/notes/javascript-unicode) for a detailed 79 | discussion of the Unicode problem in JavaScript. Note that the numbers put in 80 | the error messages that ``fail`` produce are still based on the ``column`` field 81 | and thus use the new meaning of ``column``. If you want error message that use 82 | ``columnIndex`` you may override the ``fail`` method. 83 | 84 | 85 | 86 | 87 | ## [4.0.2](https://github.com/lddubeau/saxes/compare/v4.0.0-rc.4...v4.0.2) (2019-10-14) 88 | 89 | 90 | 91 | 92 | ## [4.0.1](https://github.com/lddubeau/saxes/compare/v4.0.0...v4.0.1) (2019-10-14) 93 | 94 | 95 | 96 | 97 | # [4.0.0](https://github.com/lddubeau/saxes/compare/v4.0.0-rc.4...v4.0.0) (2019-10-14) 98 | 99 | 100 | 101 | 102 | # [4.0.0-rc.4](https://github.com/lddubeau/saxes/compare/v4.0.0-rc.2...v4.0.0-rc.4) (2019-10-11) 103 | 104 | 105 | ### Bug Fixes 106 | 107 | * fix a bug in EOL handling ([bed38a8](https://github.com/lddubeau/saxes/commit/bed38a8)) 108 | * implement attribute normalization ([be51114](https://github.com/lddubeau/saxes/commit/be51114)), closes [#24](https://github.com/lddubeau/saxes/issues/24) 109 | 110 | 111 | ### Performance Improvements 112 | 113 | * inline closeText ([07a3b51](https://github.com/lddubeau/saxes/commit/07a3b51)) 114 | 115 | 116 | 117 | 118 | # [4.0.0-rc.3](https://github.com/lddubeau/saxes/compare/v4.0.0-rc.2...v4.0.0-rc.3) (2019-10-11) 119 | 120 | 121 | ### Bug Fixes 122 | 123 | * fix a bug in EOL handling ([03b1567](https://github.com/lddubeau/saxes/commit/03b1567)) 124 | * implement attribute normalization ([6580844](https://github.com/lddubeau/saxes/commit/6580844)), closes [#24](https://github.com/lddubeau/saxes/issues/24) 125 | 126 | 127 | ### Performance Improvements 128 | 129 | * inline closeText ([1c8df1a](https://github.com/lddubeau/saxes/commit/1c8df1a)) 130 | 131 | 132 | 133 | 134 | # [4.0.0-rc.2](https://github.com/lddubeau/saxes/compare/v4.0.0-rc.1...v4.0.0-rc.2) (2019-10-04) 135 | 136 | 137 | ### Performance Improvements 138 | 139 | * drop the originalNL flag in favor of a NL_LIKE fake character ([f690725](https://github.com/lddubeau/saxes/commit/f690725)) 140 | * dump isNaN; it is very costly ([7d97e1a](https://github.com/lddubeau/saxes/commit/7d97e1a)) 141 | * eliminate extra buffers ([3412fcb](https://github.com/lddubeau/saxes/commit/3412fcb)) 142 | * reduce the number of calls to closeText ([3e68df5](https://github.com/lddubeau/saxes/commit/3e68df5)) 143 | * remove more extra buffers ([b5ee774](https://github.com/lddubeau/saxes/commit/b5ee774)) 144 | * use -1 to mean EOC (end-of-chunk) ([55c0b1b](https://github.com/lddubeau/saxes/commit/55c0b1b)) 145 | 146 | 147 | 148 | 149 | # [4.0.0-rc.1](https://github.com/lddubeau/saxes/compare/v3.1.11...v4.0.0-rc.1) (2019-10-02) 150 | 151 | 152 | ### Bug Fixes 153 | 154 | * don't serialize the fileName as undefined: when not present ([4ff2365](https://github.com/lddubeau/saxes/commit/4ff2365)) 155 | * fix bug with initial eol characters ([7b3db75](https://github.com/lddubeau/saxes/commit/7b3db75)) 156 | * handling of end of line characters ([f13247a](https://github.com/lddubeau/saxes/commit/f13247a)) 157 | 158 | 159 | ### Features 160 | 161 | * add forceXMLVersion ([1eedbf8](https://github.com/lddubeau/saxes/commit/1eedbf8)) 162 | * saxes handles chunks that "break" unicode ([1272448](https://github.com/lddubeau/saxes/commit/1272448)) 163 | * support for XML 1.1 ([36704fb](https://github.com/lddubeau/saxes/commit/36704fb)) 164 | 165 | 166 | ### Performance Improvements 167 | 168 | * don't depend on limit to know when we hit the end of buffer ([ad4ab53](https://github.com/lddubeau/saxes/commit/ad4ab53)) 169 | * don't increment a column number ([490fc24](https://github.com/lddubeau/saxes/commit/490fc24)) 170 | * don't repeatedly read this.i in the getCode methods ([d3f196c](https://github.com/lddubeau/saxes/commit/d3f196c)) 171 | * improve performance of text handling ([9c13099](https://github.com/lddubeau/saxes/commit/9c13099)) 172 | * make the most common path of getCode functions the shortest ([4d66bbb](https://github.com/lddubeau/saxes/commit/4d66bbb)) 173 | * minimine concatenation by adding the capability to unget codes ([27fa8b9](https://github.com/lddubeau/saxes/commit/27fa8b9)) 174 | * use isCharAndNotRestricted rather than call two functions ([f0b67a4](https://github.com/lddubeau/saxes/commit/f0b67a4)) 175 | * use slice rather than substring ([c1fed89](https://github.com/lddubeau/saxes/commit/c1fed89)) 176 | 177 | 178 | ### BREAKING CHANGES 179 | 180 | * previous versions of saxes did not consistently convert end of 181 | line characters to NL (0xA) in the data reported by event handlers. This has 182 | been fixed. If your code relied on the old (incorrect) behavior then you'll have 183 | to update it. 184 | * previous versions of saxes would parse files with an XML 185 | declaration set to 1.1 as 1.0 documents. The support for 1.1 entails that if a 186 | document has an XML declaration that specifies version 1.1 it is parsed as a 1.1 187 | document. 188 | * when ``fileName`` is undefined in the parser options saxes does 189 | not show a file name in error messages. Previously it was showing the name 190 | ``undefined``. To get the previous behavior, in all cases where you'd leave 191 | ``fileName`` undefined, you must set it to the string ``"undefined"`` instead. 192 | 193 | 194 | 195 | 196 | ## [3.1.11](https://github.com/lddubeau/saxes/compare/v3.1.10...v3.1.11) (2019-06-25) 197 | 198 | 199 | ### Bug Fixes 200 | 201 | * pay attention to comments and processing instructions in DTDs ([52ffd90](https://github.com/lddubeau/saxes/commit/52ffd90)), closes [#19](https://github.com/lddubeau/saxes/issues/19) 202 | 203 | 204 | ### Performance Improvements 205 | 206 | * check the most common case first ([40a34d5](https://github.com/lddubeau/saxes/commit/40a34d5)) 207 | * improve some more the speed of ]]> detection ([a0216cd](https://github.com/lddubeau/saxes/commit/a0216cd)) 208 | * move more common/valid cases first ([a65586e](https://github.com/lddubeau/saxes/commit/a65586e)) 209 | * split sText into two specialized loops ([732325e](https://github.com/lddubeau/saxes/commit/732325e)) 210 | * use specialized code for sAttribValueQuoted ([6c484f3](https://github.com/lddubeau/saxes/commit/6c484f3)) 211 | 212 | 213 | 214 | 215 | ## [3.1.10](https://github.com/lddubeau/saxes/compare/v3.1.9...v3.1.10) (2019-06-11) 216 | 217 | 218 | ### Performance Improvements 219 | 220 | * improve the check for ]]> in character data ([21df9b5](https://github.com/lddubeau/saxes/commit/21df9b5)) 221 | 222 | 223 | 224 | 225 | ## [3.1.9](https://github.com/lddubeau/saxes/compare/v3.1.7...v3.1.9) (2019-02-25) 226 | 227 | 228 | ### Bug Fixes 229 | 230 | * move eslint to devDependencies ([d747538](https://github.com/lddubeau/saxes/commit/d747538)) 231 | 232 | 233 | 234 | 235 | ## [3.1.8](https://github.com/lddubeau/saxes/compare/v3.1.7...v3.1.8) (2019-02-25) 236 | 237 | 238 | 239 | 240 | ## [3.1.7](https://github.com/lddubeau/saxes/compare/v3.1.6...v3.1.7) (2019-02-22) 241 | 242 | 243 | ### Bug Fixes 244 | 245 | * npm audit warning ([a6c9ba8](https://github.com/lddubeau/saxes/commit/a6c9ba8)) 246 | * **typings:** "selfClosing" => "isSelfClosing" ([d96a2bd](https://github.com/lddubeau/saxes/commit/d96a2bd)) 247 | 248 | 249 | 250 | 251 | ## [3.1.6](https://github.com/lddubeau/saxes/compare/v3.1.5...v3.1.6) (2019-01-17) 252 | 253 | 254 | ### Bug Fixes 255 | 256 | * detect unclosed tags in fragments ([5642f36](https://github.com/lddubeau/saxes/commit/5642f36)) 257 | 258 | 259 | 260 | 261 | ## [3.1.5](https://github.com/lddubeau/saxes/compare/v3.1.4...v3.1.5) (2019-01-08) 262 | 263 | 264 | ### Bug Fixes 265 | 266 | * generate an error on prefix with empty local name ([89a3b86](https://github.com/lddubeau/saxes/commit/89a3b86)), closes [#5](https://github.com/lddubeau/saxes/issues/5) 267 | 268 | 269 | 270 | 271 | ## [3.1.4](https://github.com/lddubeau/saxes/compare/v3.1.3...v3.1.4) (2018-12-03) 272 | 273 | 274 | ### Bug Fixes 275 | 276 | * add fragment and additionalNamespaces to SaxesOption typing ([02d8275](https://github.com/lddubeau/saxes/commit/02d8275)) 277 | 278 | 279 | 280 | 281 | ## [3.1.3](https://github.com/lddubeau/saxes/compare/v3.1.2...v3.1.3) (2018-10-01) 282 | 283 | 284 | ### Bug Fixes 285 | 286 | * use the latest xmlchars ([b30a714](https://github.com/lddubeau/saxes/commit/b30a714)) 287 | 288 | 289 | ### Performance Improvements 290 | 291 | * don't check twice if this.textNode is set ([00536cc](https://github.com/lddubeau/saxes/commit/00536cc)) 292 | * reduce the frequency at which we clear attribValue ([1570615](https://github.com/lddubeau/saxes/commit/1570615)) 293 | 294 | 295 | 296 | 297 | ## [3.1.2](https://github.com/lddubeau/saxes/compare/v3.1.1...v3.1.2) (2018-08-31) 298 | 299 | 300 | ### Bug Fixes 301 | 302 | * CDATA end in attributes must not cause an error ([a7495ac](https://github.com/lddubeau/saxes/commit/a7495ac)) 303 | * normalize \r\n and \r followed by something else to \n ([d7b1abe](https://github.com/lddubeau/saxes/commit/d7b1abe)), closes [#2](https://github.com/lddubeau/saxes/issues/2) 304 | 305 | 306 | 307 | 308 | ## [3.1.1](https://github.com/lddubeau/saxes/compare/v3.1.0...v3.1.1) (2018-08-29) 309 | 310 | 311 | ### Bug Fixes 312 | 313 | * resolve is now part of the public API ([bb4bed5](https://github.com/lddubeau/saxes/commit/bb4bed5)) 314 | 315 | 316 | 317 | 318 | # [3.1.0](https://github.com/lddubeau/saxes/compare/v3.0.0...v3.1.0) (2018-08-28) 319 | 320 | 321 | ### Bug Fixes 322 | 323 | * correct typo ([97bc5da](https://github.com/lddubeau/saxes/commit/97bc5da)) 324 | 325 | 326 | ### Performance Improvements 327 | 328 | * add emitNodes to skip checking text buffer more than needed ([9d5e357](https://github.com/lddubeau/saxes/commit/9d5e357)) 329 | * capture names in the ``name`` field ([c7dffd5](https://github.com/lddubeau/saxes/commit/c7dffd5)) 330 | * introduce a specialized version of captureWhile ([04855d6](https://github.com/lddubeau/saxes/commit/04855d6)) 331 | * introduce captureTo and captureToChar ([76eb95a](https://github.com/lddubeau/saxes/commit/76eb95a)) 332 | * remove skipWhitespace ([c8b7ae2](https://github.com/lddubeau/saxes/commit/c8b7ae2)) 333 | * remove some redundant buffer resets ([5ded326](https://github.com/lddubeau/saxes/commit/5ded326)) 334 | * use charCodeAt and handle surrogates ourselves ([b8ec232](https://github.com/lddubeau/saxes/commit/b8ec232)) 335 | 336 | 337 | 338 | 339 | # [3.0.0](https://github.com/lddubeau/saxes/compare/v2.2.1...v3.0.0) (2018-08-21) 340 | 341 | 342 | ### Features 343 | 344 | * process the xmlns attribute the customary way ([2c9672a](https://github.com/lddubeau/saxes/commit/2c9672a)) 345 | 346 | 347 | ### BREAKING CHANGES 348 | 349 | * In previous versions the attribute `xmlns` (as in `` would 350 | be reported as having the prefix `"xmlns"` and the local name `""`. This 351 | behavior was inherited from sax. There was some logic to it, but this behavior 352 | was surprising to users of the library. The principle of least surprise favors 353 | eliminating that surprising behavior in favor of something less surprising. 354 | 355 | This commit makes it so that `xmlns` is not reported as having a prefix of `""` 356 | and a local name of `"xmlns"`. This accords with how people interpret attribute 357 | names like `foo`, `bar`, `moo` which all have no prefix and a local name. 358 | 359 | Code that deals with namespace bindings or cares about `xmlns` probably needs to 360 | be changed. 361 | 362 | 363 | 364 | 365 | ## [2.2.1](https://github.com/lddubeau/saxes/compare/v2.2.0...v2.2.1) (2018-08-20) 366 | 367 | 368 | ### Bug Fixes 369 | 370 | * use `isNameChar` for later chars in PI target ([83d2b61](https://github.com/lddubeau/saxes/commit/83d2b61)) 371 | 372 | 373 | 374 | 375 | # [2.2.0](https://github.com/lddubeau/saxes/compare/v2.1.0...v2.2.0) (2018-08-20) 376 | 377 | 378 | ### Features 379 | 380 | * add the `resolvePrefix` option ([90301fb](https://github.com/lddubeau/saxes/commit/90301fb)) 381 | 382 | 383 | 384 | 385 | # [2.1.0](https://github.com/lddubeau/saxes/compare/v2.0.0...v2.1.0) (2018-08-20) 386 | 387 | 388 | ### Features 389 | 390 | * add support for parsing fragments ([1ff2d6a](https://github.com/lddubeau/saxes/commit/1ff2d6a)) 391 | * stronger check on bad cdata closure ([d416760](https://github.com/lddubeau/saxes/commit/d416760)) 392 | 393 | 394 | ### Performance Improvements 395 | 396 | * concatenate openWakaBang just once ([07345bf](https://github.com/lddubeau/saxes/commit/07345bf)) 397 | * improve text node checking speed ([f270e8b](https://github.com/lddubeau/saxes/commit/f270e8b)) 398 | * minor optimizations ([c7e36bf](https://github.com/lddubeau/saxes/commit/c7e36bf)) 399 | * remove an unnecessary variable ([ac03a1c](https://github.com/lddubeau/saxes/commit/ac03a1c)) 400 | * remove handler check ([fbe35ff](https://github.com/lddubeau/saxes/commit/fbe35ff)) 401 | * simplify captureWhile ([bb2085c](https://github.com/lddubeau/saxes/commit/bb2085c)) 402 | * simplify the skip functions ([c7b8c3b](https://github.com/lddubeau/saxes/commit/c7b8c3b)) 403 | * the c field has been unused for a while: remove it ([9ca0246](https://github.com/lddubeau/saxes/commit/9ca0246)) 404 | * use strings for the general states ([3869908](https://github.com/lddubeau/saxes/commit/3869908)) 405 | 406 | 407 | 408 | 409 | # [2.0.0](https://github.com/lddubeau/saxes/compare/v1.2.4...v2.0.0) (2018-07-23) 410 | 411 | 412 | ### Bug Fixes 413 | 414 | * "X" is not a valid hex prefix for char references ([465038b](https://github.com/lddubeau/saxes/commit/465038b)) 415 | * add namespace checks ([9f94c4b](https://github.com/lddubeau/saxes/commit/9f94c4b)) 416 | * always run in strict mode ([ed8b0b1](https://github.com/lddubeau/saxes/commit/ed8b0b1)) 417 | * check that the characters we read are valid char data ([7611a85](https://github.com/lddubeau/saxes/commit/7611a85)) 418 | * disallow spaces after open waka ([da7f76d](https://github.com/lddubeau/saxes/commit/da7f76d)) 419 | * drop the lowercase option ([987d4bf](https://github.com/lddubeau/saxes/commit/987d4bf)) 420 | * emit CDATA on empty CDATA section too ([95d192f](https://github.com/lddubeau/saxes/commit/95d192f)) 421 | * emit empty comment ([b3db392](https://github.com/lddubeau/saxes/commit/b3db392)) 422 | * entities are always strict ([0f6a30e](https://github.com/lddubeau/saxes/commit/0f6a30e)) 423 | * fail on colon at start of QName ([507addd](https://github.com/lddubeau/saxes/commit/507addd)) 424 | * harmonize error messages and initialize flags ([9a20cad](https://github.com/lddubeau/saxes/commit/9a20cad)) 425 | * just one error for text before the root, and text after ([101ea50](https://github.com/lddubeau/saxes/commit/101ea50)) 426 | * more namespace checks ([a1add21](https://github.com/lddubeau/saxes/commit/a1add21)) 427 | * move namespace checks to their proper place ([4a1c99f](https://github.com/lddubeau/saxes/commit/4a1c99f)) 428 | * only accept uppercase CDATA to mark the start of CDATA ([e86534d](https://github.com/lddubeau/saxes/commit/e86534d)) 429 | * prevent colons in pi and entity names when xmlns is true ([4327eec](https://github.com/lddubeau/saxes/commit/4327eec)) 430 | * prevent empty entities ([04e1593](https://github.com/lddubeau/saxes/commit/04e1593)) 431 | * raise an error if the document does not have a root ([f2de520](https://github.com/lddubeau/saxes/commit/f2de520)) 432 | * raise an error on ]]> in character data ([2964381](https://github.com/lddubeau/saxes/commit/2964381)) 433 | * raise an error on < in attribute values ([4fd67a1](https://github.com/lddubeau/saxes/commit/4fd67a1)) 434 | * raise an error on multiple root elements ([45047ae](https://github.com/lddubeau/saxes/commit/45047ae)) 435 | * raise error on CDATA before or after root ([604241f](https://github.com/lddubeau/saxes/commit/604241f)) 436 | * raise error on character reference outside CHAR production ([30fb540](https://github.com/lddubeau/saxes/commit/30fb540)) 437 | * remove broken or pointless examples ([1a5b642](https://github.com/lddubeau/saxes/commit/1a5b642)) 438 | * report an error on duplicate attributes ([ee4e340](https://github.com/lddubeau/saxes/commit/ee4e340)) 439 | * report an error on whitespace at the start of end tag ([c13b122](https://github.com/lddubeau/saxes/commit/c13b122)) 440 | * report processing instructions that do not have a target ([c007e39](https://github.com/lddubeau/saxes/commit/c007e39)) 441 | * treat ?? in processing instructions correctly ([bc1e1d4](https://github.com/lddubeau/saxes/commit/bc1e1d4)) 442 | * trim URIs ([78cc6f3](https://github.com/lddubeau/saxes/commit/78cc6f3)) 443 | * use xmlchars for checking names ([2c939fe](https://github.com/lddubeau/saxes/commit/2c939fe)) 444 | * verify that character references match the CHAR production ([369afde](https://github.com/lddubeau/saxes/commit/369afde)) 445 | 446 | 447 | ### Code Refactoring 448 | 449 | * adjust the names used for processing instructions ([3b508e9](https://github.com/lddubeau/saxes/commit/3b508e9)) 450 | * convert code to ES6 ([fe81170](https://github.com/lddubeau/saxes/commit/fe81170)) 451 | * drop attribute event ([c7c2e80](https://github.com/lddubeau/saxes/commit/c7c2e80)) 452 | * drop buffer size checks ([9ce2f7a](https://github.com/lddubeau/saxes/commit/9ce2f7a)) 453 | * drop normalize ([9c6d84c](https://github.com/lddubeau/saxes/commit/9c6d84c)) 454 | * drop opencdata and on closecdata ([3287d2c](https://github.com/lddubeau/saxes/commit/3287d2c)) 455 | * drop SGML declaration parsing ([4aaf2d9](https://github.com/lddubeau/saxes/commit/4aaf2d9)) 456 | * drop the ``parser`` function, rename SAXParser ([0878a6c](https://github.com/lddubeau/saxes/commit/0878a6c)) 457 | * drop trim ([c03c7d0](https://github.com/lddubeau/saxes/commit/c03c7d0)) 458 | * pass the actual tag to onclosetag ([7020e64](https://github.com/lddubeau/saxes/commit/7020e64)) 459 | * provide default no-op implementation for events ([a94687f](https://github.com/lddubeau/saxes/commit/a94687f)) 460 | * remove the API based on Stream ([ebb659a](https://github.com/lddubeau/saxes/commit/ebb659a)) 461 | * simplify namespace processing ([2d4ce0f](https://github.com/lddubeau/saxes/commit/2d4ce0f)) 462 | 463 | 464 | ### Features 465 | 466 | * drop the resume() method; and have onerror() throw ([ac601e5](https://github.com/lddubeau/saxes/commit/ac601e5)) 467 | * handle XML declarations ([5258939](https://github.com/lddubeau/saxes/commit/5258939)) 468 | * revamped error messages ([cf9c589](https://github.com/lddubeau/saxes/commit/cf9c589)) 469 | * the flush method returns its parser ([68c2020](https://github.com/lddubeau/saxes/commit/68c2020)) 470 | 471 | 472 | ### BREAKING CHANGES 473 | 474 | * Sax was only passing the tag name. We pass the whole object. 475 | * The API no longer takes a ``strict`` argument anywhere. This also 476 | effectively removes support for HTML processing, or allow processing 477 | without errors anything which is less than full XML. It also removes 478 | special processing of ``script`` elements. 479 | * ``attribute`` is not a particularly useful event for parsing XML. The only thing 480 | it adds over looking at attributes on tag objects is that you get the order of 481 | the attributes from the source, but attribute order in XML is irrelevant. 482 | * The opencdata and closecdata events became redundant once we removed the buffer 483 | size limitations. So we remove these events. 484 | * The ``parser`` function is removed. Just create a new instance with 485 | ``new``. 486 | 487 | ``SAXParser`` is now ``SaxesParser.`` So ``new 488 | require("saxes").SaxesParser(...)``. 489 | * The API based on Stream is gone. There were multiple issues with it. It was 490 | Node-specific. It used an ancient Node API (the so-called "classic 491 | streams"). Its behavior was idiosyncratic. 492 | * Sax had no default error handler but if you wanted to continue calling 493 | ``write()`` after an error you had to call ``resume()``. We do away with 494 | ``resume()`` and instead install a default ``onerror`` which throws. Replace 495 | with a no-op handler if you want to continue after errors. 496 | * The "processinginstruction" now produces a "target" field instead of a "name" 497 | field. The nomenclature "target" is the one used in the XML literature. 498 | * * The ``ns`` field is no longer using the prototype trick that sax used. The 499 | ``ns`` field of a tag contains only those namespaces that the tag declares. 500 | 501 | * We no longer have ``opennamespace`` and ``closenamespace`` events. The 502 | information they provide can be obtained by examining the tags passed to tag 503 | events. 504 | * SGML declaration is not supported by XML. This is an XML parser. So we 505 | remove support for SGML declarations. They now cause errors. 506 | * We removed support for the code that checked buffer sizes and would 507 | raise errors if a buffer was close to an arbitrary limit or emitted 508 | multiple ``text`` or ``cdata`` events in order avoid passing strings 509 | greater than an arbitrary size. So ``MAX_BUFFER_LENGTH`` is gone. 510 | 511 | The feature always seemed a bit awkward. Client code could limit the 512 | size of buffers to 1024K, for instance, and not get a ``text`` event 513 | with a text payload greater than 1024K... so far so good but if the 514 | same document contained a comment with more than 1024K that would 515 | result in an error. Hmm.... why? The distinction seems entirely 516 | arbitrary. 517 | 518 | The upshot is that client code needs to be ready to handle strings of 519 | any length supported by the platform. 520 | 521 | If there's a clear need to reintroduce it, we'll reassess. 522 | * It is no longer possible to load the library as-is through a 523 | ``script`` element. It needs building. 524 | 525 | The library now assumes a modern runtime. It no longer contains any 526 | code to polyfill what's missing. It is up to developers using this 527 | code to deal with polyfills as needed. 528 | * We drop the ``trim`` option. It is up to client code to trip text if 529 | it needs it. 530 | * We no longer support the ``normalize`` option. It is up to client code 531 | to perform whatever normalization it wants. 532 | * The ``lowercase`` option makes no sense for XML. It is removed. 533 | * Remove support for strictEntities. Entities are now always strict, as 534 | required by the XML specification. 535 | * By default parsers now have a default no-op implementation for each 536 | event it supports. This would break code that determines whether a 537 | custom handler was added by checking whether there's any handler at 538 | all. This removes the necessity for the parser implementation to check 539 | whether there is a handler before calling it. 540 | 541 | In the process of making this change, we've removed support for the 542 | ``on...`` properties on streams objects. Their existence was not 543 | warranted by any standard API provided by Node. (``EventEmitter`` does 544 | not have ``on...`` properties for events it supports, nor does 545 | ``Stream``.) Their existence was also undocumented. And their 546 | functioning was awkward. For instance, with sax, this: 547 | 548 | ``` 549 | const s = sax.createStream(); 550 | const handler = () => console.log("moo"); 551 | s.on("cdata", handler); 552 | console.log(s.oncdata === handler); 553 | ``` 554 | 555 | would print ``false``. If you examine ``s.oncdata`` you see it is glue 556 | code instead of the handler assigned. This is just bizarre, so we 557 | removed it. 558 | 559 | 560 | 561 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Write A Test 2 | 3 | Isaac had established this rule for sax, it still goes for saxes: 4 | 5 | **NO PATCHES WITHOUT A TEST** 6 | 7 | **TEST MUST PASS WITH THE PATCH.** 8 | 9 | **TEST MUST FAIL WITHOUT THE PATCH.** 10 | 11 | **NO EXCEPTIONS.** 12 | 13 | # EVERY PULL REQUEST MUST HAVE A TEST. 14 | 15 | Seriously. This is a very strict rule, and I will not bend it for any 16 | patch, no matter how minor. 17 | 18 | Write a test. 19 | 20 | ## Optimize for the Well-formed Case 21 | 22 | Optimize your PR for well-formed documents. For instance, if a value is only 23 | going to be useful for an error message, and requires additional processing to 24 | compute, then don't compute it if the error does not occur. 25 | 26 | This principle is why saxes continues producing data even when a well-formedness 27 | error occurs. The XML specification forbids XML processors from continuing to 28 | pass data when a such error occurs. They may only report other errors. However, 29 | implementing the XML specification requirement would require that saxes 30 | continually check whether it may emit non-error events and this would have a 31 | measurable cost even when parsing well-formed documents. We decided always emit 32 | the events anyway and diverge a bit from the XML specifications. 33 | 34 | ## Optimize for "Clean" XML 35 | 36 | This is well-formed XML: 37 | 38 | ```xml 39 | something 40 | ``` 41 | 42 | This is also well-formed and represents the same document as the previous 43 | example: 44 | 45 | ```xml 46 | something 52 | 53 | 54 | ``` 55 | 56 | We want both documents to be parsed without error by saxes but, when writing 57 | code, optimize for the former rather than the later. 58 | 59 | ## Pay Attention to Performance 60 | 61 | The more a PR harms performance, 62 | 63 | 1. The more justified the PR must be. The best justification for a drop in 64 | performance is providing a fix that fixes a bug which causes saxes to not be 65 | conformant. 66 | 67 | 2. The more likely we'll ask that the PR be optimized before merging. 68 | 69 | 3. The more likelihood it'll be ultimately rejected. 70 | 71 | Please verify the performance impact before submitting the PR. An easy way to do 72 | it is: 73 | 74 | ```terminal 75 | $ node examples/null-parser.js examples/data/docbook.rng 76 | ``` 77 | 78 | You should run it before you make your change and after. Make sure you run it 79 | when your CPU is relatively idle, and run it multiple times to get a valid 80 | result. If the code takes 10 times longer with your change, you know you have a 81 | problem. Address it before submitting your PR. 82 | 83 | In general, we want both elegance and performance. However, in those cases where 84 | one must be sacrificed for the other, **we are willing to sacrifice elegance in 85 | favor of performance.** A good example of this is how saxes handles the 86 | normalization of end-of-line (EOL) characters to newlines (NL). In September 87 | 2019 I (lddubeau) discovered that saxes was not doing the normalization 88 | correctly. I came up with two fixes: 89 | 90 | 1. One fix modified the ``write`` method to split chunks on all EOL characters 91 | except NL, and would normalize those characters to NL early on. It would also 92 | adjust positioning data to handle skipping ``\r`` in the ``\r\n`` sequence, 93 | etc. It performed excellently with files containing only ``\n`` but it 94 | performed terribly with files that needed normalization. **However**, a file 95 | with ``\n`` as EOL would process half as fast if its EOL characters were 96 | replaced with ``\r\n`` prior to parsing. The performance for files using 97 | anything else than ``\n`` for EOL was atrocious. 98 | 99 | This approach was the more elegant of the two approaches mentioned here 100 | because it made all the logic normalizing EOL characters localized to one 101 | spot in the code. It was also the least error-prone approach, for the same 102 | reason. Adding a new ``captureSomething`` method would not run the risk of 103 | forgetting to handle EOL characters properly. 104 | 105 | 2. Another fix took the approach of recording state while running ``getCode`` 106 | and using this state in all places where substrings are extracted from chunks 107 | to handle the presence of EOL characters. This fix performs about as fast as 108 | the other fix described above for files that contain ``\n`` as EOL, and maybe 109 | 10-20% slower when a file contains ``\r\n``. 110 | 111 | This approach is definitely not as elegant as the first. It spreads the logic 112 | for handling EOL into multiple locations, and there's a risk of forgetting to 113 | add the proper logic when adding a new ``captureSomething`` method. 114 | 115 | Of the two approaches, the 2nd one was the one selected for posterity. Though 116 | the first method was more elegant, its performance was unacceptable. 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The ISC License 2 | 3 | Copyright (c) Contributors 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 15 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | ==== 18 | 19 | The following license is the one that governed sax, from which saxes 20 | was forked. Isaac Schlueter is not *directly* involved with saxes so 21 | don't go bugging him for saxes issues. 22 | 23 | The ISC License 24 | 25 | Copyright (c) Isaac Z. Schlueter and Contributors 26 | 27 | Permission to use, copy, modify, and/or distribute this software for any 28 | purpose with or without fee is hereby granted, provided that the above 29 | copyright notice and this permission notice appear in all copies. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 32 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 33 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 34 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 35 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 36 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 37 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 38 | 39 | ==== 40 | 41 | `String.fromCodePoint` by Mathias Bynens is no longer used, but it can 42 | still be found in old commits. It was once used according to terms of 43 | MIT License, as follows: 44 | 45 | Copyright Mathias Bynens 46 | 47 | Permission is hereby granted, free of charge, to any person obtaining 48 | a copy of this software and associated documentation files (the 49 | "Software"), to deal in the Software without restriction, including 50 | without limitation the rights to use, copy, modify, merge, publish, 51 | distribute, sublicense, and/or sell copies of the Software, and to 52 | permit persons to whom the Software is furnished to do so, subject to 53 | the following conditions: 54 | 55 | The above copyright notice and this permission notice shall be 56 | included in all copies or substantial portions of the Software. 57 | 58 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 59 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 60 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 61 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 62 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 63 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 64 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # saxes 2 | 3 | A sax-style non-validating parser for XML. 4 | 5 | Saxes is a fork of [sax](https://github.com/isaacs/sax-js) 1.2.4. All mentions 6 | of sax in this project's documentation are references to sax 1.2.4. 7 | 8 | Designed with [node](http://nodejs.org/) in mind, but should work fine in the 9 | browser or other CommonJS implementations. 10 | 11 | Saxes does not support Node versions older than 10. 12 | 13 | ## Notable Differences from Sax. 14 | 15 | * Saxes aims to be much stricter than sax with regards to XML 16 | well-formedness. Sax, even in its so-called "strict mode", is not strict. It 17 | silently accepts structures that are not well-formed XML. Projects that need 18 | better compliance with well-formedness constraints cannot use sax as-is. 19 | 20 | Consequently, saxes does not support HTML, or pseudo-XML, or bad XML. Saxes 21 | will report well-formedness errors in all these cases but it won't try to 22 | extract data from malformed documents like sax does. 23 | 24 | * Saxes is much much faster than sax, mostly because of a substantial redesign 25 | of the internal parsing logic. The speed improvement is not merely due to 26 | removing features that were supported by sax. That helped a bit, but saxes 27 | adds some expensive checks in its aim for conformance with the XML 28 | specification. Redesigning the parsing logic is what accounts for most of the 29 | performance improvement. 30 | 31 | * Saxes does not aim to support antiquated platforms. We will not pollute the 32 | source or the default build with support for antiquated platforms. If you want 33 | support for IE 11, you are welcome to produce a PR that adds a *new build* 34 | transpiled to ES5. 35 | 36 | * Saxes handles errors differently from sax: it provides a default onerror 37 | handler which throws. You can replace it with your own handler if you want. If 38 | your handler does nothing, there is no `resume` method to call. 39 | 40 | * There's no `Stream` API. A revamped API may be introduced later. (It is still 41 | a "streaming parser" in the general sense that you write a character stream to 42 | it.) 43 | 44 | * Saxes does not have facilities for limiting the size the data chunks passed to 45 | event handlers. See the FAQ entry for more details. 46 | 47 | ## Conformance 48 | 49 | Saxes supports: 50 | 51 | * [XML 1.0 fifth edition](https://www.w3.org/TR/2008/REC-xml-20081126/) 52 | * [XML 1.1 second edition](https://www.w3.org/TR/2006/REC-xml11-20060816/) 53 | * [Namespaces in XML 1.0 (Third Edition)](https://www.w3.org/TR/2009/REC-xml-names-20091208/). 54 | * [Namespaces in XML 1.1 (Second Edition)](https://www.w3.org/TR/2006/REC-xml-names11-20060816/). 55 | 56 | ## Limitations 57 | 58 | This is a non-validating parser so it only verifies whether the document is 59 | well-formed. We do aim to raise errors for all malformed constructs 60 | encountered. However, this parser does not thorougly parse the contents of 61 | DTDs. So most malformedness errors caused by errors **in DTDs** cannot be 62 | reported. 63 | 64 | ## Regarding `Hello, world!').close(); 110 | ``` 111 | 112 | ### Constructor Arguments 113 | 114 | Settings supported: 115 | 116 | * `xmlns` - Boolean. If `true`, then namespaces are supported. Default 117 | is `false`. 118 | 119 | * `position` - Boolean. If `false`, then don't track line/col/position. Unset is 120 | treated as `true`. Default is unset. Currently, setting this to `false` only 121 | results in a cosmetic change: the errors reported do not contain position 122 | information. sax-js would literally turn off the position-computing logic if 123 | this flag was set to false. The notion was that it would optimize 124 | execution. In saxes at least it turns out that continually testing this flag 125 | causes a cost that offsets the benefits of turning off this logic. 126 | 127 | * `fileName` - String. Set a file name for error reporting. This is useful only 128 | when tracking positions. You may leave it unset. 129 | 130 | * `fragment` - Boolean. If `true`, parse the XML as an XML fragment. Default is 131 | `false`. 132 | 133 | * `additionalNamespaces` - A plain object whose key, value pairs define 134 | namespaces known before parsing the XML file. It is not legal to pass 135 | bindings for the namespaces `"xml"` or `"xmlns"`. 136 | 137 | * `defaultXMLVersion` - The default version of the XML specification to use if 138 | the document contains no XML declaration. If the document does contain an XML 139 | declaration, then this setting is ignored. Must be `"1.0"` or `"1.1"`. The 140 | default is `"1.0"`. 141 | 142 | * `forceXMLVersion` - Boolean. A flag indicating whether to force the XML 143 | version used for parsing to the value of ``defaultXMLVersion``. When this flag 144 | is ``true``, ``defaultXMLVersion`` must be specified. If unspecified, the 145 | default value of this flag is ``false``. 146 | 147 | Example: suppose you are parsing a document that has an XML declaration 148 | specifying XML version 1.1. 149 | 150 | If you set ``defaultXMLVersion`` to ``"1.0"`` without setting 151 | ``forceXMLVersion`` then the XML declaration will override the value of 152 | ``defaultXMLVersion`` and the document will be parsed according to XML 1.1. 153 | 154 | If you set ``defaultXMLVersion`` to ``"1.0"`` and set ``forceXMLVersion`` to 155 | ``true``, then the XML declaration will be ignored and the document will be 156 | parsed according to XML 1.0. 157 | 158 | ### Methods 159 | 160 | `write` - Write bytes onto the stream. You don't have to pass the whole document 161 | in one `write` call. You can read your source chunk by chunk and call `write` 162 | with each chunk. 163 | 164 | `close` - Close the stream. Once closed, no more data may be written until it is 165 | done processing the buffer, which is signaled by the `end` event. 166 | 167 | ### Properties 168 | 169 | The parser has the following properties: 170 | 171 | `line`, `column`, `columnIndex`, `position` - Indications of the position in the 172 | XML document where the parser currently is looking. The `columnIndex` property 173 | counts columns as if indexing into a JavaScript string, whereas the `column` 174 | property counts Unicode characters. 175 | 176 | `closed` - Boolean indicating whether or not the parser can be written to. If 177 | it's `true`, then wait for the `ready` event to write again. 178 | 179 | `opt` - Any options passed into the constructor. 180 | 181 | `xmlDecl` - The XML declaration for this document. It contains the fields 182 | `version`, `encoding` and `standalone`. They are all `undefined` before 183 | encountering the XML declaration. If they are undefined after the XML 184 | declaration, the corresponding value was not set by the declaration. There is no 185 | event associated with the XML declaration. In a well-formed document, the XML 186 | declaration may be preceded only by an optional BOM. So by the time any event 187 | generated by the parser happens, the declaration has been processed if present 188 | at all. Otherwise, you have a malformed document, and as stated above, you 189 | cannot rely on the parser data! 190 | 191 | ### Error Handling 192 | 193 | The parser continues to parse even upon encountering errors, and does its best 194 | to continue reporting errors. You should heed all errors reported. After an 195 | error, however, saxes may interpret your document incorrectly. For instance 196 | ```` is invalid XML. Did you mean to have ```` or 197 | ```` or some other variation? For the sake of continuing to 198 | provide errors, saxes will continue parsing the document, but the structure it 199 | reports may be incorrect. It is only after the errors are fixed in the document 200 | that saxes can provide a reliable interpretation of the document. 201 | 202 | That leaves you with two rules of thumb when using saxes: 203 | 204 | * Pay attention to the errors that saxes report. The default `onerror` handler 205 | throws, so by default, you cannot miss errors. 206 | 207 | * **ONCE AN ERROR HAS BEEN ENCOUNTERED, STOP RELYING ON THE EVENT HANDLERS OTHER 208 | THAN `onerror`.** As explained above, when saxes runs into a well-formedness 209 | problem, it makes a guess in order to continue reporting more errors. The guess 210 | may be wrong. 211 | 212 | ### Events 213 | 214 | To listen to an event, override `on`. The list of supported events 215 | are also in the exported `EVENTS` array. 216 | 217 | See the JSDOC comments in the source code for a description of each supported 218 | event. 219 | 220 | ### Parsing XML Fragments 221 | 222 | The XML specification does not define any method by which to parse XML 223 | fragments. However, there are usage scenarios in which it is desirable to parse 224 | fragments. In order to allow this, saxes provides three initialization options. 225 | 226 | If you pass the option `fragment: true` to the parser constructor, the parser 227 | will expect an XML fragment. It essentially starts with a parsing state 228 | equivalent to the one it would be in if `parser.write(")` had been called 229 | right after initialization. In other words, it expects content which is 230 | acceptable inside an element. This also turns off well-formedness checks that 231 | are inappropriate when parsing a fragment. 232 | 233 | The option `additionalNamespaces` allows you to define additional prefix-to-URI 234 | bindings known before parsing starts. You would use this over `resolvePrefix` if 235 | you have at the ready a series of namespaces bindings to use. 236 | 237 | The option `resolvePrefix` allows you to pass a function which saxes will use if 238 | it is unable to resolve a namespace prefix by itself. You would use this over 239 | `additionalNamespaces` in a context where getting a complete list of defined 240 | namespaces is onerous. 241 | 242 | Note that you can use `additionalNamespaces` and `resolvePrefix` together if you 243 | want. `additionalNamespaces` applies before `resolvePrefix`. 244 | 245 | The options `additionalNamespaces` and `resolvePrefix` are really meant to be 246 | used for parsing fragments. However, saxes won't prevent you from using them 247 | with `fragment: false`. Note that if you do this, your document may parse 248 | without errors and yet be malformed because the document can refer to namespaces 249 | which are not defined *in* the document. 250 | 251 | Of course, `additionalNamespaces` and `resolvePrefix` are used only if `xmlns` 252 | is `true`. If you are parsing a fragment that does not use namespaces, there's 253 | no point in setting these options. 254 | 255 | ### Performance Tips 256 | 257 | * saxes works faster on files that use newlines (``\u000A``) as end of line 258 | markers than files that use other end of line markers (like ``\r`` or 259 | ``\r\n``). The XML specification requires that conformant applications behave 260 | as if all characters that are to be treated as end of line characters are 261 | converted to ``\u000A`` prior to parsing. The optimal code path for saxes is a 262 | file in which all end of line characters are already ``\u000A``. 263 | 264 | * Don't split Unicode strings you feed to saxes across surrogates. When you 265 | naively split a string in JavaScript, you run the risk of splitting a Unicode 266 | character into two surrogates. e.g. In the following example ``a`` and ``b`` 267 | each contain half of a single Unicode character: ``const a = "\u{1F4A9}"[0]; 268 | const b = "\u{1F4A9}"[1]`` If you feed such split surrogates to versions of 269 | saxes prior to 4, you'd get errors. Saxes version 4 and over are able to 270 | detect when a chunk of data ends with a surrogate and carry over the surrogate 271 | to the next chunk. However this operation entails slicing and concatenating 272 | strings. If you can feed your data in a way that does not split surrogates, 273 | you should do it. (Obviously, feeding all the data at once with a single write 274 | is fastest.) 275 | 276 | * Don't set event handlers you don't need. Saxes has always aimed to avoid doing 277 | work that will just be tossed away but future improvements hope to do this 278 | more aggressively. One way saxes knows whether or not some data is needed is 279 | by checking whether a handler has been set for a specific event. 280 | 281 | ## FAQ 282 | 283 | Q. Why has saxes dropped support for limiting the size of data chunks passed to 284 | event handlers? 285 | 286 | A. With sax you could set ``MAX_BUFFER_LENGTH`` to cause the parser to limit the 287 | size of data chunks passed to event handlers. So if you ran into a span of text 288 | above the limit, multiple ``text`` events with smaller data chunks were fired 289 | instead of a single event with a large chunk. 290 | 291 | However, that functionality had some problematic characteristics. It had an 292 | arbitrary default value. It was library-wide so all parsers created from a 293 | single instance of the ``sax`` library shared it. This could potentially cause 294 | conflicts among libraries running in the same VM but using sax for different 295 | purposes. 296 | 297 | These issues could have been easily fixed, but there were larger issues. The 298 | buffer limit arbitrarily applied to some events but not others. It would split 299 | ``text``, ``cdata`` and ``script`` events. However, if a ``comment``, 300 | ``doctype``, ``attribute`` or ``processing instruction`` were more than the 301 | limit, the parser would generate an error and you were left picking up the 302 | pieces. 303 | 304 | It was not intuitive to use. You'd think setting the limit to 1K would prevent 305 | chunks bigger than 1K to be passed to event handlers. But that was not the 306 | case. A comment in the source code told you that you might go over the limit if 307 | you passed large chunks to ``write``. So if you want a 1K limit, don't pass 64K 308 | chunks to ``write``. Fair enough. You know what limit you want so you can 309 | control the size of the data you pass to ``write``. So you limit the chunks to 310 | ``write`` to 1K at a time. Even if you do this, your event handlers may get data 311 | chunks that are 2K in size. Suppose on the previous ``write`` the parser has 312 | just finished processing an open tag, so it is ready for text. Your ``write`` 313 | passes 1K of text. You are not above the limit yet, so no event is generated 314 | yet. The next ``write`` passes another 1K of text. It so happens that sax checks 315 | buffer limits only once per ``write``, after the chunk of data has been 316 | processed. Now you've hit the limit and you get a ``text`` event with 2K of 317 | data. So even if you limit your ``write`` calls to the buffer limit you've set, 318 | you may still get events with chunks at twice the buffer size limit you've 319 | specified. 320 | 321 | We may consider reinstating an equivalent functionality, provided that it 322 | addresses the issues above and does not cause a huge performance drop for 323 | use-case scenarios that don't need it. 324 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | extends: ["@commitlint/config-angular"], 5 | }; 6 | -------------------------------------------------------------------------------- /examples/dump-events.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* eslint-disable no-console */ 4 | 5 | const fs = require("fs"); 6 | const saxes = require("../build/dist/saxes"); 7 | 8 | const xml = fs.readFileSync(process.argv[2]); 9 | const start = Date.now(); 10 | const parser = new saxes.SaxesParser({ xmlns: true }); 11 | 12 | for (const ev of saxes.EVENTS) { 13 | parser.on(ev, console.log.bind(console.log, ev)); 14 | } 15 | 16 | parser.on("error", err => { 17 | console.error(err); 18 | }); 19 | 20 | parser.write(xml); 21 | parser.close(); 22 | console.log(`Parsing time: ${Date.now() - start}`); 23 | -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* eslint-disable no-console */ 4 | 5 | const fs = require("fs"); 6 | const path = require("path"); 7 | const saxes = require("../build/dist/saxes"); 8 | 9 | const parser = new saxes.SaxesParser(); 10 | 11 | const inspector = ev => function handler(data) { 12 | console.error("%s %s %j", `${parser.line}:${parser.column}`, ev, data); 13 | }; 14 | 15 | saxes.EVENTS.forEach(ev => { 16 | parser.on(ev, inspector(ev)); 17 | }); 18 | 19 | parser.on("end", () => { 20 | console.error("end"); 21 | console.error(parser); 22 | }); 23 | 24 | let xml = fs.readFileSync(path.join(__dirname, "test.xml"), "utf8"); 25 | function processChunk() { 26 | if (xml) { 27 | const c = Math.ceil(Math.random() * 1000); 28 | parser.write(xml.substr(0, c)); 29 | xml = xml.substr(c); 30 | process.nextTick(processChunk); 31 | } 32 | else { 33 | parser.close(); 34 | } 35 | } 36 | 37 | processChunk(); 38 | -------------------------------------------------------------------------------- /examples/null-parser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* eslint-disable no-console */ 4 | 5 | const fs = require("fs"); 6 | const saxes = require("../build/dist/saxes"); 7 | 8 | const { argv } = process; 9 | let bs; 10 | const first = argv[2]; 11 | let filePath = first; 12 | if (first.startsWith("--bs=")) { 13 | bs = Number(first.substring(first.indexOf("=") + 1)); 14 | if (Number.isNaN(bs)) { 15 | throw new Error("bs is not a number"); 16 | } 17 | // eslint-disable-next-line prefer-destructuring 18 | filePath = argv[3]; 19 | } 20 | 21 | if (bs === undefined) { 22 | const xml = fs.readFileSync(filePath); 23 | const start = Date.now(); 24 | const parser = new saxes.SaxesParser({ xmlns: true }); 25 | parser.on("error", err => { 26 | console.error(err); 27 | }); 28 | 29 | parser.write(xml); 30 | parser.close(); 31 | console.log(`Parsing time: ${Date.now() - start}`); 32 | } 33 | else { 34 | const input = fs.createReadStream(filePath); 35 | const start = Date.now(); 36 | const parser = new saxes.SaxesParser({ xmlns: true }); 37 | parser.on("error", err => { 38 | console.error(err); 39 | }); 40 | 41 | input.on("readable", () => { 42 | // eslint-disable-next-line no-constant-condition 43 | while (true) { 44 | const chunk = input.read(bs); 45 | if (chunk === null) { 46 | return; 47 | } 48 | 49 | parser.write(chunk); 50 | } 51 | }); 52 | 53 | input.on("end", () => { 54 | parser.close(); 55 | console.log(`Parsing time: ${Date.now() - start}`); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "saxes", 3 | "private": true, 4 | "description": "An evented streaming XML parser in JavaScript", 5 | "author": "Louis-Dominique Dubeau ", 6 | "version": "6.0.0", 7 | "main": "saxes.js", 8 | "types": "saxes.d.ts", 9 | "license": "ISC", 10 | "engines": { 11 | "node": ">=v12.22.12" 12 | }, 13 | "scripts": { 14 | "tsc": "tsc", 15 | "copy": "cp -p README.md build/dist && sed -e'/\"private\": true/d' package.json > build/dist/package.json", 16 | "build": "npm run tsc && npm run copy", 17 | "test": "npm run build && mocha --delay", 18 | "lint": "eslint --ignore-path .gitignore '**/*.ts' '**/*.js'", 19 | "lint-fix": "npm run lint -- --fix", 20 | "posttest": "npm run lint", 21 | "typedoc": "typedoc --tsconfig tsconfig.json --name saxes --out build/docs/ --listInvalidSymbolLinks --excludePrivate --excludeNotExported", 22 | "build-docs": "npm run typedoc", 23 | "gh-pages": "npm run build-docs && mkdir -p build && (cd build; rm -rf gh-pages; git clone .. --branch gh-pages gh-pages) && mkdir -p build/gh-pages/latest && find build/gh-pages/latest -type f -delete && cp -rp build/docs/* build/gh-pages/latest && find build/gh-pages -type d -empty -delete", 24 | "self:publish": "cd build/dist && npm_config_tag=`simple-dist-tag` npm publish", 25 | "version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md", 26 | "postversion": "npm run test && npm run self:publish", 27 | "postpublish": "git push origin --follow-tags" 28 | }, 29 | "repository": "https://github.com/lddubeau/saxes.git", 30 | "devDependencies": { 31 | "@commitlint/cli": "^16.3.0", 32 | "@commitlint/config-angular": "^16.3.0", 33 | "@types/chai": "^4.3.16", 34 | "@types/mocha": "^9.1.1", 35 | "@types/node": "^16.18.104", 36 | "@typescript-eslint/eslint-plugin": "^5.62.0", 37 | "@typescript-eslint/eslint-plugin-tslint": "^5.62.0", 38 | "@typescript-eslint/parser": "^5.62.0", 39 | "@xml-conformance-suite/js": "^3.0.0", 40 | "@xml-conformance-suite/mocha": "^3.0.0", 41 | "@xml-conformance-suite/test-data": "^3.0.0", 42 | "chai": "^4.5.0", 43 | "conventional-changelog-cli": "^2.2.2", 44 | "eslint": "^8.57.0", 45 | "eslint-config-lddubeau-base": "^6.1.0", 46 | "eslint-config-lddubeau-ts": "^2.0.2", 47 | "eslint-import-resolver-typescript": "^2.7.1", 48 | "eslint-plugin-import": "^2.29.1", 49 | "eslint-plugin-jsx-a11y": "^6.9.0", 50 | "eslint-plugin-prefer-arrow": "^1.2.3", 51 | "eslint-plugin-react": "^7.35.0", 52 | "eslint-plugin-simple-import-sort": "^7.0.0", 53 | "husky": "^7.0.4", 54 | "mocha": "^9.2.2", 55 | "renovate-config-lddubeau": "^1.0.0", 56 | "simple-dist-tag": "^1.0.2", 57 | "ts-node": "^10.9.2", 58 | "tsd": "^0.22.0", 59 | "tslint": "^6.1.3", 60 | "tslint-microsoft-contrib": "^6.2.0", 61 | "typedoc": "^0.26.5", 62 | "typescript": "^4.9.5" 63 | }, 64 | "dependencies": { 65 | "xmlchars": "^2.2.0" 66 | }, 67 | "husky": { 68 | "hooks": { 69 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/attribute-name.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "attribute name", 5 | xml: "", 6 | expect: [ 7 | ["opentagstart", { name: "root", attributes: {}, ns: {} }], 8 | ["attribute", { 9 | name: "length", 10 | value: "12345", 11 | prefix: "", 12 | local: "length", 13 | }], 14 | [ 15 | "opentag", 16 | { 17 | name: "root", 18 | prefix: "", 19 | local: "root", 20 | uri: "", 21 | attributes: { 22 | length: { 23 | name: "length", 24 | value: "12345", 25 | prefix: "", 26 | local: "length", 27 | uri: "", 28 | }, 29 | }, 30 | ns: {}, 31 | isSelfClosing: false, 32 | }, 33 | ], 34 | [ 35 | "closetag", 36 | { 37 | name: "root", 38 | prefix: "", 39 | local: "root", 40 | uri: "", 41 | attributes: { 42 | length: { 43 | name: "length", 44 | value: "12345", 45 | prefix: "", 46 | local: "length", 47 | uri: "", 48 | }, 49 | }, 50 | ns: {}, 51 | isSelfClosing: false, 52 | }, 53 | ], 54 | ], 55 | opt: { 56 | xmlns: true, 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /test/attribute-no-space.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | // should give an error, but still parse 4 | test({ 5 | name: "attributes without a space", 6 | xml: "", 7 | expect: [ 8 | ["opentagstart", { name: "root", attributes: {} }], 9 | ["attribute", { 10 | name: "attr1", 11 | value: "first", 12 | }], 13 | ["error", "1:20: no whitespace between attributes."], 14 | ["attribute", { 15 | name: "attr2", 16 | value: "second", 17 | }], 18 | ["opentag", { 19 | name: "root", 20 | attributes: { 21 | attr1: "first", 22 | attr2: "second", 23 | }, 24 | isSelfClosing: true, 25 | }], 26 | ["closetag", { 27 | name: "root", 28 | attributes: { 29 | attr1: "first", 30 | attr2: "second", 31 | }, 32 | isSelfClosing: true, 33 | }], 34 | ], 35 | }); 36 | 37 | // other cases should still pass 38 | test({ 39 | name: "attributes separated by a space", 40 | xml: "", 41 | expect: [ 42 | ["opentagstart", { 43 | name: "root", 44 | attributes: {}, 45 | }], 46 | ["attribute", { 47 | name: "attr1", 48 | value: "first", 49 | }], 50 | ["attribute", { 51 | name: "attr2", 52 | value: "second", 53 | }], 54 | ["opentag", { 55 | name: "root", 56 | attributes: { 57 | attr1: "first", 58 | attr2: "second", 59 | }, 60 | isSelfClosing: true, 61 | }], 62 | ["closetag", { 63 | name: "root", 64 | attributes: { 65 | attr1: "first", 66 | attr2: "second", 67 | }, 68 | isSelfClosing: true, 69 | }], 70 | ], 71 | }); 72 | 73 | // other cases should still pass 74 | test({ 75 | name: "attributes separated by a newline", 76 | xml: "", 77 | expect: [ 78 | ["opentagstart", { 79 | name: "root", 80 | attributes: {}, 81 | }], 82 | ["attribute", { 83 | name: "attr1", 84 | value: "first", 85 | }], 86 | ["attribute", { 87 | name: "attr2", 88 | value: "second", 89 | }], 90 | ["opentag", { 91 | name: "root", 92 | attributes: { 93 | attr1: "first", 94 | attr2: "second", 95 | }, 96 | isSelfClosing: true, 97 | }], 98 | ["closetag", { 99 | name: "root", 100 | attributes: { 101 | attr1: "first", 102 | attr2: "second", 103 | }, 104 | isSelfClosing: true, 105 | }], 106 | ], 107 | }); 108 | 109 | // other cases should still pass 110 | test({ 111 | name: "attributes separated by spaces", 112 | xml: "", 113 | expect: [ 114 | ["opentagstart", { 115 | name: "root", 116 | attributes: {}, 117 | }], 118 | ["attribute", { 119 | name: "attr1", 120 | value: "first", 121 | }], 122 | ["attribute", { 123 | name: "attr2", 124 | value: "second", 125 | }], 126 | ["opentag", { 127 | name: "root", 128 | attributes: { 129 | attr1: "first", 130 | attr2: "second", 131 | }, 132 | isSelfClosing: true, 133 | }], 134 | ["closetag", { 135 | name: "root", 136 | attributes: { 137 | attr1: "first", 138 | attr2: "second", 139 | }, 140 | isSelfClosing: true, 141 | }], 142 | ], 143 | }); 144 | -------------------------------------------------------------------------------- /test/attribute-normalization.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "attribute normalization", 5 | // \n\r is converted to \n\n internally. The reverse would be converted to 6 | // only \n. 7 | // eslint-disable-next-line @typescript-eslint/quotes 8 | xml: ``, 9 | expect: [ 10 | ["opentagstart", { 11 | name: "root", 12 | attributes: {}, 13 | }], 14 | ["attribute", { 15 | name: "attr1", 16 | value: "\r \n\t", 17 | }], 18 | ["attribute", { 19 | name: "attr2", 20 | value: " a b", 21 | }], 22 | ["opentag", { 23 | name: "root", 24 | attributes: { attr1: "\r \n\t", attr2: " a b" }, 25 | isSelfClosing: true, 26 | }], 27 | ["closetag", { 28 | name: "root", 29 | attributes: { attr1: "\r \n\t", attr2: " a b" }, 30 | isSelfClosing: true, 31 | }], 32 | ], 33 | opt: { }, 34 | }); 35 | -------------------------------------------------------------------------------- /test/attribute-unquoted.ts: -------------------------------------------------------------------------------- 1 | import { SaxesParser } from "../build/dist/saxes"; 2 | import { test } from "./testutil"; 3 | 4 | test({ 5 | name: "attribute unquoted", 6 | expect: [ 7 | ["opentagstart", { name: "root", attributes: {}, ns: {} }], 8 | ["error", "1:14: unquoted attribute value."], 9 | ["attribute", { 10 | name: "length", 11 | value: "12345", 12 | prefix: "", 13 | local: "length", 14 | }], 15 | ["opentag", { 16 | name: "root", 17 | attributes: { 18 | length: { 19 | name: "length", 20 | value: "12345", 21 | prefix: "", 22 | local: "length", 23 | uri: "", 24 | }, 25 | }, 26 | ns: {}, 27 | prefix: "", 28 | local: "root", 29 | uri: "", 30 | isSelfClosing: false, 31 | }], 32 | ["closetag", { 33 | name: "root", 34 | attributes: { 35 | length: { 36 | name: "length", 37 | value: "12345", 38 | prefix: "", 39 | local: "length", 40 | uri: "", 41 | }, 42 | }, 43 | ns: {}, 44 | prefix: "", 45 | local: "root", 46 | uri: "", 47 | isSelfClosing: false, 48 | }], 49 | ["end", undefined], 50 | ["ready", undefined], 51 | ], 52 | opt: { 53 | xmlns: true, 54 | }, 55 | fn(parser: SaxesParser): void { 56 | parser.write("").close(); 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /test/bad-entities.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "empty entity", 5 | xml: "&;", 6 | expect: [ 7 | ["opentagstart", { name: "r", attributes: {} }], 8 | ["opentag", { name: "r", attributes: {}, isSelfClosing: false }], 9 | ["error", "1:5: empty entity name."], 10 | ["text", "&;"], 11 | ["closetag", { name: "r", attributes: {}, isSelfClosing: false }], 12 | ], 13 | }); 14 | 15 | test({ 16 | name: "empty decimal entity", 17 | xml: "&#;", 18 | expect: [ 19 | ["opentagstart", { name: "r", attributes: {} }], 20 | ["opentag", { name: "r", attributes: {}, isSelfClosing: false }], 21 | ["error", "1:6: malformed character entity."], 22 | ["text", "&#;"], 23 | ["closetag", { name: "r", attributes: {}, isSelfClosing: false }], 24 | ], 25 | }); 26 | 27 | test({ 28 | name: "empty hex entity", 29 | xml: "&#x;", 30 | expect: [ 31 | ["opentagstart", { name: "r", attributes: {} }], 32 | ["opentag", { name: "r", attributes: {}, isSelfClosing: false }], 33 | ["error", "1:7: malformed character entity."], 34 | ["text", "&#x;"], 35 | ["closetag", { name: "r", attributes: {}, isSelfClosing: false }], 36 | ], 37 | }); 38 | -------------------------------------------------------------------------------- /test/bom.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | // BOM at the very begining of the stream should be ignored 4 | test({ 5 | name: "BOM at start", 6 | xml: "\uFEFF

", 7 | expect: [ 8 | ["opentagstart", { name: "P", attributes: {} }], 9 | ["opentag", { name: "P", attributes: {}, isSelfClosing: false }], 10 | ["closetag", { name: "P", attributes: {}, isSelfClosing: false }], 11 | ], 12 | }); 13 | 14 | // In all other places it should be consumed 15 | test({ 16 | name: "BOM in contents", 17 | xml: "\uFEFF

\uFEFFStarts and ends with BOM\uFEFF

", 18 | expect: [ 19 | ["opentagstart", { name: "P", attributes: {} }], 20 | ["attribute", { name: "BOM", value: "\uFEFF" }], 21 | ["opentag", { 22 | name: "P", 23 | attributes: { BOM: "\uFEFF" }, 24 | isSelfClosing: false, 25 | }], 26 | ["text", "\uFEFFStarts and ends with BOM\uFEFF"], 27 | ["closetag", { 28 | name: "P", 29 | attributes: { BOM: "\uFEFF" }, 30 | isSelfClosing: false, 31 | }], 32 | ], 33 | }); 34 | 35 | // BOM after a whitespace is an error 36 | test({ 37 | name: "BOM outside of root, but not initial", 38 | xml: " \uFEFF

", 39 | expect: [ 40 | ["text", "\uFEFF"], 41 | ["error", "1:3: text data outside of root node."], 42 | ["opentagstart", { name: "P", attributes: {} }], 43 | ["opentag", { name: "P", attributes: {}, isSelfClosing: false }], 44 | ["closetag", { name: "P", attributes: {}, isSelfClosing: false }], 45 | ], 46 | }); 47 | 48 | // There is only one BOM allowed at the start 49 | test({ 50 | name: "multiple BOMs", 51 | xml: "\uFEFF\uFEFF

", 52 | expect: [ 53 | ["text", "\uFEFF"], 54 | ["error", "1:3: text data outside of root node."], 55 | ["opentagstart", { name: "P", attributes: {} }], 56 | ["opentag", { name: "P", attributes: {}, isSelfClosing: false }], 57 | ["closetag", { name: "P", attributes: {}, isSelfClosing: false }], 58 | ], 59 | }); 60 | 61 | // There is only one BOM allowed at the start 62 | test({ 63 | name: "multiple BOMs (multiple chunks)", 64 | xml: ["\uFEFF", "\uFEFF

"], 65 | expect: [ 66 | ["text", "\uFEFF"], 67 | ["error", "1:3: text data outside of root node."], 68 | ["opentagstart", { name: "P", attributes: {} }], 69 | ["opentag", { name: "P", attributes: {}, isSelfClosing: false }], 70 | ["closetag", { name: "P", attributes: {}, isSelfClosing: false }], 71 | ], 72 | }); 73 | -------------------------------------------------------------------------------- /test/cdata-chunked.ts: -------------------------------------------------------------------------------- 1 | import { SaxesParser } from "../build/dist/saxes"; 2 | import { test } from "./testutil"; 3 | 4 | test({ 5 | name: "cdata chunked", 6 | expect: [ 7 | ["opentagstart", { name: "r", attributes: {} }], 8 | ["opentag", { name: "r", attributes: {}, isSelfClosing: false }], 9 | ["cdata", " this is character data  "], 10 | ["closetag", { name: "r", attributes: {}, isSelfClosing: false }], 11 | ], 12 | fn(parser: SaxesParser): void { 13 | parser.write("") 15 | .close(); 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /test/cdata-end-split.ts: -------------------------------------------------------------------------------- 1 | import { SaxesParser } from "../build/dist/saxes"; 2 | import { test } from "./testutil"; 3 | 4 | test({ 5 | name: "cadat end split", 6 | expect: [ 7 | ["opentagstart", { name: "r", attributes: {} }], 8 | ["opentag", { name: "r", attributes: {}, isSelfClosing: false }], 9 | ["cdata", " this is "], 10 | ["closetag", { name: "r", attributes: {}, isSelfClosing: false }], 11 | ], 12 | fn(parser: SaxesParser): void { 13 | parser.write("") 15 | .write("") 16 | .close(); 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /test/cdata-fake-end.ts: -------------------------------------------------------------------------------- 1 | import { SaxesParser } from "../build/dist/saxes"; 2 | import { test } from "./testutil"; 3 | 4 | test({ 5 | name: "cdata fake end", 6 | expect: [ 7 | ["opentagstart", { name: "r", attributes: {} }], 8 | ["opentag", { name: "r", attributes: {}, isSelfClosing: false }], 9 | ["cdata", "[[[[[[[[]]]]]]]]"], 10 | ["closetag", { name: "r", attributes: {}, isSelfClosing: false }], 11 | ], 12 | fn(parser: SaxesParser): void { 13 | const x = ""; 14 | for (let i = 0; i < x.length; i++) { 15 | parser.write(x.charAt(i)); 16 | } 17 | parser.close(); 18 | }, 19 | }); 20 | 21 | test({ 22 | name: "cdata fake end 2", 23 | expect: [ 24 | ["opentagstart", { name: "r", attributes: {} }], 25 | ["opentag", { name: "r", attributes: {}, isSelfClosing: false }], 26 | ["cdata", "[[[[[[[[]]]]]]]]"], 27 | ["closetag", { name: "r", attributes: {}, isSelfClosing: false }], 28 | ], 29 | xml: "", 30 | }); 31 | -------------------------------------------------------------------------------- /test/cdata-multiple.ts: -------------------------------------------------------------------------------- 1 | import { SaxesParser } from "../build/dist/saxes"; 2 | import { test } from "./testutil"; 3 | 4 | test({ 5 | name: "cdata multiple", 6 | expect: [ 7 | ["opentagstart", { name: "r", attributes: {} }], 8 | ["opentag", { name: "r", attributes: {}, isSelfClosing: false }], 9 | ["cdata", " this is "], 10 | ["cdata", "character data  "], 11 | ["closetag", { name: "r", attributes: {}, isSelfClosing: false }], 12 | ], 13 | fn(parser: SaxesParser): void { 14 | parser.write("").write("") 19 | .close(); 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /test/cdata.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "cdata", 5 | xml: "", 6 | expect: [ 7 | ["opentagstart", { name: "r", attributes: {} }], 8 | ["opentag", { name: "r", attributes: {}, isSelfClosing: false }], 9 | ["cdata", " this is character data  "], 10 | ["cdata", ""], 11 | ["closetag", { name: "r", attributes: {}, isSelfClosing: false }], 12 | ], 13 | }); 14 | 15 | test({ 16 | name: "cdata end in attribute", 17 | expect: [ 18 | ["opentagstart", { name: "r", attributes: {} }], 19 | ["attribute", { name: "foo", value: "]]>" }], 20 | ["opentag", { 21 | name: "r", 22 | attributes: { 23 | foo: "]]>", 24 | }, 25 | isSelfClosing: true, 26 | }], 27 | ["closetag", { 28 | name: "r", 29 | attributes: { 30 | foo: "]]>", 31 | }, 32 | isSelfClosing: true, 33 | }], 34 | ], 35 | xml: "", 36 | }); 37 | 38 | test({ 39 | name: "surrounded by whitespace", 40 | expect: [ 41 | ["opentagstart", { name: "content:encoded", attributes: {} }], 42 | ["opentag", { 43 | name: "content:encoded", 44 | attributes: {}, 45 | isSelfClosing: false, 46 | }], 47 | ["text", "\n "], 48 | ["cdata", "spacetime is four dimensional"], 49 | ["text", "\n "], 50 | ["closetag", { 51 | name: "content:encoded", 52 | attributes: {}, 53 | isSelfClosing: false, 54 | }], 55 | ], 56 | xml: ` 57 | 58 | `, 59 | }); 60 | -------------------------------------------------------------------------------- /test/close-without-open.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "close a tag that was not opened", 5 | xml: "
", 6 | expect: [ 7 | ["error", "1:7: unmatched closing tag: root."], 8 | ["error", "1:7: document must contain a root element."], 9 | ["text", "
"], 10 | ], 11 | opt: { 12 | xmlns: true, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /test/comment.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "comment", 5 | xml: "", 6 | expect: [ 7 | ["opentagstart", { name: "r", attributes: {} }], 8 | ["opentag", { name: "r", attributes: {}, isSelfClosing: false }], 9 | ["comment", "foo"], 10 | ["comment", ""], 11 | ["closetag", { name: "r", attributes: {}, isSelfClosing: false }], 12 | ], 13 | }); 14 | -------------------------------------------------------------------------------- /test/conformance-candidates.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | // These are tests that are candidates to be added to the XML conformance suite, 4 | // after confirmation that they really plug a hole in the suite. 5 | 6 | test({ 7 | name: "bad pi target starting character", 8 | // The first character of the processing instruction is wrong. Testing the 9 | // first character matters because the restrictions on this character are more 10 | // stringent than on later characters of the name. Code writers are liable to 11 | // forget that the first character needs special treatment. 12 | xml: "", 13 | expect: [ 14 | ["error", "1:3: disallowed character in processing instruction name."], 15 | ["processinginstruction", { target: "-abcde", body: "" }], 16 | ["opentagstart", { name: "root", attributes: {}, ns: {} }], 17 | [ 18 | "opentag", 19 | { 20 | name: "root", 21 | prefix: "", 22 | local: "root", 23 | uri: "", 24 | attributes: {}, 25 | ns: {}, 26 | isSelfClosing: true, 27 | }, 28 | ], 29 | [ 30 | "closetag", 31 | { 32 | name: "root", 33 | prefix: "", 34 | local: "root", 35 | uri: "", 36 | attributes: {}, 37 | ns: {}, 38 | isSelfClosing: true, 39 | }, 40 | ], 41 | ], 42 | opt: { 43 | xmlns: true, 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /test/conformance.ts: -------------------------------------------------------------------------------- 1 | import { BaseDriver } from "@xml-conformance-suite/js/drivers/base"; 2 | import { ResourceLoader } from "@xml-conformance-suite/js/resource-loader"; 3 | import { TestHandling } from "@xml-conformance-suite/js/selection"; 4 | import { BaseSelection } from "@xml-conformance-suite/js/selections/base"; 5 | import { loadTests } from "@xml-conformance-suite/js/test-parser"; 6 | import { TestSpec } from "@xml-conformance-suite/js/test-spec"; 7 | import { Test } from "@xml-conformance-suite/js/test-suite"; 8 | import { build } from "@xml-conformance-suite/mocha/builders/basic"; 9 | 10 | import { SaxesParser } from "../build/dist/saxes"; 11 | 12 | // 13 | // ENTITIES: this test requires parsing ENTITY declrations in DTDs, 14 | // which we don't support yet. 15 | // 16 | // DTD: this test requires reporting wf errors in a DTD, which we 17 | // don't support yet. 18 | // 19 | 20 | const SKIP: Record = { 21 | "not-wf-sa-056": "DTD", 22 | "not-wf-sa-078": "DTD", 23 | "not-wf-sa-079": "DTD", 24 | "not-wf-sa-080": "DTD", 25 | "not-wf-sa-084": "DTD", 26 | "not-wf-sa-113": "DTD", 27 | "not-wf-sa-114": "DTD", 28 | "not-wf-sa-121": "DTD", 29 | "not-wf-sa-128": "DTD", 30 | "not-wf-sa-149": "DTD", 31 | "not-wf-sa-160": "ENTITIES", 32 | "not-wf-sa-161": "ENTITIES", 33 | "not-wf-sa-162": "ENTITIES", 34 | "not-wf-sa-180": "ENTITIES", 35 | "valid-sa-023": "ENTITIES", 36 | "valid-sa-024": "ENTITIES", 37 | "valid-sa-053": "ENTITIES", 38 | "valid-sa-066": "ENTITIES", 39 | "valid-sa-085": "ENTITIES", 40 | "valid-sa-089": "ENTITIES", 41 | "valid-sa-108": "ENTITIES", 42 | "valid-sa-110": "ENTITIES", 43 | "valid-sa-114": "ENTITIES", 44 | "valid-sa-115": "ENTITIES", 45 | "sa02": "DTD", 46 | "pi": "DTD", 47 | "o-p43pass1": "ENTITIES", 48 | "o-p68pass1": "ENTITIES", 49 | "o-p09fail3": "DTD", 50 | "o-p12fail1": "DTD", 51 | "o-p12fail2": "DTD", 52 | "o-p12fail3": "DTD", 53 | "o-p12fail4": "DTD", 54 | "o-p12fail5": "DTD", 55 | "o-p12fail6": "DTD", 56 | "o-p12fail7": "DTD", 57 | "o-p29fail1": "DTD", 58 | "o-p69fail1": "DTD", 59 | "o-p69fail2": "DTD", 60 | "o-p69fail3": "DTD", 61 | "ibm-not-wf-P28-ibm28n01.xml": "DTD", 62 | "ibm-not-wf-P28-ibm28n02.xml": "DTD", 63 | "ibm-not-wf-P28-ibm28n03.xml": "DTD", 64 | "ibm-not-wf-P28-ibm28n04.xml": "DTD", 65 | "ibm-not-wf-P28-ibm28n05.xml": "DTD", 66 | "ibm-not-wf-P28-ibm28n06.xml": "DTD", 67 | "ibm-not-wf-P29-ibm29n01.xml": "DTD", 68 | "ibm-not-wf-P29-ibm29n02.xml": "DTD", 69 | "ibm-not-wf-P29-ibm29n03.xml": "DTD", 70 | "ibm-not-wf-P29-ibm29n04.xml": "DTD", 71 | "ibm-not-wf-P29-ibm29n05.xml": "DTD", 72 | "ibm-not-wf-P29-ibm29n06.xml": "DTD", 73 | "ibm-not-wf-P29-ibm29n07.xml": "DTD", 74 | "ibm-not-wf-P66-ibm66n01.xml": "DTD", 75 | "ibm-not-wf-P66-ibm66n03.xml": "DTD", 76 | "ibm-not-wf-P66-ibm66n05.xml": "DTD", 77 | "ibm-not-wf-P66-ibm66n07.xml": "DTD", 78 | "ibm-not-wf-P66-ibm66n09.xml": "DTD", 79 | "ibm-not-wf-P66-ibm66n11.xml": "DTD", 80 | "ibm-not-wf-P68-ibm68n07.xml": "DTD", 81 | "ibm-not-wf-P69-ibm69n01.xml": "DTD", 82 | "ibm-not-wf-P69-ibm69n02.xml": "DTD", 83 | "ibm-not-wf-P69-ibm69n03.xml": "DTD", 84 | "ibm-not-wf-P69-ibm69n04.xml": "DTD", 85 | "ibm-not-wf-P69-ibm69n05.xml": "DTD", 86 | "ibm-not-wf-P69-ibm69n06.xml": "DTD", 87 | "ibm-not-wf-P69-ibm69n07.xml": "DTD", 88 | "ibm-not-wf-P82-ibm82n03.xml": "DTD", 89 | "ibm-not-wf-P85-ibm85n01.xml": "DTD", 90 | "ibm-not-wf-P85-ibm85n02.xml": "DTD", 91 | "ibm-not-wf-P88-ibm88n01.xml": "DTD", 92 | "ibm-not-wf-P88-ibm88n02.xml": "DTD", 93 | "ibm-not-wf-P89-ibm89n01.xml": "DTD", 94 | "ibm-not-wf-P89-ibm89n02.xml": "DTD", 95 | "ibm-valid-P09-ibm09v01.xml": "ENTITIES", 96 | "ibm-valid-P09-ibm09v02.xml": "ENTITIES", 97 | "ibm-valid-P09-ibm09v04.xml": "ENTITIES", 98 | "ibm-valid-P10-ibm10v01.xml": "ENTITIES", 99 | "ibm-valid-P10-ibm10v02.xml": "ENTITIES", 100 | "ibm-valid-P10-ibm10v03.xml": "ENTITIES", 101 | "ibm-valid-P10-ibm10v04.xml": "ENTITIES", 102 | "ibm-valid-P10-ibm10v05.xml": "ENTITIES", 103 | "ibm-valid-P10-ibm10v06.xml": "ENTITIES", 104 | "ibm-valid-P10-ibm10v07.xml": "ENTITIES", 105 | "ibm-valid-P10-ibm10v08.xml": "ENTITIES", 106 | "ibm-valid-P29-ibm29v01.xml": "ENTITIES", 107 | "ibm-valid-P43-ibm43v01.xml": "ENTITIES", 108 | "ibm-valid-P67-ibm67v01.xml": "ENTITIES", 109 | "ibm-1-1-not-wf-P77-ibm77n14.xml": "DTD", 110 | "ibm-1-1-valid-P02-ibm02v04.xml": "ENTITIES", 111 | "ibm-1-1-valid-P03-ibm03v05.xml": "ENTITIES", 112 | "ibm-1-1-valid-P03-ibm03v06.xml": "ENTITIES", 113 | "ibm-1-1-valid-P03-ibm03v07.xml": "ENTITIES", 114 | "rmt-e2e-15e": "ENTITIES", 115 | "rmt-e2e-15f": "ENTITIES", 116 | "rmt-ns10-043": "DTD", 117 | "rmt-ns10-044": "DTD", 118 | "rmt-e3e-06i": "DTD", 119 | "rmt-e3e-12": "DTD", 120 | }; 121 | 122 | // 123 | // This table was adapted from the chrome.js selection in xml-conformance-suite 124 | // 125 | // Most likely platform issues (i.e. issues outside the XML parser itself but 126 | // with the JavaScript runtime, either due to the ES standard or the runtime's 127 | // own quirks): 128 | // 129 | // - surrogate encoding: 130 | // 131 | // V8 goes off the rails when it encounters a surrogate outside a pair. There 132 | // does not appear to be a workaround. 133 | // 134 | // - unicode garbage-in garbage-out: 135 | // 136 | // V8 passes garbage upon encountering bad unicode instead of throwing a 137 | // runtime error. (Python throws.) 138 | // 139 | // - xml declaration encoding: 140 | // 141 | // By the time the parser sees the document, it cannot know what the original 142 | // encoding was. It may have been UTF16, which was converted correctly to an 143 | // internal format. 144 | // 145 | // These are genuine parser errors: 146 | // 147 | // - ignores wf errors in DOCTYPE: 148 | // 149 | // Even non-validating parsers must report wellformedness errors in DOCTYPE. 150 | // 151 | 152 | const PLATFORM_ISSUES: Record = { 153 | "not-wf-sa-168": "surrogate encoding", 154 | "not-wf-sa-169": "surrogate encoding", 155 | "not-wf-sa-170": "unicode garbage-in garbage-out", 156 | "ibm-not-wf-P02-ibm02n30.xml": "surrogate encoding", 157 | "ibm-not-wf-P02-ibm02n31.xml": "surrogate encoding", 158 | "rmt-e2e-27": "surrogate encoding", 159 | "rmt-e2e-50": "xml declaration encoding", 160 | "rmt-e2e-61": "xml declaration encoding", 161 | "rmt-011": "xml declarations encoding", 162 | "rmt-034": "xml declarations encoding", 163 | "rmt-035": "xml declarations encoding", 164 | "rmt-041": "xml declarations encoding", 165 | "rmt-050": "xml declarations encoding", 166 | "rmt-051": "xml declarations encoding", 167 | "rmt-054": "xml declarations encoding", 168 | "x-ibm-1-0.5-not-wf-P04-ibm04n21.xml": "surrogate encoding", 169 | "x-ibm-1-0.5-not-wf-P04-ibm04n22.xml": "surrogate encoding", 170 | "x-ibm-1-0.5-not-wf-P04-ibm04n23.xml": "surrogate encoding", 171 | "x-ibm-1-0.5-not-wf-P04-ibm04n24.xml": "surrogate encoding", 172 | "x-ibm-1-0.5-not-wf-P04a-ibm04an21.xml": "surrogate encoding", 173 | "x-ibm-1-0.5-not-wf-P04a-ibm04an22.xml": "surrogate encoding", 174 | "x-ibm-1-0.5-not-wf-P04a-ibm04an23.xml": "surrogate encoding", 175 | "x-ibm-1-0.5-not-wf-P04a-ibm04an24.xml": "surrogate encoding", 176 | "ibm-1-1-not-wf-P02-ibm02n67.xml": "surrogate encoding", 177 | "ibm-1-1-not-wf-P04-ibm04n21.xml": "surrogate encoding", 178 | "ibm-1-1-not-wf-P04-ibm04n22.xml": "surrogate encoding", 179 | "ibm-1-1-not-wf-P04-ibm04n23.xml": "surrogate encoding", 180 | "ibm-1-1-not-wf-P04-ibm04n24.xml": "surrogate encoding", 181 | "ibm-1-1-not-wf-P04a-ibm04an21.xml": "surrogate encoding", 182 | "ibm-1-1-not-wf-P04a-ibm04an22.xml": "surrogate encoding", 183 | "ibm-1-1-not-wf-P04a-ibm04an23.xml": "surrogate encoding", 184 | "ibm-1-1-not-wf-P04a-ibm04an24.xml": "surrogate encoding", 185 | "hst-lhs-007": "xml declaration encoding", 186 | }; 187 | 188 | class SaxesSelection extends BaseSelection { 189 | // eslint-disable-next-line class-methods-use-this 190 | getHandlingByType(test: TestSpec): TestHandling { 191 | const { testType } = test; 192 | switch (testType) { 193 | case "not-wf": 194 | return "fails"; 195 | case "valid": 196 | return "succeeds"; 197 | case "invalid": 198 | case "error": 199 | return "skip"; 200 | default: 201 | throw new Error(`unexpected test type: ${testType as string}`); 202 | } 203 | } 204 | 205 | // eslint-disable-next-line class-methods-use-this 206 | async shouldSkipTest(test: TestSpec): Promise { 207 | return (SKIP[test.id] !== undefined) || 208 | (PLATFORM_ISSUES[test.id] !== undefined) || 209 | // These sections are excluded because they require 210 | // parsing DTDs. 211 | test.includesSections(["3.2", "3.2.1", "3.2.2", "3.3", "3.3.1", 212 | "3.3.2", "4.2", "4.2.2", "4.5", "4.7"]) || 213 | test.includesProductions(["[12]", "[13]", "[69]"]) || 214 | !((test.includesVersion("1.0") && test.includesEdition("5")) || 215 | test.includesEdition("1.1")) ? 216 | true : 217 | // The tests that use BOM rely on the parser being able to look at 218 | // the *raw* data, without decoding. There does not seem to be a way 219 | // to do this. 220 | test.getHasBOM(); 221 | } 222 | } 223 | 224 | class SaxesDriver extends BaseDriver { 225 | constructor(private readonly resourceLoader: ResourceLoader) { 226 | super("saxes"); 227 | } 228 | 229 | // eslint-disable-next-line class-methods-use-this 230 | writeSource(parser: SaxesParser, source: string): void { 231 | parser.write(source); 232 | parser.close(); 233 | } 234 | 235 | async run(test: Test, handling: TestHandling): Promise { 236 | const { resolvedURI } = test; 237 | const source = await this.resourceLoader.loadFile(resolvedURI); 238 | const errors = []; 239 | const parser = new SaxesParser({ 240 | xmlns: !test.forbidsNamespaces, 241 | }); 242 | parser.on("error", (err: Error) => { 243 | if (process?.env?.DEBUG !== undefined) { 244 | console.log(err); 245 | } 246 | errors.push(err); 247 | }); 248 | 249 | this.writeSource(parser, source); 250 | this.processResult(test, handling, errors.length === 0); 251 | } 252 | } 253 | 254 | class CharByCharDriver extends SaxesDriver { 255 | // eslint-disable-next-line class-methods-use-this 256 | writeSource(parser: SaxesParser, source: string): void { 257 | for (const x of source) { 258 | parser.write(x); 259 | } 260 | parser.close(); 261 | } 262 | } 263 | 264 | const resourceLoader = new ResourceLoader(); 265 | void loadTests(resourceLoader) 266 | .then(suite => Promise.all([ 267 | build(suite, "conformance (one write)", resourceLoader, 268 | SaxesDriver, SaxesSelection), 269 | build(suite, "conformance (char-by-char)", resourceLoader, 270 | CharByCharDriver, SaxesSelection), 271 | ])) 272 | .then(run) 273 | .catch(e => console.log(e)); 274 | -------------------------------------------------------------------------------- /test/cyrillic.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "cyrillic", 5 | xml: "<Р>тест", 6 | expect: [ 7 | ["opentagstart", { name: "Р", attributes: {} }], 8 | ["opentag", { name: "Р", attributes: {}, isSelfClosing: false }], 9 | ["text", "тест"], 10 | ["closetag", { name: "Р", attributes: {}, isSelfClosing: false }], 11 | ], 12 | }); 13 | -------------------------------------------------------------------------------- /test/dtd.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | describe("dtd", () => { 4 | test({ 5 | name: "DTD with comment containing a quote", 6 | xml: `\ 7 | 8 | 10 | ]> 11 | `, 12 | expect: [ 13 | [ 14 | "xmldecl", 15 | { 16 | encoding: "UTF-8", 17 | version: "1.0", 18 | standalone: undefined, 19 | }, 20 | ], 21 | ["text", "\n"], 22 | ["doctype", ` root [ 23 | 24 | ]`], 25 | ["text", "\n"], 26 | ["opentagstart", { name: "root", attributes: {} }], 27 | ["opentag", { name: "root", attributes: {}, isSelfClosing: true }], 28 | ["closetag", { name: "root", attributes: {}, isSelfClosing: true }], 29 | ], 30 | }); 31 | 32 | test({ 33 | name: "DTD with processing instruction containing a quote", 34 | xml: `\ 35 | 36 | 38 | ]> 39 | `, 40 | expect: [ 41 | [ 42 | "xmldecl", 43 | { 44 | encoding: "UTF-8", 45 | version: "1.0", 46 | standalone: undefined, 47 | }, 48 | ], 49 | ["text", "\n"], 50 | ["doctype", ` root [ 51 | 52 | ]`], 53 | ["text", "\n"], 54 | ["opentagstart", { name: "root", attributes: {} }], 55 | ["opentag", { name: "root", attributes: {}, isSelfClosing: true }], 56 | ["closetag", { name: "root", attributes: {}, isSelfClosing: true }], 57 | ], 58 | }); 59 | 60 | test({ 61 | name: "DTD with ]> in a string", 62 | xml: `\ 63 | 64 | "> 66 | ]> 67 | `, 68 | expect: [ 69 | [ 70 | "xmldecl", 71 | { 72 | encoding: "UTF-8", 73 | version: "1.0", 74 | standalone: undefined, 75 | }, 76 | ], 77 | ["text", "\n"], 78 | ["doctype", ` root [ 79 | "> 80 | ]`], 81 | ["text", "\n"], 82 | ["opentagstart", { name: "root", attributes: {} }], 83 | ["opentag", { name: "root", attributes: {}, isSelfClosing: true }], 84 | ["closetag", { name: "root", attributes: {}, isSelfClosing: true }], 85 | ], 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/duplicate-attribute.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "duplicate attribute", 5 | xml: "", 6 | expect: [ 7 | ["opentagstart", { 8 | name: "span", 9 | attributes: {}, 10 | }], 11 | ["attribute", { name: "id", value: "hello" }], 12 | ["attribute", { name: "id", value: "there" }], 13 | ["error", "1:28: duplicate attribute: id."], 14 | ["opentag", { 15 | name: "span", 16 | attributes: { id: "there" }, 17 | isSelfClosing: false, 18 | }], 19 | ["closetag", { 20 | name: "span", 21 | attributes: { id: "there" }, 22 | isSelfClosing: false, 23 | }], 24 | ], 25 | opt: {}, 26 | }); 27 | -------------------------------------------------------------------------------- /test/emoji.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | // split high-order numeric attributes into surrogate pairs 4 | test({ 5 | name: "emoji", 6 | xml: "🔥", 7 | expect: [ 8 | ["opentagstart", { name: "a", attributes: {} }], 9 | ["opentag", { name: "a", attributes: {}, isSelfClosing: false }], 10 | ["text", "\ud83d\udd25"], 11 | ["closetag", { name: "a", attributes: {}, isSelfClosing: false }], 12 | ], 13 | opt: {}, 14 | }); 15 | -------------------------------------------------------------------------------- /test/entities.ts: -------------------------------------------------------------------------------- 1 | import { execFile as _execFile } from "child_process"; 2 | import * as util from "util"; 3 | 4 | import { test } from "./testutil"; 5 | 6 | const execFile = util.promisify(_execFile); 7 | 8 | test({ 9 | name: "entities", 10 | xml: "& < > >", 11 | expect: [ 12 | ["opentagstart", { name: "r", attributes: {} }], 13 | ["opentag", { name: "r", attributes: {}, isSelfClosing: false }], 14 | ["text", "& < > >"], 15 | ["closetag", { name: "r", attributes: {}, isSelfClosing: false }], 16 | ], 17 | }); 18 | 19 | // This test mainly exists to check parsing speed of a file with a lot of 20 | // entities. 21 | it("mass entities", async () => { 22 | await execFile("node", ["./examples/null-parser.js", 23 | "test/files/entities.xml"]); 24 | }); 25 | -------------------------------------------------------------------------------- /test/entity-nan.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "entity NaN", 5 | xml: "&#NaN;", 6 | expect: [ 7 | ["opentagstart", { name: "r", attributes: {} }], 8 | ["opentag", { name: "r", attributes: {}, isSelfClosing: false }], 9 | ["error", "1:9: malformed character entity."], 10 | ["text", "&#NaN;"], 11 | ["closetag", { name: "r", attributes: {}, isSelfClosing: false }], 12 | ], 13 | }); 14 | -------------------------------------------------------------------------------- /test/eol-handling.ts: -------------------------------------------------------------------------------- 1 | import { SaxesParser } from "../build/dist/saxes"; 2 | import { test } from "./testutil"; 3 | 4 | describe("eol handling", () => { 5 | describe("mixed", () => { 6 | /* eslint-disable linebreak-style */ 7 | const xml = `\ 8 | 9 | 10 | 13 | abc 14 | def 15 | ghi 16 | xx xx 17 | 18 | `; 19 | /* eslint-enable linebreak-style */ 20 | 21 | const expect = [ 22 | [ 23 | "xmldecl", 24 | { 25 | encoding: "utf-8", 26 | version: "1.0", 27 | standalone: undefined, 28 | }, 29 | ], 30 | ["text", "\n\n"], 31 | ["opentagstart", { name: "moo", attributes: {} }], 32 | ["attribute", { 33 | name: "a", 34 | value: "12 3", 35 | }], 36 | ["opentag", { 37 | name: "moo", 38 | attributes: { 39 | a: "12 3", 40 | }, 41 | isSelfClosing: false, 42 | }], 43 | ["text", "\n abc\n def\r\n ghi\n\n xx\nxx\n"], 44 | ["closetag", { 45 | name: "moo", 46 | attributes: { 47 | a: "12 3", 48 | }, 49 | isSelfClosing: false, 50 | }], 51 | ["text", "\n"], 52 | ]; 53 | 54 | test({ 55 | name: "one chunk", 56 | xml, 57 | expect, 58 | }); 59 | 60 | test({ 61 | name: "char-by-char", 62 | expect, 63 | fn(parser: SaxesParser): void { 64 | for (const x of xml) { 65 | parser.write(x); 66 | } 67 | parser.close(); 68 | }, 69 | }); 70 | }); 71 | 72 | // The comprehensive test is meant to have EOL in all possible contexts. 73 | describe("comprehensive", () => { 74 | const nl = `\ 75 | 86 | 92 | 95 | " 99 | > 100 | ]> 101 | 103 | 105 | 116 | abc 117 | 118 | 119 | 121 | 123 | 128 | 130 | `; 131 | const xmlDeclEnd = nl.indexOf("?>"); 132 | 133 | const expect = [ 134 | [ 135 | "xmldecl", 136 | { 137 | encoding: "utf-8", 138 | version: "1.0", 139 | standalone: "no", 140 | }, 141 | ], 142 | ["text", "\n"], 143 | ["doctype", ` 144 | root 145 | [ 146 | 149 | 152 | " 156 | > 157 | ]`], 158 | ["text", "\n"], 159 | ["comment", "\n"], 160 | ["text", "\n"], 161 | ["processinginstruction", { target: "fnord", body: "" }], 162 | ["text", "\n"], 163 | ["opentagstart", { name: "moo", attributes: {} }], 164 | ["attribute", { 165 | name: "a", 166 | value: "12 3", 167 | }], 168 | ["attribute", { 169 | name: "b", 170 | value: " z ", 171 | }], 172 | ["opentag", { 173 | name: "moo", 174 | attributes: { 175 | a: "12 3", 176 | b: " z ", 177 | }, 178 | isSelfClosing: false, 179 | }], 180 | ["text", ` 181 | abc 182 | \r 183 | 184 | `], 185 | ["comment", "\n"], 186 | ["text", "\n"], 187 | ["processinginstruction", { target: "fnord", body: "" }], 188 | ["text", "\n"], 189 | ["opentagstart", { name: "abc", attributes: {} }], 190 | ["attribute", { 191 | name: "a", 192 | value: "bc", 193 | }], 194 | ["opentag", { 195 | name: "abc", 196 | attributes: { 197 | a: "bc", 198 | }, 199 | isSelfClosing: true, 200 | }], 201 | ["closetag", { 202 | name: "abc", 203 | attributes: { 204 | a: "bc", 205 | }, 206 | isSelfClosing: true, 207 | }], 208 | ["text", "\n"], 209 | ["closetag", { 210 | name: "moo", 211 | attributes: { 212 | a: "12 3", 213 | b: " z ", 214 | }, 215 | isSelfClosing: false, 216 | }], 217 | ["text", "\n"], 218 | ] as const; 219 | 220 | const fixed11 = 221 | expect.map(x => (x[0] === "xmldecl" ? 222 | [x[0], { ...x[1], version: "1.1" }] : x)); 223 | 224 | describe("nl", () => { 225 | test({ 226 | name: "one chunk", 227 | xml: nl, 228 | expect, 229 | }); 230 | 231 | test({ 232 | name: "char-by-char", 233 | expect, 234 | fn(parser: SaxesParser): void { 235 | for (const x of nl) { 236 | parser.write(x); 237 | } 238 | parser.close(); 239 | }, 240 | }); 241 | }); 242 | 243 | describe("cr", () => { 244 | const cr = nl.replace(/\n/g, "\r"); 245 | 246 | test({ 247 | name: "one chunk", 248 | xml: cr, 249 | expect, 250 | }); 251 | 252 | test({ 253 | name: "char-by-char", 254 | expect, 255 | fn(parser: SaxesParser): void { 256 | for (const x of cr) { 257 | parser.write(x); 258 | } 259 | parser.close(); 260 | }, 261 | }); 262 | }); 263 | 264 | describe("crnl", () => { 265 | const crnl = nl.replace(/\n/g, "\r\n"); 266 | 267 | test({ 268 | name: "one chunk", 269 | xml: crnl, 270 | expect, 271 | }); 272 | 273 | test({ 274 | name: "char-by-char", 275 | expect, 276 | fn(parser: SaxesParser): void { 277 | for (const x of crnl) { 278 | parser.write(x); 279 | } 280 | parser.close(); 281 | }, 282 | }); 283 | }); 284 | 285 | describe("nel", () => { 286 | // We have to switch the EOL characters after the XML declaration. 287 | const nel = nl.slice(0, xmlDeclEnd).replace("1.0", "1.1") + 288 | nl.slice(xmlDeclEnd).replace(/\n/g, "\u0085"); 289 | 290 | test({ 291 | name: "one chunk", 292 | xml: nel, 293 | expect: fixed11, 294 | }); 295 | 296 | test({ 297 | name: "char-by-char", 298 | expect: fixed11, 299 | fn(parser: SaxesParser): void { 300 | for (const x of nel) { 301 | parser.write(x); 302 | } 303 | parser.close(); 304 | }, 305 | }); 306 | }); 307 | 308 | describe("ls", () => { 309 | // We have to switch the EOL characters after the XML declaration. 310 | const ls = nl.slice(0, xmlDeclEnd).replace("1.0", "1.1") + 311 | nl.slice(xmlDeclEnd).replace(/\n/g, "\u2028"); 312 | 313 | test({ 314 | name: "one chunk", 315 | xml: ls, 316 | expect: fixed11, 317 | }); 318 | 319 | test({ 320 | name: "char-by-char", 321 | expect: fixed11, 322 | fn(parser: SaxesParser): void { 323 | for (const x of ls) { 324 | parser.write(x); 325 | } 326 | parser.close(); 327 | }, 328 | }); 329 | }); 330 | }); 331 | 332 | describe("bad start", () => { 333 | const xml = ` 334 | `; 335 | const expect = [ 336 | [ 337 | "error", 338 | "2:6: an XML declaration must be at the start of the document.", 339 | ], 340 | [ 341 | "xmldecl", 342 | { 343 | encoding: "utf-8", 344 | version: "1.0", 345 | standalone: undefined, 346 | }, 347 | ], 348 | ["opentagstart", { 349 | name: "doc", 350 | attributes: {}, 351 | }], 352 | ["opentag", { 353 | name: "doc", 354 | attributes: {}, 355 | isSelfClosing: true, 356 | }], 357 | ["closetag", { 358 | name: "doc", 359 | attributes: {}, 360 | isSelfClosing: true, 361 | }], 362 | ]; 363 | 364 | describe("with nl as eol", () => { 365 | test({ 366 | name: "one chunk", 367 | xml, 368 | expect, 369 | }); 370 | 371 | test({ 372 | name: "char-by-char", 373 | expect, 374 | fn(parser: SaxesParser): void { 375 | for (const x of xml) { 376 | parser.write(x); 377 | } 378 | parser.close(); 379 | }, 380 | }); 381 | }); 382 | 383 | describe("with crnl as eol", () => { 384 | const crnl = xml.replace(/\n/g, "\r\n"); 385 | test({ 386 | name: "one chunk", 387 | xml: crnl, 388 | expect, 389 | }); 390 | 391 | test({ 392 | name: "char-by-char", 393 | expect, 394 | fn(parser: SaxesParser): void { 395 | for (const x of crnl) { 396 | parser.write(x); 397 | } 398 | parser.close(); 399 | }, 400 | }); 401 | }); 402 | }); 403 | }); 404 | -------------------------------------------------------------------------------- /test/errors.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "without fileName", 5 | xml: "", 6 | expect: [ 7 | ["opentagstart", { name: "doc", attributes: {} }], 8 | ["opentag", { name: "doc", isSelfClosing: false, attributes: {} }], 9 | ["error", "1:5: unclosed tag: doc"], 10 | ], 11 | opt: {}, 12 | }); 13 | 14 | test({ 15 | name: "without fileName, when not tracking position position", 16 | xml: "", 17 | expect: [ 18 | ["opentagstart", { name: "doc", attributes: {} }], 19 | ["opentag", { name: "doc", isSelfClosing: false, attributes: {} }], 20 | ["error", "unclosed tag: doc"], 21 | ], 22 | opt: { 23 | position: false, 24 | }, 25 | }); 26 | 27 | test({ 28 | name: "with fileName", 29 | xml: "", 30 | expect: [ 31 | ["opentagstart", { name: "doc", attributes: {} }], 32 | ["opentag", { name: "doc", isSelfClosing: false, attributes: {} }], 33 | ["error", "foobar.xml:1:5: unclosed tag: doc"], 34 | ], 35 | opt: { 36 | fileName: "foobar.xml", 37 | }, 38 | }); 39 | 40 | test({ 41 | name: "with fileName, when not tracking position position", 42 | xml: "", 43 | expect: [ 44 | ["opentagstart", { name: "doc", attributes: {} }], 45 | ["opentag", { name: "doc", isSelfClosing: false, attributes: {} }], 46 | ["error", "foobar.xml: unclosed tag: doc"], 47 | ], 48 | opt: { 49 | fileName: "foobar.xml", 50 | position: false, 51 | }, 52 | }); 53 | -------------------------------------------------------------------------------- /test/fragments.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | describe("fragments", () => { 4 | test({ 5 | name: "empty", 6 | xml: "", 7 | expect: [], 8 | opt: { 9 | xmlns: true, 10 | fragment: true, 11 | }, 12 | }); 13 | 14 | test({ 15 | name: "text only", 16 | xml: " Something ", 17 | expect: [ 18 | ["text", " Something "], 19 | ], 20 | opt: { 21 | xmlns: true, 22 | fragment: true, 23 | }, 24 | }); 25 | 26 | test({ 27 | name: "text and elements", 28 | xml: "Something blah something", 29 | expect: [ 30 | ["text", "Something "], 31 | ["opentagstart", { 32 | name: "blah", 33 | attributes: {}, 34 | ns: {}, 35 | }], 36 | ["opentag", { 37 | name: "blah", 38 | local: "blah", 39 | prefix: "", 40 | uri: "", 41 | attributes: {}, 42 | ns: {}, 43 | isSelfClosing: false, 44 | }], 45 | ["opentagstart", { 46 | name: "more", 47 | attributes: {}, 48 | ns: {}, 49 | }], 50 | ["opentag", { 51 | name: "more", 52 | local: "more", 53 | prefix: "", 54 | uri: "", 55 | attributes: {}, 56 | ns: {}, 57 | isSelfClosing: false, 58 | }], 59 | ["text", "blah"], 60 | ["closetag", { 61 | name: "more", 62 | local: "more", 63 | prefix: "", 64 | uri: "", 65 | attributes: {}, 66 | ns: {}, 67 | isSelfClosing: false, 68 | }], 69 | ["closetag", { 70 | name: "blah", 71 | local: "blah", 72 | prefix: "", 73 | uri: "", 74 | attributes: {}, 75 | ns: {}, 76 | isSelfClosing: false, 77 | }], 78 | ["text", " something"], 79 | ], 80 | opt: { 81 | xmlns: true, 82 | fragment: true, 83 | }, 84 | }); 85 | 86 | test({ 87 | name: "two top-level elements", 88 | xml: "Something 12 something", 89 | expect: [ 90 | ["text", "Something "], 91 | ["opentagstart", { 92 | name: "blah", 93 | attributes: {}, 94 | ns: {}, 95 | }], 96 | ["opentag", { 97 | name: "blah", 98 | local: "blah", 99 | prefix: "", 100 | uri: "", 101 | attributes: {}, 102 | ns: {}, 103 | isSelfClosing: false, 104 | }], 105 | ["text", "1"], 106 | ["closetag", { 107 | name: "blah", 108 | local: "blah", 109 | prefix: "", 110 | uri: "", 111 | attributes: {}, 112 | ns: {}, 113 | isSelfClosing: false, 114 | }], 115 | ["opentagstart", { 116 | name: "more", 117 | attributes: {}, 118 | ns: {}, 119 | }], 120 | ["opentag", { 121 | name: "more", 122 | local: "more", 123 | prefix: "", 124 | uri: "", 125 | attributes: {}, 126 | ns: {}, 127 | isSelfClosing: false, 128 | }], 129 | ["text", "2"], 130 | ["closetag", { 131 | name: "more", 132 | local: "more", 133 | prefix: "", 134 | uri: "", 135 | attributes: {}, 136 | ns: {}, 137 | isSelfClosing: false, 138 | }], 139 | ["text", " something"], 140 | ], 141 | opt: { 142 | xmlns: true, 143 | fragment: true, 144 | }, 145 | }); 146 | 147 | // This case was added to check for a bug in the parsing logic that prevented 148 | // detecting if an element was not closed. 149 | test({ 150 | name: "unclosed tag", 151 | xml: "Something 12", 152 | expect: [ 153 | ["text", "Something "], 154 | ["opentagstart", { 155 | name: "blah", 156 | attributes: {}, 157 | ns: {}, 158 | }], 159 | ["opentag", { 160 | name: "blah", 161 | local: "blah", 162 | prefix: "", 163 | uri: "", 164 | attributes: {}, 165 | ns: {}, 166 | isSelfClosing: false, 167 | }], 168 | ["text", "1"], 169 | ["closetag", { 170 | name: "blah", 171 | local: "blah", 172 | prefix: "", 173 | uri: "", 174 | attributes: {}, 175 | ns: {}, 176 | isSelfClosing: false, 177 | }], 178 | ["opentagstart", { 179 | name: "more", 180 | attributes: {}, 181 | ns: {}, 182 | }], 183 | ["opentag", { 184 | name: "more", 185 | local: "more", 186 | prefix: "", 187 | uri: "", 188 | attributes: {}, 189 | ns: {}, 190 | isSelfClosing: false, 191 | }], 192 | ["error", "1:31: unclosed tag: more"], 193 | ["text", "2"], 194 | ], 195 | opt: { 196 | xmlns: true, 197 | fragment: true, 198 | }, 199 | }); 200 | 201 | test({ 202 | name: "namespaces", 203 | xml: "Something 1 something", 204 | expect: [ 205 | ["text", "Something "], 206 | ["opentagstart", { 207 | name: "foo:blah", 208 | attributes: {}, 209 | ns: {}, 210 | }], 211 | ["opentag", { 212 | name: "foo:blah", 213 | local: "blah", 214 | prefix: "foo", 215 | uri: "foo-uri", 216 | attributes: {}, 217 | ns: {}, 218 | isSelfClosing: false, 219 | }], 220 | ["text", "1"], 221 | ["closetag", { 222 | name: "foo:blah", 223 | local: "blah", 224 | prefix: "foo", 225 | uri: "foo-uri", 226 | attributes: {}, 227 | ns: {}, 228 | isSelfClosing: false, 229 | }], 230 | ["text", " something"], 231 | ], 232 | opt: { 233 | xmlns: true, 234 | fragment: true, 235 | additionalNamespaces: { 236 | foo: "foo-uri", 237 | }, 238 | }, 239 | }); 240 | 241 | test({ 242 | name: "resolvePrefix", 243 | xml: "Something 1 something", 244 | expect: [ 245 | ["text", "Something "], 246 | ["opentagstart", { 247 | name: "foo:blah", 248 | attributes: {}, 249 | ns: {}, 250 | }], 251 | ["opentag", { 252 | name: "foo:blah", 253 | local: "blah", 254 | prefix: "foo", 255 | uri: "foo-uri", 256 | attributes: {}, 257 | ns: {}, 258 | isSelfClosing: false, 259 | }], 260 | ["text", "1"], 261 | ["closetag", { 262 | name: "foo:blah", 263 | local: "blah", 264 | prefix: "foo", 265 | uri: "foo-uri", 266 | attributes: {}, 267 | ns: {}, 268 | isSelfClosing: false, 269 | }], 270 | ["text", " something"], 271 | ], 272 | opt: { 273 | xmlns: true, 274 | fragment: true, 275 | resolvePrefix: (prefix: string): string | undefined => ({ 276 | foo: "foo-uri", 277 | } as Record)[prefix], 278 | }, 279 | }); 280 | }); 281 | -------------------------------------------------------------------------------- /test/issue-23.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "issue 23", 5 | xml: "" + 6 | "" + 7 | "653724009" + 8 | "-1" + 9 | "01pG0000002KoSUIA0" + 10 | "-1" + 11 | "CalendarController" + 12 | "true" + 13 | "" + 14 | "", 15 | expect: [ 16 | ["opentagstart", { name: "compileClassesResponse", attributes: {} }], 17 | ["opentag", 18 | { name: "compileClassesResponse", attributes: {}, isSelfClosing: false }], 19 | ["opentagstart", { name: "result", attributes: {} }], 20 | ["opentag", { name: "result", attributes: {}, isSelfClosing: false }], 21 | ["opentagstart", { name: "bodyCrc", attributes: {} }], 22 | ["opentag", { name: "bodyCrc", attributes: {}, isSelfClosing: false }], 23 | ["text", "653724009"], 24 | ["closetag", { name: "bodyCrc", attributes: {}, isSelfClosing: false }], 25 | ["opentagstart", { name: "column", attributes: {} }], 26 | ["opentag", { name: "column", attributes: {}, isSelfClosing: false }], 27 | ["text", "-1"], 28 | ["closetag", { name: "column", attributes: {}, isSelfClosing: false }], 29 | ["opentagstart", { name: "id", attributes: {} }], 30 | ["opentag", { name: "id", attributes: {}, isSelfClosing: false }], 31 | ["text", "01pG0000002KoSUIA0"], 32 | ["closetag", { name: "id", attributes: {}, isSelfClosing: false }], 33 | ["opentagstart", { name: "line", attributes: {} }], 34 | ["opentag", { name: "line", attributes: {}, isSelfClosing: false }], 35 | ["text", "-1"], 36 | ["closetag", { name: "line", attributes: {}, isSelfClosing: false }], 37 | ["opentagstart", { name: "name", attributes: {} }], 38 | ["opentag", { name: "name", attributes: {}, isSelfClosing: false }], 39 | ["text", "CalendarController"], 40 | ["closetag", { name: "name", attributes: {}, isSelfClosing: false }], 41 | ["opentagstart", { name: "success", attributes: {} }], 42 | ["opentag", { name: "success", attributes: {}, isSelfClosing: false }], 43 | ["text", "true"], 44 | ["closetag", { name: "success", attributes: {}, isSelfClosing: false }], 45 | ["closetag", { name: "result", attributes: {}, isSelfClosing: false }], 46 | ["closetag", 47 | { name: "compileClassesResponse", attributes: {}, isSelfClosing: false }], 48 | ], 49 | opt: {}, 50 | }); 51 | -------------------------------------------------------------------------------- /test/issue-33.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | // https://github.com/isaacs/sax-js/issues/33 4 | test({ 5 | name: "issue 33 (comment with single dash)", 6 | xml: "\n" + 7 | "\n" + 10 | "\n" + 11 | "", 12 | expect: [ 13 | ["opentagstart", { name: "xml", attributes: {} }], 14 | ["opentag", { name: "xml", attributes: {}, isSelfClosing: false }], 15 | ["text", "\n"], 16 | ["comment", " \n comment with a single dash- in it\n"], 17 | ["text", "\n"], 18 | ["opentagstart", { name: "data", attributes: {} }], 19 | ["opentag", { name: "data", attributes: {}, isSelfClosing: true }], 20 | ["closetag", { name: "data", attributes: {}, isSelfClosing: true }], 21 | ["text", "\n"], 22 | ["closetag", { name: "xml", attributes: {}, isSelfClosing: false }], 23 | ], 24 | opt: {}, 25 | }); 26 | -------------------------------------------------------------------------------- /test/issue-35.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | // https://github.com/isaacs/sax-js/issues/35 4 | test({ 5 | name: "issue 35 (leading 0 in entity numeric code)", 6 | xml: " \n", 7 | expect: [ 8 | ["opentagstart", { name: "xml", attributes: {} }], 9 | ["opentag", { name: "xml", attributes: {}, isSelfClosing: false }], 10 | ["text", "\r\r\n"], 11 | ["closetag", { name: "xml", attributes: {}, isSelfClosing: false }], 12 | ], 13 | opt: {}, 14 | }); 15 | -------------------------------------------------------------------------------- /test/issue-38.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "issue 38", 5 | xml: `\ 6 | 7 | 8 | Fnord '<' and then some. 9 | 10 | 11 | `, 12 | expect: [ 13 | ["opentag", { name: "top", attributes: {}, isSelfClosing: false }], 14 | ["opentag", { name: "x", attributes: {}, isSelfClosing: false }], 15 | ["opentag", { name: "x", attributes: { x: "foo" }, isSelfClosing: false }], 16 | ], 17 | opt: {}, 18 | events: ["opentag"], 19 | }); 20 | -------------------------------------------------------------------------------- /test/issue-47.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | // https://github.com/isaacs/sax-js/issues/47 4 | test({ 5 | name: "issue 47", 6 | xml: "", 7 | expect: [ 8 | ["opentagstart", { name: "a", attributes: {} }], 9 | ["attribute", { name: "href", value: "query.svc?x=1&y=2&z=3" }], 10 | ["opentag", { 11 | name: "a", 12 | attributes: { href: "query.svc?x=1&y=2&z=3" }, 13 | isSelfClosing: true, 14 | }], 15 | ["closetag", { 16 | name: "a", 17 | attributes: { href: "query.svc?x=1&y=2&z=3" }, 18 | isSelfClosing: true, 19 | }], 20 | ], 21 | opt: {}, 22 | }); 23 | -------------------------------------------------------------------------------- /test/issue-84.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | // https://github.com/isaacs/sax-js/issues/84 4 | test({ 5 | name: "issue 84 (unbalanced quotes in pi)", 6 | xml: "body", 7 | expect: [ 8 | ["opentagstart", { name: "xml", attributes: {} }], 9 | ["opentag", { name: "xml", attributes: {}, isSelfClosing: false }], 10 | ["processinginstruction", { target: "has", body: "unbalanced \"quotes" }], 11 | ["text", "body"], 12 | ["closetag", { name: "xml", attributes: {}, isSelfClosing: false }], 13 | ], 14 | opt: {}, 15 | }); 16 | -------------------------------------------------------------------------------- /test/issue-86.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "issue 86 (prevent open waka after root has closed)", 5 | xml: "abcde { 6 | const parser = new SaxesParser(); 7 | let seen = false; 8 | parser.on("opentag", (node: SaxesTagPlain) => { 9 | expect(node).to.deep.equal({ 10 | name: "x", 11 | attributes: {}, 12 | isSelfClosing: false, 13 | }); 14 | seen = true; 15 | }); 16 | parser.write(Buffer.from("y")).close(); 17 | expect(seen).to.be.true; 18 | }); 19 | -------------------------------------------------------------------------------- /test/opentagstart.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | describe("openstarttag", () => { 4 | test({ 5 | name: "good name, xmlns true", 6 | xml: "", 7 | expect: [ 8 | [ 9 | "opentagstart", 10 | { 11 | name: "root", 12 | ns: {}, 13 | attributes: {}, 14 | }, 15 | ], 16 | [ 17 | "attribute", 18 | { 19 | name: "length", 20 | value: "12345", 21 | prefix: "", 22 | local: "length", 23 | }, 24 | ], 25 | [ 26 | "opentag", 27 | { 28 | name: "root", 29 | prefix: "", 30 | local: "root", 31 | uri: "", 32 | attributes: { 33 | length: { 34 | name: "length", 35 | value: "12345", 36 | prefix: "", 37 | local: "length", 38 | uri: "", 39 | }, 40 | }, 41 | ns: {}, 42 | isSelfClosing: false, 43 | }, 44 | ], 45 | [ 46 | "closetag", 47 | { 48 | name: "root", 49 | prefix: "", 50 | local: "root", 51 | uri: "", 52 | attributes: { 53 | length: { 54 | name: "length", 55 | value: "12345", 56 | prefix: "", 57 | local: "length", 58 | uri: "", 59 | }, 60 | }, 61 | ns: {}, 62 | isSelfClosing: false, 63 | }, 64 | ], 65 | ], 66 | opt: { 67 | xmlns: true, 68 | }, 69 | }); 70 | 71 | test({ 72 | name: "good name, xmlns false", 73 | xml: "", 74 | expect: [ 75 | [ 76 | "opentagstart", 77 | { 78 | name: "root", 79 | attributes: {}, 80 | }, 81 | ], 82 | [ 83 | "attribute", 84 | { 85 | name: "length", 86 | value: "12345", 87 | }, 88 | ], 89 | [ 90 | "opentag", 91 | { 92 | name: "root", 93 | attributes: { 94 | length: "12345", 95 | }, 96 | isSelfClosing: false, 97 | }, 98 | ], 99 | [ 100 | "closetag", 101 | { 102 | name: "root", 103 | attributes: { 104 | length: "12345", 105 | }, 106 | isSelfClosing: false, 107 | }, 108 | ], 109 | ], 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/parser-position.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | import { EVENTS, SaxesOptions, SaxesParser } from "../build/dist/saxes"; 4 | import { test } from "./testutil"; 5 | 6 | interface ExpectedPosition { 7 | position?: number; 8 | line?: number; 9 | column?: number; 10 | } 11 | 12 | type ExpectedEvent = readonly [string, ExpectedPosition, string?]; 13 | 14 | function testPosition(name: string, chunks: string[], 15 | expectedEvents: readonly ExpectedEvent[], 16 | options?: SaxesOptions): void { 17 | it(name, () => { 18 | const parser = new SaxesParser(options); 19 | let expectedIx = 0; 20 | for (const ev of EVENTS) { 21 | // eslint-disable-next-line no-loop-func, @typescript-eslint/no-explicit-any 22 | parser.on(ev, (thing: any) => { 23 | const [expectedEvent, expectedPosition, expectedText] = 24 | expectedEvents[expectedIx++]; 25 | expect(expectedEvent).to.equal(ev); 26 | // eslint-disable-next-line guard-for-in 27 | for (const prop in expectedPosition) { 28 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access 29 | expect((parser as any)[prop], `bad ${prop}`) 30 | .to.deep.equal(expectedPosition[prop as keyof ExpectedPosition]); 31 | } 32 | 33 | if (expectedEvent === "text" && expectedText !== undefined) { 34 | expect(thing).to.equal(expectedText); 35 | } 36 | }); 37 | } 38 | 39 | for (const chunk of chunks) { 40 | parser.write(chunk); 41 | } 42 | }); 43 | } 44 | 45 | describe("parser position", () => { 46 | testPosition( 47 | "with single chunk", 48 | ["
abcdefgh
"], [ 49 | ["opentagstart", { position: 5 }], 50 | ["opentag", { position: 5 }], 51 | ["text", { position: 14 }], 52 | ["closetag", { position: 19 }], 53 | ]); 54 | 55 | testPosition( 56 | "with multiple chunks", 57 | ["
abcde", "fgh
"], [ 58 | ["opentagstart", { position: 5 }], 59 | ["opentag", { position: 5 }], 60 | ["text", { position: 14 }], 61 | ["closetag", { position: 19 }], 62 | ]); 63 | 64 | const newlines = "
abcde\r\nf\rgh\r\ri\u0085j\u2028k
"; 65 | const trailingCr: string[] = []; 66 | for (const x of newlines.split("\r")) { 67 | trailingCr.push(x, "\r"); 68 | } 69 | const oneByOne = newlines.split(""); 70 | 71 | describe("XML 1.0", () => { 72 | const expected = [ 73 | ["opentagstart", { position: 5, line: 1, column: 5 }], 74 | ["opentag", { position: 5, line: 1, column: 5 }], 75 | ["text", { position: 13, line: 2, column: 1 }, "abcde\n"], 76 | ["opentagstart", { position: 17, line: 2, column: 5 }], 77 | ["opentag", { position: 18, line: 2, column: 6 }], 78 | ["closetag", { position: 18, line: 2, column: 6 }], 79 | ["text", { position: 30, line: 5, column: 6 }, 80 | "f\ngh\n\ni\u0085j\u2028k"], 81 | ["closetag", { position: 35, line: 5, column: 11 }], 82 | ] as const; 83 | 84 | testPosition("with various newlines", [newlines], expected); 85 | testPosition("with various newlines (one-by-one)", oneByOne, expected); 86 | testPosition("trailing CR", trailingCr, expected); 87 | }); 88 | 89 | describe("XML 1.1", () => { 90 | const expected = [ 91 | ["opentagstart", { position: 5, line: 1, column: 5 }], 92 | ["opentag", { position: 5, line: 1, column: 5 }], 93 | ["text", { position: 13, line: 2, column: 1 }, "abcde\n"], 94 | ["opentagstart", { position: 17, line: 2, column: 5 }], 95 | ["opentag", { position: 18, line: 2, column: 6 }], 96 | ["closetag", { position: 18, line: 2, column: 6 }], 97 | ["text", { position: 30, line: 7, column: 2 }, "f\ngh\n\ni\nj\nk"], 98 | ["closetag", { position: 35, line: 7, column: 7 }], 99 | ] as const; 100 | 101 | testPosition("with various newlines", [newlines], expected, { 102 | defaultXMLVersion: "1.1", 103 | }); 104 | testPosition("with various newlines (one-by-one)", oneByOne, expected, { 105 | defaultXMLVersion: "1.1", 106 | }); 107 | testPosition("trailing CR", trailingCr, expected, { 108 | defaultXMLVersion: "1.1", 109 | }); 110 | }); 111 | 112 | testPosition("astral plane", ["
💩"], [ 113 | ["opentagstart", { position: 3, line: 1, column: 3 }], 114 | ["opentag", { position: 3, line: 1, column: 3 }], 115 | ["text", { position: 6, line: 1, column: 5 }], 116 | ["closetag", { position: 9, line: 1, column: 8 }], 117 | ]); 118 | 119 | test({ 120 | name: "empty document", 121 | xml: "", 122 | expect: [ 123 | ["error", "fnord.xml:1:0: document must contain a root element."], 124 | ], 125 | opt: { 126 | fileName: "fnord.xml", 127 | }, 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/pi.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | describe("processing instructions", () => { 4 | test({ 5 | name: "with well-formed name", 6 | xml: "", 7 | expect: [ 8 | ["processinginstruction", { 9 | target: "xml-stylesheet", 10 | body: "version='1.0'", 11 | }], 12 | ["opentagstart", { 13 | name: "foo", 14 | attributes: {}, 15 | ns: {}, 16 | }], 17 | ["opentag", { 18 | name: "foo", 19 | local: "foo", 20 | prefix: "", 21 | uri: "", 22 | attributes: {}, 23 | ns: {}, 24 | isSelfClosing: true, 25 | }], 26 | ["closetag", { 27 | name: "foo", 28 | local: "foo", 29 | prefix: "", 30 | uri: "", 31 | attributes: {}, 32 | ns: {}, 33 | isSelfClosing: true, 34 | }], 35 | ], 36 | opt: { 37 | xmlns: true, 38 | }, 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/self-closing-child.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "self-closing child", 5 | xml: "" + 6 | "" + 7 | "" + 8 | "" + 9 | "" + 10 | "=(|)" + 11 | "" + 12 | "", 13 | expect: [ 14 | ["opentagstart", { 15 | name: "root", 16 | attributes: {}, 17 | }], 18 | ["opentag", { 19 | name: "root", 20 | attributes: {}, 21 | isSelfClosing: false, 22 | }], 23 | ["opentagstart", { 24 | name: "child", 25 | attributes: {}, 26 | }], 27 | ["opentag", { 28 | name: "child", 29 | attributes: {}, 30 | isSelfClosing: false, 31 | }], 32 | ["opentagstart", { 33 | name: "haha", 34 | attributes: {}, 35 | }], 36 | ["opentag", { 37 | name: "haha", 38 | attributes: {}, 39 | isSelfClosing: true, 40 | }], 41 | ["closetag", { 42 | name: "haha", 43 | attributes: {}, 44 | isSelfClosing: true, 45 | }], 46 | ["closetag", { 47 | name: "child", 48 | attributes: {}, 49 | isSelfClosing: false, 50 | }], 51 | ["opentagstart", { 52 | name: "monkey", 53 | attributes: {}, 54 | }], 55 | ["opentag", { 56 | name: "monkey", 57 | attributes: {}, 58 | isSelfClosing: false, 59 | }], 60 | ["text", "=(|)"], 61 | ["closetag", { 62 | name: "monkey", 63 | attributes: {}, 64 | isSelfClosing: false, 65 | }], 66 | ["closetag", { 67 | name: "root", 68 | attributes: {}, 69 | isSelfClosing: false, 70 | }], 71 | ["end", undefined], 72 | ["ready", undefined], 73 | ], 74 | opt: {}, 75 | }); 76 | -------------------------------------------------------------------------------- /test/self-closing-tag.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "self-closing tag", 5 | xml: " " + 6 | " " + 7 | " " + 8 | " " + 9 | "=(|) " + 10 | "" + 11 | " ", 12 | expect: [ 13 | ["opentagstart", { name: "root", attributes: {} }], 14 | ["opentag", { name: "root", attributes: {}, isSelfClosing: false }], 15 | ["text", " "], 16 | ["opentagstart", { name: "haha", attributes: {} }], 17 | ["opentag", { name: "haha", attributes: {}, isSelfClosing: true }], 18 | ["closetag", { name: "haha", attributes: {}, isSelfClosing: true }], 19 | ["text", " "], 20 | ["opentagstart", { name: "haha", attributes: {} }], 21 | ["opentag", { name: "haha", attributes: {}, isSelfClosing: true }], 22 | ["closetag", { name: "haha", attributes: {}, isSelfClosing: true }], 23 | ["text", " "], 24 | // ["opentag", {name:"haha", attributes:{}}], 25 | // ["closetag", "haha"], 26 | ["opentagstart", { name: "monkey", attributes: {} }], 27 | ["opentag", { name: "monkey", attributes: {}, isSelfClosing: false }], 28 | ["text", " =(|) "], 29 | ["closetag", { name: "monkey", attributes: {}, isSelfClosing: false }], 30 | ["closetag", { name: "root", attributes: {}, isSelfClosing: false }], 31 | ["text", " "], 32 | ], 33 | opt: {}, 34 | }); 35 | -------------------------------------------------------------------------------- /test/testutil.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | import { EventName, EVENTS, SaxesOptions, 4 | SaxesParser } from "../build/dist/saxes"; 5 | 6 | export interface TestOptions { 7 | xml?: string | readonly string[]; 8 | name: string; 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | expect: readonly any[]; 11 | // eslint-disable-next-line @typescript-eslint/ban-types 12 | fn?: (parser: SaxesParser<{}>) => void; 13 | opt?: SaxesOptions; 14 | events?: readonly EventName[]; 15 | } 16 | 17 | export function test(options: TestOptions): void { 18 | const { xml, name, expect: expected, fn, events } = options; 19 | it(name, () => { 20 | const parser = new SaxesParser(options.opt); 21 | let expectedIx = 0; 22 | for (const ev of events ?? EVENTS) { 23 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-loop-func 24 | parser.on(ev, (n: any) => { 25 | if (process.env.DEBUG !== undefined) { 26 | console.error({ 27 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 28 | expected: expected[expectedIx], 29 | actual: [ev, n], 30 | }); 31 | } 32 | if (expectedIx >= expected.length && (ev === "end" || ev === "ready")) { 33 | return; 34 | } 35 | 36 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access 37 | expect([ev, ev === "error" ? n.message : n]).to.deep 38 | .equal(expected[expectedIx]); 39 | expectedIx++; 40 | }); 41 | } 42 | 43 | expect(xml !== undefined || fn !== undefined, "must use xml or fn") 44 | .to.be.true; 45 | 46 | if (xml !== undefined) { 47 | if (Array.isArray(xml)) { 48 | for (const chunk of xml as readonly string[]) { 49 | parser.write(chunk); 50 | } 51 | parser.close(); 52 | } 53 | else { 54 | parser.write(xml as string).close(); 55 | } 56 | } 57 | 58 | fn?.(parser); 59 | 60 | expect(expectedIx).to.equal(expected.length); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /test/trailing-attribute-no-value.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "trailing attribute without value", 5 | xml: "", 6 | expect: [ 7 | ["opentagstart", { name: "root", attributes: {} }], 8 | ["error", "1:13: attribute without value."], 9 | ["attribute", { name: "attrib", value: "attrib" }], 10 | ["opentag", 11 | { name: "root", attributes: { attrib: "attrib" }, isSelfClosing: false }], 12 | ["closetag", 13 | { name: "root", attributes: { attrib: "attrib" }, isSelfClosing: false }], 14 | ], 15 | opt: {}, 16 | }); 17 | -------------------------------------------------------------------------------- /test/trailing-non-whitespace.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "trailing non-whitespace text", 5 | xml: "Welcome, to monkey land", 6 | expect: [ 7 | ["opentagstart", { 8 | name: "span", 9 | attributes: {}, 10 | }], 11 | ["opentag", { 12 | name: "span", 13 | attributes: {}, 14 | isSelfClosing: false, 15 | }], 16 | ["text", "Welcome,"], 17 | ["closetag", { 18 | name: "span", 19 | attributes: {}, 20 | isSelfClosing: false, 21 | }], 22 | ["error", "1:36: text data outside of root node."], 23 | ["text", " to monkey land"], 24 | ["end", undefined], 25 | ["ready", undefined], 26 | ], 27 | opt: {}, 28 | }); 29 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["mocha", "node"], 5 | "declaration": true, 6 | "sourceMap": true, 7 | "baseUrl": "." 8 | }, 9 | "include": [ 10 | "**/*.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/tshook.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | require("ts-node").register({ 4 | project: "test/tsconfig.json", 5 | cache: true, 6 | cacheDirectory: "test/tscache", 7 | }); 8 | -------------------------------------------------------------------------------- /test/typings.ts: -------------------------------------------------------------------------------- 1 | // import { expectType } from "tsd"; 2 | 3 | // import { SaxesParser, SaxesStartTag, SaxesStartTagNS, SaxesStartTagPlain, 4 | // SaxesTag, SaxesTagNS, SaxesTagPlain } from "../build/dist/saxes"; 5 | 6 | // describe("typings", () => { 7 | // describe("of onopentagstart", () => { 8 | // it("a parser with xmlns true passes SaxesStartTagNS", () => { 9 | // expectType<(tag: SaxesStartTagNS) => void>( 10 | // // eslint-disable-next-line @typescript-eslint/unbound-method 11 | // new SaxesParser({ xmlns: true }).onopentagstart); 12 | // }); 13 | 14 | // it("a parser with xmlns false is a SaxesParserPlain", () => { 15 | // expectType<(tag: SaxesStartTagPlain) => void>( 16 | // // eslint-disable-next-line @typescript-eslint/unbound-method 17 | // new SaxesParser({ xmlns: false }).onopentagstart); 18 | // }); 19 | 20 | // it("a parser with xmlns unset is a SaxesParser", () => { 21 | // expectType<(tag: SaxesStartTag) => void>( 22 | // // eslint-disable-next-line @typescript-eslint/unbound-method 23 | // new SaxesParser(Object.create(null)).onopentagstart); 24 | // }); 25 | // }); 26 | 27 | // describe("of onopentag", () => { 28 | // it("a parser with xmlns true passes SaxesStartTagNS", () => { 29 | // expectType<(tag: SaxesTagNS) => void>( 30 | // // eslint-disable-next-line @typescript-eslint/unbound-method 31 | // new SaxesParser({ xmlns: true }).onopentag); 32 | // }); 33 | 34 | // it("a parser with xmlns false is a SaxesParserPlain", () => { 35 | // expectType<(tag: SaxesTagPlain) => void>( 36 | // // eslint-disable-next-line @typescript-eslint/unbound-method 37 | // new SaxesParser({ xmlns: false }).onopentag); 38 | // }); 39 | 40 | // it("a parser with xmlns unset is a SaxesParser", () => { 41 | // expectType<(tag: SaxesTag) => void>( 42 | // // eslint-disable-next-line @typescript-eslint/unbound-method 43 | // new SaxesParser(Object.create(null)).onopentag); 44 | // }); 45 | // }); 46 | // }); 47 | -------------------------------------------------------------------------------- /test/unclosed-root.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "unclosed root", 5 | xml: "", 6 | expect: [ 7 | [ 8 | "opentagstart", 9 | { 10 | name: "root", 11 | attributes: {}, 12 | }, 13 | ], 14 | [ 15 | "opentag", 16 | { 17 | name: "root", 18 | attributes: {}, 19 | isSelfClosing: false, 20 | }, 21 | ], 22 | [ 23 | "error", 24 | "1:6: unclosed tag: root", 25 | ], 26 | ], 27 | opt: {}, 28 | }); 29 | -------------------------------------------------------------------------------- /test/unicode.ts: -------------------------------------------------------------------------------- 1 | import { SaxesParser } from "../build/dist/saxes"; 2 | import { test } from "./testutil"; 3 | 4 | describe("unicode test", () => { 5 | describe("poop", () => { 6 | const xml = "💩"; 7 | const expect = [ 8 | ["opentagstart", { name: "a", attributes: {} }], 9 | ["opentag", { name: "a", attributes: {}, isSelfClosing: false }], 10 | ["text", "💩"], 11 | ["closetag", { name: "a", attributes: {}, isSelfClosing: false }], 12 | ]; 13 | 14 | test({ 15 | name: "intact", 16 | xml, 17 | expect, 18 | }); 19 | 20 | test({ 21 | name: "sliced", 22 | fn(parser: SaxesParser): void { 23 | // This test purposely slices the string into the poop character. 24 | parser.write(xml.slice(0, 4)); 25 | parser.write(xml.slice(4)); 26 | parser.close(); 27 | }, 28 | expect, 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/wrong-cdata-closure.ts: -------------------------------------------------------------------------------- 1 | import { SaxesParser } from "../build/dist/saxes"; 2 | import { test } from "./testutil"; 3 | 4 | const xml = "somethingx]]>moo"; 5 | 6 | describe("wrong cdata closure", () => { 7 | test({ 8 | name: "one shot", 9 | xml, 10 | expect: [ 11 | ["opentagstart", { 12 | name: "span", 13 | attributes: {}, 14 | }], 15 | ["opentag", { 16 | name: "span", 17 | attributes: {}, 18 | isSelfClosing: false, 19 | }], 20 | ["error", 21 | "1:19: the string \"]]>\" is disallowed in char data."], 22 | ["text", "somethingx]]>moo"], 23 | ["closetag", { 24 | name: "span", 25 | attributes: {}, 26 | isSelfClosing: false, 27 | }], 28 | ["end", undefined], 29 | ["ready", undefined], 30 | ], 31 | opt: {}, 32 | }); 33 | 34 | test({ 35 | name: "one-by-one", 36 | fn(parser: SaxesParser): void { 37 | for (const x of xml) { 38 | parser.write(x); 39 | } 40 | parser.close(); 41 | }, 42 | expect: [ 43 | ["opentagstart", { 44 | name: "span", 45 | attributes: {}, 46 | }], 47 | ["opentag", { 48 | name: "span", 49 | attributes: {}, 50 | isSelfClosing: false, 51 | }], 52 | ["error", 53 | "1:19: the string \"]]>\" is disallowed in char data."], 54 | ["text", "somethingx]]>moo"], 55 | ["closetag", { 56 | name: "span", 57 | attributes: {}, 58 | isSelfClosing: false, 59 | }], 60 | ["end", undefined], 61 | ["ready", undefined], 62 | ], 63 | opt: {}, 64 | }); 65 | 66 | test({ 67 | name: "two-by-two", 68 | fn(parser: SaxesParser): void { 69 | for (let ix = 0; ix < xml.length; ix += 2) { 70 | parser.write(xml.slice(ix, ix + 2)); 71 | } 72 | parser.close(); 73 | }, 74 | expect: [ 75 | ["opentagstart", { 76 | name: "span", 77 | attributes: {}, 78 | }], 79 | ["opentag", { 80 | name: "span", 81 | attributes: {}, 82 | isSelfClosing: false, 83 | }], 84 | ["error", 85 | "1:19: the string \"]]>\" is disallowed in char data."], 86 | ["text", "somethingx]]>moo"], 87 | ["closetag", { 88 | name: "span", 89 | attributes: {}, 90 | isSelfClosing: false, 91 | }], 92 | ["end", undefined], 93 | ["ready", undefined], 94 | ], 95 | opt: {}, 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/xml-declaration.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | import { SaxesOptions, SaxesParser } from "../build/dist/saxes"; 4 | import { test } from "./testutil"; 5 | 6 | const XML_1_0_DECLARATION = ""; 7 | const XML_1_1_DECLARATION = ""; 8 | 9 | const WELL_FORMED_1_0_NOT_1_1 = "\u007F"; 10 | const WELL_FORMED_1_1_NOT_1_0 = ""; 11 | 12 | type UnforcedExpectation = { version?: "1.0" | "1.1"; expectError: boolean }; 13 | type ForcedExpectation = { version: "1.0" | "1.1"; expectError: boolean }; 14 | 15 | describe("xml declaration", () => { 16 | test({ 17 | name: "empty declaration", 18 | xml: "", 19 | expect: [ 20 | ["error", "1:7: XML declaration must contain a version."], 21 | [ 22 | "xmldecl", 23 | { 24 | encoding: undefined, 25 | version: undefined, 26 | standalone: undefined, 27 | }, 28 | ], 29 | ["opentagstart", { name: "root", attributes: {}, ns: {} }], 30 | [ 31 | "opentag", 32 | { 33 | name: "root", 34 | prefix: "", 35 | local: "root", 36 | uri: "", 37 | attributes: {}, 38 | ns: {}, 39 | isSelfClosing: true, 40 | }, 41 | ], 42 | [ 43 | "closetag", 44 | { 45 | name: "root", 46 | prefix: "", 47 | local: "root", 48 | uri: "", 49 | attributes: {}, 50 | ns: {}, 51 | isSelfClosing: true, 52 | }, 53 | ], 54 | ], 55 | opt: { 56 | xmlns: true, 57 | }, 58 | }); 59 | 60 | test({ 61 | name: "version without equal", 62 | xml: "", 63 | expect: [ 64 | ["error", "1:14: XML declaration is incomplete."], 65 | [ 66 | "xmldecl", 67 | { 68 | encoding: undefined, 69 | version: undefined, 70 | standalone: undefined, 71 | }, 72 | ], 73 | ["opentagstart", { name: "root", attributes: {}, ns: {} }], 74 | [ 75 | "opentag", 76 | { 77 | name: "root", 78 | prefix: "", 79 | local: "root", 80 | uri: "", 81 | attributes: {}, 82 | ns: {}, 83 | isSelfClosing: true, 84 | }, 85 | ], 86 | [ 87 | "closetag", 88 | { 89 | name: "root", 90 | prefix: "", 91 | local: "root", 92 | uri: "", 93 | attributes: {}, 94 | ns: {}, 95 | isSelfClosing: true, 96 | }, 97 | ], 98 | ], 99 | opt: { 100 | xmlns: true, 101 | }, 102 | }); 103 | 104 | test({ 105 | name: "version without value", 106 | xml: "", 107 | expect: [ 108 | ["error", "1:15: XML declaration is incomplete."], 109 | [ 110 | "xmldecl", 111 | { 112 | encoding: undefined, 113 | version: undefined, 114 | standalone: undefined, 115 | }, 116 | ], 117 | ["opentagstart", { name: "root", attributes: {}, ns: {} }], 118 | [ 119 | "opentag", 120 | { 121 | name: "root", 122 | prefix: "", 123 | local: "root", 124 | uri: "", 125 | attributes: {}, 126 | ns: {}, 127 | isSelfClosing: true, 128 | }, 129 | ], 130 | [ 131 | "closetag", 132 | { 133 | name: "root", 134 | prefix: "", 135 | local: "root", 136 | uri: "", 137 | attributes: {}, 138 | ns: {}, 139 | isSelfClosing: true, 140 | }, 141 | ], 142 | ], 143 | opt: { 144 | xmlns: true, 145 | }, 146 | }); 147 | 148 | test({ 149 | name: "unquoted value", 150 | xml: "", 151 | expect: [ 152 | ["error", "1:15: value must be quoted."], 153 | ["error", "1:16: XML declaration is incomplete."], 154 | [ 155 | "xmldecl", 156 | { 157 | encoding: undefined, 158 | version: undefined, 159 | standalone: undefined, 160 | }, 161 | ], 162 | ["opentagstart", { name: "root", attributes: {}, ns: {} }], 163 | [ 164 | "opentag", 165 | { 166 | name: "root", 167 | prefix: "", 168 | local: "root", 169 | uri: "", 170 | attributes: {}, 171 | ns: {}, 172 | isSelfClosing: true, 173 | }, 174 | ], 175 | [ 176 | "closetag", 177 | { 178 | name: "root", 179 | prefix: "", 180 | local: "root", 181 | uri: "", 182 | attributes: {}, 183 | ns: {}, 184 | isSelfClosing: true, 185 | }, 186 | ], 187 | ], 188 | opt: { 189 | xmlns: true, 190 | }, 191 | }); 192 | 193 | test({ 194 | name: "unterminated value", 195 | xml: "", 196 | expect: [ 197 | ["error", "1:17: XML declaration is incomplete."], 198 | [ 199 | "xmldecl", 200 | { 201 | encoding: undefined, 202 | version: undefined, 203 | standalone: undefined, 204 | }, 205 | ], 206 | ["opentagstart", { name: "root", attributes: {}, ns: {} }], 207 | [ 208 | "opentag", 209 | { 210 | name: "root", 211 | prefix: "", 212 | local: "root", 213 | uri: "", 214 | attributes: {}, 215 | ns: {}, 216 | isSelfClosing: true, 217 | }, 218 | ], 219 | [ 220 | "closetag", 221 | { 222 | name: "root", 223 | prefix: "", 224 | local: "root", 225 | uri: "", 226 | attributes: {}, 227 | ns: {}, 228 | isSelfClosing: true, 229 | }, 230 | ], 231 | ], 232 | opt: { 233 | xmlns: true, 234 | }, 235 | }); 236 | 237 | test({ 238 | name: "bad version", 239 | xml: "", 240 | expect: [ 241 | ["error", "1:17: version number must match /^1\\.[0-9]+$/."], 242 | [ 243 | "xmldecl", 244 | { 245 | encoding: undefined, 246 | version: "a", 247 | standalone: undefined, 248 | }, 249 | ], 250 | ["opentagstart", { name: "root", attributes: {}, ns: {} }], 251 | [ 252 | "opentag", 253 | { 254 | name: "root", 255 | prefix: "", 256 | local: "root", 257 | uri: "", 258 | attributes: {}, 259 | ns: {}, 260 | isSelfClosing: true, 261 | }, 262 | ], 263 | [ 264 | "closetag", 265 | { 266 | name: "root", 267 | prefix: "", 268 | local: "root", 269 | uri: "", 270 | attributes: {}, 271 | ns: {}, 272 | isSelfClosing: true, 273 | }, 274 | ], 275 | ], 276 | opt: { 277 | xmlns: true, 278 | }, 279 | }); 280 | 281 | it("well-formed", () => { 282 | const parser = new SaxesParser(); 283 | let seen = false; 284 | parser.on("opentagstart", () => { 285 | expect(parser.xmlDecl).to.deep.equal({ 286 | version: "1.1", 287 | encoding: "utf-8", 288 | standalone: "yes", 289 | }); 290 | seen = true; 291 | }); 292 | parser.write( 293 | ""); 294 | parser.close(); 295 | expect(seen).to.be.true; 296 | }); 297 | 298 | function parse(source: string, options?: SaxesOptions): boolean { 299 | const parser = new SaxesParser(options); 300 | let error = false; 301 | parser.on("error", () => { 302 | error = true; 303 | }); 304 | parser.write(source); 305 | parser.close(); 306 | return error; 307 | } 308 | 309 | function makeDefaultXMLVersionTests( 310 | groupName: string, 311 | xmlDeclaration: string, 312 | document: string, 313 | expecteUnforcedResults: UnforcedExpectation[], 314 | expectedForcedResults: ForcedExpectation[] = []): void { 315 | describe(groupName, () => { 316 | for (const { version, expectError } of expecteUnforcedResults) { 317 | const errorLabel = expectError ? "errors" : "no errors"; 318 | const title = version === undefined ? 319 | `and without defaultXMLVersion: ${errorLabel}` : 320 | `and with defaultXMLVersion === ${version}: ${errorLabel}`; 321 | 322 | it(title, () => { 323 | expect(parse( 324 | xmlDeclaration + document, 325 | version === undefined ? undefined : { defaultXMLVersion: version })) 326 | .to.equal(expectError); 327 | }); 328 | } 329 | 330 | if (xmlDeclaration !== "") { 331 | for (const { version, expectError } of expectedForcedResults) { 332 | const errorLabel = expectError ? "errors" : "no errors"; 333 | it(`and with forced xml version ${version}: ${errorLabel}`, () => { 334 | expect(parse(xmlDeclaration + document, { 335 | defaultXMLVersion: version, 336 | forceXMLVersion: true, 337 | })).to.equal(expectError); 338 | }); 339 | } 340 | } 341 | }); 342 | } 343 | 344 | describe("document well-formed for 1.0, not 1.1", () => { 345 | makeDefaultXMLVersionTests("without XML declaration", "", 346 | WELL_FORMED_1_0_NOT_1_1, [{ 347 | version: undefined, 348 | expectError: false, 349 | }, { 350 | version: "1.0", 351 | expectError: false, 352 | }, { 353 | version: "1.1", 354 | expectError: true, 355 | }]); 356 | 357 | makeDefaultXMLVersionTests("with XML 1.0 declaration", XML_1_0_DECLARATION, 358 | WELL_FORMED_1_0_NOT_1_1, [{ 359 | version: undefined, 360 | expectError: false, 361 | }, { 362 | version: "1.0", 363 | expectError: false, 364 | }, { 365 | version: "1.1", 366 | // The XML declaration overrides 367 | // defaultXMLVersion. 368 | expectError: false, 369 | }], [{ 370 | version: "1.0", 371 | expectError: false, 372 | }, { 373 | version: "1.1", 374 | expectError: true, 375 | }]); 376 | 377 | makeDefaultXMLVersionTests("with XML 1.1 declaration", XML_1_1_DECLARATION, 378 | WELL_FORMED_1_0_NOT_1_1, [{ 379 | version: undefined, 380 | // The XML declaration overrides 381 | // defaultXMLVersion. 382 | expectError: true, 383 | }, { 384 | version: "1.0", 385 | // The XML declaration overrides 386 | // defaultXMLVersion. 387 | expectError: true, 388 | }, { 389 | version: "1.1", 390 | expectError: true, 391 | }], [{ 392 | version: "1.0", 393 | expectError: false, 394 | }, { 395 | version: "1.1", 396 | expectError: true, 397 | }]); 398 | }); 399 | 400 | describe("document well-formed for 1.1, not 1.0", () => { 401 | makeDefaultXMLVersionTests("without XML declaration", "", 402 | WELL_FORMED_1_1_NOT_1_0, [{ 403 | version: undefined, 404 | expectError: true, 405 | }, { 406 | version: "1.0", 407 | expectError: true, 408 | }, { 409 | version: "1.1", 410 | expectError: false, 411 | }]); 412 | 413 | makeDefaultXMLVersionTests("with XML 1.0 declaration", 414 | XML_1_0_DECLARATION, 415 | WELL_FORMED_1_1_NOT_1_0, [{ 416 | version: undefined, 417 | expectError: true, 418 | }, { 419 | version: "1.0", 420 | expectError: true, 421 | }, { 422 | version: "1.1", 423 | // The XML declaration overrides 424 | // defaultXMLVersion. 425 | expectError: true, 426 | }], [{ 427 | version: "1.0", 428 | expectError: true, 429 | }, { 430 | version: "1.1", 431 | expectError: false, 432 | }]); 433 | 434 | makeDefaultXMLVersionTests("with XML 1.1 declaration", 435 | XML_1_1_DECLARATION, 436 | WELL_FORMED_1_1_NOT_1_0, [{ 437 | version: undefined, 438 | // The XML declaration overrides 439 | // defaultXMLVersion. 440 | expectError: false, 441 | }, { 442 | version: "1.0", 443 | // The XML declaration overrides 444 | // defaultXMLVersion. 445 | expectError: false, 446 | }, { 447 | version: "1.1", 448 | expectError: false, 449 | }], [{ 450 | version: "1.0", 451 | expectError: true, 452 | }, { 453 | version: "1.1", 454 | expectError: false, 455 | }]); 456 | }); 457 | }); 458 | -------------------------------------------------------------------------------- /test/xml-internal-entities.ts: -------------------------------------------------------------------------------- 1 | import { SaxesParser } from "../build/dist/saxes"; 2 | import { test } from "./testutil"; 3 | 4 | // generates xml like test0="&control;" 5 | const entitiesToTest: Record = { 6 | // 'ENTITY_NAME': IS_VALID || invalidCharPos, 7 | "control0": true, // This is a vanilla control. 8 | // entityStart 9 | "_uscore": true, 10 | "#hash": false, 11 | ":colon": true, 12 | "-bad": false, 13 | ".bad": false, 14 | // general entity 15 | "u_score": true, 16 | "d-ash": true, 17 | "d.ot": true, 18 | "all:_#-.": false, 19 | }; 20 | 21 | let xmlStart = " = {}; 23 | myAttributes.test = "&"; 24 | 25 | let entI = 0; 26 | 27 | const attributeErrors = []; 28 | const ENTITIES: Record = {}; 29 | // eslint-disable-next-line guard-for-in 30 | for (const entity in entitiesToTest) { 31 | const attribName = `test${entI}`; 32 | const attribValue = `Testing ${entity}`; 33 | 34 | // add the first part to use in calculation below 35 | xmlStart += `${attribName}="&`; 36 | 37 | if (!entitiesToTest[entity]) { 38 | const pos = xmlStart.length + entity.length + 1; 39 | 40 | const msg = entity[0] === "#" ? "malformed character entity." : 41 | "disallowed character in entity name."; 42 | attributeErrors.push(["error", `1:${pos}: ${msg}`]); 43 | myAttributes[attribName] = `&${entity};`; 44 | } 45 | else { 46 | ENTITIES[entity] = attribValue; 47 | myAttributes[attribName] = attribValue; 48 | } 49 | 50 | xmlStart += `${entity};" `; 51 | entI++; 52 | } 53 | 54 | test({ 55 | name: "xml internal entities", 56 | expect: [ 57 | ...attributeErrors, 58 | ], 59 | // We only care about errrors for this test. 60 | events: ["error"], 61 | fn(parser: SaxesParser): void { 62 | Object.assign(parser.ENTITIES, ENTITIES); 63 | parser.write(`${xmlStart}/>`).close(); 64 | }, 65 | }); 66 | -------------------------------------------------------------------------------- /test/xmlns-as-tag-name.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "xmlns as tag name", 5 | xml: "", 6 | expect: [ 7 | [ 8 | "opentagstart", 9 | { 10 | name: "xmlns", 11 | attributes: {}, 12 | ns: {}, 13 | }, 14 | ], 15 | [ 16 | "opentag", 17 | { 18 | name: "xmlns", 19 | uri: "", 20 | prefix: "", 21 | local: "xmlns", 22 | attributes: {}, 23 | ns: {}, 24 | isSelfClosing: true, 25 | }, 26 | ], 27 | [ 28 | "closetag", 29 | { 30 | name: "xmlns", 31 | uri: "", 32 | prefix: "", 33 | local: "xmlns", 34 | attributes: {}, 35 | ns: {}, 36 | isSelfClosing: true, 37 | }, 38 | ], 39 | ], 40 | opt: { 41 | xmlns: true, 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /test/xmlns-issue-41.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | const expect = [ 4 | ["opentagstart", { name: "parent", attributes: {}, ns: {} }], 5 | [ 6 | "attribute", 7 | { 8 | name: "xmlns:a", 9 | local: "a", 10 | prefix: "xmlns", 11 | value: "http://ATTRIBUTE", 12 | }, 13 | ], 14 | [ 15 | "attribute", 16 | { 17 | name: "a:attr", 18 | local: "attr", 19 | prefix: "a", 20 | value: "value", 21 | }, 22 | ], 23 | [ 24 | "opentag", 25 | { 26 | name: "parent", 27 | uri: "", 28 | prefix: "", 29 | local: "parent", 30 | attributes: { 31 | "a:attr": { 32 | name: "a:attr", 33 | local: "attr", 34 | prefix: "a", 35 | uri: "http://ATTRIBUTE", 36 | value: "value", 37 | }, 38 | "xmlns:a": { 39 | name: "xmlns:a", 40 | local: "a", 41 | prefix: "xmlns", 42 | uri: "http://www.w3.org/2000/xmlns/", 43 | value: "http://ATTRIBUTE", 44 | }, 45 | }, 46 | ns: { 47 | a: "http://ATTRIBUTE", 48 | }, 49 | isSelfClosing: true, 50 | }, 51 | ], 52 | [ 53 | "closetag", 54 | { 55 | name: "parent", 56 | uri: "", 57 | prefix: "", 58 | local: "parent", 59 | attributes: { 60 | "a:attr": { 61 | name: "a:attr", 62 | local: "attr", 63 | prefix: "a", 64 | uri: "http://ATTRIBUTE", 65 | value: "value", 66 | }, 67 | "xmlns:a": { 68 | name: "xmlns:a", 69 | local: "a", 70 | prefix: "xmlns", 71 | uri: "http://www.w3.org/2000/xmlns/", 72 | value: "http://ATTRIBUTE", 73 | }, 74 | }, 75 | ns: { 76 | a: "http://ATTRIBUTE", 77 | }, 78 | isSelfClosing: true, 79 | }, 80 | ], 81 | ]; 82 | 83 | // should be the same both ways. 84 | const xmls = [ 85 | "", 86 | "", 87 | ]; 88 | // Take the first expect array and create a new one with elements at indexes 1 89 | // and 2 swapped. 90 | const expect2 = expect.slice(); 91 | const tmp = expect2[1]; 92 | // eslint-disable-next-line prefer-destructuring 93 | expect2[1] = expect2[2]; 94 | expect2[2] = tmp; 95 | const expects = [ 96 | expect, 97 | expect2, 98 | ]; 99 | describe("issue 41", () => { 100 | xmls.forEach((x, i) => { 101 | test({ 102 | name: `order ${i}`, 103 | xml: x, 104 | expect: expects[i], 105 | opt: { 106 | xmlns: true, 107 | }, 108 | }); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /test/xmlns-rebinding.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "xmlns rebinding", 5 | xml: "" + 6 | "" + 7 | "" + 8 | "" + 9 | "" + 10 | "", 11 | expect: [ 12 | [ 13 | "opentagstart", 14 | { 15 | name: "root", 16 | attributes: {}, 17 | ns: {}, 18 | }, 19 | ], 20 | [ 21 | "attribute", 22 | { 23 | name: "xmlns:x", 24 | value: "x1", 25 | prefix: "xmlns", 26 | local: "x", 27 | }, 28 | ], 29 | [ 30 | "attribute", 31 | { 32 | name: "xmlns:y", 33 | value: "y1", 34 | prefix: "xmlns", 35 | local: "y", 36 | }, 37 | ], 38 | [ 39 | "attribute", 40 | { 41 | name: "x:a", 42 | value: "x1", 43 | prefix: "x", 44 | local: "a", 45 | }, 46 | ], 47 | [ 48 | "attribute", 49 | { 50 | name: "y:a", 51 | value: "y1", 52 | prefix: "y", 53 | local: "a", 54 | }, 55 | ], 56 | [ 57 | "opentag", 58 | { 59 | name: "root", 60 | uri: "", 61 | prefix: "", 62 | local: "root", 63 | attributes: { 64 | "xmlns:x": { 65 | name: "xmlns:x", 66 | value: "x1", 67 | uri: "http://www.w3.org/2000/xmlns/", 68 | prefix: "xmlns", 69 | local: "x", 70 | }, 71 | "xmlns:y": { 72 | name: "xmlns:y", 73 | value: "y1", 74 | uri: "http://www.w3.org/2000/xmlns/", 75 | prefix: "xmlns", 76 | local: "y", 77 | }, 78 | "x:a": { 79 | name: "x:a", 80 | value: "x1", 81 | uri: "x1", 82 | prefix: "x", 83 | local: "a", 84 | }, 85 | "y:a": { 86 | name: "y:a", 87 | value: "y1", 88 | uri: "y1", 89 | prefix: "y", 90 | local: "a", 91 | }, 92 | }, 93 | ns: { 94 | x: "x1", 95 | y: "y1", 96 | }, 97 | isSelfClosing: false, 98 | }, 99 | ], 100 | [ 101 | "opentagstart", 102 | { 103 | name: "rebind", 104 | attributes: {}, 105 | ns: {}, 106 | }, 107 | ], 108 | [ 109 | "attribute", 110 | { 111 | name: "xmlns:x", 112 | value: "x2", 113 | prefix: "xmlns", 114 | local: "x", 115 | }, 116 | ], 117 | [ 118 | "opentag", 119 | { 120 | name: "rebind", 121 | uri: "", 122 | prefix: "", 123 | local: "rebind", 124 | attributes: { 125 | "xmlns:x": { 126 | name: "xmlns:x", 127 | value: "x2", 128 | uri: "http://www.w3.org/2000/xmlns/", 129 | prefix: "xmlns", 130 | local: "x", 131 | }, 132 | }, 133 | ns: { 134 | x: "x2", 135 | }, 136 | isSelfClosing: false, 137 | }, 138 | ], 139 | [ 140 | "opentagstart", 141 | { 142 | name: "check", 143 | attributes: {}, 144 | ns: {}, 145 | }, 146 | ], 147 | [ 148 | "attribute", 149 | { 150 | name: "x:a", 151 | value: "x2", 152 | prefix: "x", 153 | local: "a", 154 | }, 155 | ], 156 | [ 157 | "attribute", 158 | { 159 | name: "y:a", 160 | value: "y1", 161 | prefix: "y", 162 | local: "a", 163 | }, 164 | ], 165 | [ 166 | "opentag", 167 | { 168 | name: "check", 169 | uri: "", 170 | prefix: "", 171 | local: "check", 172 | attributes: { 173 | "x:a": { 174 | name: "x:a", 175 | value: "x2", 176 | uri: "x2", 177 | prefix: "x", 178 | local: "a", 179 | }, 180 | "y:a": { 181 | name: "y:a", 182 | value: "y1", 183 | uri: "y1", 184 | prefix: "y", 185 | local: "a", 186 | }, 187 | }, 188 | ns: {}, 189 | isSelfClosing: true, 190 | }, 191 | ], 192 | [ 193 | "closetag", 194 | { 195 | name: "check", 196 | uri: "", 197 | prefix: "", 198 | local: "check", 199 | attributes: { 200 | "x:a": { 201 | name: "x:a", 202 | value: "x2", 203 | uri: "x2", 204 | prefix: "x", 205 | local: "a", 206 | }, 207 | "y:a": { 208 | name: "y:a", 209 | value: "y1", 210 | uri: "y1", 211 | prefix: "y", 212 | local: "a", 213 | }, 214 | }, 215 | ns: {}, 216 | isSelfClosing: true, 217 | }, 218 | ], 219 | [ 220 | "closetag", 221 | { 222 | name: "rebind", 223 | uri: "", 224 | prefix: "", 225 | local: "rebind", 226 | attributes: { 227 | "xmlns:x": { 228 | name: "xmlns:x", 229 | value: "x2", 230 | uri: "http://www.w3.org/2000/xmlns/", 231 | prefix: "xmlns", 232 | local: "x", 233 | }, 234 | }, 235 | ns: { 236 | x: "x2", 237 | }, 238 | isSelfClosing: false, 239 | }, 240 | ], 241 | [ 242 | "opentagstart", 243 | { 244 | name: "check", 245 | attributes: {}, 246 | ns: {}, 247 | }, 248 | ], 249 | [ 250 | "attribute", 251 | { 252 | name: "x:a", 253 | value: "x1", 254 | prefix: "x", 255 | local: "a", 256 | }, 257 | ], 258 | [ 259 | "attribute", 260 | { 261 | name: "y:a", 262 | value: "y1", 263 | prefix: "y", 264 | local: "a", 265 | }, 266 | ], 267 | [ 268 | "opentag", 269 | { 270 | name: "check", 271 | uri: "", 272 | prefix: "", 273 | local: "check", 274 | attributes: { 275 | "x:a": { 276 | name: "x:a", 277 | value: "x1", 278 | uri: "x1", 279 | prefix: "x", 280 | local: "a", 281 | }, 282 | "y:a": { 283 | name: "y:a", 284 | value: "y1", 285 | uri: "y1", 286 | prefix: "y", 287 | local: "a", 288 | }, 289 | }, 290 | ns: {}, 291 | isSelfClosing: true, 292 | }, 293 | ], 294 | [ 295 | "closetag", 296 | { 297 | name: "check", 298 | uri: "", 299 | prefix: "", 300 | local: "check", 301 | attributes: { 302 | "x:a": { 303 | name: "x:a", 304 | value: "x1", 305 | uri: "x1", 306 | prefix: "x", 307 | local: "a", 308 | }, 309 | "y:a": { 310 | name: "y:a", 311 | value: "y1", 312 | uri: "y1", 313 | prefix: "y", 314 | local: "a", 315 | }, 316 | }, 317 | ns: {}, 318 | isSelfClosing: true, 319 | }, 320 | ], 321 | [ 322 | "closetag", 323 | { 324 | name: "root", 325 | uri: "", 326 | prefix: "", 327 | local: "root", 328 | attributes: { 329 | "xmlns:x": { 330 | name: "xmlns:x", 331 | value: "x1", 332 | uri: "http://www.w3.org/2000/xmlns/", 333 | prefix: "xmlns", 334 | local: "x", 335 | }, 336 | "xmlns:y": { 337 | name: "xmlns:y", 338 | value: "y1", 339 | uri: "http://www.w3.org/2000/xmlns/", 340 | prefix: "xmlns", 341 | local: "y", 342 | }, 343 | "x:a": { 344 | name: "x:a", 345 | value: "x1", 346 | uri: "x1", 347 | prefix: "x", 348 | local: "a", 349 | }, 350 | "y:a": { 351 | name: "y:a", 352 | value: "y1", 353 | uri: "y1", 354 | prefix: "y", 355 | local: "a", 356 | }, 357 | }, 358 | ns: { 359 | x: "x1", 360 | y: "y1", 361 | }, 362 | isSelfClosing: false, 363 | }, 364 | ], 365 | ], 366 | opt: { 367 | xmlns: true, 368 | }, 369 | }); 370 | -------------------------------------------------------------------------------- /test/xmlns-strict.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | test({ 4 | name: "xmlns strict", 5 | xml: "" + 6 | "" + 7 | "" + 8 | "" + 9 | "" + 10 | "" + 11 | "" + 12 | "" + 13 | "" + 14 | "", 15 | expect: [ 16 | [ 17 | "opentagstart", 18 | { 19 | name: "root", 20 | attributes: {}, 21 | ns: {}, 22 | }, 23 | ], 24 | [ 25 | "opentag", 26 | { 27 | name: "root", 28 | prefix: "", 29 | local: "root", 30 | uri: "", 31 | attributes: {}, 32 | ns: {}, 33 | isSelfClosing: false, 34 | }, 35 | ], 36 | [ 37 | "opentagstart", 38 | { 39 | name: "plain", 40 | attributes: {}, 41 | ns: {}, 42 | }, 43 | ], 44 | [ 45 | "attribute", 46 | { 47 | name: "attr", 48 | value: "normal", 49 | prefix: "", 50 | local: "attr", 51 | }, 52 | ], 53 | [ 54 | "opentag", 55 | { 56 | name: "plain", 57 | prefix: "", 58 | local: "plain", 59 | uri: "", 60 | attributes: { 61 | attr: { 62 | name: "attr", 63 | value: "normal", 64 | prefix: "", 65 | local: "attr", 66 | uri: "", 67 | }, 68 | }, 69 | ns: {}, 70 | isSelfClosing: true, 71 | }, 72 | ], 73 | [ 74 | "closetag", 75 | { 76 | name: "plain", 77 | prefix: "", 78 | local: "plain", 79 | uri: "", 80 | attributes: { 81 | attr: { 82 | name: "attr", 83 | value: "normal", 84 | prefix: "", 85 | local: "attr", 86 | uri: "", 87 | }, 88 | }, 89 | ns: {}, 90 | isSelfClosing: true, 91 | }, 92 | ], 93 | [ 94 | "opentagstart", 95 | { 96 | name: "ns1", 97 | attributes: {}, 98 | ns: {}, 99 | }, 100 | ], 101 | [ 102 | "attribute", 103 | { 104 | name: "xmlns", 105 | value: "uri:default", 106 | prefix: "", 107 | local: "xmlns", 108 | }, 109 | ], 110 | [ 111 | "opentag", 112 | { 113 | name: "ns1", 114 | prefix: "", 115 | local: "ns1", 116 | uri: "uri:default", 117 | attributes: { 118 | xmlns: { 119 | name: "xmlns", 120 | value: "uri:default", 121 | prefix: "", 122 | local: "xmlns", 123 | uri: "http://www.w3.org/2000/xmlns/", 124 | }, 125 | }, 126 | ns: { 127 | "": "uri:default", 128 | }, 129 | isSelfClosing: false, 130 | }, 131 | ], 132 | [ 133 | "opentagstart", 134 | { 135 | name: "plain", 136 | ns: {}, 137 | attributes: {}, 138 | }, 139 | ], 140 | [ 141 | "attribute", 142 | { 143 | name: "attr", 144 | value: "normal", 145 | prefix: "", 146 | local: "attr", 147 | }, 148 | ], 149 | [ 150 | "opentag", 151 | { 152 | name: "plain", 153 | prefix: "", 154 | local: "plain", 155 | uri: "uri:default", 156 | ns: {}, 157 | attributes: { 158 | attr: { 159 | name: "attr", 160 | value: "normal", 161 | prefix: "", 162 | local: "attr", 163 | uri: "", 164 | }, 165 | }, 166 | isSelfClosing: true, 167 | }, 168 | ], 169 | [ 170 | "closetag", 171 | { 172 | name: "plain", 173 | prefix: "", 174 | local: "plain", 175 | uri: "uri:default", 176 | ns: {}, 177 | attributes: { 178 | attr: { 179 | name: "attr", 180 | value: "normal", 181 | prefix: "", 182 | local: "attr", 183 | uri: "", 184 | }, 185 | }, 186 | isSelfClosing: true, 187 | }, 188 | ], 189 | [ 190 | "closetag", 191 | { 192 | name: "ns1", 193 | prefix: "", 194 | local: "ns1", 195 | uri: "uri:default", 196 | attributes: { 197 | xmlns: { 198 | name: "xmlns", 199 | value: "uri:default", 200 | prefix: "", 201 | local: "xmlns", 202 | uri: "http://www.w3.org/2000/xmlns/", 203 | }, 204 | }, 205 | ns: { 206 | "": "uri:default", 207 | }, 208 | isSelfClosing: false, 209 | }, 210 | ], 211 | [ 212 | "opentagstart", 213 | { 214 | name: "ns2", 215 | attributes: {}, 216 | ns: {}, 217 | }, 218 | ], 219 | [ 220 | "attribute", 221 | { 222 | name: "xmlns:a", 223 | value: "uri:nsa", 224 | prefix: "xmlns", 225 | local: "a", 226 | }, 227 | ], 228 | [ 229 | "opentag", 230 | { 231 | name: "ns2", 232 | prefix: "", 233 | local: "ns2", 234 | uri: "", 235 | attributes: { 236 | "xmlns:a": { 237 | name: "xmlns:a", 238 | value: "uri:nsa", 239 | prefix: "xmlns", 240 | local: "a", 241 | uri: "http://www.w3.org/2000/xmlns/", 242 | }, 243 | }, 244 | ns: { 245 | a: "uri:nsa", 246 | }, 247 | isSelfClosing: false, 248 | }, 249 | ], 250 | [ 251 | "opentagstart", 252 | { 253 | name: "plain", 254 | attributes: {}, 255 | ns: {}, 256 | }, 257 | ], 258 | [ 259 | "attribute", 260 | { 261 | name: "attr", 262 | value: "normal", 263 | prefix: "", 264 | local: "attr", 265 | }, 266 | ], 267 | [ 268 | "opentag", 269 | { 270 | name: "plain", 271 | prefix: "", 272 | local: "plain", 273 | uri: "", 274 | attributes: { 275 | attr: { 276 | name: "attr", 277 | value: "normal", 278 | prefix: "", 279 | local: "attr", 280 | uri: "", 281 | }, 282 | }, 283 | ns: {}, 284 | isSelfClosing: true, 285 | }, 286 | ], 287 | [ 288 | "closetag", 289 | { 290 | name: "plain", 291 | prefix: "", 292 | local: "plain", 293 | uri: "", 294 | attributes: { 295 | attr: { 296 | name: "attr", 297 | value: "normal", 298 | prefix: "", 299 | local: "attr", 300 | uri: "", 301 | }, 302 | }, 303 | ns: {}, 304 | isSelfClosing: true, 305 | }, 306 | ], 307 | [ 308 | "opentagstart", 309 | { 310 | name: "a:ns", 311 | attributes: {}, 312 | ns: {}, 313 | }, 314 | ], 315 | [ 316 | "attribute", 317 | { 318 | name: "a:attr", 319 | value: "namespaced", 320 | prefix: "a", 321 | local: "attr", 322 | }, 323 | ], 324 | [ 325 | "opentag", 326 | { 327 | name: "a:ns", 328 | prefix: "a", 329 | local: "ns", 330 | uri: "uri:nsa", 331 | attributes: { 332 | "a:attr": { 333 | name: "a:attr", 334 | value: "namespaced", 335 | prefix: "a", 336 | local: "attr", 337 | uri: "uri:nsa", 338 | }, 339 | }, 340 | ns: {}, 341 | isSelfClosing: true, 342 | }, 343 | ], 344 | [ 345 | "closetag", 346 | { 347 | name: "a:ns", 348 | prefix: "a", 349 | local: "ns", 350 | uri: "uri:nsa", 351 | attributes: { 352 | "a:attr": { 353 | name: "a:attr", 354 | value: "namespaced", 355 | prefix: "a", 356 | local: "attr", 357 | uri: "uri:nsa", 358 | }, 359 | }, 360 | ns: {}, 361 | isSelfClosing: true, 362 | }, 363 | ], 364 | [ 365 | "closetag", 366 | { 367 | name: "ns2", 368 | prefix: "", 369 | local: "ns2", 370 | uri: "", 371 | attributes: { 372 | "xmlns:a": { 373 | name: "xmlns:a", 374 | value: "uri:nsa", 375 | prefix: "xmlns", 376 | local: "a", 377 | uri: "http://www.w3.org/2000/xmlns/", 378 | }, 379 | }, 380 | ns: { 381 | a: "uri:nsa", 382 | }, 383 | isSelfClosing: false, 384 | }, 385 | ], 386 | [ 387 | "closetag", 388 | { 389 | name: "root", 390 | prefix: "", 391 | local: "root", 392 | uri: "", 393 | attributes: {}, 394 | ns: {}, 395 | isSelfClosing: false, 396 | }, 397 | ], 398 | ], 399 | opt: { 400 | xmlns: true, 401 | }, 402 | }); 403 | -------------------------------------------------------------------------------- /test/xmlns-unbound.ts: -------------------------------------------------------------------------------- 1 | import { SaxesParser } from "../build/dist/saxes"; 2 | import { test } from "./testutil"; 3 | 4 | describe("xmlns unbound prefixes", () => { 5 | test({ 6 | name: "unbound element", 7 | opt: { xmlns: true }, 8 | expect: [ 9 | [ 10 | "opentagstart", 11 | { 12 | name: "unbound:root", 13 | attributes: {}, 14 | ns: {}, 15 | }, 16 | ], 17 | [ 18 | "error", 19 | "1:15: unbound namespace prefix: \"unbound\".", 20 | ], 21 | [ 22 | "opentag", 23 | { 24 | name: "unbound:root", 25 | uri: "unbound", 26 | prefix: "unbound", 27 | local: "root", 28 | attributes: {}, 29 | ns: {}, 30 | isSelfClosing: true, 31 | }, 32 | ], 33 | [ 34 | "closetag", 35 | { 36 | name: "unbound:root", 37 | uri: "unbound", 38 | prefix: "unbound", 39 | local: "root", 40 | attributes: {}, 41 | ns: {}, 42 | isSelfClosing: true, 43 | }, 44 | ], 45 | ], 46 | fn(parser: SaxesParser): void { 47 | parser.write(""); 48 | }, 49 | }); 50 | 51 | test({ 52 | name: "bound element", 53 | opt: { 54 | xmlns: true, 55 | }, 56 | expect: [ 57 | [ 58 | "opentagstart", 59 | { 60 | name: "unbound:root", 61 | attributes: {}, 62 | ns: {}, 63 | }, 64 | ], 65 | [ 66 | "attribute", 67 | { 68 | name: "xmlns:unbound", 69 | value: "someuri", 70 | prefix: "xmlns", 71 | local: "unbound", 72 | }, 73 | ], 74 | [ 75 | "opentag", 76 | { 77 | name: "unbound:root", 78 | uri: "someuri", 79 | prefix: "unbound", 80 | local: "root", 81 | attributes: { 82 | "xmlns:unbound": { 83 | name: "xmlns:unbound", 84 | value: "someuri", 85 | prefix: "xmlns", 86 | local: "unbound", 87 | uri: "http://www.w3.org/2000/xmlns/", 88 | }, 89 | }, 90 | ns: { 91 | unbound: "someuri", 92 | }, 93 | isSelfClosing: true, 94 | }, 95 | ], 96 | [ 97 | "closetag", 98 | { 99 | name: "unbound:root", 100 | uri: "someuri", 101 | prefix: "unbound", 102 | local: "root", 103 | attributes: { 104 | "xmlns:unbound": { 105 | name: "xmlns:unbound", 106 | value: "someuri", 107 | prefix: "xmlns", 108 | local: "unbound", 109 | uri: "http://www.w3.org/2000/xmlns/", 110 | }, 111 | }, 112 | ns: { 113 | unbound: "someuri", 114 | }, 115 | isSelfClosing: true, 116 | }, 117 | ], 118 | ], 119 | fn(parser: SaxesParser): void { 120 | parser.write(""); 121 | }, 122 | }); 123 | 124 | test({ 125 | name: "unbound attribute", 126 | opt: { xmlns: true }, 127 | expect: [ 128 | [ 129 | "opentagstart", 130 | { 131 | name: "root", 132 | attributes: {}, 133 | ns: {}, 134 | }, 135 | ], 136 | [ 137 | "attribute", 138 | { 139 | name: "unbound:attr", 140 | value: "value", 141 | prefix: "unbound", 142 | local: "attr", 143 | }, 144 | ], 145 | [ 146 | "error", 147 | "1:28: unbound namespace prefix: \"unbound\".", 148 | ], 149 | [ 150 | "opentag", 151 | { 152 | name: "root", 153 | uri: "", 154 | prefix: "", 155 | local: "root", 156 | attributes: { 157 | "unbound:attr": { 158 | name: "unbound:attr", 159 | value: "value", 160 | uri: "unbound", 161 | prefix: "unbound", 162 | local: "attr", 163 | }, 164 | }, 165 | ns: {}, 166 | isSelfClosing: true, 167 | }, 168 | ], 169 | [ 170 | "closetag", 171 | { 172 | name: "root", 173 | uri: "", 174 | prefix: "", 175 | local: "root", 176 | attributes: { 177 | "unbound:attr": { 178 | name: "unbound:attr", 179 | value: "value", 180 | uri: "unbound", 181 | prefix: "unbound", 182 | local: "attr", 183 | }, 184 | }, 185 | ns: {}, 186 | isSelfClosing: true, 187 | }, 188 | ], 189 | ], 190 | fn(parser: SaxesParser): void { 191 | parser.write(""); 192 | }, 193 | }); 194 | }); 195 | -------------------------------------------------------------------------------- /test/xmlns-xml-default-ns.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | const xmlnsAttr = { 4 | name: "xmlns", 5 | value: "http://foo", 6 | prefix: "", 7 | local: "xmlns", 8 | uri: "http://www.w3.org/2000/xmlns/", 9 | }; 10 | 11 | const attrAttr = { 12 | name: "attr", 13 | value: "bar", 14 | prefix: "", 15 | local: "attr", 16 | uri: "", 17 | }; 18 | 19 | test({ 20 | name: "xmlns set default namespace", 21 | xml: "", 22 | expect: [ 23 | [ 24 | "opentagstart", 25 | { 26 | name: "elm", 27 | attributes: {}, 28 | ns: {}, 29 | }, 30 | ], 31 | [ 32 | "attribute", 33 | { 34 | name: "xmlns", 35 | value: "http://foo", 36 | prefix: "", 37 | local: "xmlns", 38 | }, 39 | ], 40 | [ 41 | "attribute", 42 | { 43 | name: "attr", 44 | value: "bar", 45 | prefix: "", 46 | local: "attr", 47 | }, 48 | ], 49 | [ 50 | "opentag", 51 | { 52 | name: "elm", 53 | prefix: "", 54 | local: "elm", 55 | uri: "http://foo", 56 | ns: { "": "http://foo" }, 57 | attributes: { 58 | xmlns: xmlnsAttr, 59 | attr: attrAttr, 60 | }, 61 | isSelfClosing: true, 62 | }, 63 | ], 64 | [ 65 | "closetag", 66 | { 67 | name: "elm", 68 | prefix: "", 69 | local: "elm", 70 | uri: "http://foo", 71 | ns: { "": "http://foo" }, 72 | attributes: { 73 | xmlns: xmlnsAttr, 74 | attr: attrAttr, 75 | }, 76 | isSelfClosing: true, 77 | }, 78 | ], 79 | ], 80 | opt: { 81 | xmlns: true, 82 | }, 83 | }); 84 | -------------------------------------------------------------------------------- /test/xmlns-xml-default-prefix.ts: -------------------------------------------------------------------------------- 1 | import { test } from "./testutil"; 2 | 3 | describe("xml default prefix", () => { 4 | test({ 5 | name: "element", 6 | xml: "", 7 | expect: [ 8 | [ 9 | "opentagstart", 10 | { 11 | name: "xml:root", 12 | attributes: {}, 13 | ns: {}, 14 | }, 15 | ], 16 | [ 17 | "opentag", 18 | { 19 | name: "xml:root", 20 | uri: "http://www.w3.org/XML/1998/namespace", 21 | prefix: "xml", 22 | local: "root", 23 | attributes: {}, 24 | ns: {}, 25 | isSelfClosing: true, 26 | }, 27 | ], 28 | [ 29 | "closetag", 30 | { 31 | name: "xml:root", 32 | uri: "http://www.w3.org/XML/1998/namespace", 33 | prefix: "xml", 34 | local: "root", 35 | attributes: {}, 36 | ns: {}, 37 | isSelfClosing: true, 38 | }, 39 | ], 40 | ], 41 | opt: { xmlns: true }, 42 | }); 43 | 44 | test({ 45 | name: "attribute", 46 | xml: "", 47 | expect: [ 48 | [ 49 | "opentagstart", 50 | { 51 | name: "root", 52 | attributes: {}, 53 | ns: {}, 54 | }, 55 | ], 56 | [ 57 | "attribute", 58 | { 59 | name: "xml:lang", 60 | local: "lang", 61 | prefix: "xml", 62 | value: "en", 63 | }, 64 | ], 65 | [ 66 | "opentag", 67 | { 68 | name: "root", 69 | uri: "", 70 | prefix: "", 71 | local: "root", 72 | attributes: { 73 | "xml:lang": { 74 | name: "xml:lang", 75 | local: "lang", 76 | prefix: "xml", 77 | uri: "http://www.w3.org/XML/1998/namespace", 78 | value: "en", 79 | }, 80 | }, 81 | ns: {}, 82 | isSelfClosing: true, 83 | }, 84 | ], 85 | [ 86 | "closetag", 87 | { 88 | name: "root", 89 | uri: "", 90 | prefix: "", 91 | local: "root", 92 | attributes: { 93 | "xml:lang": { 94 | name: "xml:lang", 95 | local: "lang", 96 | prefix: "xml", 97 | uri: "http://www.w3.org/XML/1998/namespace", 98 | value: "en", 99 | }, 100 | }, 101 | ns: {}, 102 | isSelfClosing: true, 103 | }, 104 | ], 105 | ], 106 | opt: { xmlns: true }, 107 | }); 108 | 109 | test({ 110 | name: "cannot be redefined", 111 | xml: "", 112 | expect: [ 113 | [ 114 | "opentagstart", 115 | { 116 | name: "xml:root", 117 | attributes: {}, 118 | ns: {}, 119 | }, 120 | ], 121 | [ 122 | "attribute", 123 | { 124 | name: "xmlns:xml", 125 | local: "xml", 126 | prefix: "xmlns", 127 | value: "ERROR", 128 | }, 129 | ], 130 | [ 131 | "error", 132 | "1:27: xml prefix must be bound to \ 133 | http://www.w3.org/XML/1998/namespace.", 134 | ], 135 | [ 136 | "opentag", 137 | { 138 | name: "xml:root", 139 | uri: "ERROR", 140 | prefix: "xml", 141 | local: "root", 142 | attributes: { 143 | "xmlns:xml": { 144 | name: "xmlns:xml", 145 | local: "xml", 146 | prefix: "xmlns", 147 | uri: "http://www.w3.org/2000/xmlns/", 148 | value: "ERROR", 149 | }, 150 | }, 151 | ns: { 152 | xml: "ERROR", 153 | }, 154 | isSelfClosing: true, 155 | }, 156 | ], 157 | [ 158 | "closetag", 159 | { 160 | name: "xml:root", 161 | uri: "ERROR", 162 | prefix: "xml", 163 | local: "root", 164 | attributes: { 165 | "xmlns:xml": { 166 | name: "xmlns:xml", 167 | local: "xml", 168 | prefix: "xmlns", 169 | uri: "http://www.w3.org/2000/xmlns/", 170 | value: "ERROR", 171 | }, 172 | }, 173 | ns: { 174 | xml: "ERROR", 175 | }, 176 | isSelfClosing: true, 177 | }, 178 | ], 179 | ], 180 | opt: { xmlns: true }, 181 | }); 182 | }); 183 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "downlevelIteration": true, 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "declaration": true, 8 | "sourceMap": true, 9 | "outDir": "build/dist/", 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "lib": ["es2016", "dom"] 15 | }, 16 | "include": [ 17 | "src/**/*.ts" 18 | ] 19 | } 20 | --------------------------------------------------------------------------------