├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── extract.js ├── index.js ├── package.json ├── template-parse.js ├── template-parser.js ├── template-safe-parse.js ├── template-safe-parser.js ├── template-tokenize.js └── test ├── comment.js ├── html.js ├── index.js ├── interpolation.js ├── safe-parser.js ├── vue.js └── xslt.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/node 2 | 3 | ### Node ### 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | 64 | 65 | # End of https://www.gitignore.io/api/node 66 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pid 3 | *.seed 4 | .editorconfig 5 | .eslintrc* 6 | .git 7 | .gitignore 8 | .grunt 9 | .lock-wscript 10 | .node_repl_history 11 | .nyc_output 12 | .stylelintrc* 13 | .travis.yml 14 | .vscode 15 | appveyor.yml 16 | coverage 17 | gulpfile.js 18 | lib-cov 19 | logs 20 | node_modules 21 | npm-debug.log* 22 | pids 23 | test 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: false 3 | language: node_js 4 | 5 | node_js: 6 | - stable 7 | 8 | before_install: 9 | - curl -s https://raw.githubusercontent.com/gucong3000/postcss-syntaxes/HEAD/deps.js | node 10 | 11 | after_script: 12 | - codecov 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 刘祺 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PostCSS HTML Syntax 2 | ==== 3 | 4 | [![NPM version](https://img.shields.io/npm/v/postcss-html.svg?style=flat-square)](https://www.npmjs.com/package/postcss-html) 5 | [![Travis](https://img.shields.io/travis/gucong3000/postcss-html.svg)](https://travis-ci.org/gucong3000/postcss-html) 6 | [![Travis](https://img.shields.io/travis/gucong3000/postcss-syntaxes.svg?label=integration)](https://travis-ci.org/gucong3000/postcss-syntaxes) 7 | [![Codecov](https://img.shields.io/codecov/c/github/gucong3000/postcss-html.svg)](https://codecov.io/gh/gucong3000/postcss-html) 8 | [![David](https://img.shields.io/david/gucong3000/postcss-html.svg)](https://david-dm.org/gucong3000/postcss-html) 9 | 10 | 13 | 14 | [PostCSS](https://github.com/postcss/postcss) syntax for parsing HTML (and HTML-like) 15 | - [PHP](http://php.net) 16 | - [Vue Single-File Component](https://vue-loader.vuejs.org/spec.html) 17 | - [Quick App](https://doc.quickapp.cn/framework/source-file.html) 18 | - [XSLT](https://www.w3.org/TR/xslt-30/) 19 | 20 | ## Getting Started 21 | 22 | First thing's first, install the module: 23 | 24 | ``` 25 | npm install postcss-syntax postcss-html --save-dev 26 | ``` 27 | 28 | If you want support SCSS/SASS/LESS/SugarSS syntax, you need to install the corresponding module. 29 | 30 | - SCSS: [postcss-scss](https://github.com/postcss/postcss-scss) 31 | - SASS: [postcss-sass](https://github.com/aleshaoleg/postcss-sass) 32 | - LESS: [postcss-less](https://github.com/shellscape/postcss-less) 33 | - SugarSS: [sugarss](https://github.com/postcss/sugarss) 34 | 35 | ## Use Cases 36 | 37 | ```js 38 | const postcss = require('postcss'); 39 | const syntax = require('postcss-html')({ 40 | // syntax for parse scss (non-required options) 41 | scss: require('postcss-scss'), 42 | // syntax for parse less (non-required options) 43 | less: require('postcss-less'), 44 | // syntax for parse css blocks (non-required options) 45 | css: require('postcss-safe-parser'), 46 | }); 47 | postcss(plugins).process(source, { syntax: syntax }).then(function (result) { 48 | // An alias for the result.css property. Use it with syntaxes that generate non-CSS output. 49 | result.content 50 | }); 51 | ``` 52 | 53 | If you want support SCSS/SASS/LESS/SugarSS syntax, you need to install these module: 54 | 55 | - SCSS: [postcss-scss](https://github.com/postcss/postcss-scss) 56 | - SASS: [postcss-sass](https://github.com/aleshaoleg/postcss-sass) 57 | - LESS: [postcss-less](https://github.com/shellscape/postcss-less) 58 | - SugarSS: [sugarss](https://github.com/postcss/sugarss) 59 | 60 | ## Advanced Use Cases 61 | 62 | See: [postcss-syntax](https://github.com/gucong3000/postcss-syntax) 63 | 64 | ## Turning PostCSS off from within your HTML 65 | 66 | PostCSS can be temporarily turned off by using special comments in your HTML. For example: 67 | 68 | ```html 69 | 70 | 71 | 72 | 73 | 74 | ``` 75 | 76 | ## Style Transformations 77 | 78 | The main use case of this plugin is to apply PostCSS transformations to `", 16 | "", 17 | "", 22 | "", 23 | "", 24 | "", 25 | ].join("\n"); 26 | const document = syntax.parse(html); 27 | expect(document.nodes).to.have.lengthOf(1); 28 | }); 29 | it("inline style in disable block", () => { 30 | const html = [ 31 | "", 32 | "", 33 | "", 34 | "", 35 | "", 36 | "", 37 | "", 38 | ].join("\n"); 39 | const document = syntax.parse(html); 40 | expect(document.nodes).to.have.lengthOf(1); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/html.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const postcss = require("postcss"); 5 | const syntax = require("../"); 6 | 7 | describe("html tests", () => { 8 | it("Invalid HTML", () => { 9 | const html = "<"; 10 | const document = syntax.parse(html, { 11 | from: "invalid.html", 12 | }); 13 | expect(document.source).to.haveOwnProperty("lang", "html"); 14 | expect(document.nodes).to.have.lengthOf(0); 15 | expect(document.toString()).equal(html); 16 | }); 17 | 18 | it("less", () => { 19 | const html = [ 20 | "", 21 | "", 22 | "", 27 | "", 28 | "", 29 | "
", 30 | "
", 31 | "", 32 | "", 33 | ].join("\n"); 34 | const document = syntax.parse(html, { 35 | from: "less.html", 36 | }); 37 | expect(document.source).to.haveOwnProperty("lang", "html"); 38 | expect(document.nodes).to.have.lengthOf(2); 39 | expect(document.toString()).equal(html); 40 | }); 41 | 42 | it("stringify for append node", () => { 43 | const html = [ 44 | "", 45 | "", 50 | "", 51 | ].join("\n"); 52 | return postcss([ 53 | function (root) { 54 | root.append({ 55 | selector: "b", 56 | }); 57 | }, 58 | ]).process(html, { 59 | syntax: syntax, 60 | from: "append.html", 61 | }).then(result => { 62 | expect(result.root.source).to.haveOwnProperty("lang", "html"); 63 | expect(result.content).to.equal([ 64 | "", 65 | "", 71 | "", 72 | ].join("\n")); 73 | }); 74 | }); 75 | 76 | it("stringify for prepend node", () => { 77 | const html = [ 78 | "", 79 | "", 84 | "", 85 | ].join("\n"); 86 | return postcss([ 87 | function (root) { 88 | root.prepend({ 89 | selector: "b", 90 | }); 91 | }, 92 | ]).process(html, { 93 | syntax: syntax, 94 | from: "prepend.html", 95 | }).then(result => { 96 | expect(result.root.source).to.haveOwnProperty("lang", "html"); 97 | expect(result.content).to.equal([ 98 | "", 99 | "", 105 | "", 106 | ].join("\n")); 107 | }); 108 | }); 109 | 110 | it("stringify for insertBefore node", () => { 111 | const html = [ 112 | "", 113 | "", 118 | "", 123 | "", 124 | ].join("\n"); 125 | return postcss([ 126 | function (root) { 127 | root.insertBefore(root.last, { 128 | selector: "b", 129 | }); 130 | }, 131 | ]).process(html, { 132 | syntax: syntax, 133 | from: "insertBefore.html", 134 | }).then(result => { 135 | expect(result.root.source).to.haveOwnProperty("lang", "html"); 136 | expect(result.content).to.equal([ 137 | "", 138 | "", 144 | "", 150 | "", 151 | ].join("\n")); 152 | }); 153 | }); 154 | 155 | it("stringify for insertAfter node", () => { 156 | const html = [ 157 | "", 158 | "", 163 | "", 168 | "", 169 | ].join("\n"); 170 | return postcss([ 171 | function (root) { 172 | root.insertAfter(root.first, { 173 | selector: "b", 174 | }); 175 | }, 176 | ]).process(html, { 177 | syntax: syntax, 178 | from: "insertAfter.html", 179 | }).then(result => { 180 | expect(result.root.source).to.haveOwnProperty("lang", "html"); 181 | expect(result.content).to.equal([ 182 | "", 183 | "", 189 | "", 195 | "", 196 | ].join("\n")); 197 | }); 198 | }); 199 | 200 | it("stringify for unshift node", () => { 201 | const html = [ 202 | "", 203 | "", 208 | "", 209 | ].join("\n"); 210 | return postcss([ 211 | function (root) { 212 | root.nodes.unshift(postcss.parse("b {}")); 213 | }, 214 | ]).process(html, { 215 | syntax: syntax, 216 | from: "unshift.html", 217 | }).then(result => { 218 | expect(result.root.source).to.haveOwnProperty("lang", "html"); 219 | expect(result.content).to.equal([ 220 | "", 221 | "", 226 | "", 227 | ].join("\n")); 228 | }); 229 | }); 230 | 231 | it("stringify for push node", () => { 232 | const html = [ 233 | "", 234 | "", 239 | "", 240 | ].join("\n"); 241 | 242 | return postcss([ 243 | root => { 244 | root.nodes.push(postcss.parse("b {}")); 245 | }, 246 | ]).process(html, { 247 | syntax: syntax, 248 | from: "push.html", 249 | }).then(result => { 250 | expect(result.root.source).to.haveOwnProperty("lang", "html"); 251 | expect(result.content).to.equal([ 252 | "", 253 | "", 258 | "", 259 | ].join("\n")); 260 | }); 261 | }); 262 | 263 | it("stringify for nodes array", () => { 264 | const html = [ 265 | "", 266 | "", 268 | "", 269 | ].join("\n"); 270 | return postcss([ 271 | root => { 272 | root.nodes = [postcss.parse("b {}")]; 273 | }, 274 | ]).process(html, { 275 | syntax: syntax, 276 | from: "push.html", 277 | }).then(result => { 278 | expect(result.root.source).to.haveOwnProperty("lang", "html"); 279 | expect(result.content).to.equal([ 280 | "", 281 | "", 283 | "", 284 | ].join("\n")); 285 | }); 286 | }); 287 | 288 | it(""; 290 | const document = syntax.parse(html, { 291 | from: "style_tag_in_last_line.html", 292 | }); 293 | expect(document.source).to.haveOwnProperty("lang", "html"); 294 | expect(document.nodes).to.be.lengthOf(1); 295 | expect(document.toString()).equal(html); 296 | expect(document.first.source.start.line).to.equal(2); 297 | expect(document.first.source.start.column).to.equal(8); 298 | }); 299 | 300 | it("react inline styles", () => { 301 | const html = ` 302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 | `; 311 | const document = syntax.parse(html, { 312 | from: "react_inline_styles.html", 313 | }); 314 | expect(document.source).to.haveOwnProperty("lang", "html"); 315 | expect(document.nodes).to.be.lengthOf(0); 316 | expect(document.toString()).equal(html); 317 | }); 318 | }); 319 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const postcss = require("postcss"); 5 | const syntax = require("../"); 6 | 7 | describe("API", () => { 8 | const html = [ 9 | "", 13 | "", 17 | "", 20 | ].join("\n"); 21 | 22 | it("config map object", () => { 23 | return postcss([ 24 | 25 | ]).process(html, { 26 | syntax: syntax({ 27 | css: postcss, 28 | sugarss: "sugarss", 29 | less: "postcss-less", 30 | }), 31 | from: "api.vue", 32 | }).then(result => { 33 | expect(result.root.nodes).to.have.lengthOf(3); 34 | }); 35 | }); 36 | 37 | it("single line syntax error", () => { 38 | expect(() => { 39 | syntax.parse("", { 40 | from: "SyntaxError.vue", 41 | }); 42 | }).to.throw(/SyntaxError.vue:1:8: Unclosed block\b/); 43 | }); 44 | 45 | it("single line with line ending syntax error", () => { 46 | expect(() => { 47 | syntax.parse("\n", { 48 | from: "SyntaxError.vue", 49 | }); 50 | }).to.throw(/SyntaxError.vue:1:8: Unclosed block\b/); 51 | }); 52 | 53 | it("multi line syntax error", () => { 54 | expect(() => { 55 | syntax.parse([ 56 | "", 57 | "", 58 | "", 59 | ].join("\n"), { 60 | from: "SyntaxError.html", 61 | }); 62 | }).to.throw(/SyntaxError.html:2:8: Unclosed block\b/); 63 | }); 64 | 65 | it("custom parse error", () => { 66 | expect(() => { 67 | syntax({ 68 | css: { 69 | parse: function () { 70 | throw new TypeError("custom parse error"); 71 | }, 72 | }, 73 | }).parse([ 74 | "", 75 | "", 76 | "", 77 | ].join("\n"), { 78 | from: "CustomError.html", 79 | }); 80 | }).to.throw("custom parse error"); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/interpolation.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const syntax = require("../"); 5 | 6 | describe("template interpolation", () => { 7 | it("PHP", () => { 8 | const document = syntax.parse( 9 | [ 10 | ";font-size:px\">", 11 | "", 12 | ].join("\n"), { 13 | from: "quickapp.ux", 14 | } 15 | ); 16 | 17 | expect(document.nodes).to.have.lengthOf(1); 18 | 19 | const root = document.first; 20 | expect(root.source).to.have.property("lang", "css"); 21 | expect(root.nodes).to.have.lengthOf(2); 22 | expect(root.first).to.have.property("type", "decl"); 23 | expect(root.first).to.have.property("prop", "color"); 24 | expect(root.first).to.have.property("value", "#"); 25 | expect(root.last).to.have.property("type", "decl"); 26 | expect(root.last).to.have.property("prop", "font-size"); 27 | expect(root.last).to.have.property("value", "px"); 28 | }); 29 | 30 | it("EJS", () => { 31 | const document = syntax.parse( 32 | [ 33 | ";font-size:<%= user.size %>px\">", 34 | "", 35 | ].join("\n"), { 36 | from: "quickapp.ux", 37 | } 38 | ); 39 | 40 | expect(document.nodes).to.have.lengthOf(1); 41 | 42 | const root = document.first; 43 | expect(root.source).to.have.property("lang", "css"); 44 | expect(root.nodes).to.have.lengthOf(2); 45 | expect(root.first).to.have.property("type", "decl"); 46 | expect(root.first).to.have.property("prop", "color"); 47 | expect(root.first).to.have.property("value", "#<%= user.color %>"); 48 | expect(root.last).to.have.property("type", "decl"); 49 | expect(root.last).to.have.property("prop", "font-size"); 50 | expect(root.last).to.have.property("value", "<%= user.size %>px"); 51 | }); 52 | 53 | it("Quick App", () => { 54 | const document = syntax.parse( 55 | [ 56 | "", 60 | ].join("\n"), { 61 | from: "quickapp.ux", 62 | } 63 | ); 64 | 65 | expect(document.nodes).to.have.lengthOf(2); 66 | expect(document.first.source).to.have.property("lang", "custom-template"); 67 | expect(document.last.source).to.have.property("lang", "css"); 68 | 69 | const root = document.first; 70 | expect(root.nodes).to.have.lengthOf(2); 71 | root.nodes.forEach(node => { 72 | expect(node).to.have.property("type", "decl"); 73 | }); 74 | 75 | expect(root.first).to.have.property("prop", "color"); 76 | expect(root.first).to.have.property("value", "#{{notice.color}}"); 77 | 78 | expect(root.last).to.have.property("prop", "font-size"); 79 | expect(root.last).to.have.property("value", "{{notice.font_size}}px"); 80 | }); 81 | 82 | it("VUE", () => { 83 | const document = syntax.parse( 84 | [ 85 | "", 89 | ].join("\n"), { 90 | from: "vue-sfc.vue", 91 | } 92 | ); 93 | 94 | expect(document.nodes).to.have.lengthOf(2); 95 | expect(document.first.source).to.have.property("lang", "custom-template"); 96 | expect(document.last.source).to.have.property("lang", "css"); 97 | 98 | const root = document.first; 99 | expect(root.nodes).to.have.lengthOf(2); 100 | root.nodes.forEach(node => { 101 | expect(node).to.have.property("type", "decl"); 102 | }); 103 | 104 | expect(root.first).to.have.property("prop", "color"); 105 | expect(root.first).to.have.property("value", "#{{notice.color}}"); 106 | 107 | expect(root.last).to.have.property("prop", "font-size"); 108 | expect(root.last).to.have.property("value", "{{notice.font_size}}px"); 109 | }); 110 | 111 | it("Svelte", () => { 112 | const document = syntax.parse( 113 | [ 114 | "", 115 | "", 116 | ].join("\n"), { 117 | from: "quickapp.ux", 118 | } 119 | ); 120 | expect(document.nodes).to.have.lengthOf(1); 121 | expect(document.first.source).to.have.property("lang", "custom-template"); 122 | const root = document.first; 123 | expect(root.nodes).to.have.lengthOf(1); 124 | root.nodes.forEach(node => { 125 | expect(node).to.have.property("type", "decl"); 126 | }); 127 | expect(root.first).to.have.property("prop", "display"); 128 | expect(root.first).to.have.property("value", "{ dynamicProperties }"); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /test/safe-parser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const syntax = require("../"); 5 | 6 | describe("postcss-safe-parser", () => { 7 | it("Quick App", () => { 8 | const document = syntax({ 9 | css: "postcss-safe-parser", 10 | }).parse( 11 | [ 12 | "", 15 | ].join("\n"), { 16 | from: "quickapp.ux", 17 | } 18 | ); 19 | 20 | expect(document.nodes).to.have.lengthOf(1); 21 | const root = document.first; 22 | expect(root.nodes).to.have.lengthOf(2); 23 | root.nodes.forEach(node => { 24 | expect(node).to.have.property("type", "decl"); 25 | }); 26 | 27 | // color:{{notice.color}} 28 | expect(root.first).to.have.property("prop", "color"); 29 | expect(root.first).to.have.property("value", "{{notice.color}}"); 30 | 31 | // font-size:{{notice.font_size}}px 32 | expect(root.last).to.have.property("prop", "font-size"); 33 | expect(root.last).to.have.property("value", "{{notice.font_size}}px"); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/vue.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const autoprefixer = require("autoprefixer"); 5 | const postcss = require("postcss"); 6 | const syntax = require("../")({ 7 | stylus: "css", 8 | }); 9 | 10 | describe("vue tests", () => { 11 | it("autoprefixer", () => { 12 | return postcss([ 13 | autoprefixer({ 14 | browsers: ["Chrome >= 1"], 15 | cascade: false, 16 | }), 17 | ]).process([ 18 | "", 29 | "", 33 | "", 36 | ].join("\n"), { 37 | syntax, 38 | from: "autoprefixer.vue", 39 | }).then(result => { 40 | expect(result.content).to.equal([ 41 | "", 56 | "", 60 | "", 63 | ].join("\n")); 64 | }); 65 | }); 66 | it("vue with empty ", 69 | "", 71 | "", 72 | ].join("\n"), { 73 | syntax, 74 | from: "empty.vue", 75 | }).then(result => { 76 | expect(result.root.nodes).to.have.lengthOf(2); 77 | }); 78 | }); 79 | it("vue with lang(s)", () => { 80 | const vue = [ 81 | "", 92 | "", 108 | ].join("\n"); 109 | const root = syntax.parse(vue, { 110 | from: "lang.vue", 111 | }); 112 | expect(root.nodes).to.have.lengthOf(2); 113 | expect(root.toString()).to.equal(vue); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/xslt.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const postcss = require("postcss"); 5 | const syntax = require("../"); 6 | 7 | describe("XSLT tests", () => { 8 | // https://msdn.microsoft.com/en-us/library/ms764661(v=vs.85).aspx 9 | it("Incorporating 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | `; 31 | return postcss([ 32 | ]).process(xml, { 33 | syntax: syntax, 34 | from: "XSLT.xsl", 35 | }).then(result => { 36 | expect(result.root.nodes).to.be.lengthOf(2); 37 | expect(result.root.first.nodes).to.be.lengthOf(1); 38 | expect(result.root.last.nodes).to.be.lengthOf(2); 39 | }); 40 | }); 41 | }); 42 | --------------------------------------------------------------------------------