├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── main.yaml │ └── semver-check.yaml ├── .gitignore ├── .husky └── pre-commit ├── .lgtm.yml ├── .npmignore ├── .nycrc.json ├── .releaserc.js ├── .renovaterc.json ├── .snyk ├── .tidelift.yml ├── .vscode └── launch.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── PULL_REQUEST_TEMPLATE.MD ├── README.md ├── examples ├── hast │ ├── index.js │ ├── package-lock.json │ └── package.json └── jsdom │ ├── helper.js │ ├── index.js │ ├── package-lock.json │ └── package.json ├── package-lock.json ├── package.json ├── src ├── cli.js ├── compiler │ ├── Compiler.js │ ├── DomHandler.js │ ├── ExpressionFormatter.js │ ├── ExternalTemplateLoader.js │ ├── JSCodeGenVisitor.js │ ├── JSCodeTemplate.js │ ├── JSPureTemplate.js │ ├── JSRuntimeTemplate.js │ ├── ScriptResolver.js │ └── TemplateLoader.js ├── index.js ├── main.js ├── parser │ ├── commands │ │ ├── AddAttribute.js │ │ ├── Command.js │ │ ├── CommandStream.js │ │ ├── Comment.js │ │ ├── Conditional.js │ │ ├── CreateElement.js │ │ ├── DebugCommandVisitor.js │ │ ├── Doctype.js │ │ ├── ExternalCode.js │ │ ├── FileReference.js │ │ ├── FunctionBlock.js │ │ ├── FunctionCall.js │ │ ├── Loop.js │ │ ├── OutText.js │ │ ├── OutputVariable.js │ │ ├── PopElement.js │ │ ├── PushElement.js │ │ └── VariableBinding.js │ ├── generated │ │ ├── .gitignore │ │ ├── grammar.html │ │ └── grammar.js │ ├── grammar │ │ ├── handler.js │ │ ├── lexer.js │ │ └── sightly.ne │ ├── htl │ │ ├── DebugVisitor.js │ │ ├── HTLParser.js │ │ ├── OptionHandler.js │ │ └── nodes │ │ │ ├── ArrayLiteral.js │ │ │ ├── Atom.js │ │ │ ├── BinaryOperation.js │ │ │ ├── BinaryOperator.js │ │ │ ├── BooleanConstant.js │ │ │ ├── Expression.js │ │ │ ├── ExpressionNode.js │ │ │ ├── Identifier.js │ │ │ ├── Interpolation.js │ │ │ ├── MapLiteral.js │ │ │ ├── MultiOperation.js │ │ │ ├── NullLiteral.js │ │ │ ├── NumericConstant.js │ │ │ ├── PropertyAccess.js │ │ │ ├── PropertyIdentifier.js │ │ │ ├── RuntimeCall.js │ │ │ ├── StringConstant.js │ │ │ ├── TernaryOperation.js │ │ │ ├── UnaryOperation.js │ │ │ └── UnaryOperator.js │ ├── html │ │ ├── ElementContext.js │ │ ├── ExpressionContext.js │ │ ├── ExpressionTransformer.js │ │ ├── HTMLParser.js │ │ ├── MarkupContext.js │ │ ├── MarkupHandler.js │ │ ├── Plugin.js │ │ ├── PluginContext.js │ │ ├── PluginProxy.js │ │ ├── PluginSignature.js │ │ ├── SymbolGenerator.js │ │ ├── TagTokenizer.js │ │ └── TemplateParser.js │ └── plugins │ │ ├── AttributePlugin.js │ │ ├── CallPlugin.js │ │ ├── ElementPlugin.js │ │ ├── IncludePlugin.js │ │ ├── ListPlugin.js │ │ ├── RepeatPlugin.js │ │ ├── ResourcePlugin.js │ │ ├── SetPlugin.js │ │ ├── TemplatePlugin.js │ │ ├── TestPlugin.js │ │ ├── TextPlugin.js │ │ ├── UnwrapPlugin.js │ │ └── UsePlugin.js └── runtime │ ├── DOMFactory.d.ts │ ├── DOMFactory.js │ ├── HDOMFactory.js │ ├── HtmlDOMFactory.js │ ├── Runtime.js │ ├── VDOMFactory.js │ ├── format.js │ ├── format_uri.js │ ├── format_xss.js │ ├── fsResourceLoader.js │ ├── resly.js │ └── xss_api.js └── test ├── TestHandler.js ├── compiler_test.js ├── engine_test.js ├── external_templates_test.js ├── htl_parser_test.js ├── html_parser_test.js ├── include └── main.htl ├── multi ├── list-expected.html ├── list.htl ├── tabs-expected.html ├── tabs.htl ├── templates │ ├── header.htl │ ├── item.htl │ ├── placeholder.html │ └── templateLib.html └── test-data.js ├── resly_test.js ├── runtime_test.js ├── specs ├── attribute.js ├── attribute.spec ├── attribute_hast.spec ├── attribute_jsd.spec ├── call.js ├── call.spec ├── call_hast.spec ├── call_spec │ └── recursion_test.js ├── context.js ├── context.spec ├── context_hast.spec ├── context_jsd.spec ├── dom.js ├── dom_hast.spec ├── dom_jsd.spec ├── element.js ├── element.spec ├── element_hast.spec ├── format.js ├── format.spec ├── format_hast.spec ├── include.js ├── include.spec ├── list.js ├── list.spec ├── list_hast.spec ├── repeat.js ├── repeat.spec ├── resource.js ├── resource.spec ├── resource_hast.spec ├── resource_spec │ ├── test_page.html │ └── test_page.selector.html ├── set.js ├── set.spec ├── simple.js ├── simple.spec ├── simple_hast.spec ├── template.js ├── template.spec ├── template_hast.spec ├── template_spec │ ├── button.htl │ ├── calendar.htl │ ├── group.htl │ ├── heading.htl │ ├── item.htl │ ├── jcr_root │ │ └── apps │ │ │ ├── project1 │ │ │ ├── template.htl │ │ │ └── test.htl │ │ │ └── test.htl │ ├── library.htl │ ├── lv │ │ ├── item.htl │ │ └── itemContent.htl │ ├── primaryButton.htl │ ├── template.htl │ ├── templateLib.html │ └── templateLib1.html ├── tst.js ├── tst.spec ├── tst_hast.spec ├── unwrap.js ├── unwrap.spec ├── unwrap_hast.spec ├── uri.js ├── uri.spec ├── uri_hast.spec ├── uri_jsd.spec ├── use.js ├── use.spec ├── use_hast.spec └── use_spec │ ├── test_page.js │ └── test_page_async.js ├── tag_tokenizer_test.js └── templates ├── 400kb.htm ├── 700kb.htm ├── custom_modules ├── exp.js └── src.htl ├── custom_template.js ├── fragment.htl ├── fragment_body.html ├── fragment_frag.html ├── relative_and_global ├── exp.html ├── header.htl ├── item.htl └── list.htl ├── simple.htm ├── simple2.htl ├── simple2.html ├── xss.htl ├── xss.html └── xss_unsafe.html /.eslintignore: -------------------------------------------------------------------------------- 1 | src/parser/generated 2 | test/generated 3 | examples 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | module.exports = { 14 | env: { 15 | node: true, 16 | es6: true, 17 | }, 18 | // this is the root project for all sub modules. stop searching for any 19 | // eslintrc files in parent directories. 20 | root: true, 21 | parserOptions: { 22 | sourceType: 'script', 23 | // async/await support 24 | ecmaVersion: 8, 25 | }, 26 | plugins: [ 27 | 'header', 28 | ], 29 | extends: 'airbnb-base', 30 | rules: { 31 | strict: 0, 32 | 'import/extensions': 0, 33 | 34 | // allow dangling underscores for 'fields' 35 | 'no-underscore-dangle': ['error', { allowAfterThis: true }], 36 | 37 | // enforce license header 38 | 'header/header': [2, 'block', ['', 39 | { pattern: ' * Copyright \\d{4} .*\\. All rights reserved\\.', template: ' * Copyright 2019 Adobe. All rights reserved.' }, 40 | ' * This file is licensed to you under the Apache License, Version 2.0 (the "License");', 41 | ' * you may not use this file except in compliance with the License. You may obtain a copy', 42 | ' * of the License at http://www.apache.org/licenses/LICENSE-2.0', 43 | ' *', 44 | ' * Unless required by applicable law or agreed to in writing, software distributed under', 45 | ' * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS', 46 | ' * OF ANY KIND, either express or implied. See the License for the specific language', 47 | ' * governing permissions and limitations under the License.', 48 | ' ', 49 | ]], 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push] 3 | 4 | env: 5 | CI_BUILD_NUM: ${{ github.run_id }} 6 | CI_BRANCH: ${{ github.ref_name }} 7 | 8 | jobs: 9 | test: 10 | name: Test 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Use Node.js 20.x 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: '22.x' 18 | - run: npm ci 19 | - run: git config --global user.email "test@project-helix.io" && git config --global user.name "Test Build" 20 | - run: git config --global protocol.file.allow always 21 | - run: npm run lint 22 | - run: npm test 23 | - uses: codecov/codecov-action@v5 24 | with: 25 | token: ${{ secrets.CODECOV_TOKEN }} 26 | - name: Semantic Release (Dry Run) 27 | run: npm run semantic-release-dry 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | NPM_TOKEN: ${{ secrets.ADOBE_BOT_NPM_TOKEN }} 31 | 32 | test_win: 33 | name: Test (Windows) 34 | runs-on: windows-latest 35 | steps: 36 | - run: git config --global core.autocrlf false 37 | - uses: actions/checkout@v4 38 | - name: Use Node.js 20.x 39 | uses: actions/setup-node@v4 40 | with: 41 | node-version: '22.x' 42 | - run: npm ci 43 | - run: git config --global user.email "test@project-helix.io" && git config --global user.name "Test Build" 44 | - run: git config --global protocol.file.allow always 45 | - run: npm run test 46 | 47 | release: 48 | name: Release 49 | runs-on: ubuntu-latest 50 | if: github.ref == 'refs/heads/main' 51 | needs: [test, test_win] 52 | steps: 53 | - uses: actions/checkout@v4 54 | with: 55 | persist-credentials: false 56 | - name: Use Node.js 20.x 57 | uses: actions/setup-node@v4 58 | with: 59 | node-version: '22.x' 60 | - run: npm ci 61 | - run: npm run semantic-release 62 | env: 63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 64 | NPM_TOKEN: ${{ secrets.ADOBE_BOT_NPM_TOKEN }} 65 | -------------------------------------------------------------------------------- /.github/workflows/semver-check.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches-ignore: 4 | - 'main' 5 | 6 | jobs: 7 | ci_trigger: 8 | runs-on: ubuntu-latest 9 | name: Comment Semantic Release Status 10 | steps: 11 | - name: Comment 12 | id: comment 13 | uses: adobe-rnd/github-semantic-release-comment-action@main 14 | with: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/generated/ 3 | out.js 4 | out.js.map 5 | .nyc_output 6 | coverage 7 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.lgtm.yml: -------------------------------------------------------------------------------- 1 | extraction: 2 | javascript: 3 | index: 4 | filters: 5 | - exclude: "src/compiler/JSCodeTemplate.js" 6 | - exclude: "src/compiler/JSRuntimeTemplate.js" 7 | - exclude: "src/compiler/JSPureTemplate.js" 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .circleci 2 | .github 3 | .gitignore 4 | .nyc_output 5 | .lgtm.yml 6 | out.js 7 | .DS_Store 8 | .snyk 9 | .vscode 10 | .idea 11 | .releaserc.js 12 | logs 13 | greenkeeper.json 14 | test 15 | coverage 16 | examples 17 | -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "all": false, 3 | "extension": [".js"], 4 | "reporter": [ 5 | "lcov", 6 | "text", 7 | "text-summary" 8 | ], 9 | "check-coverage": true, 10 | "lines": 95, 11 | "branches": 89, 12 | "statements": 95 13 | } 14 | -------------------------------------------------------------------------------- /.releaserc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | "@semantic-release/commit-analyzer", 4 | "@semantic-release/release-notes-generator", 5 | ["@semantic-release/changelog", { 6 | "changelogFile": "CHANGELOG.md", 7 | }], 8 | "@semantic-release/npm", 9 | ["@semantic-release/git", { 10 | "assets": ["package.json", "package-lock.json", "CHANGELOG.md"], 11 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 12 | }], 13 | ["@semantic-release/github", {}] 14 | ], 15 | branches: ['main'] 16 | }; 17 | -------------------------------------------------------------------------------- /.renovaterc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>adobe/helix-shared"], 3 | "packageRules": [ 4 | { 5 | "packageNames": ["rehype-parse"], 6 | "allowedVersions": "<8.0.0" 7 | }, 8 | { 9 | "packageNames": ["rehype-stringify"], 10 | "allowedVersions": "<9.0.0" 11 | }, 12 | { 13 | "packageNames": ["remark-rehype"], 14 | "allowedVersions": "<9.0.0" 15 | }, 16 | { 17 | "packageNames": ["remark-parse"], 18 | "allowedVersions": "<10.0.0" 19 | }, 20 | { 21 | "packageNames": ["unified"], 22 | "allowedVersions": "<10.0.0" 23 | }, 24 | { 25 | "packageNames": ["unist-util-inspect"], 26 | "allowedVersions": "<7.0.0" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.13.5 3 | ignore: {} 4 | patch: {} 5 | -------------------------------------------------------------------------------- /.tidelift.yml: -------------------------------------------------------------------------------- 1 | licensing: 2 | disallowed: 3 | - AGPL-1.0-only 4 | - AGPL-1.0-or-later 5 | - AGPL-3.0-only 6 | - AGPL-3.0-or-later 7 | - AGPL-1.0 8 | - AGPL-3.0 9 | - CC-BY-NC-ND-1.0 10 | - CC-BY-NC-ND-2.0 11 | - CC-BY-NC-ND-2.5 12 | - CC-BY-NC-ND-3.0 13 | - CC-BY-NC-ND-4.0 14 | - CC-BY-NC-SA-1.0 15 | - CC-BY-NC-SA-2.0 16 | - CC-BY-NC-SA-2.5 17 | - CC-BY-NC-SA-3.0 18 | - CC-BY-NC-SA-4.0 19 | - CC-BY-SA-1.0 20 | - CC-BY-SA-2.0 21 | - CC-BY-SA-2.5 22 | - CC-BY-SA-3.0 23 | - CC-BY-SA-4.0 24 | - GPL-1.0-only 25 | - GPL-1.0-or-later 26 | - GPL-2.0-only 27 | - GPL-2.0-or-later 28 | - GPL-3.0-only 29 | - GPL-3.0-or-later 30 | - SSPL-1.0 31 | - Sleepycat 32 | - Facebook -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Mocha All", 11 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 12 | "args": [ 13 | "--timeout", 14 | "999999", 15 | "--colors", 16 | "${workspaceFolder}/test" 17 | ], 18 | "console": "integratedTerminal", 19 | "internalConsoleOptions": "neverOpen" 20 | }, 21 | { 22 | "type": "node", 23 | "request": "launch", 24 | "name": "Mocha Current File", 25 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 26 | "args": [ 27 | "--timeout", 28 | "999999", 29 | "--colors", 30 | "${file}" 31 | ], 32 | "console": "integratedTerminal", 33 | "internalConsoleOptions": "neverOpen" 34 | }, 35 | ] 36 | } -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.MD: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Related Issue 8 | 9 | 10 | 11 | 12 | 13 | 14 | ## Motivation and Context 15 | 16 | 17 | 18 | ## How Has This Been Tested? 19 | 20 | 21 | 22 | 23 | 24 | ## Screenshots (if appropriate): 25 | 26 | ## Types of changes 27 | 28 | 29 | 30 | - [ ] Bug fix (non-breaking change which fixes an issue) 31 | - [ ] New feature (non-breaking change which adds functionality) 32 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 33 | 34 | ## Checklist: 35 | 36 | 37 | 38 | 39 | - [ ] I have signed the [Adobe Open Source CLA](http://opensource.adobe.com/cla.html). 40 | - [ ] My code follows the code style of this project. 41 | - [ ] My change requires a change to the documentation. 42 | - [ ] I have updated the documentation accordingly. 43 | - [ ] I have read the **CONTRIBUTING** document. 44 | - [ ] I have added tests to cover my changes. 45 | - [ ] All new and existing tests passed. 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTL Engine for Javascript 2 | 3 | This engine can parse [HTL](https://github.com/Adobe-Marketing-Cloud/htl-spec) scripts and builds a command stream. The command stream can either be intepreted or used to generate code. This project provides a Javascript (ES6) generator and runtime which allows to execute the scripts and use-classes. 4 | 5 | ## Status 6 | 7 | [![codecov](https://img.shields.io/codecov/c/github/adobe/htlengine.svg)](https://codecov.io/gh/adobe/htlengine) 8 | [![CircleCI](https://img.shields.io/circleci/project/github/adobe/htlengine.svg)](https://circleci.com/gh/adobe/htlengine) 9 | [![GitHub license](https://img.shields.io/github/license/adobe/htlengine.svg)](https://github.com/adobe/htlengine/blob/main/LICENSE.txt) 10 | [![GitHub issues](https://img.shields.io/github/issues/adobe/htlengine.svg)](https://github.com/adobe/htlengine/issues) 11 | 12 | [![LGTM Code Quality Grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/adobe/htlengine.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/adobe/htlengine) 13 | 14 | ## Install 15 | 16 | ```bash 17 | npm install @adobe/htlengine 18 | ``` 19 | 20 | ## Build 21 | 22 | ```bash 23 | npm install 24 | ``` 25 | 26 | ## run 27 | 28 | currently not very cool. just passes the given file into the HTML parser and outputs the tree again. 29 | 30 | ```bash 31 | node src/cli.js test/simple2.html 32 | ``` 33 | 34 | ## Webpack 35 | 36 | Compile the HTL templates wth webpack using the [htl-loader](https://github.com/backflip/htl-loader) 37 | 38 | ## API 39 | 40 | You can also use the API directly: 41 | 42 | ```javascript 43 | const { Compiler } = require('@adobe/htlengine'); 44 | 45 | const compiler = new Compiler() 46 | .withDirectory('') 47 | .includeRuntime(true) 48 | .withRuntimeGlobalName('it'); 49 | 50 | const js = await compiler.compileToString(code); 51 | // the result can be saved as a file or eval'd 52 | ``` 53 | 54 | ## examples 55 | 56 | - see [HAST Example](./examples/hast/index.js) that uses a [hast](https://github.com/syntax-tree/hast) tree as resource document. 57 | - see [JSDOM Example](./examples/jsdom/index.js) that uses a [jsdom](https://github.com/jsdom/jsdom) document as resource. 58 | 59 | ## test 60 | 61 | The tests are more comprehensive. They validate if the the HTL expressions are parsed and re-created using the generated parse tree. 62 | 63 | ```bash 64 | npm test 65 | ``` 66 | 67 | ## rebuild generated [nearley](https://nearley.js.org/) grammar 68 | 69 | ```bash 70 | npm run build 71 | ``` 72 | -------------------------------------------------------------------------------- /examples/hast/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | /* eslint-disable */ 13 | 14 | const fs = require('fs'); 15 | const path = require('path'); 16 | 17 | const unified = require('unified'); 18 | const stream = require('unified-stream'); 19 | const markdown = require('remark-parse'); 20 | const remark2rehype = require('remark-rehype'); 21 | const html = require('rehype-stringify'); 22 | 23 | const { resly } = require('@adobe/htlengine'); 24 | 25 | const code = '${dom.children[0].children[0].value}\n' 26 | + '\n' 27 | + '

Table of Contents

\n' 28 | + '\n' 31 | + ''; 32 | 33 | const processor = unified() 34 | .use(markdown) 35 | .use(remark2rehype) 36 | .use(resly({ code })) 37 | .use(html); 38 | 39 | const readme = fs.createReadStream(path.resolve(__dirname, '../../', 'README.md')); 40 | readme.pipe(stream(processor)).pipe(process.stdout); 41 | -------------------------------------------------------------------------------- /examples/hast/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "htlengine-example-hast", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Example to use htlengine with unified / hast", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "node index.js" 9 | }, 10 | "author": "", 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "@adobe/htlengine": "file:../../", 14 | "remark-parse": "^6.0.3", 15 | "remark-rehype": "^4.0.1", 16 | "unified": "^7.1.0", 17 | "unified-stream": "^1.0.6" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/jsdom/helper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | module.exports = class Helper { 13 | async use(globals) { 14 | const document = globals.document; 15 | return { 16 | get title() { 17 | return document.querySelector('h1').innerHTML; 18 | }, 19 | 20 | get headings() { 21 | // HTL iterator doesn't support NodeLists correctly yet. 22 | const ret = []; 23 | for(const h of document.querySelectorAll('h1,h2,h3').values()) { 24 | ret.push(h); 25 | } 26 | return ret; 27 | }, 28 | }; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /examples/jsdom/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | /* eslint-disable */ 13 | 14 | const { JSDOM } = require('jsdom'); 15 | const { Compiler, Runtime } = require('@adobe/htlengine'); 16 | 17 | const code = '' 18 | + '' 19 | + '' 20 | + '${doc.title}\n' 21 | + '\n' 22 | + '

Table of Contents

\n' 23 | + '\n' 26 | + '' 27 | + ''; 28 | 29 | const html = '' 30 | + '

JSDOM Example

' 31 | + 'This example shows how t use JSDOM with the HTL Engine.' 32 | + '

Install

' 33 | + 'foo bar...' 34 | + '

Run

' 35 | + 'npm test' 36 | + '

Development

' 37 | + 'contributions welcome'; 38 | 39 | async function run() { 40 | // setup the HTL compiler 41 | const compiler = new Compiler().withRuntimeVar('document'); 42 | 43 | const template = await compiler.compileToFunction(code, __dirname, require); 44 | 45 | // generate the input data using JSDOM 46 | const document = new JSDOM(html).window.document; 47 | 48 | // create a dom factory, providing a document implementation 49 | const domFactory = new Runtime.VDOMFactory(document.implementation); 50 | 51 | // create the HTL runtime 52 | const runtime = new Runtime() 53 | .withDomFactory(domFactory) 54 | .setGlobal({ 55 | document 56 | }); 57 | 58 | // finally, execute the template. the result is a Document. 59 | const result = await template(runtime); 60 | return `${result.documentElement.outerHTML}`; 61 | } 62 | 63 | run().then(console.log).catch(console.error); 64 | -------------------------------------------------------------------------------- /examples/jsdom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "htlengine-example-jsom", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Example to use htlengine with JSDOM", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "node index.js" 9 | }, 10 | "author": "", 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "@adobe/htlengine": "file:../../", 14 | "jsdom": "^15.2.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@adobe/htlengine", 3 | "version": "6.4.34", 4 | "description": "Javascript Based HTL (Sightly) parser", 5 | "main": "src/index.js", 6 | "license": "Apache-2.0", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/adobe/htlengine.git" 10 | }, 11 | "engines": { 12 | "node": ">=14" 13 | }, 14 | "scripts": { 15 | "build": "npm run build:compile && npm run build:railroad", 16 | "build:compile": "nearleyc ./src/parser/grammar/sightly.ne --out ./src/parser/generated/grammar.js", 17 | "build:railroad": "nearley-railroad ./src/parser/grammar/sightly.ne --out ./src/parser/generated/grammar.html", 18 | "semantic-release": "semantic-release", 19 | "semantic-release-dry": "semantic-release --dry-run --branches $CI_BRANCH", 20 | "start": "node src/run.js", 21 | "test": "c8 mocha", 22 | "lint": "eslint .", 23 | "prepare": "husky" 24 | }, 25 | "dependencies": { 26 | "dompurify": "3.2.6", 27 | "fs-extra": "11.3.0", 28 | "he": "1.2.0", 29 | "jsdom": "26.1.0", 30 | "moment": "2.30.1", 31 | "moo": "0.5.2", 32 | "nearley": "2.20.1", 33 | "node-esapi": "0.0.1", 34 | "numeral": "2.0.6", 35 | "rehype-parse": "7.0.1", 36 | "source-map": "0.7.4", 37 | "unified": "9.2.2", 38 | "unist-util-inspect": "6.0.1", 39 | "xregexp": "5.1.2" 40 | }, 41 | "devDependencies": { 42 | "@semantic-release/changelog": "6.0.3", 43 | "@semantic-release/git": "10.0.1", 44 | "c8": "10.1.3", 45 | "eslint": "8.57.1", 46 | "eslint-config-airbnb-base": "15.0.0", 47 | "eslint-plugin-header": "3.1.1", 48 | "eslint-plugin-import": "2.31.0", 49 | "husky": "9.1.7", 50 | "lint-staged": "16.1.0", 51 | "mocha": "11.6.0", 52 | "mocha-junit-reporter": "2.2.1", 53 | "rehype-stringify": "8.0.0", 54 | "remark-parse": "9.0.0", 55 | "remark-rehype": "8.1.0", 56 | "semantic-release": "24.2.5" 57 | }, 58 | "lint-staged": { 59 | "*.js": "eslint" 60 | }, 61 | "bugs": { 62 | "url": "https://github.com/adobe/htlengine/issues" 63 | }, 64 | "homepage": "https://github.com/adobe/htlengine#readme" 65 | } 66 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | // declared dependencies 14 | const fse = require('fs-extra'); 15 | // local modules 16 | const engine = require('./main'); 17 | 18 | (async () => { 19 | const filename = process.argv[2]; 20 | const template = await fse.readFile(filename, 'utf-8'); 21 | 22 | const resource = { 23 | world: 'Earth', 24 | properties: { 25 | title: 'Hello, world.', 26 | fruits: ['Apple', 'Banana', 'Orange'], 27 | comma: ', ', 28 | }, 29 | nav: { 30 | foo: 'This is foo. ', 31 | }, 32 | test: 'This is a test', 33 | qttMin: 4, 34 | qttMax: 4, 35 | expression: 'this is an expression.', 36 | it: { 37 | html: 'foo barty!', 38 | title: 'Hello, world!', 39 | children: [ 40 | '
A
', 41 | '
B
', 42 | ], 43 | }, 44 | }; 45 | 46 | engine(resource, template).then((ret) => { 47 | // eslint-disable-next-line no-console 48 | console.log(ret.body); 49 | }); 50 | })(); 51 | -------------------------------------------------------------------------------- /src/compiler/ExternalTemplateLoader.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | const path = require('path'); 13 | const fse = require('fs-extra'); 14 | 15 | /** 16 | * Creates a template loader that compiles the templates and returns their exec function. 17 | */ 18 | module.exports = function createLoader(opts) { 19 | const { 20 | compiler, 21 | outputDirectory, 22 | } = opts; 23 | 24 | /** 25 | * Load the template. 26 | * @param {string} templatePath template path 27 | * @param {string} scriptId the script id 28 | * @returns {Promise<>} the template source and resolved path 29 | */ 30 | async function load(templatePath, scriptId) { 31 | const comp = await compiler.createTemplateCompiler(templatePath, outputDirectory, scriptId); 32 | const filename = `${path.basename(templatePath)}.js`; 33 | const outfile = path.resolve(outputDirectory, filename); 34 | const source = await fse.readFile(templatePath, 'utf-8'); 35 | const file = await comp.compileToFile(source, outfile, compiler.dir); 36 | return { 37 | path: file, 38 | code: `require(${JSON.stringify(file)})($);`, 39 | }; 40 | } 41 | 42 | return load; 43 | }; 44 | -------------------------------------------------------------------------------- /src/compiler/JSCodeTemplate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | /* eslint-disable */ 14 | 15 | module.exports = function main($) { 16 | // RUNTIME_GLOBALS 17 | 18 | // TEMPLATES 19 | 20 | return $.run(function* () { 21 | // CODE 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /src/compiler/JSPureTemplate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | /* eslint-disable */ 14 | 15 | module.exports = function main($) { 16 | // RUNTIME_GLOBALS 17 | 18 | // TEMPLATES 19 | }; 20 | -------------------------------------------------------------------------------- /src/compiler/JSRuntimeTemplate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | /* eslint-disable */ 14 | const { Runtime } = require('MOD_HTLENGINE'); 15 | 16 | function run($) { 17 | // RUNTIME_GLOBALS 18 | 19 | // TEMPLATES 20 | 21 | return $.run(function* () { 22 | // CODE 23 | }); 24 | } 25 | 26 | module.exports.main = async function main(resource) { 27 | const runtime = new Runtime(); 28 | runtime.setGlobal(resource); 29 | return await run(runtime); 30 | }; 31 | -------------------------------------------------------------------------------- /src/compiler/ScriptResolver.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | const path = require('path'); 13 | const fse = require('fs-extra'); 14 | 15 | /** 16 | * Creates a script resolver. 17 | * @param {string[]} roots Root directories for resolution. 18 | */ 19 | module.exports = function createResolver(roots) { 20 | if (!Array.isArray(roots)) { 21 | // eslint-disable-next-line no-param-reassign 22 | roots = [roots]; 23 | } 24 | 25 | /** 26 | * Resolves the script against the given roots. 27 | * @param {string} baseDir additional root 28 | * @param {string} uri script uri 29 | * @returns {Promise} 30 | */ 31 | async function resolve(baseDir, uri) { 32 | let bases = [baseDir, ...roots]; 33 | 34 | // if uri starts with '.' or '..', only consider specified bases. 35 | // otherwise also consider apps and libs. 36 | if (!uri.startsWith('./') && !uri.startsWith('../')) { 37 | bases = bases.reduce((prev, root) => { 38 | prev.push(root); 39 | prev.push(path.resolve(root, 'apps')); 40 | prev.push(path.resolve(root, 'libs')); 41 | return prev; 42 | }, []); 43 | } 44 | 45 | // eslint-disable-next-line no-restricted-syntax 46 | for (const base of bases) { 47 | const scriptPath = path.resolve(base, uri); 48 | // eslint-disable-next-line no-await-in-loop 49 | if (await fse.pathExists(scriptPath)) { 50 | return scriptPath; 51 | } 52 | } 53 | throw Error(`Unable to resolve script: ${uri}. Search Path: ${bases}`); 54 | } 55 | 56 | return resolve; 57 | }; 58 | -------------------------------------------------------------------------------- /src/compiler/TemplateLoader.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | const fse = require('fs-extra'); 13 | 14 | /** 15 | * Creates an template loader. 16 | */ 17 | module.exports = function createLoader() { 18 | /** 19 | * @param {string} filePath Template path 20 | * @returns {Promise<>} the template source and path 21 | */ 22 | async function load(filePath) { 23 | return { 24 | data: await fse.readFile(filePath, 'utf-8'), 25 | path: filePath, 26 | }; 27 | } 28 | 29 | return load; 30 | }; 31 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Compiler = require('./compiler/Compiler.js'); 14 | const createTemplateLoader = require('./compiler/TemplateLoader.js'); 15 | const createScriptResolver = require('./compiler/ScriptResolver.js'); 16 | const Runtime = require('./runtime/Runtime.js'); 17 | const resly = require('./runtime/resly.js'); 18 | 19 | module.exports = Object.freeze({ 20 | Compiler, 21 | Runtime, 22 | createTemplateLoader, 23 | createScriptResolver, 24 | resly, 25 | }); 26 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const { Compiler, Runtime } = require('./index.js'); 14 | 15 | /** 16 | * Simple engine that compiles the given template and executes it. 17 | * @param {*} resource the global object to pass into the script 18 | * @param {string} template the HTL script 19 | * @returns A promise that resolves to the evaluated code. 20 | */ 21 | module.exports = async function main(resource, template) { 22 | // setup the HTL compiler 23 | const compiler = new Compiler().withRuntimeVar(Object.keys(resource)); 24 | 25 | // compile the script to a executable template function 26 | const fn = await compiler.compileToFunction(template); 27 | 28 | // create the HTL runtime 29 | const runtime = new Runtime() 30 | .setGlobal(resource); 31 | 32 | // finally, execute the template function and return the result 33 | return fn(runtime); 34 | }; 35 | -------------------------------------------------------------------------------- /src/parser/commands/AddAttribute.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Command = require('./Command'); 14 | const MarkupContext = require('../html/MarkupContext'); 15 | 16 | module.exports = class AddAttribute extends Command { 17 | constructor(name, value, context = MarkupContext.ATTRIBUTE) { 18 | super(); 19 | this._name = name; 20 | this._value = value; 21 | this._context = context; 22 | } 23 | 24 | get name() { 25 | return this._name; 26 | } 27 | 28 | get value() { 29 | return this._value; 30 | } 31 | 32 | get context() { 33 | return this._context; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/parser/commands/Command.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | /** 14 | * A {@code Command} represents the type of instruction a certain HTL expression or block element 15 | * should execute. 16 | * Commands are immutable and can only be processed through a {@link CommandVisitor}. 17 | */ 18 | module.exports = class Command { 19 | constructor(location) { 20 | this._location = location; 21 | } 22 | 23 | /** 24 | * Accept a visitor. 25 | * 26 | * @param {CommandVisitor} visitor the visitor that will process this command 27 | */ 28 | accept(visitor) { 29 | visitor.visit(this); 30 | } 31 | 32 | get location() { 33 | return this._location; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/parser/commands/CommandStream.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const FunctionBlock = require('./FunctionBlock'); 14 | const OutText = require('./OutText'); 15 | 16 | module.exports = class CommandStream { 17 | constructor() { 18 | this._commands = []; 19 | this._warnings = []; 20 | this._wasText = false; 21 | this._ignore = 0; 22 | } 23 | 24 | write(command, force) { 25 | if (this._ignore && !force) { 26 | return; 27 | } 28 | const isText = command instanceof OutText; 29 | if (isText) { 30 | if (this._wasText) { 31 | this._commands[this._commands.length - 1].append(command.text); 32 | return; 33 | } 34 | } 35 | this._wasText = isText; 36 | this._commands.push(command); 37 | } 38 | 39 | warn(message, code) { 40 | this._warnings.push({ 41 | message, 42 | code, 43 | }); 44 | } 45 | 46 | // eslint-disable-next-line class-methods-use-this 47 | close() { 48 | } 49 | 50 | get commands() { 51 | return this._commands; 52 | } 53 | 54 | beginFunction(expression, options) { 55 | this.write(new FunctionBlock.Start(expression, options)); 56 | } 57 | 58 | endFunction() { 59 | this.write(FunctionBlock.END); 60 | } 61 | 62 | beginIgnore() { 63 | this._ignore += 1; 64 | this._wasText = false; 65 | } 66 | 67 | endIgnore() { 68 | this._ignore -= 1; 69 | if (this._ignore < 0) { 70 | throw Error('stream ignore block mismatch'); 71 | } 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /src/parser/commands/Comment.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Command = require('./Command'); 14 | 15 | module.exports = class Comment extends Command { 16 | constructor(text, location) { 17 | super(location); 18 | this._text = text; 19 | } 20 | 21 | get text() { 22 | return this._text; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/parser/commands/Conditional.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | // eslint-disable-next-line max-classes-per-file 14 | const Command = require('./Command'); 15 | 16 | module.exports = { 17 | 18 | Start: class Start extends Command { 19 | constructor(expression, negate, location) { 20 | super(location); 21 | this._expression = expression; 22 | this._negate = negate; 23 | } 24 | 25 | get expression() { 26 | return this._expression; 27 | } 28 | 29 | get negate() { 30 | return this._negate; 31 | } 32 | }, 33 | 34 | End: class End extends Command { 35 | 36 | }, 37 | 38 | }; 39 | 40 | module.exports.END = new module.exports.End(); 41 | -------------------------------------------------------------------------------- /src/parser/commands/CreateElement.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Command = require('./Command'); 14 | 15 | module.exports = class CreateElement extends Command { 16 | constructor(name, isEmpty, isVoid) { 17 | super(); 18 | this._name = name; 19 | this._isEmpty = isEmpty; 20 | this._isVoid = isVoid; 21 | } 22 | 23 | get name() { 24 | return this._name; 25 | } 26 | 27 | get isEmpty() { 28 | return this._isEmpty; 29 | } 30 | 31 | get isVoid() { 32 | return this._isVoid; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/parser/commands/Doctype.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Command = require('./Command'); 14 | 15 | module.exports = class Doctype extends Command { 16 | constructor(name, publicId, systemId, location) { 17 | super(location); 18 | this._name = name; 19 | this._publicId = publicId; 20 | this._systemId = systemId; 21 | } 22 | 23 | get name() { 24 | return this._name; 25 | } 26 | 27 | get publicId() { 28 | return this._publicId; 29 | } 30 | 31 | get systemId() { 32 | return this._systemId; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/parser/commands/ExternalCode.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Command = require('./Command'); 14 | 15 | module.exports = class ExternalCode extends Command { 16 | constructor(code, location) { 17 | super(location); 18 | this._code = code; 19 | } 20 | 21 | get code() { 22 | return this._code; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/parser/commands/FileReference.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Command = require('./Command'); 14 | 15 | module.exports = class FileReference extends Command { 16 | constructor(name, filename, args) { 17 | super(); 18 | this._name = name; 19 | this._filename = filename; 20 | this._args = args || []; 21 | this._id = null; 22 | } 23 | 24 | get name() { 25 | return this._name; 26 | } 27 | 28 | get filename() { 29 | return this._filename; 30 | } 31 | 32 | set id(value) { 33 | this._id = value; 34 | } 35 | 36 | get id() { 37 | return this._id; 38 | } 39 | 40 | get args() { 41 | return this._args; 42 | } 43 | 44 | isTemplate() { 45 | return this._filename.endsWith('.htl') || this._filename.endsWith('.html'); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/parser/commands/FunctionBlock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Deloitte Digital. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | // eslint-disable-next-line max-classes-per-file 14 | const Command = require('./Command'); 15 | 16 | class Start extends Command { 17 | constructor(expression, options) { 18 | super(); 19 | this._expression = expression; 20 | this._options = options; 21 | this._id = null; 22 | } 23 | 24 | set id(value) { 25 | this._id = value; 26 | } 27 | 28 | get id() { 29 | return this._id; 30 | } 31 | 32 | get expression() { 33 | return this._expression; 34 | } 35 | 36 | get options() { 37 | return this._options; 38 | } 39 | 40 | get args() { 41 | return Object.keys(this._options); 42 | } 43 | } 44 | 45 | class End extends Command {} 46 | 47 | const END = new End(); 48 | 49 | module.exports = { 50 | Start, 51 | End, 52 | END, 53 | }; 54 | -------------------------------------------------------------------------------- /src/parser/commands/FunctionCall.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Command = require('./Command'); 14 | 15 | module.exports = class FunctionCall extends Command { 16 | constructor(functionName, args, location) { 17 | super(location); 18 | this._functionName = functionName; 19 | this._args = args; 20 | } 21 | 22 | get functionName() { 23 | return this._functionName; 24 | } 25 | 26 | get args() { 27 | return this._args; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/parser/commands/Loop.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | // eslint-disable-next-line max-classes-per-file 14 | const Command = require('./Command'); 15 | 16 | module.exports = { 17 | 18 | Init: class Init extends Command { 19 | constructor(variableName, expression, options = {}) { 20 | super(); 21 | this._variableName = variableName; 22 | this._expression = expression; 23 | this._options = options; 24 | } 25 | 26 | get variableName() { 27 | return this._variableName; 28 | } 29 | 30 | get expression() { 31 | return this._expression; 32 | } 33 | 34 | get options() { 35 | return this._options; 36 | } 37 | }, 38 | 39 | Start: class Start extends Command { 40 | constructor(listVariable, itemVariable, indexVariable) { 41 | super(); 42 | this._listVariable = listVariable; 43 | this._itemVariable = itemVariable; 44 | this._indexVariable = indexVariable; 45 | } 46 | 47 | get listVariable() { 48 | return this._listVariable; 49 | } 50 | 51 | get itemVariable() { 52 | return this._itemVariable; 53 | } 54 | 55 | get indexVariable() { 56 | return this._indexVariable; 57 | } 58 | }, 59 | 60 | End: class End extends Command { 61 | 62 | }, 63 | 64 | }; 65 | 66 | module.exports.END = new module.exports.End(); 67 | -------------------------------------------------------------------------------- /src/parser/commands/OutText.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Command = require('./Command'); 14 | 15 | module.exports = class OutText extends Command { 16 | constructor(text, location) { 17 | super(location); 18 | this._text = text; 19 | } 20 | 21 | get text() { 22 | return this._text; 23 | } 24 | 25 | append(text) { 26 | this._text += text; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/parser/commands/OutputVariable.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Command = require('./Command'); 14 | 15 | module.exports = class OutputVariable extends Command { 16 | constructor(variableName, location) { 17 | super(location); 18 | this._variableName = variableName; 19 | } 20 | 21 | get variableName() { 22 | return this._variableName; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/parser/commands/PopElement.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Command = require('./Command'); 14 | 15 | module.exports = class PopElement extends Command { 16 | constructor(name, isEmpty, isVoid) { 17 | super(); 18 | this._name = name; 19 | this._isEmpty = isEmpty; 20 | this._isVoid = isVoid; 21 | } 22 | 23 | get name() { 24 | return this._name; 25 | } 26 | 27 | get isEmpty() { 28 | return this._isEmpty; 29 | } 30 | 31 | get isVoid() { 32 | return this._isVoid; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/parser/commands/PushElement.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Command = require('./Command'); 14 | 15 | module.exports = class PushElement extends Command { 16 | constructor(name, isEmpty, isVoid) { 17 | super(); 18 | this._name = name; 19 | this._isEmpty = isEmpty; 20 | this._isVoid = isVoid; 21 | } 22 | 23 | get name() { 24 | return this._name; 25 | } 26 | 27 | get isEmpty() { 28 | return this._isEmpty; 29 | } 30 | 31 | get isVoid() { 32 | return this._isVoid; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/parser/commands/VariableBinding.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | // eslint-disable-next-line max-classes-per-file 14 | const Command = require('./Command'); 15 | 16 | module.exports = { 17 | 18 | Start: class Start extends Command { 19 | constructor(variableName, expression) { 20 | super(); 21 | this._variableName = variableName; 22 | this._expression = expression; 23 | } 24 | 25 | get variableName() { 26 | return this._variableName; 27 | } 28 | 29 | get expression() { 30 | return this._expression; 31 | } 32 | }, 33 | 34 | End: class End extends Command { 35 | 36 | }, 37 | 38 | Global: class Global extends Command { 39 | constructor(variableName, expression) { 40 | super(); 41 | this._variableName = variableName.toLowerCase(); 42 | this._expression = expression; 43 | } 44 | 45 | get variableName() { 46 | return this._variableName; 47 | } 48 | 49 | get expression() { 50 | return this._expression; 51 | } 52 | }, 53 | }; 54 | 55 | module.exports.END = new module.exports.End(); 56 | -------------------------------------------------------------------------------- /src/parser/generated/.gitignore: -------------------------------------------------------------------------------- 1 | *.interp 2 | *.tokens 3 | -------------------------------------------------------------------------------- /src/parser/grammar/lexer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | /* eslint-disable no-template-curly-in-string,no-control-regex */ 13 | const moo = require('moo'); 14 | 15 | function parseDString(str) { 16 | return JSON.parse(`{"s":${str}}`).s; 17 | } 18 | 19 | function parseSString(str) { 20 | const sb = str.substring(1, str.length - 1) 21 | .replace(/[^\\]"/g, '\\"') 22 | .replace('\\\'', '\''); 23 | return JSON.parse(`{"s":"${sb}"}`).s; 24 | } 25 | 26 | const lexer = moo.states({ 27 | main: { 28 | TEXT_PART: { match: /[^$\\\x03]+/, lineBreaks: true }, 29 | EXPR_START: { match: /\$\{/, push: 'expr' }, 30 | ESC_EXPR: /\\\$/, 31 | DOLLAR: '$', 32 | EOF: /\03/, 33 | }, 34 | expr: { 35 | EOF: /\x03/, 36 | COMMENT: //, 37 | EXPR_END: { match: '}', pop: 1 }, 38 | DOT: '.', 39 | LBRACKET: '(', 40 | RBRACKET: ')', 41 | AND_OP: '&&', 42 | OR_OP: '||', 43 | NEQ: { 44 | match: '!=', 45 | value: () => '!==', 46 | }, 47 | NOT_OP: '!', 48 | COMMA: ',', 49 | ARRAY_START: '[', 50 | ARRAY_END: ']', 51 | OPTION_SEP: '@', 52 | TERNARY_Q_OP: '?', 53 | TERNARY_BRANCHES_OP: ':', 54 | LEQ: '<=', 55 | LT: '<', 56 | GEQ: '>=', 57 | GT: '>', 58 | EQ: { 59 | match: '==', 60 | value: () => '===', 61 | }, 62 | ASSIGN: '=', 63 | ID: { 64 | match: /[a-zA-Z_][a-zA-Z0-9_:]*/, 65 | type: moo.keywords({ 66 | BOOL_CONSTANT: ['true', 'false'], 67 | IN_OP: 'in', 68 | }), 69 | }, 70 | FLOAT: /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/, 71 | INT: /-?\d+/, 72 | WS: { match: /\s+/, lineBreaks: true }, 73 | 74 | S_STRING: { 75 | match: /'(?:\\u[a-fA-F0-9]{4}|\\["'\\btnfr]|[^'\n\\])*'/, 76 | value: parseSString, 77 | }, 78 | D_STRING: { 79 | match: /"(?:\\u[a-fA-F0-9]{4,6}|\\["'\\btnfr]|[^"\n\\])*"/, 80 | value: parseDString, 81 | }, 82 | }, 83 | }); 84 | 85 | module.exports = lexer; 86 | -------------------------------------------------------------------------------- /src/parser/htl/HTLParser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const nearley = require('nearley'); 14 | const inspect = require('unist-util-inspect'); 15 | const grammar = require('../generated/grammar.js'); 16 | const Interpolation = require('./nodes/Interpolation'); 17 | 18 | module.exports = class HTLParser { 19 | constructor() { 20 | this.debug = false; 21 | } 22 | 23 | /** 24 | * Parses the input and returns an Interpolation. 25 | * @param {string} text Input text 26 | * @return {object} The parsed abstract syntax tree. 27 | */ 28 | // eslint-disable-next-line class-methods-use-this 29 | parse(text, { line = 0, column = 0 } = {}) { 30 | // eslint-disable-next-line no-param-reassign 31 | text = text || ''; // avoid null 32 | if (this.debug) { 33 | process.stdout.write(`[${line}:${column}] ${text.substring(0, 40).replace(/\n\r/g, ' ')}\n`); 34 | } 35 | let htl = new Interpolation(); 36 | if (text) { 37 | const parser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar)); 38 | parser.feed(`${text}\x03`); 39 | // eslint-disable-next-line prefer-destructuring 40 | htl = parser.results[0]; 41 | } 42 | 43 | if (this.debug) { 44 | process.stdout.write(inspect(htl)); 45 | process.stdout.write('\n\n'); 46 | } 47 | return htl; 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/ArrayLiteral.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const ExpressionNode = require('./ExpressionNode'); 14 | 15 | module.exports = class ArrayLiteral extends ExpressionNode { 16 | /** 17 | * @param {ExpressionNode[]} items Items 18 | */ 19 | constructor(items) { 20 | super(); 21 | this.type = 'array'; 22 | this.children = items || []; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/Atom.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const ExpressionNode = require('./ExpressionNode'); 14 | 15 | module.exports = class Atom extends ExpressionNode { 16 | get text() { // eslint-disable-line class-methods-use-this 17 | throw new TypeError('Abstract method error'); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/BinaryOperation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const ExpressionNode = require('./ExpressionNode'); 14 | 15 | module.exports = class BinaryOperation extends ExpressionNode { 16 | /** 17 | * 18 | * @param {BinaryOperator} operator Operator 19 | * @param {ExpressionNode} leftOperand Left operand expression 20 | * @param {ExpressionNode} rightOperand Right operand expression 21 | */ 22 | constructor(operator, leftOperand, rightOperand) { 23 | super(); 24 | this.type = 'binary'; 25 | Object.defineProperty(this, '_operator', { value: operator, enumerable: false }); 26 | this.name = operator.sym; 27 | this.children = [leftOperand, rightOperand]; 28 | } 29 | 30 | get operator() { 31 | return this._operator; 32 | } 33 | 34 | get leftOperand() { 35 | return this.children[0]; 36 | } 37 | 38 | get rightOperand() { 39 | return this.children[1]; 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/BooleanConstant.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Atom = require('./Atom'); 14 | 15 | class BooleanConstant extends Atom { 16 | constructor(value) { 17 | super(); 18 | this.type = 'bool'; 19 | this.value = value; 20 | } 21 | 22 | get text() { 23 | return this.value ? 'true' : 'false'; 24 | } 25 | } 26 | BooleanConstant.TRUE = new BooleanConstant(true); 27 | BooleanConstant.FALSE = new BooleanConstant(false); 28 | 29 | module.exports = BooleanConstant; 30 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/Expression.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | module.exports = class Expression { 14 | /** 15 | * 16 | * @param {ExpressionNode} root Root node 17 | * @param {Map} options Options 18 | * @param {String} rawText Raw text 19 | */ 20 | constructor(root, options, rawText) { 21 | this.type = 'expression'; 22 | this._options = options || {}; 23 | this.children = [root]; 24 | this._rawText = rawText; 25 | } 26 | 27 | get root() { 28 | return this.children[0]; 29 | } 30 | 31 | get options() { 32 | return this._options; 33 | } 34 | 35 | get rawText() { 36 | return this._rawText; 37 | } 38 | 39 | withRawText(rawText) { 40 | return new Expression(this._root, this._options, rawText); 41 | } 42 | 43 | withNode(node) { 44 | return new Expression(node, this._options, null); 45 | } 46 | 47 | containsOption(option) { 48 | return option in this._options; 49 | } 50 | 51 | containsSomeOption(options) { 52 | return options.some((opt) => opt in this._options); 53 | } 54 | 55 | /** 56 | * Removes the given option from this expression. 57 | * 58 | * @param {String} option the option to be removed 59 | * @return the option, or {@code null} if the option doesn't exist 60 | */ 61 | removeOption(option) { 62 | const ret = this._options[option]; 63 | delete this._options[option]; 64 | return ret; 65 | } 66 | 67 | accept(visitor) { 68 | return visitor.visit(this); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/ExpressionNode.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | module.exports = class ExpressionNode { 14 | constructor() { 15 | this.type = 'node'; 16 | } 17 | 18 | withHasParens(p) { 19 | this._hasParens = p; 20 | return this; 21 | } 22 | 23 | get hasParens() { 24 | return this._hasParens; 25 | } 26 | 27 | accept(visitor) { 28 | return visitor.visit(this); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/Identifier.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Atom = require('./Atom'); 14 | 15 | class Identifier extends Atom { 16 | constructor(name) { 17 | super(); 18 | this.type = 'identifier'; 19 | this.name = name; 20 | } 21 | 22 | get value() { 23 | return this.name; 24 | } 25 | 26 | get text() { 27 | return this._name; 28 | } 29 | } 30 | 31 | Identifier.NULL = new Identifier('null'); 32 | 33 | module.exports = Identifier; 34 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/Interpolation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | /** 14 | * A sequence with alternating string fragments and HTL expressions. These result from parsing HTML 15 | * attributes or string nodes. 16 | * For instance "Hello ${World}!" would result in 3 fragments: "Hello ", ${World} and "!" 17 | */ 18 | module.exports = class Interpolation { 19 | constructor() { 20 | this.type = 'interpolation'; 21 | this.children = []; 22 | this._content = ''; 23 | this._isPlainText = true; 24 | } 25 | 26 | addExpression(expression) { 27 | this._isPlainText = false; 28 | this.children.push(expression); 29 | } 30 | 31 | addText(text) { 32 | this.children.push({ 33 | type: 'text', 34 | value: text, 35 | }); 36 | } 37 | 38 | get fragments() { 39 | return this.children; 40 | } 41 | 42 | get content() { 43 | return this._content; 44 | } 45 | 46 | set content(content) { 47 | this._content = content; 48 | } 49 | 50 | accept(visitor) { 51 | return visitor.visit(this); 52 | } 53 | 54 | getPlainText() { 55 | if (!this._isPlainText) { 56 | return null; 57 | } 58 | let text = ''; 59 | for (let i = 0; i < this.children.length; i += 1) { 60 | text += this.children[i].value; 61 | } 62 | return text; 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/MapLiteral.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const ExpressionNode = require('./ExpressionNode'); 14 | 15 | module.exports = class MapLiteral extends ExpressionNode { 16 | constructor(map) { 17 | super(); 18 | this.type = 'map'; 19 | this._map = map; 20 | } 21 | 22 | /** 23 | * Returns an {@link ExpressionNode} from the backing map. 24 | * 25 | * @param {String} key the key under which the node is stored 26 | * @return {ExpressionNode} the node, if one is stored under that key; {@code null} otherwise 27 | */ 28 | getValue(key) { 29 | return this._map[key]; 30 | } 31 | 32 | /** 33 | * Checks if the map contains the property identified by the passed property name. 34 | * 35 | * @param {String} name the property name 36 | * @return {@code true} if the map contains the property, {@code false} otherwise 37 | */ 38 | containsKey(name) { 39 | return name in this._map; 40 | } 41 | 42 | get map() { 43 | return this._map; 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/MultiOperation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const ExpressionNode = require('./ExpressionNode'); 14 | 15 | module.exports = class MultiOperation extends ExpressionNode { 16 | /** 17 | * @param {BinaryOperator} operator Operator 18 | * @param {ExpressionNode[]} operands The operands 19 | */ 20 | constructor(operator, operands) { 21 | super(); 22 | this.type = 'multi'; 23 | Object.defineProperty(this, '_operator', { value: operator, enumerable: false }); 24 | this.name = operator.sym; 25 | this.children = operands; 26 | } 27 | 28 | get operator() { 29 | return this._operator; 30 | } 31 | 32 | get operands() { 33 | return this.children; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/NullLiteral.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const ExpressionNode = require('./ExpressionNode'); 14 | 15 | class NullLiteral extends ExpressionNode { 16 | constructor() { 17 | super(); 18 | this.type = 'null'; 19 | } 20 | 21 | // eslint-disable-next-line class-methods-use-this 22 | toString() { 23 | return 'null'; 24 | } 25 | } 26 | 27 | NullLiteral.INSTANCE = new NullLiteral(); 28 | 29 | module.exports = NullLiteral; 30 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/NumericConstant.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Atom = require('./Atom'); 14 | 15 | /** 16 | * Defines a numeric constant expression (e.g. "42.1"). 17 | */ 18 | class NumericConstant extends Atom { 19 | /** 20 | * Creates a numeric constant. 21 | * 22 | * @param {*} text the text representation 23 | */ 24 | constructor(value) { 25 | super(); 26 | this.type = 'number'; 27 | this.value = value; 28 | } 29 | 30 | get text() { 31 | return String(this.value); 32 | } 33 | } 34 | 35 | NumericConstant.ZERO = new NumericConstant(0); 36 | NumericConstant.ONE = new NumericConstant(1); 37 | NumericConstant.TWO = new NumericConstant(2); 38 | 39 | module.exports = NumericConstant; 40 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/PropertyAccess.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const ExpressionNode = require('./ExpressionNode'); 14 | 15 | module.exports = class PropertyAccess extends ExpressionNode { 16 | /** 17 | * @param {ExpressionNode} target Target expression 18 | * @param {ExpressionNode} property Property Expression 19 | */ 20 | constructor(target, property) { 21 | super(); 22 | this.type = 'access'; 23 | this.children = [target, property]; 24 | } 25 | 26 | get target() { 27 | return this.children[0]; 28 | } 29 | 30 | get property() { 31 | return this.children[1]; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/PropertyIdentifier.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Identifier = require('./Identifier'); 14 | 15 | class PropertyIdentifier extends Identifier { 16 | constructor(name) { 17 | super(name); 18 | this.type = 'property'; 19 | } 20 | } 21 | 22 | module.exports = PropertyIdentifier; 23 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/RuntimeCall.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const ExpressionNode = require('./ExpressionNode'); 14 | 15 | /** 16 | * A {@code RuntimeCall} is a special expression which provides access to utility functions from the 17 | * runtime. 18 | */ 19 | module.exports = class RuntimeCall extends ExpressionNode { 20 | /** 21 | * Creates a {@code RuntimeCall} based on a {@code functionName} and an array 22 | * of {@code arguments}. 23 | * 24 | * @param {String} functionName the name of the function identifying the runtime call 25 | * @param {ExpressionNode} expression the node this call is applied on 26 | * @param {ExpressionNode...} args the arguments passed to the runtime call 27 | */ 28 | constructor(functionName, expression, args) { 29 | super(); 30 | this.type = 'runtimeCall'; 31 | this._functionName = functionName; 32 | this._expression = expression; 33 | this._args = args || []; 34 | } 35 | 36 | get children() { 37 | return [this._expression, ...this._args]; 38 | } 39 | 40 | get functionName() { 41 | return this._functionName; 42 | } 43 | 44 | get expression() { 45 | return this._expression; 46 | } 47 | 48 | get args() { 49 | return this._args; 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/StringConstant.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Atom = require('./Atom'); 14 | 15 | class StringConstant extends Atom { 16 | static parse(str) { 17 | if (str[0] === '\'') { 18 | const sb = str.substring(1, str.length - 1) 19 | .replace(/[^\\]"/g, '\\"') 20 | .replace('\\\'', '\''); 21 | return new StringConstant(JSON.parse(`{"s":"${sb}"}`).s); 22 | } 23 | return new StringConstant(JSON.parse(`{"s":${str}}`).s); 24 | } 25 | 26 | constructor(text) { 27 | super(); 28 | this.type = 'string'; 29 | this.value = text; 30 | } 31 | 32 | get text() { 33 | return this.value; 34 | } 35 | } 36 | 37 | StringConstant.EMPTY = new StringConstant(''); 38 | StringConstant.TRUE = new StringConstant('true'); 39 | StringConstant.FALSE = new StringConstant('false'); 40 | 41 | module.exports = StringConstant; 42 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/TernaryOperation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const ExpressionNode = require('./ExpressionNode'); 14 | 15 | module.exports = class TernaryOperation extends ExpressionNode { 16 | /** 17 | * 18 | * @param {ExpressionNode} condition Condition expression 19 | * @param {ExpressionNode} thenBranch Then branch expression 20 | * @param {ExpressionNode} elseBranch Else branch expression 21 | */ 22 | constructor(condition, thenBranch, elseBranch) { 23 | super(); 24 | this.type = 'ternary'; 25 | this.children = [condition, thenBranch, elseBranch]; 26 | } 27 | 28 | get condition() { 29 | return this.children[0]; 30 | } 31 | 32 | get thenBranch() { 33 | return this.children[1]; 34 | } 35 | 36 | get elseBranch() { 37 | return this.children[2]; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/UnaryOperation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const ExpressionNode = require('./ExpressionNode'); 14 | 15 | module.exports = class UnaryOperation extends ExpressionNode { 16 | /** 17 | * 18 | * @param {BinaryOperator} operator Binary operator 19 | * @param {ExpressionNode} target Target expression 20 | */ 21 | constructor(operator, target) { 22 | super(); 23 | this.type = 'unary'; 24 | Object.defineProperty(this, '_operator', { value: operator, enumerable: false }); 25 | this.name = operator.sym; 26 | this.children = [target]; 27 | } 28 | 29 | get operator() { 30 | return this._operator; 31 | } 32 | 33 | get target() { 34 | return this.children[0]; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/parser/htl/nodes/UnaryOperator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | /** 14 | * Binary operators used in expressions. 15 | */ 16 | module.exports = Object.freeze({ 17 | 18 | /** 19 | * Evaluates the logical negation of the operand 20 | */ 21 | NOT: { 22 | sym: '!', 23 | calc: (operand) => { 24 | if (Array.isArray(operand)) { 25 | return !operand.length; 26 | } 27 | return !operand; 28 | }, 29 | }, 30 | 31 | /** 32 | * Evaluates whether the operand is a string of only whitespace characters 33 | */ 34 | IS_WHITESPACE: { 35 | sym: 'ws:', 36 | calc: (operand) => String(operand).trim().length === 0, 37 | }, 38 | 39 | /** 40 | * Evaluates the length of a collection 41 | */ 42 | LENGTH: { 43 | sym: 'len:', 44 | calc: (operand) => { 45 | if (Array.isArray(operand)) { 46 | return operand.length; 47 | } 48 | if (operand.length && (typeof operand.length === 'function')) { 49 | return operand.length(); 50 | } 51 | return -1; 52 | }, 53 | }, 54 | 55 | /** 56 | * Evaluates if a value is empty 57 | */ 58 | IS_EMPTY: { 59 | sym: 'emp:', 60 | calc: (operand) => { 61 | if (Array.isArray(operand)) { 62 | return operand.length === 0; 63 | } 64 | if (operand.length && (typeof operand.length === 'function')) { 65 | return operand.length() === 0; 66 | } 67 | return !`${operand}`; 68 | }, 69 | }, 70 | 71 | }); 72 | -------------------------------------------------------------------------------- /src/parser/html/ElementContext.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const PluginProxy = require('./PluginProxy'); 14 | 15 | /** 16 | * The element context contains the information about the current processed element in the markup 17 | * handler, @type {module.ElementContext} 18 | */ 19 | module.exports = class ElementContext { 20 | constructor(tagName) { 21 | this._tagName = tagName; 22 | this._attributes = {}; 23 | this._isSlyTag = tagName.toLowerCase() === 'sly'; 24 | this._plugin = new PluginProxy(); 25 | } 26 | 27 | addAttribute(name, value, location) { 28 | this._attributes[name] = { 29 | name, 30 | value, 31 | location, 32 | }; 33 | } 34 | 35 | addPluginAttribute(name, signature, expression) { 36 | this._attributes[name] = { 37 | name, 38 | signature, 39 | expression, 40 | }; 41 | } 42 | 43 | addCallbackAttribute(name, callback) { 44 | this._attributes[name] = { 45 | name, 46 | callback, 47 | }; 48 | } 49 | 50 | get tagName() { 51 | return this._tagName; 52 | } 53 | 54 | get isSlyTag() { 55 | return this._isSlyTag; 56 | } 57 | 58 | get attributes() { 59 | return this._attributes; 60 | } 61 | 62 | addPlugin(p) { 63 | this._plugin.add(p); 64 | } 65 | 66 | get plugin() { 67 | return this._plugin; 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /src/parser/html/ExpressionContext.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const PLUGIN_PREFIX = 'PLUGIN_DATA_SLY_'; 14 | 15 | const ECTX = { 16 | 17 | // Plugin contexts 18 | PLUGIN_DATA_SLY_USE: 'PLUGIN_DATA_SLY_USE', 19 | PLUGIN_DATA_SLY_TEXT: 'PLUGIN_DATA_SLY_TEXT', 20 | PLUGIN_DATA_SLY_SET: 'PLUGIN_DATA_SLY_SET', 21 | PLUGIN_DATA_SLY_ATTRIBUTE: 'PLUGIN_DATA_SLY_ATTRIBUTE', 22 | PLUGIN_DATA_SLY_ELEMENT: 'PLUGIN_DATA_SLY_ELEMENT', 23 | PLUGIN_DATA_SLY_TEST: 'PLUGIN_DATA_SLY_TEST', 24 | PLUGIN_DATA_SLY_LIST: 'PLUGIN_DATA_SLY_LIST', 25 | PLUGIN_DATA_SLY_REPEAT: 'PLUGIN_DATA_SLY_REPEAT', 26 | PLUGIN_DATA_SLY_INCLUDE: 'PLUGIN_DATA_SLY_INCLUDE', 27 | PLUGIN_DATA_SLY_RESOURCE: 'PLUGIN_DATA_SLY_RESOURCE', 28 | PLUGIN_DATA_SLY_TEMPLATE: 'PLUGIN_DATA_SLY_TEMPLATE', 29 | PLUGIN_DATA_SLY_CALL: 'PLUGIN_DATA_SLY_CALL', 30 | PLUGIN_DATA_SLY_UNWRAP: 'PLUGIN_DATA_SLY_UNWRAP', 31 | 32 | // Markup contexts 33 | ELEMENT: 'ELEMENT', 34 | TEXT: 'TEXT', 35 | ATTRIBUTE: 'ATTRIBUTE', 36 | 37 | getContextForPlugin: (name) => { 38 | const ctx = ECTX[PLUGIN_PREFIX + name.toUpperCase()]; 39 | if (!ctx) { 40 | throw new Error(`invalid plugin: ${name}`); 41 | } 42 | return ctx; 43 | }, 44 | }; 45 | 46 | module.exports = ECTX; 47 | -------------------------------------------------------------------------------- /src/parser/html/ExpressionTransformer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const StringConstant = require('../htl/nodes/StringConstant'); 14 | const Expression = require('../htl/nodes/Expression'); 15 | const MultiOperation = require('../htl/nodes/MultiOperation'); 16 | const BinaryOperator = require('../htl/nodes/BinaryOperator'); 17 | const OptionHandler = require('../htl/OptionHandler'); 18 | 19 | function join(nodes) { 20 | if (nodes.length === 0) { 21 | return StringConstant.EMPTY; 22 | } 23 | if (nodes.length === 1) { 24 | return nodes[0]; 25 | } 26 | return new MultiOperation(BinaryOperator.CONCATENATE, nodes); 27 | } 28 | 29 | module.exports = class ExpressionTransformer { 30 | transform(interpolation, markupContext, expressionContext) { 31 | const nodes = []; 32 | const /* HashMap */ options = {}; 33 | interpolation.fragments.forEach((fragment) => { 34 | if (fragment.type === 'text') { 35 | nodes.push(new StringConstant(fragment.value)); 36 | } else { 37 | const xformed = this.adjustToContext(fragment, markupContext, expressionContext); 38 | nodes.push(xformed.root); 39 | Object.assign(options, xformed.options); 40 | } 41 | }); 42 | 43 | const root = join(nodes); 44 | 45 | if (interpolation.fragments.length > 1 && options.context) { 46 | // context must not be calculated by merging 47 | delete options.context; 48 | } 49 | return new Expression(root, options, interpolation.content); 50 | } 51 | 52 | // eslint-disable-next-line class-methods-use-this 53 | adjustToContext(expression, markupContext, expressionContext) { 54 | const expr = expression; 55 | if (markupContext != null && !expr.options.context) { 56 | expr.options.context = new StringConstant(markupContext); 57 | } 58 | return OptionHandler.filter(expr, expressionContext); 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /src/parser/html/MarkupContext.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const MCTX = { 14 | HTML: 'html', 15 | TEXT: 'text', 16 | ELEMENT_NAME: 'elementName', 17 | ELEMENT_NAME_NF: 'elementNameNoFallback', 18 | ATTRIBUTE_NAME: 'attributeName', 19 | ATTRIBUTE: 'attribute', 20 | URI: 'uri', 21 | SCRIPT_TOKEN: 'scriptToken', 22 | SCRIPT_STRING: 'scriptString', 23 | SCRIPT_COMMENT: 'scriptComment', 24 | SCRIPT_REGEXP: 'scriptRegExp', 25 | STYLE_TOKEN: 'styleToken', 26 | STYLE_STRING: 'styleString', 27 | STYLE_COMMENT: 'styleComment', 28 | COMMENT: 'comment', 29 | NUMBER: 'number', 30 | UNSAFE: 'unsafe', 31 | 32 | attributeContext: (name) => { 33 | const upName = name.toLowerCase(); 34 | if (upName === 'src' || upName === 'href') { 35 | return MCTX.URI; 36 | } 37 | return MCTX.ATTRIBUTE; 38 | }, 39 | }; 40 | 41 | const reverse = {}; 42 | Object.keys(MCTX).forEach((k) => { 43 | reverse[MCTX[k]] = k; 44 | }); 45 | 46 | MCTX.lookup = (k) => reverse[k]; 47 | 48 | module.exports = MCTX; 49 | -------------------------------------------------------------------------------- /src/parser/html/Plugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | /* eslint-disable no-unused-vars,class-methods-use-this */ 14 | 15 | module.exports = class Plugin { 16 | constructor(signature, pluginContext, expression, location) { 17 | this._signature = signature; 18 | this._pluginContext = pluginContext; 19 | this._expression = expression; 20 | this._location = location; 21 | } 22 | 23 | isValid() { 24 | return true; 25 | } 26 | 27 | get pluginContext() { 28 | return this._pluginContext; 29 | } 30 | 31 | get expression() { 32 | return this._expression; 33 | } 34 | 35 | get location() { 36 | return this._location; 37 | } 38 | 39 | beforeElement(stream, tagName, elementContext) { 40 | } 41 | 42 | beforeTagOpen(stream) { 43 | } 44 | 45 | beforeAttributes(stream) { 46 | } 47 | 48 | beforeAttribute(stream, attributeName) { 49 | } 50 | 51 | beforeAttributeValue(stream, attributeName, attributeValue) { 52 | } 53 | 54 | afterAttributeValue(stream, attributeName) { 55 | } 56 | 57 | afterAttribute(stream, attributeName) { 58 | } 59 | 60 | onPluginCall(stream, signature, expression) { 61 | } 62 | 63 | afterAttributes(stream) { 64 | } 65 | 66 | afterTagOpen(stream) { 67 | } 68 | 69 | beforeChildren(stream) { 70 | } 71 | 72 | afterChildren(stream) { 73 | } 74 | 75 | beforeTagClose(stream, isSelfClosing) { 76 | } 77 | 78 | afterTagClose(stream, isSelfClosing) { 79 | } 80 | 81 | afterElement(stream) { 82 | } 83 | }; 84 | -------------------------------------------------------------------------------- /src/parser/html/PluginContext.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const RuntimeCall = require('../htl/nodes/RuntimeCall'); 14 | 15 | module.exports = class PluginContext { 16 | constructor(symbolGenerator, transformer, stream) { 17 | this._symbolGenerator = symbolGenerator; 18 | this._transformer = transformer; 19 | this._stream = stream; 20 | } 21 | 22 | get symbolGenerator() { 23 | return this._symbolGenerator; 24 | } 25 | 26 | get transformer() { 27 | return this._transformer; 28 | } 29 | 30 | get stream() { 31 | return this._stream; 32 | } 33 | 34 | generateVariable(hint) { 35 | return this._symbolGenerator.next(hint); 36 | } 37 | 38 | adjustToContext(expression, markupContext, expressionContext) { 39 | const { root } = expression; 40 | if (root instanceof RuntimeCall) { 41 | if (root.functionName === 'xss') { 42 | return expression; 43 | } 44 | } 45 | return this._transformer.adjustToContext(expression, markupContext, expressionContext); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/parser/html/PluginProxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Plugin = require('./Plugin'); 14 | 15 | module.exports = class PluginProxy { 16 | constructor() { 17 | this._plugins = []; 18 | } 19 | 20 | add(plugin) { 21 | this._plugins.push(plugin); 22 | } 23 | 24 | _delegate(fn, args) { 25 | this._plugins.forEach((p) => { 26 | p[fn](...args); 27 | }); 28 | } 29 | }; 30 | 31 | Object.getOwnPropertyNames(Plugin.prototype).filter((p) => p.startsWith('before') || p.startsWith('after') || p.startsWith('on')).forEach((fn) => { 32 | module.exports.prototype[fn] = function () { // eslint-disable-line func-names 33 | this._delegate(fn, arguments); // eslint-disable-line prefer-rest-params 34 | }; 35 | }); 36 | -------------------------------------------------------------------------------- /src/parser/html/PluginSignature.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const PLUGIN_ATTRIBUTE_PREFIX = 'data-sly-'; 14 | 15 | module.exports = class PluginSignature { 16 | constructor(name, args) { 17 | this._name = name; 18 | this._arguments = args || []; 19 | } 20 | 21 | get name() { 22 | return this._name; 23 | } 24 | 25 | get args() { 26 | return this._arguments; 27 | } 28 | 29 | static fromAttribute(attributeName) { 30 | if (!attributeName.startsWith(PLUGIN_ATTRIBUTE_PREFIX)) { 31 | return null; 32 | } 33 | 34 | const fragment = attributeName.substring(9); 35 | const parts = fragment.split('.'); 36 | if (parts.length === 0) { 37 | return null; 38 | } 39 | return new PluginSignature(parts[0], parts.slice(1)); 40 | } 41 | 42 | getVariableName(defaultValue) { 43 | const args = this._arguments; 44 | if (args.length > 0) { 45 | return args[0]; 46 | } 47 | return defaultValue; 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/parser/html/SymbolGenerator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const DEFAULT_VAR_PREFIX = 'var_'; 14 | 15 | module.exports = class SymbolGenerator { 16 | constructor(prefix) { 17 | this._prefix = prefix || DEFAULT_VAR_PREFIX; 18 | this._counter = -1; 19 | } 20 | 21 | next(hint) { 22 | const middle = hint ? hint.replace(/-/g, '_') : ''; 23 | this._counter += 1; 24 | return this._prefix + middle + this._counter; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/parser/html/TemplateParser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const MarkupHandler = require('./MarkupHandler'); 14 | const CommandStream = require('../commands/CommandStream'); 15 | const HTMLParser = require('./HTMLParser'); 16 | 17 | module.exports = class TemplateParser { 18 | /** 19 | * Sets the default markup context when writing properties to the response. 20 | * @param {MarkupContext} context the default context 21 | * @return this 22 | */ 23 | withDefaultMarkupContext(context) { 24 | this._defaultMarkupContext = context; 25 | return this; 26 | } 27 | 28 | /** 29 | * Parses the input and returns an the generated commands. 30 | * @param {String} input Input text 31 | * @return {Command[]} The generated commands 32 | */ 33 | // eslint-disable-next-line class-methods-use-this 34 | parse(input) { 35 | const stream = new CommandStream(); 36 | const handler = new MarkupHandler(stream); 37 | if (this._defaultMarkupContext !== undefined) { 38 | handler.withDefaultMarkupContext(this._defaultMarkupContext); 39 | } 40 | 41 | HTMLParser.parse(input, handler); 42 | return stream.commands; 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/parser/plugins/CallPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Deloitte Digital. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | const Plugin = require('../html/Plugin'); 13 | const FunctionCall = require('../commands/FunctionCall'); 14 | const MapLiteral = require('../htl/nodes/MapLiteral'); 15 | 16 | module.exports = class CallPlugin extends Plugin { 17 | beforeChildren(stream) { 18 | stream.write( 19 | new FunctionCall( 20 | this._expression.root, 21 | new MapLiteral(this._expression.options), 22 | this.location, 23 | ), 24 | ); 25 | stream.beginIgnore(); 26 | } 27 | 28 | // eslint-disable-next-line class-methods-use-this 29 | afterChildren(stream) { 30 | stream.endIgnore(); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/parser/plugins/ElementPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Plugin = require('../html/Plugin'); 14 | const VariableBinding = require('../commands/VariableBinding'); 15 | const Conditional = require('../commands/Conditional'); 16 | const OutputVariable = require('../commands/OutputVariable'); 17 | const CreateElement = require('../commands/CreateElement'); 18 | const PopElement = require('../commands/PopElement'); 19 | const MarkupContext = require('../html/MarkupContext'); 20 | const ExpressionContext = require('../html/ExpressionContext'); 21 | 22 | module.exports = class ElementPlugin extends Plugin { 23 | constructor(signature, pluginContext, expression) { 24 | super(signature, pluginContext, expression); 25 | 26 | this.node = pluginContext.adjustToContext( 27 | expression, 28 | MarkupContext.ELEMENT_NAME_NF, 29 | ExpressionContext.ELEMENT, 30 | ).root; 31 | this.tagVar = pluginContext.generateVariable('tagVar'); 32 | } 33 | 34 | beforeElement(stream) { 35 | stream.write(new VariableBinding.Start(this.tagVar, this.node)); 36 | } 37 | 38 | beforeTagOpen(stream, isEmpty, isVoid) { 39 | stream.write(new Conditional.Start(this.tagVar)); 40 | stream.write(new CreateElement(new OutputVariable(this.tagVar), isEmpty, isVoid)); 41 | stream.write(Conditional.END); 42 | stream.write(new Conditional.Start(this.tagVar, true)); 43 | } 44 | 45 | // eslint-disable-next-line class-methods-use-this 46 | beforeAttributes(stream) { 47 | stream.write(Conditional.END); 48 | } 49 | 50 | beforeTagClose(stream, isEmpty, isVoid) { 51 | stream.write(new Conditional.Start(this.tagVar)); 52 | stream.write(new PopElement(new OutputVariable(this.tagVar), isEmpty, isVoid)); 53 | stream.write(Conditional.END); 54 | stream.write(new Conditional.Start(this.tagVar, true)); 55 | } 56 | 57 | // eslint-disable-next-line class-methods-use-this 58 | afterTagClose(stream/* , isSelfClosing */) { 59 | stream.write(Conditional.END); 60 | } 61 | 62 | // eslint-disable-next-line class-methods-use-this 63 | afterElement(stream) { 64 | stream.write(VariableBinding.END); 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /src/parser/plugins/ResourcePlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Plugin = require('../html/Plugin'); 14 | const OutputVariable = require('../commands/OutputVariable'); 15 | const RuntimeCall = require('../htl/nodes/RuntimeCall'); 16 | const MapLiteral = require('../htl/nodes/MapLiteral'); 17 | const VariableBinding = require('../commands/VariableBinding'); 18 | 19 | module.exports = class ResourcePlugin extends Plugin { 20 | beforeChildren(stream) { 21 | const variableName = this.pluginContext.generateVariable('resourceContent'); 22 | const runtimeCall = new RuntimeCall('resource', this._expression.root, [new MapLiteral(this._expression.options)]); 23 | stream.write(new VariableBinding.Start(variableName, runtimeCall)); 24 | stream.write(new OutputVariable(variableName)); 25 | stream.write(VariableBinding.END); 26 | stream.beginIgnore(); 27 | } 28 | 29 | // eslint-disable-next-line class-methods-use-this 30 | afterChildren(stream) { 31 | stream.endIgnore(); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/parser/plugins/SetPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Plugin = require('../html/Plugin'); 14 | const VariableBinding = require('../commands/VariableBinding'); 15 | 16 | module.exports = class SetPlugin extends Plugin { 17 | beforeElement(stream) { 18 | const variableName = this._signature.getVariableName(null); 19 | stream.write(new VariableBinding.Global(variableName, this.expression.root)); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/parser/plugins/TemplatePlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Deloitte Digital. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | const Plugin = require('../html/Plugin'); 13 | 14 | module.exports = class TemplatePlugin extends Plugin { 15 | beforeElement(stream) { 16 | const variableName = this._signature.getVariableName(null); 17 | if (variableName === null) { 18 | throw new Error('data-sly-template must be called with an identifier'); 19 | } 20 | stream.beginFunction(variableName, this._expression.options); 21 | stream.beginIgnore(); 22 | } 23 | 24 | // eslint-disable-next-line class-methods-use-this 25 | beforeChildren(stream) { 26 | stream.endIgnore(); 27 | } 28 | 29 | // eslint-disable-next-line class-methods-use-this 30 | afterChildren(stream) { 31 | stream.endFunction(); 32 | stream.beginIgnore(); 33 | } 34 | 35 | // eslint-disable-next-line class-methods-use-this 36 | afterElement(stream) { 37 | stream.endIgnore(); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /src/parser/plugins/TestPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Plugin = require('../html/Plugin'); 14 | const VariableBinding = require('../commands/VariableBinding'); 15 | const Conditional = require('../commands/Conditional'); 16 | const Identifier = require('../htl/nodes/Identifier'); 17 | 18 | module.exports = class TestPlugin extends Plugin { 19 | beforeElement(stream) { 20 | const ctx = this.pluginContext; 21 | let variableName = this._signature.getVariableName(null); 22 | this._useGlobalBinding = variableName != null; 23 | if (variableName == null) { 24 | variableName = ctx.generateVariable('testVariable'); 25 | } else { 26 | variableName = variableName.toLowerCase(); 27 | } 28 | this._variableName = variableName; 29 | 30 | if (this._useGlobalBinding) { 31 | stream.write(new VariableBinding.Global(variableName, this.expression.root)); 32 | } else { 33 | stream.write(new VariableBinding.Start(variableName, this.expression.root)); 34 | } 35 | } 36 | 37 | beforeTagOpen(stream) { 38 | stream.write(new Conditional.Start(new Identifier(this._variableName)), true); 39 | } 40 | 41 | afterElement(stream) { 42 | stream.write(Conditional.END); 43 | if (!this._useGlobalBinding) { 44 | stream.write(VariableBinding.END); 45 | } 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/parser/plugins/TextPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Plugin = require('../html/Plugin'); 14 | const VariableBinding = require('../commands/VariableBinding'); 15 | const OutputVariable = require('../commands/OutputVariable'); 16 | const ExpressionContext = require('../html/ExpressionContext'); 17 | const MarkupContext = require('../html/MarkupContext'); 18 | 19 | module.exports = class TextPlugin extends Plugin { 20 | beforeChildren(stream) { 21 | const ctx = this.pluginContext; 22 | const variable = ctx.generateVariable('textContent'); 23 | stream.write(new VariableBinding.Start( 24 | variable, 25 | ctx.adjustToContext(this.expression, MarkupContext.TEXT, ExpressionContext.TEXT).root, 26 | )); 27 | stream.write(new OutputVariable(variable)); 28 | stream.write(VariableBinding.END); 29 | stream.beginIgnore(); 30 | } 31 | 32 | // eslint-disable-next-line class-methods-use-this 33 | afterChildren(stream) { 34 | stream.endIgnore(); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/parser/plugins/UnwrapPlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Plugin = require('../html/Plugin'); 14 | const VariableBinding = require('../commands/VariableBinding'); 15 | const Conditional = require('../commands/Conditional'); 16 | const BooleanConstant = require('../htl/nodes/BooleanConstant'); 17 | const StringConstant = require('../htl/nodes/StringConstant'); 18 | 19 | module.exports = class UnwrapPlugin extends Plugin { 20 | beforeElement(stream/* , elemContext */) { 21 | const ctx = this.pluginContext; 22 | this.variableName = this._signature.getVariableName(null); 23 | this._useGlobalBinding = this.variableName != null; 24 | if (this.variableName == null) { 25 | this.variableName = ctx.generateVariable('unwrapCondition'); 26 | } 27 | this.variableName = this.variableName.toLowerCase(); 28 | this.unwrapTest = new Conditional.Start(this.variableName, true); 29 | 30 | if (this._useGlobalBinding) { 31 | stream.write(new VariableBinding.Global(this.variableName, this.expression.root)); 32 | } else { 33 | stream.write(new VariableBinding.Start(this.variableName, this.testRoot())); 34 | } 35 | } 36 | 37 | beforeTagOpen(stream) { 38 | stream.write(this.unwrapTest); 39 | } 40 | 41 | // eslint-disable-next-line class-methods-use-this 42 | afterTagOpen(stream) { 43 | stream.write(Conditional.END); 44 | } 45 | 46 | beforeTagClose(stream) { 47 | stream.write(this.unwrapTest); 48 | } 49 | 50 | // eslint-disable-next-line class-methods-use-this 51 | afterTagClose(stream) { 52 | stream.write(Conditional.END); 53 | } 54 | 55 | afterElement(stream) { 56 | if (!this._useGlobalBinding) { 57 | stream.write(VariableBinding.END); 58 | } 59 | } 60 | 61 | testRoot() { 62 | const testNode = this.expression.root instanceof StringConstant 63 | && this.expression.root.text.length === 0; 64 | return testNode ? BooleanConstant.TRUE : this.expression.root; 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /src/parser/plugins/UsePlugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | const Plugin = require('../html/Plugin'); 14 | const FileReference = require('../commands/FileReference'); 15 | const MapLiteral = require('../htl/nodes/MapLiteral'); 16 | const StringConstant = require('../htl/nodes/StringConstant'); 17 | 18 | const DEFAULT_VARIABLE_NAME = 'useBean'; 19 | 20 | module.exports = class UsePlugin extends Plugin { 21 | beforeElement(stream) { 22 | const variableName = this._signature.getVariableName(DEFAULT_VARIABLE_NAME).toLowerCase(); 23 | if (this._expression.root instanceof StringConstant) { 24 | const lib = this._expression.root.text; 25 | stream.write(new FileReference( 26 | variableName, 27 | lib, 28 | [new MapLiteral(this._expression.options)], 29 | ), true); 30 | return; 31 | } 32 | throw new Error('data-sly-use only supports static references.'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/runtime/DOMFactory.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | /* eslint-disable class-methods-use-this,no-unused-vars */ 13 | module.exports = class DOMFactory { 14 | start() {} 15 | 16 | end() {} 17 | 18 | doctype(node, text) {} 19 | 20 | text(node, text) {} 21 | 22 | rem(node, text) {} 23 | 24 | append(node, value) {} 25 | 26 | create(name) {} 27 | 28 | attr(node, name, value) {} 29 | 30 | push(parent, node) {} 31 | 32 | pop(node) {} 33 | }; 34 | -------------------------------------------------------------------------------- /src/runtime/HDOMFactory.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | /* eslint-disable class-methods-use-this */ 13 | const unified = require('unified'); 14 | const parse = require('rehype-parse'); 15 | const DOMFactory = require('./DOMFactory'); 16 | 17 | module.exports = class HDOMFactory extends DOMFactory { 18 | start() { 19 | this._root = { type: 'root', children: [] }; 20 | return this._root; 21 | } 22 | 23 | end() { 24 | return this._root; 25 | } 26 | 27 | doctype(node, name) { 28 | node.children.push({ type: 'doctype', name }); 29 | return node; 30 | } 31 | 32 | create(tagName) { 33 | return { 34 | type: 'element', 35 | tagName, 36 | properties: {}, 37 | children: [], 38 | }; 39 | } 40 | 41 | append(node, value) { 42 | if (typeof value === 'object' && value.type) { 43 | // todo: clone ? 44 | } else { 45 | // eslint-disable-next-line no-param-reassign 46 | value = unified().use(parse, { fragment: true }).parse(String(value)); 47 | } 48 | node.children.push(value); 49 | } 50 | 51 | text(node, text) { 52 | node.children.push({ type: 'text', value: text }); 53 | } 54 | 55 | rem(node, text) { 56 | node.children.push({ type: 'comment', value: text }); 57 | } 58 | 59 | attr(node, name, value) { 60 | // eslint-disable-next-line no-param-reassign 61 | node.properties[name] = Array.isArray(value) ? value.join(',') : `${value}`; 62 | } 63 | 64 | push(parent, node) { 65 | parent.children.push(node); 66 | // eslint-disable-next-line no-param-reassign 67 | node.parent = parent; 68 | return node; 69 | } 70 | 71 | pop(node) { 72 | const ret = node.parent; 73 | // eslint-disable-next-line no-param-reassign 74 | delete node.parent; 75 | return ret; 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /src/runtime/HtmlDOMFactory.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | const DOMFactory = require('./DOMFactory'); 13 | const formatXss = require('./format_xss'); 14 | 15 | module.exports = class HtmlDOMFactory extends DOMFactory { 16 | start() { 17 | this._stack = []; 18 | this._buf = ''; 19 | } 20 | 21 | _out(text) { 22 | this._buf += text; 23 | } 24 | 25 | end() { 26 | return this._buf; 27 | } 28 | 29 | doctype(node, name) { 30 | this._out(``); 31 | return node; 32 | } 33 | 34 | create(tagName, isEmpty, isVoid) { 35 | this._out(`<${tagName}`); 36 | return { 37 | tagName, 38 | isEmpty, 39 | isVoid, 40 | }; 41 | } 42 | 43 | append(node, value) { 44 | this._out(value); 45 | } 46 | 47 | text(node, text) { 48 | this._out(text); 49 | } 50 | 51 | rem(node, text) { 52 | this._out(``); 53 | } 54 | 55 | attr(node, name, value, context) { 56 | if (value === true || value === null) { 57 | this._out(` ${name}`); 58 | return; 59 | } 60 | if (typeof value === 'string' && !value) { 61 | this._out(` ${name}=""`); 62 | return; 63 | } 64 | const escaped = formatXss(value, context, name); 65 | if (escaped) { 66 | this._out(` ${name}="${escaped}"`); 67 | } 68 | } 69 | 70 | push(parent, node) { 71 | if (node.isEmpty) { 72 | this._out('/>'); 73 | } else { 74 | this._out('>'); 75 | } 76 | this._stack.push(node); 77 | } 78 | 79 | pop() { 80 | const n = this._stack.pop(); 81 | if (!n.isEmpty && !n.isVoid) { 82 | this._out(``); 83 | } 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /src/runtime/fsResourceLoader.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | const path = require('path'); 13 | const fs = require('fs'); 14 | 15 | /** 16 | * Creates an FS based resource loader using the `resourceDir` as base directory. 17 | * @param {string} resourceDir The directory to resolve resources. 18 | */ 19 | module.exports = function createResourceLoader(resourceDir) { 20 | /** 21 | * Load the resource with the give uri. 22 | * @param {Runtime} runtime current runtime 23 | * @param {string} uri resource uri 24 | * @returns {Promise<*>} the resource 25 | */ 26 | return async (runtime, uri) => { 27 | const resourcePath = path.resolve(resourceDir, uri); 28 | 29 | return new Promise((resolve, reject) => { 30 | fs.readFile(resourcePath, 'utf8', (err, data) => { 31 | if (err) { 32 | reject(err); 33 | } else { 34 | resolve(data); 35 | } 36 | }); 37 | }); 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /src/runtime/resly.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | const Compiler = require('../compiler/Compiler'); 13 | const Runtime = require('./Runtime'); 14 | 15 | /** 16 | * HTL transformer for unified. 17 | * @param {*} options The transformer options 18 | * @param {string} options.code The HTL template code 19 | * @returns {function(): transformer} 20 | */ 21 | module.exports = function resly(options = {}) { 22 | if (!options.code) { 23 | throw Error('option.code is mandatory.'); 24 | } 25 | 26 | async function compile(code) { 27 | const compiler = new Compiler() 28 | .withRuntimeVar('dom'); 29 | return compiler.compileToFunction(code); 30 | } 31 | 32 | return function hastUtilHTL() { 33 | const runtime = new Runtime() 34 | .withDomFactory(new Runtime.HDOMFactory()); 35 | 36 | return function transformer(node, file, next) { 37 | runtime.setGlobal('dom', node); 38 | 39 | compile(options.code) 40 | .then((main) => main(runtime)) 41 | .then((result) => { 42 | next(null, result, file); 43 | }).catch(next); 44 | }; 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /test/TestHandler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | /* eslint-disable no-unused-vars */ 13 | module.exports = class TestHandler { 14 | onDocumentStart() { 15 | this._result = ''; 16 | } 17 | 18 | // eslint-disable-next-line class-methods-use-this 19 | onDocumentEnd() { 20 | } 21 | 22 | onOpenTagStart(tagName) { 23 | this._result += `<${tagName}`; 24 | } 25 | 26 | onAttribute(name, value, quoteChar, line, column) { 27 | if (value !== null) { 28 | if (quoteChar === '"') { 29 | // eslint-disable-next-line no-param-reassign 30 | value = value.replace('"', '"'); 31 | } 32 | this._result += ` ${name}=${quoteChar}${value}${quoteChar}`; 33 | } else { 34 | this._result += ` ${name}`; 35 | } 36 | } 37 | 38 | onOpenTagEnd(isEmpty, isVoid) { 39 | const markup = isEmpty ? '/>' : '>'; 40 | this._result += markup; 41 | } 42 | 43 | onCloseTag(tagName, isVoid) { 44 | if (!isVoid) { 45 | this._result += ``; 46 | } 47 | } 48 | 49 | onText(text, line, column) { 50 | this._result += text; 51 | } 52 | 53 | onComment(markup, line, column) { 54 | this._result += ``; 55 | } 56 | 57 | onDocType(markup, line, column) { 58 | this._result += markup; 59 | } 60 | 61 | get result() { 62 | return this._result; 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /test/engine_test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | /* eslint-env mocha */ 14 | 15 | // built-in modules 16 | const assert = require('assert'); 17 | const path = require('path'); 18 | // declared dependencies 19 | const fse = require('fs-extra'); 20 | // local modules 21 | const engine = require('../src/main'); 22 | 23 | const TEMPLATE_SIMPLE_2 = path.resolve(__dirname, 'templates', 'simple2.htl'); 24 | const EXPECTED_SIMPLE_2 = path.resolve(__dirname, 'templates', 'simple2.html'); 25 | const GLOBALS = { 26 | world: 'Earth', 27 | properties: { 28 | title: 'Hello, world.', 29 | fruits: ['Apple', 'Banana', 'Orange'], 30 | comma: ', ', 31 | }, 32 | nav: { 33 | foo: 'This is foo. ', 34 | }, 35 | test: 'This is a test', 36 | qttMin: 4, 37 | qttMax: 4, 38 | expression: 'this is an expression.', 39 | it: { 40 | html: 'foo barty!', 41 | title: 'Hello, world!', 42 | children: [ 43 | '
A
', 44 | '
B
', 45 | ], 46 | }, 47 | }; 48 | 49 | describe('Engine test', async () => { 50 | it('Simple htl can be executed', async () => { 51 | const template = await fse.readFile(TEMPLATE_SIMPLE_2, 'utf-8'); 52 | const expected = await fse.readFile(EXPECTED_SIMPLE_2, 'utf-8'); 53 | const ret = await engine(GLOBALS, template); 54 | assert.equal(ret, expected); 55 | }); 56 | }); 57 | 58 | describe('Engine Performance test', async () => { 59 | const TEST_FILES = ['simple2.html', '400kb.htm', '700kb.htm']; 60 | TEST_FILES.forEach((filename) => { 61 | it(`produces htl output for ${filename}`, async () => { 62 | const filePath = path.resolve(__dirname, 'templates', filename); 63 | const source = await fse.readFile(filePath, 'utf-8'); 64 | const result = await engine(GLOBALS, source); 65 | assert.equal(result, source); 66 | }).timeout(30000); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/html_parser_test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | /* eslint-env mocha */ 14 | 15 | // built-in modules 16 | const assert = require('assert'); 17 | const path = require('path'); 18 | // declared dependencies 19 | const fse = require('fs-extra'); 20 | // local modules 21 | const MarkupHandler = require('../src/parser/html/MarkupHandler'); 22 | const CommandStream = require('../src/parser/commands/CommandStream'); 23 | const HTMLParser = require('../src/parser/html/HTMLParser'); 24 | const TestHandler = require('./TestHandler'); 25 | 26 | /** 27 | * Simple tests that check if the parser can parse html 28 | */ 29 | describe('HTML Parsing', () => { 30 | const TEST_FILES = ['simple.htm']; 31 | 32 | TEST_FILES.forEach((filename) => { 33 | it(`parses ${filename}`, async () => { 34 | const filePath = path.resolve(__dirname, 'templates', filename); 35 | const source = await fse.readFile(filePath, 'utf-8'); 36 | 37 | const handler = new TestHandler(); 38 | HTMLParser.parse(source, handler); 39 | assert.equal(handler.result, source); 40 | }); 41 | }); 42 | }); 43 | 44 | /** 45 | * Simple tests that check if the parser can process all the expressions 46 | */ 47 | describe('HTML Parsing and Processing', () => { 48 | const TEST_FILES = ['simple.htm', '400kb.htm', '700kb.htm']; 49 | 50 | TEST_FILES.forEach((filename) => { 51 | it(`parses and processes ${filename}`, async () => { 52 | const filePath = path.resolve(__dirname, 'templates', filename); 53 | const source = await fse.readFile(filePath, 'utf-8'); 54 | 55 | const handler = new MarkupHandler(new CommandStream()); 56 | HTMLParser.parse(source, handler); 57 | }).timeout(30000); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/include/main.htl: -------------------------------------------------------------------------------- 1 |

Main

2 |
Hello, world. 3 | -------------------------------------- 4 | 5 | 6 |
    7 |
  • item 1
  • 8 | 9 |
  • item 2
  • 10 | 11 |
  • item 3
  • 12 |
13 |
14 |
15 |
42
16 |
17 |
18 | -------------------------------------------------------------------------------- /test/multi/list.htl: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    5 |
  • 6 |
7 |
8 | -------------------------------------------------------------------------------- /test/multi/tabs-expected.html: -------------------------------------------------------------------------------- 1 | 2 |

Hello, world.

3 | -------------------------------------- 4 | 5 | 6 |

Hello, mars.

7 |
8 |

tab 1

9 | 10 |

tab 2

11 | 12 |

tab 3

13 |
14 |
15 |
16 |
66
17 |
18 |
19 | -------------------------------------------------------------------------------- /test/multi/tabs.htl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

6 |
7 |
8 | -------------------------------------------------------------------------------- /test/multi/templates/header.htl: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /test/multi/templates/item.htl: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/multi/templates/placeholder.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/multi/templates/templateLib.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | stuff outside the template should not be rendered! 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /test/multi/test-data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | module.exports = { 13 | foo: 42, 14 | bar: 66, 15 | title: 'Hello, world.', 16 | 'jcr:title': 'Hello, mars.', 17 | list: [ 18 | { title: 'item 1' }, 19 | { title: 'item 2' }, 20 | { title: 'item 3' }, 21 | ], 22 | tabs: [ 23 | { title: 'tab 1' }, 24 | { title: 'tab 2' }, 25 | { title: 'tab 3' }, 26 | ], 27 | }; 28 | -------------------------------------------------------------------------------- /test/resly_test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | /* eslint-env mocha */ 14 | /* eslint-disable no-template-curly-in-string */ 15 | const assert = require('assert'); 16 | const unified = require('unified'); 17 | const markdown = require('remark-parse'); 18 | const remark2rehype = require('remark-rehype'); 19 | const html = require('rehype-stringify'); 20 | const { resly } = require('../src/index.js'); 21 | 22 | const code = '${dom.children[0].children[0].value}\n' 23 | + '\n' 24 | + '

Table of Contents

\n' 25 | + '
    ' 26 | + '
  • ${item}\n
  • ' 27 | + '
\n' 28 | + ''; 29 | 30 | const md = `# Example 31 | ## Part 1 32 | Foo Bar. 33 | ## Part 2 34 | Hello, world 35 | `; 36 | 37 | const expected = 'Example\n' 38 | + '\n' 39 | + '

Table of Contents

\n' 40 | + '
  • Example

    \n' 41 | + '
  • Part 1

    \n' 42 | + '
  • Part 2

    \n' 43 | + '
\n' 44 | + ''; 45 | 46 | describe('resly test', () => { 47 | it('can transform simple hast', (done) => { 48 | unified() 49 | .use(markdown) 50 | .use(remark2rehype) 51 | .use(resly({ code })) 52 | .use(html) 53 | .process(md, (err, file) => { 54 | if (err) { 55 | done(err); 56 | return; 57 | } 58 | try { 59 | assert.equal(String(file), expected); 60 | done(); 61 | } catch (e) { 62 | done(e); 63 | } 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/specs/attribute.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | module.exports = { 13 | properties: { 14 | myValues: 'some data value', 15 | myClass: 'super-green', 16 | 'jcr:content': 'the content', 17 | }, 18 | 'jcr:content': 'hello again', 19 | quotes: 'Hello, "World & Mars!"', 20 | foobar: { 21 | id: 'foo', 22 | class: 'bar', 23 | lang: '', 24 | }, 25 | attrs: { 26 | checked: true, 27 | }, 28 | date: '2019-04-02T08:44:26.000Z', 29 | }; 30 | -------------------------------------------------------------------------------- /test/specs/call.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Deloitte Digital. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | module.exports = { 13 | foo: 42, 14 | }; 15 | -------------------------------------------------------------------------------- /test/specs/call_spec/recursion_test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Deloitte Digital. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | module.exports = class MyUseClass { 13 | // eslint-disable-next-line class-methods-use-this 14 | use() { 15 | return { 16 | pages: [ 17 | { 18 | title: 'Page A', 19 | pages: [ 20 | { 21 | title: 'Page 1', 22 | }, { 23 | title: 'Page 2', 24 | }, { 25 | title: 'Page 3', 26 | }, 27 | ], 28 | }, { 29 | title: 'Page B', 30 | pages: [ 31 | { 32 | title: 'Page 1', 33 | }, { 34 | title: 'Page 2', 35 | }, 36 | ], 37 | }, { 38 | title: 'Page C', 39 | pages: [ 40 | { 41 | title: 'Page 1', 42 | }, { 43 | title: 'Page 2', 44 | }, { 45 | title: 'Page 3', 46 | }, { 47 | title: 'Page 4', 48 | }, 49 | ], 50 | }, 51 | ], 52 | }; 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /test/specs/context.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | module.exports = { 13 | properties: { 14 | title: 'HTML & Things are bold or Single \' or double " quoted.', 15 | attvalue: '" onload="alert()" "', 16 | nav: '/SUMMARY', 17 | abs: 'https://www.primordialsoup.life/index.html', 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /test/specs/dom.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | const unified = require('unified'); 13 | const parse = require('rehype-parse'); 14 | const { JSDOM } = require('jsdom'); 15 | 16 | const header = 'Hello, World'; 17 | 18 | const mydoc = new JSDOM('
  • This is foo.
  • This is bar.

Hello, world.

').window.document; 19 | 20 | module.exports = { 21 | document: { 22 | header: unified().use(parse, { fragment: true }).parse(header), 23 | footer: JSDOM.fragment(header).firstElementChild, 24 | listItem: mydoc.querySelector('li').cloneNode(true), 25 | body: mydoc.body, 26 | body1: mydoc.body.cloneNode(true), 27 | body2: mydoc.body.cloneNode(true), 28 | list: mydoc.querySelector('ul').cloneNode(true), 29 | list1: mydoc.querySelector('ul').cloneNode(true), 30 | list2: mydoc.querySelector('ul').cloneNode(true), 31 | array: ['a', 'b', 'c'], 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /test/specs/dom_hast.spec: -------------------------------------------------------------------------------- 1 | # 2 | ### unwrap with value and variable 3 | # 4 |
${document.header}
5 | === 6 |
Hello, World
7 | # 8 | ### insert an array 9 | # 10 |
${document.array}
11 | === 12 |
a,b,c
13 | # 14 | ### 15 | -------------------------------------------------------------------------------- /test/specs/element.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | module.exports = { 13 | properties: { 14 | allowed: ['section', 'nav', 'article', 'aside', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'footer', 'address', 'main', 'p', 'pre', 'blockquote', 'ol', 'li', 'dl', 'dt', 'dd', 'figure', 'figcaption', 'div', 'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr', 'data', 'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i', 'b', 'u', 'mark', 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br', 'wbr', 'ins', 'del', 'table', 'caption', 'colgroup', 'col', 'tbody', 'thead', 'tfoot', 'tr', 'td', 'th'], 15 | forbidden: ['script', 'style', 'form', 'input'], 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /test/specs/element.spec: -------------------------------------------------------------------------------- 1 | # 2 | ### sightly element replacement 3 | # 4 |
Hello, world.
5 | === 6 |

Hello, world.

7 | # 8 | ### ignore forbidden element 9 | # 10 |
Hello, world.
11 | === 12 |
Hello, world.
13 | # 14 | ### empty element 15 | # 16 |
Hello, world.
17 | === 18 |
Hello, world.
19 | # 20 | ### self closing 21 | # 22 | 23 | === 24 |
25 | # 26 | ### fallback to original element if forbidden 27 | # 28 | 29 | === 30 | 31 | # 32 | ### 33 | # 34 |
35 | 36 | === 37 |
38 |