├── .gitattributes
├── packages
├── language-server
│ ├── .gitignore
│ ├── lib
│ │ ├── constants.js
│ │ ├── api.js
│ │ ├── server.js
│ │ └── language-services.js
│ ├── .npmignore
│ ├── nyc.config.js
│ ├── webpack.config.js
│ ├── api.d.ts
│ ├── README.md
│ ├── package.json
│ └── CHANGELOG.md
├── xml-toolkit
│ ├── test
│ │ ├── testFixure
│ │ │ └── test.xml
│ │ ├── runTest.js
│ │ └── suite
│ │ │ └── index.js
│ ├── .gitignore
│ ├── resources
│ │ ├── xml-toolkit.png
│ │ └── readme
│ │ │ └── preview-syntax-validation.png
│ ├── .vscodeignore
│ ├── webpack.config.js
│ ├── README.md
│ ├── package.json
│ └── lib
│ │ └── extension.js
├── ast
│ ├── lib
│ │ ├── constants.js
│ │ ├── api.js
│ │ ├── utils.js
│ │ └── visit-ast.js
│ ├── test
│ │ ├── snapshots
│ │ │ ├── valid
│ │ │ │ ├── complex-contents
│ │ │ │ │ ├── input.xml
│ │ │ │ │ ├── sample.spec.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── attributes
│ │ │ │ │ ├── input.xml
│ │ │ │ │ ├── sample.spec.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── prolog
│ │ │ │ │ ├── input.xml
│ │ │ │ │ ├── sample.spec.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── elements
│ │ │ │ │ ├── input.xml
│ │ │ │ │ ├── sample.spec.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── elements-short-form
│ │ │ │ │ ├── input.xml
│ │ │ │ │ ├── sample.spec.js
│ │ │ │ │ └── output.js
│ │ │ │ ├── prolog-no-attriibs
│ │ │ │ │ ├── input.xml
│ │ │ │ │ ├── sample.spec.js
│ │ │ │ │ └── output.js
│ │ │ │ └── namespaces
│ │ │ │ │ ├── sample.spec.js
│ │ │ │ │ └── input.xml
│ │ │ └── invalid
│ │ │ │ ├── attributes-no-value-not-closed
│ │ │ │ ├── input.xml
│ │ │ │ ├── sample.spec.js
│ │ │ │ └── output.js
│ │ │ │ ├── attributes-no-value-last
│ │ │ │ ├── input.xml
│ │ │ │ ├── sample.spec.js
│ │ │ │ └── output.js
│ │ │ │ ├── attributes-no-value-middle
│ │ │ │ ├── input.xml
│ │ │ │ ├── sample.spec.js
│ │ │ │ └── output.js
│ │ │ │ ├── missing-top-level-element
│ │ │ │ ├── input.xml
│ │ │ │ ├── sample.spec.js
│ │ │ │ └── output.js
│ │ │ │ ├── attributes-no-value-no-eql-last
│ │ │ │ ├── input.xml
│ │ │ │ ├── sample.spec.js
│ │ │ │ └── output.js
│ │ │ │ ├── attributes-no-value-no-eql-middle
│ │ │ │ ├── input.xml
│ │ │ │ ├── sample.spec.js
│ │ │ │ └── output.js
│ │ │ │ ├── element-name-last
│ │ │ │ ├── input.xml
│ │ │ │ ├── sample.spec.js
│ │ │ │ └── output.js
│ │ │ │ ├── element-name-empty-last
│ │ │ │ ├── input.xml
│ │ │ │ ├── sample.spec.js
│ │ │ │ └── output.js
│ │ │ │ ├── element-name-empty-middle
│ │ │ │ ├── input.xml
│ │ │ │ ├── sample.spec.js
│ │ │ │ └── output.js
│ │ │ │ ├── element-name-middle
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ │ │ ├── element-name-empty-middle-comment
│ │ │ │ ├── input.xml
│ │ │ │ ├── sample.spec.js
│ │ │ │ └── output.js
│ │ │ │ ├── bad-namespace
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ │ │ └── attributes-no-value-not-closed-nested
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ ├── sample-test.js
│ │ ├── utils.js
│ │ └── visitor
│ │ │ └── visitor-spec.js
│ ├── .npmignore
│ ├── CONTRIBUTING.md
│ ├── package.json
│ ├── scripts
│ │ └── update-snapshots.js
│ └── README.md
├── parser
│ ├── test
│ │ ├── snapshots
│ │ │ ├── invalid
│ │ │ │ ├── element-closing-name
│ │ │ │ │ ├── input.xml
│ │ │ │ │ ├── sample.spec.js
│ │ │ │ │ └── output.js
│ │ │ │ └── attributes
│ │ │ │ │ ├── input.xml
│ │ │ │ │ └── sample.spec.js
│ │ │ └── valid
│ │ │ │ ├── complex-contents
│ │ │ │ ├── input.xml
│ │ │ │ ├── sample.spec.js
│ │ │ │ └── output.js
│ │ │ │ ├── attributes
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ │ │ ├── prolog
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ │ │ ├── doc-type-system
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ │ │ ├── elements
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ │ │ ├── cdata
│ │ │ │ ├── input.xml
│ │ │ │ ├── sample.spec.js
│ │ │ │ └── output.js
│ │ │ │ ├── elements-short-form
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ │ │ ├── entity-char-ref
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ │ │ ├── doc-type-public
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ │ │ ├── comments
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ │ │ ├── namespaces
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ │ │ ├── processing-instruction
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ │ │ └── multi-line-comments
│ │ │ │ ├── input.xml
│ │ │ │ └── sample.spec.js
│ │ ├── sample-test.js
│ │ └── utils.js
│ ├── .npmignore
│ ├── CONTRIBUTING.md
│ ├── lib
│ │ └── api.js
│ ├── package.json
│ ├── scripts
│ │ └── update-snapshots.js
│ └── README.md
├── validation
│ ├── lib
│ │ ├── api.js
│ │ └── validate.js
│ ├── .npmignore
│ ├── CONTRIBUTING.md
│ ├── api.d.ts
│ ├── package.json
│ ├── test
│ │ └── validate-apis-spec.js
│ └── README.md
├── common
│ ├── .npmignore
│ ├── CONTRIBUTING.md
│ ├── lib
│ │ ├── api.js
│ │ ├── find-next-textual-token.js
│ │ └── xml-ns-key.js
│ ├── README.md
│ ├── test
│ │ └── find-next-textual-token-spec.js
│ ├── package.json
│ ├── api.d.ts
│ └── CHANGELOG.md
├── constraints
│ ├── .npmignore
│ ├── CONTRIBUTING.md
│ ├── api.d.ts
│ ├── lib
│ │ ├── api.js
│ │ └── constraints
│ │ │ ├── unique-attribute-keys.js
│ │ │ └── tag-closing-name-match.js
│ ├── CHANGELOG.md
│ ├── package.json
│ ├── test
│ │ ├── api-spec.js
│ │ └── constraints
│ │ │ ├── unique-attribute-keys-spec.js
│ │ │ └── tag-closing-name-match-spec.js
│ └── README.md
├── content-assist
│ ├── .npmignore
│ ├── CONTRIBUTING.md
│ ├── lib
│ │ └── api.js
│ ├── package.json
│ └── README.md
├── simple-schema
│ ├── .npmignore
│ ├── CONTRIBUTING.md
│ ├── lib
│ │ ├── validators
│ │ │ ├── utils.js
│ │ │ ├── required-attributes.js
│ │ │ ├── required-sub-elements.js
│ │ │ ├── unknown-sub-elements.js
│ │ │ ├── duplicate-sub-elements.js
│ │ │ ├── unknown-attributes.js
│ │ │ └── attribute-value.js
│ │ ├── api.js
│ │ ├── content-assist
│ │ │ ├── attribute-name.js
│ │ │ ├── attribute-value.js
│ │ │ └── element-name.js
│ │ ├── path.js
│ │ ├── get-content-assist.js
│ │ └── get-validators.js
│ ├── test
│ │ ├── validators
│ │ │ └── utils.js
│ │ └── content-assist
│ │ │ └── utils.js
│ ├── package.json
│ ├── api.d.ts
│ └── README.md
└── ast-position
│ ├── CONTRIBUTING.md
│ ├── lib
│ ├── api.js
│ └── ast-position.js
│ ├── api.d.ts
│ ├── README.md
│ ├── package.json
│ └── CHANGELOG.md
├── .github
└── dependabot.yml
├── .reuse
└── dep5
├── lerna.json
├── .eslintrc.js
├── webpack.config.base.js
├── scripts
├── merge-coverage.js
└── trigger-release.js
├── .gitignore
├── package.json
└── .circleci
└── config.yml
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/packages/language-server/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 |
--------------------------------------------------------------------------------
/packages/xml-toolkit/test/testFixure/test.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/ast/lib/constants.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | DEFAULT_NS: "::DEFAULT",
3 | };
4 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/complex-contents/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/invalid/element-closing-name/input.xml:
--------------------------------------------------------------------------------
1 | note note>
2 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/attributes/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/invalid/attributes/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/complex-contents/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/attributes/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/prolog/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-last/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-middle/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/missing-top-level-element/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/elements/input.xml:
--------------------------------------------------------------------------------
1 |
2 | Bill
3 | Tim
4 |
5 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/doc-type-system/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/elements/input.xml:
--------------------------------------------------------------------------------
1 |
2 | Bill
3 | Tim
4 |
5 |
--------------------------------------------------------------------------------
/packages/xml-toolkit/.gitignore:
--------------------------------------------------------------------------------
1 | # These files will be copied from the root package during bundling.
2 | /LICENSE
3 |
4 | .cache
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/cdata/input.xml:
--------------------------------------------------------------------------------
1 |
2 | Hello, world!]]>
3 |
4 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/elements-short-form/input.xml:
--------------------------------------------------------------------------------
1 |
2 | Bill
3 |
4 |
5 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/elements-short-form/input.xml:
--------------------------------------------------------------------------------
1 |
2 | Bill
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: monthly
7 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/entity-char-ref/input.xml:
--------------------------------------------------------------------------------
1 |
2 | &to;
3 | Ӓ
4 | Tim
5 |
6 |
--------------------------------------------------------------------------------
/packages/validation/lib/api.js:
--------------------------------------------------------------------------------
1 | const { validate } = require("./validate");
2 |
3 | module.exports = {
4 | validate: validate,
5 | };
6 |
--------------------------------------------------------------------------------
/packages/xml-toolkit/resources/xml-toolkit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP/xml-tools/HEAD/packages/xml-toolkit/resources/xml-toolkit.png
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-last/input.xml:
--------------------------------------------------------------------------------
1 |
2 | bobi
3 | john
4 |
6 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/doc-type-public/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-empty-last/input.xml:
--------------------------------------------------------------------------------
1 |
2 | bobi
3 | john
4 | <
5 |
6 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-empty-middle/input.xml:
--------------------------------------------------------------------------------
1 |
2 | bobi
3 | <
4 | john
5 |
6 |
--------------------------------------------------------------------------------
/packages/language-server/lib/constants.js:
--------------------------------------------------------------------------------
1 | const DEFAULT_CONSUMER_NAME = "XML LS";
2 |
3 | module.exports = {
4 | DEFAULT_CONSUMER_NAME,
5 | };
6 |
--------------------------------------------------------------------------------
/packages/ast/.npmignore:
--------------------------------------------------------------------------------
1 | # NYC
2 | .nyc_output
3 | nyc.config.js
4 | coverage
5 |
6 | # tests
7 | test
8 |
9 | # contributing
10 | CONTRIBUTING.md
--------------------------------------------------------------------------------
/packages/common/.npmignore:
--------------------------------------------------------------------------------
1 | # NYC
2 | .nyc_output
3 | nyc.config.js
4 | coverage
5 |
6 | # tests
7 | test
8 |
9 | # contributing
10 | CONTRIBUTING.md
--------------------------------------------------------------------------------
/packages/parser/.npmignore:
--------------------------------------------------------------------------------
1 | # NYC
2 | .nyc_output
3 | nyc.config.js
4 | coverage
5 |
6 | # tests
7 | test
8 |
9 | # contributing
10 | CONTRIBUTING.md
--------------------------------------------------------------------------------
/packages/constraints/.npmignore:
--------------------------------------------------------------------------------
1 | # NYC
2 | .nyc_output
3 | nyc.config.js
4 | coverage
5 |
6 | # tests
7 | test
8 |
9 | # contributing
10 | CONTRIBUTING.md
--------------------------------------------------------------------------------
/packages/validation/.npmignore:
--------------------------------------------------------------------------------
1 | # NYC
2 | .nyc_output
3 | nyc.config.js
4 | coverage
5 |
6 | # tests
7 | test
8 |
9 | # contributing
10 | CONTRIBUTING.md
--------------------------------------------------------------------------------
/packages/content-assist/.npmignore:
--------------------------------------------------------------------------------
1 | # NYC
2 | .nyc_output
3 | nyc.config.js
4 | coverage
5 |
6 | # tests
7 | test
8 |
9 | # contributing
10 | CONTRIBUTING.md
--------------------------------------------------------------------------------
/packages/language-server/.npmignore:
--------------------------------------------------------------------------------
1 | # NYC
2 | .nyc_output
3 | nyc.config.js
4 | coverage
5 |
6 | # tests
7 | test
8 |
9 | # contributing
10 | CONTRIBUTING.md
--------------------------------------------------------------------------------
/packages/simple-schema/.npmignore:
--------------------------------------------------------------------------------
1 | # NYC
2 | .nyc_output
3 | nyc.config.js
4 | coverage
5 |
6 | # tests
7 | test
8 |
9 | # contributing
10 | CONTRIBUTING.md
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-middle/input.xml:
--------------------------------------------------------------------------------
1 |
2 | bobi
3 | john
5 | ny
6 |
7 |
--------------------------------------------------------------------------------
/packages/xml-toolkit/resources/readme/preview-syntax-validation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP/xml-tools/HEAD/packages/xml-toolkit/resources/readme/preview-syntax-validation.png
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/prolog-no-attriibs/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Bill
4 | hello
5 | Tim
6 | world
7 |
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/comments/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Bill
5 |
6 |
7 |
--------------------------------------------------------------------------------
/packages/ast/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guide
2 |
3 | This package does not have any unique development flows.
4 | Please see the top level [Contribution Guide](../../CONTRIBUTING.md).
5 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/input.xml:
--------------------------------------------------------------------------------
1 |
2 | bobi
3 | <
4 |
5 | john
6 |
7 |
--------------------------------------------------------------------------------
/packages/common/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guide
2 |
3 | This package does not have any unique development flows.
4 | Please see the top level [Contribution Guide](../../CONTRIBUTING.md).
5 |
--------------------------------------------------------------------------------
/packages/parser/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guide
2 |
3 | This package does not have any unique development flows.
4 | Please see the top level [Contribution Guide](../../CONTRIBUTING.md).
5 |
--------------------------------------------------------------------------------
/packages/ast-position/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guide
2 |
3 | This package does not have any unique development flows.
4 | Please see the top level [Contribution Guide](../../CONTRIBUTING.md).
5 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/bad-namespace/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Some Chair
4 | 50
5 | 67
6 |
7 |
--------------------------------------------------------------------------------
/packages/constraints/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guide
2 |
3 | This package does not have any unique development flows.
4 | Please see the top level [Contribution Guide](../../CONTRIBUTING.md).
5 |
--------------------------------------------------------------------------------
/packages/simple-schema/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guide
2 |
3 | This package does not have any unique development flows.
4 | Please see the top level [Contribution Guide](../../CONTRIBUTING.md).
5 |
--------------------------------------------------------------------------------
/packages/validation/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guide
2 |
3 | This package does not have any unique development flows.
4 | Please see the top level [Contribution Guide](../../CONTRIBUTING.md).
5 |
--------------------------------------------------------------------------------
/packages/content-assist/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guide
2 |
3 | This package does not have any unique development flows.
4 | Please see the top level [Contribution Guide](../../CONTRIBUTING.md).
5 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/input.xml:
--------------------------------------------------------------------------------
1 |
2 | hello world
4 |
5 |
6 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/namespaces/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Some Chair
4 | 50
5 | 67
6 |
7 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/processing-instruction/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/multi-line-comments/input.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
8 | Bill
9 |
10 |
14 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/attributes/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/elements/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/namespaces/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/prolog/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/cdata/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/prolog/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/attributes/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/comments/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/elements/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/namespaces/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/bad-namespace/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/complex-contents/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/elements-short-form/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/prolog-no-attriibs/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/invalid/attributes/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/complex-contents/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/doc-type-public/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/doc-type-system/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/entity-char-ref/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-last/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-middle/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/elements-short-form/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/multi-line-comments/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/processing-instruction/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-last/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-empty-last/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/invalid/element-closing-name/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/lib/api.js:
--------------------------------------------------------------------------------
1 | const { buildAst } = require("./build-ast");
2 | const { accept } = require("./visit-ast");
3 | const { DEFAULT_NS } = require("./constants");
4 |
5 | module.exports = {
6 | buildAst: buildAst,
7 | accept: accept,
8 | DEFAULT_NS: DEFAULT_NS,
9 | };
10 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-middle/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-empty-middle/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/missing-top-level-element/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/validators/utils.js:
--------------------------------------------------------------------------------
1 | const { pick } = require("lodash");
2 |
3 | function tokenToOffsetPosition(token) {
4 | return pick(token, ["startOffset", "endOffset"]);
5 | }
6 |
7 | module.exports = {
8 | tokenToOffsetPosition: tokenToOffsetPosition,
9 | };
10 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed-nested/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 | describe(`${testNameFromDir(__dirname)}`, () => {
6 | executeInValidSampleTest(__dirname);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/namespaces/input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Some Chair
4 | 50
5 | 67
6 | Awsome Chair
7 |
8 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/api.js:
--------------------------------------------------------------------------------
1 | const { getSchemaValidators } = require("./get-validators");
2 | const { getSchemaSuggestionsProviders } = require("./get-content-assist");
3 |
4 | module.exports = {
5 | getSchemaValidators: getSchemaValidators,
6 | getSchemaSuggestionsProviders: getSchemaSuggestionsProviders,
7 | };
8 |
--------------------------------------------------------------------------------
/.reuse/dep5:
--------------------------------------------------------------------------------
1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 | Upstream-Name: xml-tools
3 | Upstream-Contact: Shachar Soel
4 | Source: https://github.com/SAP/xml-tools
5 |
6 | Files: *
7 | Copyright: 2019-2024 SAP SE or an SAP affiliate company and XML Tools contributors
8 | License: Apache-2.0
9 |
--------------------------------------------------------------------------------
/packages/constraints/api.d.ts:
--------------------------------------------------------------------------------
1 | import { ValidationIssue } from "@xml-tools/validation";
2 |
3 | /**
4 | * detects XML constraints issues (e.g non well-formed XML)
5 | * - https://www.w3.org/TR/xml/#sec-starttags
6 | * - see: "Well-formedness constraint: Unique Att Spec".
7 | */
8 | export function checkConstraints(ast: XMLDocument): ValidationIssue[];
9 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": ["packages/*"],
3 | "useWorkspaces": true,
4 | "npmClient": "yarn",
5 | "version": "independent",
6 | "command": {
7 | "publish": {
8 | "conventionalCommits": true
9 | },
10 | "version": {
11 | "allowBranch": "master",
12 | "message": "chore(release): release"
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/sample.spec.js:
--------------------------------------------------------------------------------
1 | const {
2 | executeInValidSampleTest,
3 | testNameFromDir,
4 | } = require("../../../sample-test");
5 |
6 | // TODO: should we disable single token deletion in this case?
7 | describe(`${testNameFromDir(__dirname)}`, () => {
8 | executeInValidSampleTest(__dirname);
9 | });
10 |
--------------------------------------------------------------------------------
/packages/common/lib/api.js:
--------------------------------------------------------------------------------
1 | const { findNextTextualToken } = require("./find-next-textual-token");
2 | const {
3 | isXMLNamespaceKey,
4 | getXMLNamespaceKeyPrefix,
5 | } = require("./xml-ns-key.js");
6 |
7 | module.exports = {
8 | findNextTextualToken: findNextTextualToken,
9 | isXMLNamespaceKey: isXMLNamespaceKey,
10 | getXMLNamespaceKeyPrefix: getXMLNamespaceKeyPrefix,
11 | };
12 |
--------------------------------------------------------------------------------
/packages/language-server/nyc.config.js:
--------------------------------------------------------------------------------
1 | // TODO: extract to top level nyc config.
2 | module.exports = {
3 | include: ["lib/**/*.js"],
4 | // Not yet sure how to implement an Integration test for the LSP Server
5 | exclude: ["lib/server.js"],
6 | reporter: ["text", "lcov"],
7 | "check-coverage": true,
8 | all: true,
9 | branches: 100,
10 | lines: 100,
11 | functions: 100,
12 | statements: 100,
13 | };
14 |
--------------------------------------------------------------------------------
/packages/language-server/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const baseConfig = require("../../webpack.config.base");
3 |
4 | const config = {
5 | ...baseConfig,
6 | entry: "./lib/server.js",
7 | output: {
8 | path: path.resolve(__dirname, "dist"),
9 | filename: "server.js",
10 | libraryTarget: "commonjs2",
11 | devtoolModuleFilenameTemplate: "../[resource-path]",
12 | },
13 | };
14 | module.exports = config;
15 |
--------------------------------------------------------------------------------
/packages/language-server/api.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Absolute path to the server's "main" module
3 | * This is useful when launching the server in a separate process (e.g via spawn).
4 | */
5 | export const SERVER_PATH: string;
6 |
7 | export type ServerInitializationOptions = {
8 | /**
9 | * optional name of the IDE plugin/extension using this language server.
10 | * This name would be used in the `source` property of dignostic issues.
11 | */
12 | consumer?: string;
13 | };
14 |
--------------------------------------------------------------------------------
/packages/ast-position/lib/api.js:
--------------------------------------------------------------------------------
1 | const { AstPositionVisitor } = require("./ast-position");
2 | const { accept } = require("@xml-tools/ast");
3 |
4 | /**
5 | * @param {XMLDocument} xmlDoc
6 | * @param {number} offset
7 | * @returns {AstPosition | undefined}
8 | */
9 | function astPositionAtOffset(xmlDoc, offset) {
10 | const positionVisitor = new AstPositionVisitor(offset);
11 | accept(xmlDoc, positionVisitor);
12 | return positionVisitor.position;
13 | }
14 |
15 | module.exports = {
16 | astPositionAtOffset,
17 | };
18 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // Common settings for JS Files.
3 | extends: [
4 | "eslint:recommended",
5 | "plugin:eslint-comments/recommended",
6 | // Disables all formatting related rules as formatting is handled by prettier, not eslint.
7 | "prettier",
8 | ],
9 | parserOptions: {
10 | // Targeting modern nodejs versions
11 | // Consult with: https://kangax.github.io/compat-table/es2016plus/
12 | ecmaVersion: 2018,
13 | },
14 | env: {
15 | commonjs: true,
16 | mocha: true,
17 | node: true,
18 | es6: true,
19 | },
20 | rules: {
21 | "eslint-comments/require-description": ["error", { ignore: [] }],
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/packages/xml-toolkit/.vscodeignore:
--------------------------------------------------------------------------------
1 |
2 | # We are bundling our extension so no need to include the dependencies separately.
3 | node_modules
4 |
5 | # Except a **subset** language-server package which is executed as a separate process
6 | # Note only one star (*) is expended in these dirs.
7 | !**/@xml-tools/language-server/*
8 | !**/@xml-tools/language-server/dist/*
9 |
10 | # For debugging of bundled language-server in production (source maps)
11 | !**/@xml-tools/language-server/lib/**
12 |
13 | # The VSCode Extension's dev artifacts
14 | coverage/**
15 | .nyc_output/**
16 | test/**
17 | scripts
18 |
19 | # Loaded via gitcdn.xyz in the README, avoid packaging to reduce .vsix size
20 | resources/readme/**
--------------------------------------------------------------------------------
/packages/constraints/lib/api.js:
--------------------------------------------------------------------------------
1 | const { validate } = require("@xml-tools/validation");
2 | const {
3 | validateUniqueAttributeKeys,
4 | } = require("./constraints/unique-attribute-keys");
5 | const {
6 | validateTagClosingNameMatch,
7 | } = require("./constraints/tag-closing-name-match");
8 |
9 | /**
10 | * @param {XMLDocument} ast
11 | * @returns {ValidationIssue[]}
12 | */
13 | function checkConstraints(ast) {
14 | const constraintIssues = validate({
15 | doc: ast,
16 | validators: {
17 | element: [validateTagClosingNameMatch, validateUniqueAttributeKeys],
18 | },
19 | });
20 |
21 | return constraintIssues;
22 | }
23 |
24 | module.exports = {
25 | checkConstraints,
26 | };
27 |
--------------------------------------------------------------------------------
/packages/ast/lib/utils.js:
--------------------------------------------------------------------------------
1 | const { reduce, has, isArray } = require("lodash");
2 |
3 | function getAstChildrenReflective(astParent) {
4 | const astChildren = reduce(
5 | astParent,
6 | (result, prop, name) => {
7 | if (name === "parent") {
8 | // parent property is never a child...
9 | } else if (has(prop, "type")) {
10 | result.push(prop);
11 | } else if (isArray(prop) && prop.length > 0 && has(prop[0], "type")) {
12 | result = result.concat(prop);
13 | }
14 |
15 | return result;
16 | },
17 | []
18 | );
19 |
20 | return astChildren;
21 | }
22 |
23 | module.exports = {
24 | getAstChildrenReflective: getAstChildrenReflective,
25 | };
26 |
--------------------------------------------------------------------------------
/packages/parser/lib/api.js:
--------------------------------------------------------------------------------
1 | const { xmlLexer } = require("./lexer");
2 | const { xmlParser } = require("./parser");
3 |
4 | module.exports = {
5 | parse: function parse(text) {
6 | const lexResult = xmlLexer.tokenize(text);
7 | // setting a new input will RESET the parser instance's state.
8 | xmlParser.input = lexResult.tokens;
9 | // any top level rule may be used as an entry point
10 | const cst = xmlParser.document();
11 |
12 | return {
13 | cst: cst,
14 | tokenVector: lexResult.tokens,
15 | lexErrors: lexResult.errors,
16 | parseErrors: xmlParser.errors,
17 | };
18 | },
19 |
20 | BaseXmlCstVisitor: xmlParser.getBaseCstVisitorConstructor(),
21 | };
22 |
--------------------------------------------------------------------------------
/packages/constraints/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | ## [1.1.1](https://github.com/sap/xml-tools/compare/@xml-tools/constraints@1.1.0...@xml-tools/constraints@1.1.1) (2021-06-03)
7 |
8 | **Note:** Version bump only for package @xml-tools/constraints
9 |
10 | # 1.1.0 (2021-02-09)
11 |
12 | ### Features
13 |
14 | - well formed constraints validations ([#293](https://github.com/sap/xml-tools/issues/293)) ([8a9d535](https://github.com/sap/xml-tools/commit/8a9d535e95a52b9dfc1068d2e203b09bd08e1066)), closes [#13](https://github.com/sap/xml-tools/issues/13) [#14](https://github.com/sap/xml-tools/issues/14)
15 |
--------------------------------------------------------------------------------
/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | const config = {
4 | optimization: {
5 | // The Default minimization options cause JavaScript runtime errors.
6 | // Also we don't actually need to minimize as much (not targeted for browser).
7 | // Rather we mostly need to reduce the number of fileSystem access requests
8 | // by reducing the number of files.
9 | minimize: false,
10 | },
11 | target: "node",
12 | devtool: "source-map",
13 | resolve: {
14 | // Solution for resolution inside mono-repo
15 | modules: [
16 | path.resolve(__dirname, "node_modules"),
17 | path.resolve(__dirname, "../node_modules"),
18 | "node_modules",
19 | ],
20 | extensions: [".js"],
21 | },
22 | };
23 |
24 | module.exports = config;
25 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/content-assist/attribute-name.js:
--------------------------------------------------------------------------------
1 | const { difference, map } = require("lodash");
2 |
3 | /**
4 | * @param {XMLElement} elementNode
5 | * @param {XSSElement} xssElement
6 | *
7 | * @returns {CompletionSuggestion[]}
8 | */
9 | function attributeNameCompletion(elementNode, xssElement) {
10 | const possibleSuggestions = map(xssElement.attributes, (_) => _.key);
11 | const existingAttribNames = map(elementNode.attributes, (_) => _.key);
12 | const possibleNewSuggestions = difference(
13 | possibleSuggestions,
14 | existingAttribNames
15 | );
16 |
17 | const suggestions = map(possibleNewSuggestions, (_) => {
18 | return {
19 | text: _,
20 | label: _,
21 | commitCharacter: "=",
22 | };
23 | });
24 |
25 | return suggestions;
26 | }
27 |
28 | module.exports = {
29 | attributeNameCompletion: attributeNameCompletion,
30 | };
31 |
--------------------------------------------------------------------------------
/packages/common/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/@xml-tools/common)
2 |
3 | # @xml-tools/common
4 |
5 | Shared Utilities for XML-Tools
6 |
7 | ## Installation
8 |
9 | With npm:
10 |
11 | - `npm install @xml-tools/Common`
12 |
13 | With Yarn
14 |
15 | - `yarn add @xml-tools/Common`
16 |
17 | ## Usage
18 |
19 | Please see the [TypeScript Definitions](./api.d.ts) for full API details.
20 |
21 | Simply import/require the @xml-tools/common package and use the utilities
22 | as defined in the APIs above.
23 |
24 | ```javascript
25 | const xmlToolsCommon = require("@xml-tools/common");
26 | xmlToolsCommon.findNextTextualToken(/*... */);
27 | ```
28 |
29 | ## Support
30 |
31 | Please open [issues](https://github.com/SAP/xml-tols/issues) on github.
32 |
33 | ## Contributing
34 |
35 | See [CONTRIBUTING.md](./CONTRIBUTING.md).
36 |
--------------------------------------------------------------------------------
/packages/simple-schema/test/validators/utils.js:
--------------------------------------------------------------------------------
1 | const { parse } = require("@xml-tools/parser");
2 | const { buildAst } = require("@xml-tools/ast");
3 | const { validate } = require("@xml-tools/validation");
4 | const { getSchemaValidators } = require("../../");
5 |
6 | /**
7 | *
8 | * @param {string} xmlText
9 | * @param {SimpleSchema} schema
10 | * @returns {ValidationIssue[] | *}
11 | */
12 | function validateBySchema(xmlText, schema) {
13 | const { cst, tokenVector } = parse(xmlText);
14 | const xmlDoc = buildAst(cst, tokenVector);
15 | const schemaValidators = getSchemaValidators(schema);
16 | const issues = validate({
17 | doc: xmlDoc,
18 | validators: {
19 | attribute: [schemaValidators.attribute],
20 | element: [schemaValidators.element],
21 | },
22 | });
23 | return issues;
24 | }
25 |
26 | module.exports = {
27 | validateBySchema: validateBySchema,
28 | };
29 |
--------------------------------------------------------------------------------
/packages/ast-position/api.d.ts:
--------------------------------------------------------------------------------
1 | import { XMLAttribute, XMLElement, XMLDocument } from "@xml-tools/ast";
2 |
3 | /**
4 | * Not all possible kinds of positions are currently supported.
5 | * More may be added in the future.
6 | */
7 | type AstPosition =
8 | | XMLElementOpenName
9 | | XMLElementCloseName
10 | | XMLAttributeKey
11 | | XMLAttributeValue;
12 |
13 | declare function astPositionAtOffset(
14 | ast: XMLDocument,
15 | offset: number
16 | ): AstPosition | undefined;
17 |
18 | interface XMLElementOpenName {
19 | kind: "XMLElementOpenName";
20 | astNode: XMLElement;
21 | }
22 |
23 | interface XMLElementCloseName {
24 | kind: "XMLElementCloseName";
25 | astNode: XMLElement;
26 | }
27 |
28 | interface XMLAttributeKey {
29 | kind: "XMLAttributeKey";
30 | astNode: XMLAttribute;
31 | }
32 |
33 | interface XMLAttributeValue {
34 | kind: "XMLAttributeValue";
35 | astNode: XMLAttribute;
36 | }
37 |
--------------------------------------------------------------------------------
/packages/ast-position/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/@xml-tools/ast-position)
2 |
3 | # @xml-tools/ast-position
4 |
5 | Utilities related to textual positions in the XML-Tools **A**bstract **S**yntax **T**ree.
6 |
7 | ## Installation
8 |
9 | With npm:
10 |
11 | - `npm install @xml-tools/ast-position`
12 |
13 | With Yarn
14 |
15 | - `yarn add @xml-tools/ast-position`
16 |
17 | ## Usage
18 |
19 | Please see the [TypeScript Definitions](./api.d.ts) for full API details.
20 |
21 | Simply import/require the @xml-tools/ast-position package and use the utilities
22 | as declared in the TypeScript signatures above.
23 |
24 | ## Support
25 |
26 | Please open [issues](https://github.com/SAP/xml-tols/issues) on github.
27 |
28 | ## Contributing
29 |
30 | See [CONTRIBUTING.md](./CONTRIBUTING.md).
31 |
32 | [ast]: https://en.wikipedia.org/wiki/Abstract_syntax_tree
33 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/complex-contents/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [],
9 | subElements: [],
10 | textContents: [],
11 | position: { startOffset: 9, endOffset: 21 },
12 | syntax: {
13 | openName: { image: "note", startOffset: 10, endOffset: 13 },
14 | closeName: { image: "note", startOffset: 17, endOffset: 20 },
15 | isSelfClosing: false,
16 | openBody: { startOffset: 9, endOffset: 14 },
17 | closeBody: { startOffset: 15, endOffset: 21 },
18 | attributesRange: { startOffset: 15, endOffset: 13 },
19 | },
20 | },
21 | position: { startOffset: 0, endOffset: 22 },
22 | prolog: {
23 | type: "XMLProlog",
24 | attributes: [],
25 | position: { startOffset: 0, endOffset: 7 },
26 | },
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/packages/xml-toolkit/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const baseConfig = require("../../webpack.config.base");
3 |
4 | const config = {
5 | ...baseConfig,
6 | entry: "./lib/extension.js",
7 | output: {
8 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
9 | path: path.resolve(__dirname, "dist"),
10 | filename: "extension.js",
11 | libraryTarget: "commonjs2",
12 | devtoolModuleFilenameTemplate: "../[resource-path]",
13 | },
14 | // 📖 -> https://webpack.js.org/configuration/externals/
15 | externals: {
16 | // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed.
17 | vscode: "commonjs vscode",
18 | // the language-server must be bundled separately as it is executed in a separate process (by path).
19 | "@xml-tools/language-server": "commonjs @xml-tools/language-server",
20 | },
21 | };
22 |
23 | module.exports = config;
24 |
--------------------------------------------------------------------------------
/packages/validation/lib/validate.js:
--------------------------------------------------------------------------------
1 | const { accept } = require("@xml-tools/ast");
2 | const { defaultsDeep, flatMap } = require("lodash");
3 |
4 | function validate(options) {
5 | const actualOptions = defaultsDeep(options, {
6 | validators: {
7 | attribute: [],
8 | element: [],
9 | },
10 | });
11 |
12 | let issues = [];
13 |
14 | const validateVisitor = {
15 | visitXMLElement: function (node) {
16 | const newIssues = flatMap(actualOptions.validators.element, (validator) =>
17 | validator(node)
18 | );
19 | issues = issues.concat(newIssues);
20 | },
21 | visitXMLAttribute: function (node) {
22 | const newIssues = flatMap(
23 | actualOptions.validators.attribute,
24 | (validator) => validator(node)
25 | );
26 | issues = issues.concat(newIssues);
27 | },
28 | };
29 |
30 | accept(actualOptions.doc, validateVisitor);
31 |
32 | return issues;
33 | }
34 |
35 | module.exports = {
36 | validate: validate,
37 | };
38 |
--------------------------------------------------------------------------------
/packages/language-server/lib/api.js:
--------------------------------------------------------------------------------
1 | const { existsSync } = require("fs");
2 | const { resolve } = require("path");
3 |
4 | // for use in productive flows
5 | const bundledPath = resolve(__dirname, "..", "dist", "server.js");
6 | // for dev flows
7 | const sourcesPath = resolve(__dirname, "server.js");
8 |
9 | // We assume that if the `node_modules` directory exists then we are running
10 | // in development mode, We rely on the fact that the bundled .vsix file of the
11 | // VSCode extension excludes the this package's `node_modules` folder (among others).
12 | // Note this logic would not work with yarn 2.0 / npm 8 / pnpm as `node_modules`
13 | // is no longer created by those package managers.
14 | const isDevelopmentRun = existsSync(resolve(__dirname, "..", "node_modules"));
15 |
16 | let serverPath;
17 | /* istanbul ignore else - no tests (yet?) on bundled artifacts */
18 | if (isDevelopmentRun) {
19 | serverPath = sourcesPath;
20 | } else {
21 | serverPath = bundledPath;
22 | }
23 |
24 | module.exports = {
25 | SERVER_PATH: serverPath,
26 | };
27 |
--------------------------------------------------------------------------------
/packages/xml-toolkit/test/runTest.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require("path");
2 | const { runTests } = require("vscode-test");
3 |
4 | async function main() {
5 | try {
6 | // The path to the extension test environment
7 | // Passed to --extensionTestsEnv
8 | const extensionTestsEnv = {
9 | path: resolve(__dirname, "..", "..", "test", "suite", "textFixure"),
10 | };
11 |
12 | // The folder containing the Extension Manifest package.json
13 | // Passed to `--extensionDevelopmentPath`
14 | const extensionDevelopmentPath = resolve(__dirname, "..");
15 |
16 | // The path to the extension test script
17 | // Passed to --extensionTestsPath
18 | const extensionTestsPath = resolve(__dirname, "suite", "index");
19 |
20 | // Download VS Code, unzip it and run the integration test
21 | await runTests({
22 | extensionTestsEnv,
23 | extensionDevelopmentPath,
24 | extensionTestsPath,
25 | });
26 | } catch (err) {
27 | console.error("Failed to run tests");
28 | process.exit(1);
29 | }
30 | }
31 |
32 | main();
33 |
--------------------------------------------------------------------------------
/packages/xml-toolkit/test/suite/index.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require("path");
2 | const Mocha = require("mocha");
3 | const glob = require("glob");
4 |
5 | function run() {
6 | // Create the mocha test
7 | const mocha = new Mocha({
8 | ui: "bdd",
9 | timeout: 20000,
10 | });
11 | mocha.useColors(true);
12 |
13 | const testsRoot = resolve(__dirname);
14 |
15 | return new Promise((c, e) => {
16 | glob("**.test.js", { cwd: testsRoot }, (err, files) => {
17 | if (err) {
18 | return e(err);
19 | }
20 |
21 | // Add files to the test suite
22 | files.forEach((f) => mocha.addFile(resolve(testsRoot, f)));
23 |
24 | try {
25 | // Run the mocha test
26 | mocha.run((failures) => {
27 | if (failures > 0) {
28 | e(new Error(`${failures} tests failed.`));
29 | } else {
30 | c();
31 | }
32 | });
33 | } catch (err) {
34 | console.error(err);
35 | e(err);
36 | }
37 | });
38 | });
39 | }
40 |
41 | module.exports = {
42 | run: run,
43 | };
44 |
--------------------------------------------------------------------------------
/packages/language-server/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/@xml-tools/language-server)
2 |
3 | # @xml-tools/language-server
4 |
5 | XML Language Server
6 |
7 | Current Features:
8 |
9 | - Syntax Diagnostics.
10 |
11 | ## Installation
12 |
13 | With npm:
14 |
15 | - `npm install @xml-tools/language-server`
16 |
17 | With Yarn
18 |
19 | - `yarn add @xml-tools/language-server`
20 |
21 | ## Usage
22 |
23 | Please see the [TypeScript Definitions](./api.d.ts) for full API details.
24 |
25 | A simple usage example:
26 |
27 | ```javascript
28 | const { SERVER_PATH } = require("@xml-tools/language-server");
29 |
30 | // SERVER_PATH is the only API currently and it is meant to expose the "main" module's absolute
31 | // path which would then be executed in a different process
32 | console.log(SERVER_PATH); // --> .../node_module/@xml-tools/language-server/lib/server.js
33 | ```
34 |
35 | ## Support
36 |
37 | Please open [issues](https://github.com/SAP/xml-tols/issues) on github.
38 |
39 | ## Contributing
40 |
41 | See [CONTRIBUTING.md](./CONTRIBUTING.md).
42 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/missing-top-level-element/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: null,
5 | position: { startOffset: 0, endOffset: 39 },
6 | prolog: {
7 | type: "XMLProlog",
8 | attributes: [
9 | {
10 | type: "XMLPrologAttribute",
11 | position: { startOffset: 6, endOffset: 18 },
12 | key: "version",
13 | value: "1.0",
14 | syntax: {
15 | key: { image: "version", startOffset: 6, endOffset: 12 },
16 | value: { image: '"1.0"', startOffset: 14, endOffset: 18 },
17 | },
18 | },
19 | {
20 | type: "XMLPrologAttribute",
21 | position: { startOffset: 20, endOffset: 35 },
22 | key: "encoding",
23 | value: "UTF-8",
24 | syntax: {
25 | key: { image: "encoding", startOffset: 20, endOffset: 27 },
26 | value: { image: '"UTF-8"', startOffset: 29, endOffset: 35 },
27 | },
28 | },
29 | ],
30 | position: { startOffset: 0, endOffset: 37 },
31 | },
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/packages/ast-position/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@xml-tools/ast-position",
3 | "version": "2.0.7",
4 | "description": "XML Ast Position APIs",
5 | "keywords": [
6 | "xml",
7 | "position"
8 | ],
9 | "main": "lib/api.js",
10 | "repository": "https://github.com/sap/xml-tools/",
11 | "license": "Apache-2.0",
12 | "typings": "./api.d.ts",
13 | "files": [
14 | "lib",
15 | ".reuse",
16 | "LICENSES",
17 | "api.d.ts"
18 | ],
19 | "dependencies": {
20 | "@xml-tools/ast": "^5.0.5"
21 | },
22 | "devDependencies": {
23 | "@xml-tools/parser": "^1.0.11"
24 | },
25 | "scripts": {
26 | "ci": "npm-run-all type-check coverage:*",
27 | "test": "mocha \"./test/**/*spec.js\"",
28 | "coverage:run": "nyc mocha \"./test/**/*spec.js\"",
29 | "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100",
30 | "type-check": "tsc api.d.ts"
31 | },
32 | "publishConfig": {
33 | "access": "public"
34 | },
35 | "nyc": {
36 | "include": [
37 | "lib/**/*.js"
38 | ],
39 | "reporter": [
40 | "text",
41 | "lcov"
42 | ]
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-not-closed/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [
9 | {
10 | type: "XMLAttribute",
11 | position: { startOffset: 6, endOffset: 16 },
12 | key: "attr1",
13 | value: "666",
14 | syntax: {
15 | key: { image: "attr1", startOffset: 6, endOffset: 10 },
16 | value: { image: '"666"', startOffset: 12, endOffset: 16 },
17 | },
18 | },
19 | {
20 | type: "XMLAttribute",
21 | position: { startOffset: 18, endOffset: 23 },
22 | key: "attr2",
23 | value: null,
24 | syntax: { key: { image: "attr2", startOffset: 18, endOffset: 22 } },
25 | },
26 | ],
27 | subElements: [],
28 | textContents: [],
29 | position: { startOffset: 0, endOffset: 23 },
30 | syntax: { openName: { image: "note", startOffset: 1, endOffset: 4 } },
31 | },
32 | position: { startOffset: 0, endOffset: 23 },
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/packages/validation/api.d.ts:
--------------------------------------------------------------------------------
1 | import {
2 | XMLAstNode,
3 | XMLAttribute,
4 | XMLElement,
5 | XMLDocument,
6 | } from "@xml-tools/ast";
7 |
8 | declare type AttributeValidator = (node: XMLAttribute) => ValidationIssue[];
9 | declare type ElementValidator = (node: XMLElement) => ValidationIssue[];
10 | declare function validate(options: {
11 | doc: XMLDocument;
12 | validators: {
13 | attribute?: AttributeValidator[];
14 | element?: ElementValidator[];
15 | };
16 | }): ValidationIssue[];
17 |
18 | interface ValidationIssue {
19 | msg: string;
20 | /**
21 | * ASTNode where the issue occurred.
22 | */
23 | node: XMLAstNode;
24 | /**
25 | * The Severity is optional as that may need to be determined outside
26 | * the validation logic, For example in an IDE's inspections configuration.
27 | */
28 | severity?: "info" | "warning" | "error";
29 |
30 | /**
31 | * Optional property to indicate a more specific error location.
32 | * For example: For Missing Attribute error in an element we may want to
33 | * only mark the Element **Name** area instead of the whole element.
34 | */
35 | position?: { startOffset: number; endOffset: number };
36 | }
37 |
--------------------------------------------------------------------------------
/packages/common/test/find-next-textual-token-spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require("chai");
2 | const { parse } = require("@xml-tools/parser");
3 | const { findNextTextualToken } = require("../");
4 |
5 | describe("The XML-Tools Common Utils", () => {
6 | context("findNextTextualToken", () => {
7 | it("can find the next textual token in a valid XML", () => {
8 | const xmlText = `
9 | Bill
10 | Tim
11 | `;
12 |
13 | const { tokenVector } = parse(xmlText);
14 | // "28" is the endOffset of `Bill`
15 | const nextTextualToken = findNextTextualToken(tokenVector, 27);
16 | expect(nextTextualToken.image).to.eql("<");
17 | expect(nextTextualToken.startOffset).to.eql(37);
18 | });
19 |
20 | it("will return null when there is no following textual token", () => {
21 | const xmlText = `
22 | Bill
23 | `;
24 |
25 | const { tokenVector } = parse(xmlText);
26 | // "28" is the endOffset of `Bill`
27 | const nextTextualToken = findNextTextualToken(tokenVector, 27);
28 | expect(nextTextualToken).to.be.null;
29 | });
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/packages/common/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@xml-tools/common",
3 | "version": "0.1.6",
4 | "description": "XML-Tools Common Utilities",
5 | "keywords": [
6 | "xml",
7 | "validation"
8 | ],
9 | "main": "lib/api.js",
10 | "repository": "https://github.com/sap/xml-tools/",
11 | "license": "Apache-2.0",
12 | "typings": "./api.d.ts",
13 | "files": [
14 | "lib",
15 | ".reuse",
16 | "LICENSES",
17 | "api.d.ts"
18 | ],
19 | "dependencies": {
20 | "lodash": "4.17.21"
21 | },
22 | "devDependencies": {
23 | "@xml-tools/parser": "^1.0.11"
24 | },
25 | "scripts": {
26 | "ci": "npm-run-all clean type-check coverage:*",
27 | "clean": "rimraf ./coverage ./nyc_output",
28 | "test": "mocha \"./test/**/*spec.js\"",
29 | "coverage:run": "nyc mocha \"./test/**/*spec.js\"",
30 | "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100",
31 | "type-check": "tsc api.d.ts"
32 | },
33 | "publishConfig": {
34 | "access": "public"
35 | },
36 | "nyc": {
37 | "include": [
38 | "lib/**/*.js"
39 | ],
40 | "reporter": [
41 | "text",
42 | "lcov"
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/language-server/lib/server.js:
--------------------------------------------------------------------------------
1 | const {
2 | createConnection,
3 | ProposedFeatures,
4 | TextDocuments,
5 | TextDocumentSyncKind,
6 | } = require("vscode-languageserver");
7 | const { TextDocument } = require("vscode-languageserver-textdocument");
8 | const { has } = require("lodash");
9 | const { validateDocument } = require("./language-services");
10 | const { DEFAULT_CONSUMER_NAME } = require("./constants");
11 |
12 | const connection = createConnection(ProposedFeatures.all);
13 | const documents = new TextDocuments(TextDocument);
14 |
15 | let consumer;
16 |
17 | connection.onInitialize((params) => {
18 | if (has(params, "initializationOptions.consumer")) {
19 | consumer = params.initializationOptions.consumer;
20 | } else {
21 | consumer = DEFAULT_CONSUMER_NAME;
22 | }
23 |
24 | return {
25 | capabilities: {
26 | textDocumentSync: TextDocumentSyncKind.Full,
27 | },
28 | };
29 | });
30 |
31 | documents.onDidChangeContent(async (event) => {
32 | const diagnostics = await validateDocument(event.document, { consumer });
33 | connection.sendDiagnostics({ uri: event.document.uri, diagnostics });
34 | });
35 |
36 | documents.listen(connection);
37 | connection.listen();
38 |
--------------------------------------------------------------------------------
/packages/ast-position/lib/ast-position.js:
--------------------------------------------------------------------------------
1 | class AstPositionVisitor {
2 | constructor(offset) {
3 | this.offset = offset;
4 | this.position = undefined;
5 | }
6 |
7 | visitXMLElement(node) {
8 | const openName = node.syntax.openName;
9 | const closeName = node.syntax.closeName;
10 | if (this.isOffsetInRange(openName)) {
11 | this.position = { kind: "XMLElementOpenName", astNode: node };
12 | } else if (this.isOffsetInRange(closeName)) {
13 | this.position = { kind: "XMLElementCloseName", astNode: node };
14 | }
15 | }
16 |
17 | visitXMLAttribute(node) {
18 | const key = node.syntax.key;
19 | const value = node.syntax.value;
20 |
21 | if (this.isOffsetInRange(key)) {
22 | this.position = { kind: "XMLAttributeKey", astNode: node };
23 | } else if (this.isOffsetInRange(value)) {
24 | this.position = { kind: "XMLAttributeValue", astNode: node };
25 | }
26 | }
27 |
28 | isOffsetInRange(position) {
29 | return (
30 | position !== undefined &&
31 | position.startOffset <= this.offset &&
32 | position.endOffset >= this.offset
33 | );
34 | }
35 | }
36 |
37 | module.exports = {
38 | AstPositionVisitor: AstPositionVisitor,
39 | };
40 |
--------------------------------------------------------------------------------
/packages/content-assist/lib/api.js:
--------------------------------------------------------------------------------
1 | const { defaultsDeep, flatMap } = require("lodash");
2 |
3 | const { computeCompletionSyntacticContext } = require("./content-assist");
4 |
5 | function getSuggestions(options) {
6 | const actualOptions = defaultsDeep(options, {
7 | providers: {
8 | elementContent: [],
9 | elementName: [],
10 | attributeName: [],
11 | attributeValue: [],
12 | },
13 | context: undefined,
14 | });
15 |
16 | let { providerType, providerArgs } = computeCompletionSyntacticContext({
17 | cst: actualOptions.cst,
18 | tokenVector: actualOptions.tokenVector,
19 | ast: actualOptions.ast,
20 | offset: actualOptions.offset,
21 | });
22 |
23 | // Inject Additional semantic context for the content assist providers.
24 | providerArgs.context = actualOptions.context;
25 |
26 | if (providerType === null) {
27 | return [];
28 | } else {
29 | const selectedProviders = actualOptions.providers[providerType];
30 |
31 | const suggestions = flatMap(selectedProviders, (suggestionProvider) =>
32 | suggestionProvider(providerArgs)
33 | );
34 | return suggestions;
35 | }
36 | }
37 |
38 | module.exports = {
39 | getSuggestions: getSuggestions,
40 | };
41 |
--------------------------------------------------------------------------------
/packages/constraints/lib/constraints/unique-attribute-keys.js:
--------------------------------------------------------------------------------
1 | const { groupBy, pickBy, reduce, map, filter } = require("lodash");
2 |
3 | /**
4 | * @param {XMLElement} elem
5 | * @returns {ValidationIssue[]}
6 | */
7 | function validateUniqueAttributeKeys(elem) {
8 | const attributesWithKeys = filter(elem.attributes, (_) => _.key !== null);
9 | const attribByKey = groupBy(attributesWithKeys, "key");
10 | const nonUniqueAttribsGroups = pickBy(attribByKey, (_) => _.length > 1);
11 | const nonUniqueAttribs = reduce(
12 | nonUniqueAttribsGroups,
13 | (result, attribsGroup) => result.concat(attribsGroup),
14 | []
15 | );
16 |
17 | const validationIssues = map(nonUniqueAttribs, (_) => {
18 | // the `key` is guaranteed to exist because we have filtered above
19 | // for attributes with valid keys
20 | const keyToken = _.syntax.key;
21 | return {
22 | msg: `duplicate attribute: "${_.key}"`,
23 | node: _,
24 | severity: "error",
25 | position: {
26 | startOffset: keyToken.startOffset,
27 | endOffset: keyToken.endOffset,
28 | },
29 | };
30 | });
31 |
32 | return validationIssues;
33 | }
34 |
35 | module.exports = {
36 | validateUniqueAttributeKeys,
37 | };
38 |
--------------------------------------------------------------------------------
/packages/validation/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@xml-tools/validation",
3 | "version": "1.0.16",
4 | "description": "XML validation APIs",
5 | "keywords": [
6 | "xml",
7 | "validation"
8 | ],
9 | "main": "lib/api.js",
10 | "repository": "https://github.com/sap/xml-tools/",
11 | "license": "Apache-2.0",
12 | "typings": "./api.d.ts",
13 | "files": [
14 | "lib",
15 | ".reuse",
16 | "LICENSES",
17 | "api.d.ts"
18 | ],
19 | "dependencies": {
20 | "@xml-tools/ast": "^5.0.5",
21 | "lodash": "4.17.21"
22 | },
23 | "devDependencies": {
24 | "@xml-tools/parser": "^1.0.11"
25 | },
26 | "scripts": {
27 | "ci": "npm-run-all clean type-check coverage:*",
28 | "clean": "rimraf ./coverage ./nyc_output",
29 | "test": "mocha \"./test/**/*spec.js\"",
30 | "coverage:run": "nyc mocha \"./test/**/*spec.js\"",
31 | "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100",
32 | "type-check": "tsc api.d.ts"
33 | },
34 | "publishConfig": {
35 | "access": "public"
36 | },
37 | "nyc": {
38 | "include": [
39 | "lib/**/*.js"
40 | ],
41 | "reporter": [
42 | "text",
43 | "lcov"
44 | ]
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/content-assist/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@xml-tools/content-assist",
3 | "version": "3.1.11",
4 | "description": "XML Content Assist APIs",
5 | "keywords": [
6 | "xml",
7 | "auto-complete",
8 | "content-assist"
9 | ],
10 | "main": "lib/api.js",
11 | "repository": "https://github.com/sap/xml-tools/",
12 | "license": "Apache-2.0",
13 | "typings": "./api.d.ts",
14 | "files": [
15 | "lib",
16 | ".reuse",
17 | "LICENSES",
18 | "api.d.ts"
19 | ],
20 | "dependencies": {
21 | "@xml-tools/common": "^0.1.6",
22 | "@xml-tools/parser": "^1.0.11",
23 | "lodash": "4.17.21"
24 | },
25 | "scripts": {
26 | "ci": "npm-run-all clean type-check coverage:*",
27 | "clean": "rimraf ./coverage ./nyc_output",
28 | "test": "mocha \"./test/**/*spec.js\"",
29 | "coverage:run": "nyc mocha \"./test/**/*spec.js\"",
30 | "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100",
31 | "type-check": "tsc api.d.ts"
32 | },
33 | "publishConfig": {
34 | "access": "public"
35 | },
36 | "nyc": {
37 | "include": [
38 | "lib/**/*.js"
39 | ],
40 | "reporter": [
41 | "text",
42 | "lcov"
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/constraints/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@xml-tools/constraints",
3 | "version": "1.1.1",
4 | "description": "XML constraints validations",
5 | "keywords": [
6 | "xml",
7 | "validation"
8 | ],
9 | "main": "lib/api.js",
10 | "repository": "https://github.com/sap/xml-tools/",
11 | "license": "Apache-2.0",
12 | "typings": "./api.d.ts",
13 | "files": [
14 | "lib",
15 | ".reuse",
16 | "LICENSES",
17 | "api.d.ts"
18 | ],
19 | "dependencies": {
20 | "@xml-tools/validation": "^1.0.16",
21 | "lodash": "4.17.21"
22 | },
23 | "devDependencies": {
24 | "@xml-tools/ast": "^5.0.5"
25 | },
26 | "scripts": {
27 | "ci": "npm-run-all clean type-check coverage:*",
28 | "clean": "rimraf ./coverage ./nyc_output",
29 | "test": "mocha \"./test/**/*spec.js\"",
30 | "coverage:run": "nyc mocha \"./test/**/*spec.js\"",
31 | "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100",
32 | "type-check": "tsc api.d.ts"
33 | },
34 | "publishConfig": {
35 | "access": "public"
36 | },
37 | "nyc": {
38 | "include": [
39 | "lib/**/*.js"
40 | ],
41 | "reporter": [
42 | "text",
43 | "lcov"
44 | ]
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/parser/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@xml-tools/parser",
3 | "version": "1.0.11",
4 | "description": "XML Parser Implemented in JavaScript",
5 | "keywords": [
6 | "xml",
7 | "parser"
8 | ],
9 | "main": "lib/api.js",
10 | "repository": "https://github.com/sap/xml-tools/",
11 | "license": "Apache-2.0",
12 | "typings": "./api.d.ts",
13 | "files": [
14 | "lib",
15 | ".reuse",
16 | "LICENSES",
17 | "api.d.ts"
18 | ],
19 | "dependencies": {
20 | "chevrotain": "7.1.1"
21 | },
22 | "devDependencies": {
23 | "klaw-sync": "6.0.0"
24 | },
25 | "scripts": {
26 | "ci": "npm-run-all clean type-check coverage:*",
27 | "clean": "rimraf ./coverage ./nyc_output",
28 | "test": "mocha \"./test/**/*spec.js\"",
29 | "coverage:run": "nyc mocha \"./test/**/*spec.js\"",
30 | "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100",
31 | "snapshots:update": "node ./scripts/update-snapshots.js",
32 | "type-check": "tsc api.d.ts"
33 | },
34 | "publishConfig": {
35 | "access": "public"
36 | },
37 | "nyc": {
38 | "include": [
39 | "lib/**/*.js"
40 | ],
41 | "reporter": [
42 | "text",
43 | "lcov"
44 | ]
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/simple-schema/test/content-assist/utils.js:
--------------------------------------------------------------------------------
1 | const { parse } = require("@xml-tools/parser");
2 | const { buildAst } = require("@xml-tools/ast");
3 | const { getSuggestions } = require("@xml-tools/content-assist");
4 | const { getSchemaSuggestionsProviders } = require("../../");
5 |
6 | /**
7 | *
8 | * @param {string} xmlText
9 | * @param {SimpleSchema} schema
10 | * @returns {ValidationIssue[] | *}
11 | */
12 | function suggestionsBySchema(xmlText, schema) {
13 | const schemaSuggestionsProviders = getSchemaSuggestionsProviders(schema);
14 | const realXmlText = xmlText.replace("⇶", "");
15 | const offset = xmlText.indexOf("⇶");
16 | const { cst, tokenVector } = parse(realXmlText);
17 | const ast = buildAst(cst, tokenVector);
18 | const suggestions = getSuggestions({
19 | cst,
20 | tokenVector,
21 | ast,
22 | offset: offset,
23 | providers: {
24 | attributeValue: [
25 | schemaSuggestionsProviders.schemaAttributeValueCompletion,
26 | ],
27 | attributeName: [schemaSuggestionsProviders.schemaAttributeNameCompletion],
28 | elementName: [schemaSuggestionsProviders.schemaElementNameCompletion],
29 | },
30 | });
31 | return suggestions;
32 | }
33 |
34 | module.exports = {
35 | suggestionsBySchema: suggestionsBySchema,
36 | };
37 |
--------------------------------------------------------------------------------
/packages/parser/test/sample-test.js:
--------------------------------------------------------------------------------
1 | const { expect } = require("chai");
2 | const { readFileSync } = require("fs");
3 | const { resolve, basename } = require("path");
4 | const { partialRight } = require("lodash");
5 |
6 | const { transformCstForAssertions } = require("./utils");
7 | const { parse } = require("../");
8 |
9 | function executeSampleTest(dirPath, assertNoErrors) {
10 | it("Can Parse an XML text", () => {
11 | const inputPath = resolve(dirPath, "input.xml");
12 | const inputText = readFileSync(inputPath).toString("utf8");
13 | const simpleNewLinesInput = inputText.replace(/\r\n/g, "\n");
14 | const { cst, lexErrors, parseErrors } = parse(simpleNewLinesInput);
15 | if (assertNoErrors === true) {
16 | expect(lexErrors).to.be.empty;
17 | expect(parseErrors).to.be.empty;
18 | }
19 | transformCstForAssertions(cst);
20 | const expectedOutput = require(resolve(dirPath, "output.js")).cst;
21 | expect(cst).to.deep.eql(expectedOutput);
22 | });
23 | }
24 |
25 | function testNameFromDir(dirPath) {
26 | return basename(dirPath);
27 | }
28 |
29 | module.exports = {
30 | executeValidSampleTest: partialRight(executeSampleTest, true),
31 | executeInValidSampleTest: partialRight(executeSampleTest, false),
32 | testNameFromDir: testNameFromDir,
33 | };
34 |
--------------------------------------------------------------------------------
/packages/parser/test/utils.js:
--------------------------------------------------------------------------------
1 | const { has, forEach, flatMap, identity } = require("lodash");
2 |
3 | function transformCstForAssertions(cstNode) {
4 | if (has(cstNode, "children")) {
5 | reduceLocationInfo(cstNode.location);
6 | const allChildren = flatMap(cstNode.children, identity);
7 | forEach(allChildren, transformCstForAssertions);
8 | } else if (has(cstNode, "image")) {
9 | reduceTokenInfo(cstNode);
10 | } else {
11 | throw Error("None Exhaustive Match");
12 | }
13 | }
14 |
15 | function reduceLocationInfo(loc) {
16 | if (isNaN(loc.startOffset)) {
17 | loc.startOffset = null;
18 | }
19 |
20 | if (isNaN(loc.endOffset)) {
21 | loc.endOffset = null;
22 | }
23 | delete loc.startLine;
24 | delete loc.endLine;
25 | delete loc.startColumn;
26 | delete loc.endColumn;
27 | }
28 |
29 | function reduceTokenInfo(tok) {
30 | if (isNaN(tok.startOffset)) {
31 | tok.startOffset = null;
32 | }
33 |
34 | if (isNaN(tok.endOffset)) {
35 | tok.endOffset = null;
36 | }
37 | delete tok.startLine;
38 | delete tok.endLine;
39 | delete tok.startColumn;
40 | delete tok.endColumn;
41 | delete tok.tokenTypeIdx;
42 | tok.tokenType = tok.tokenType.name;
43 | }
44 |
45 | module.exports = {
46 | transformCstForAssertions: transformCstForAssertions,
47 | };
48 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/content-assist/attribute-value.js:
--------------------------------------------------------------------------------
1 | const { has, isRegExp, forEach, isArray } = require("lodash");
2 |
3 | /**
4 | * @param {XMLAttribute} attributeNode
5 | * @param {XSSAttribute} xssAttribute
6 | * @param {string} prefix
7 | *
8 | * @returns {CompletionSuggestion[]}
9 | */
10 | function attributeValueCompletion(attributeNode, xssAttribute, prefix = "") {
11 | // An XSS Attribute definition may not specify any constraints on a value
12 | if (has(xssAttribute, "value") === false) {
13 | return [];
14 | }
15 |
16 | const suggestions = [];
17 | const valueDef = xssAttribute.value;
18 | /* istanbul ignore else - defensive programming */
19 | if (isRegExp(valueDef)) {
20 | // No suggestions for regExp value definitions...
21 | } else if (isArray(valueDef)) {
22 | forEach(valueDef, (enumVal) => {
23 | if (enumVal.startsWith(prefix)) {
24 | suggestions.push({
25 | text: enumVal.substring(prefix.length),
26 | label: enumVal,
27 | });
28 | }
29 | });
30 | } else {
31 | /* istanbul ignore next defensive programming */
32 | throw Error("None Exhaustive Match");
33 | }
34 |
35 | return suggestions;
36 | }
37 |
38 | module.exports = {
39 | attributeValueCompletion: attributeValueCompletion,
40 | };
41 |
--------------------------------------------------------------------------------
/packages/common/lib/find-next-textual-token.js:
--------------------------------------------------------------------------------
1 | const { findIndex } = require("lodash");
2 |
3 | function findNextTextualToken(tokenVector, prevTokenEndOffset) {
4 | // The TokenVector is sorted, so we could use a BinarySearch to optimize performance
5 | const prevTokenIdx = findIndex(
6 | tokenVector,
7 | (tok) => tok.endOffset === prevTokenEndOffset
8 | );
9 | let nextTokenIdx = prevTokenIdx;
10 | let found = false;
11 | while (found === false) {
12 | nextTokenIdx++;
13 | const nextPossibleToken = tokenVector[nextTokenIdx];
14 | // No Next textualToken
15 | if (nextPossibleToken === undefined) {
16 | return null;
17 | }
18 | /* istanbul ignore next
19 | * I don't think this scenario can be created, however defensive coding never killed anyone...
20 | * Basically SEA_WS can only only appear in "OUTSIDE" mode, and we need a CLOSE/SLASH_CLOSE to get back to outside
21 | * mode, however if we had those this function would never have been called...
22 | */
23 | if (nextPossibleToken.tokenType.name === "SEA_WS") {
24 | // skip pure WS tokens as they do not contain any actual text
25 | } else {
26 | return nextPossibleToken;
27 | }
28 | }
29 | }
30 |
31 | module.exports = {
32 | findNextTextualToken: findNextTextualToken,
33 | };
34 |
--------------------------------------------------------------------------------
/packages/ast/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@xml-tools/ast",
3 | "version": "5.0.5",
4 | "description": "XML Ast and Utilities",
5 | "keywords": [
6 | "xml",
7 | "ast"
8 | ],
9 | "main": "lib/api.js",
10 | "repository": "https://github.com/sap/xml-tools/",
11 | "license": "Apache-2.0",
12 | "typings": "./api.d.ts",
13 | "files": [
14 | "lib",
15 | ".reuse",
16 | "LICENSES",
17 | "api.d.ts"
18 | ],
19 | "dependencies": {
20 | "@xml-tools/common": "^0.1.6",
21 | "@xml-tools/parser": "^1.0.11",
22 | "lodash": "4.17.21"
23 | },
24 | "devDependencies": {
25 | "klaw-sync": "6.0.0"
26 | },
27 | "scripts": {
28 | "ci": "npm-run-all clean type-check coverage:*",
29 | "clean": "rimraf ./coverage ./nyc_output",
30 | "test": "mocha \"./test/**/*spec.js\"",
31 | "coverage:run": "nyc mocha \"./test/**/*spec.js\"",
32 | "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100",
33 | "snapshots:update": "node ./scripts/update-snapshots.js",
34 | "type-check": "tsc api.d.ts"
35 | },
36 | "publishConfig": {
37 | "access": "public"
38 | },
39 | "nyc": {
40 | "include": [
41 | "lib/**/*.js"
42 | ],
43 | "reporter": [
44 | "text",
45 | "lcov"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/common/api.d.ts:
--------------------------------------------------------------------------------
1 | import { IToken } from "chevrotain";
2 |
3 | /**
4 | * Finds the next none "SEA_WS" Token after a specific offset.
5 | *
6 | * @param tokenVector - The TokenVector returned from @xml-tools/parser parse method.
7 | * @param prevTokEndOffset - This must be the endOffset of an existing token.
8 | */
9 | export function findNextTextualToken(
10 | tokenVector: IToken[],
11 | prevTokEndOffset: number
12 | ): IToken | null;
13 |
14 | /**
15 | * Check if an xml attribute key is an xmlns key.
16 | * Attribute keys which are xmlns keys: "xmlns", "xmlns:core".
17 | * Attribute keys which are not xmlns keys: "myattr", "xmlns:with:extra:colon".
18 | * "xmlns:" is considered an xmlns key if opts.includeEmptyPrefix is true.
19 | *
20 | * @param opts.key - the attribute key
21 | * @param opts.includeEmptyPrefix - should true be returned when there is no prefix (key === "xmlns:")
22 | */
23 | export function isXMLNamespaceKey(opts: {
24 | key: string;
25 | includeEmptyPrefix: boolean;
26 | }): boolean;
27 |
28 | /**
29 | * Get the attribute name, without its "xmlns:" prefix, from an xmlns attribute key.
30 | * If the attribute key is not an xmlns key, undefined is returned.
31 | * @param key - the attribute key
32 | */
33 | export function getXMLNamespaceKeyPrefix(key: string): string | undefined;
34 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/validators/required-attributes.js:
--------------------------------------------------------------------------------
1 | const { map, filter, difference } = require("lodash");
2 | const { tokenToOffsetPosition } = require("./utils");
3 |
4 | /**
5 | * @param {XMLElement} elem
6 | * @param {XSSElement} schema
7 | *
8 | * @returns {ValidationIssue[]}
9 | */
10 | function validateRequiredAttributes(elem, schema) {
11 | const requiredAttribsDef = filter(
12 | schema.attributes,
13 | (_) => _.required === true
14 | );
15 | const requiredAttribNames = map(requiredAttribsDef, (_) => _.key);
16 |
17 | const actualAttribNames = map(elem.attributes, (_) => _.key);
18 | const missingAttributesNames = difference(
19 | requiredAttribNames,
20 | actualAttribNames
21 | );
22 |
23 | // This elementName must always exist, otherwise we could not locate the relevant schema definition
24 | // so this validation could have never executed...
25 | const errPosition = tokenToOffsetPosition(elem.syntax.openName);
26 | const issues = map(missingAttributesNames, (_) => {
27 | return {
28 | msg: `Missing Required Attribute: <${_}>`,
29 | node: elem,
30 | severity: "error",
31 | position: errPosition,
32 | };
33 | });
34 | return issues;
35 | }
36 |
37 | module.exports = {
38 | validateRequiredAttributes: validateRequiredAttributes,
39 | };
40 |
--------------------------------------------------------------------------------
/packages/simple-schema/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@xml-tools/simple-schema",
3 | "version": "3.0.5",
4 | "description": "XML Simple Schema",
5 | "keywords": [
6 | "xml",
7 | "validation"
8 | ],
9 | "main": "lib/api.js",
10 | "repository": "https://github.com/sap/xml-tools/",
11 | "license": "Apache-2.0",
12 | "typings": "./api.d.ts",
13 | "files": [
14 | "lib",
15 | ".reuse",
16 | "LICENSES",
17 | "api.d.ts"
18 | ],
19 | "dependencies": {
20 | "@xml-tools/ast": "^5.0.5",
21 | "@xml-tools/content-assist": "^3.1.11",
22 | "lodash": "4.17.21"
23 | },
24 | "devDependencies": {
25 | "@xml-tools/parser": "^1.0.11",
26 | "@xml-tools/validation": "^1.0.16"
27 | },
28 | "scripts": {
29 | "ci": "npm-run-all clean type-check coverage:*",
30 | "clean": "rimraf ./coverage ./nyc_output",
31 | "test": "mocha \"./test/**/*spec.js\"",
32 | "coverage:run": "nyc mocha \"./test/**/*spec.js\"",
33 | "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100",
34 | "type-check": "tsc api.d.ts"
35 | },
36 | "publishConfig": {
37 | "access": "public"
38 | },
39 | "nyc": {
40 | "include": [
41 | "lib/**/*.js"
42 | ],
43 | "reporter": [
44 | "text",
45 | "lcov"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/validators/required-sub-elements.js:
--------------------------------------------------------------------------------
1 | const { map, filter, difference } = require("lodash");
2 | const { tokenToOffsetPosition } = require("./utils");
3 |
4 | /**
5 | * @param {XMLElement} elem
6 | * @param {XSSElement} schema
7 | *
8 | * @returns {ValidationIssue[]}
9 | */
10 | function validateRequiredSubElements(elem, schema) {
11 | const requiredSubElemsDef = filter(
12 | schema.elements,
13 | (_) => _.required === true
14 | );
15 | const requiredElemNames = map(requiredSubElemsDef, (_) => _.name);
16 |
17 | const actualSubElemNameNames = map(elem.subElements, (_) => _.name);
18 | const missingSubElemNames = difference(
19 | requiredElemNames,
20 | actualSubElemNameNames
21 | );
22 |
23 | // This elementName must always exist, otherwise we could not locate the relevant schema definition
24 | // so this validation could have never executed...
25 | const errPosition = tokenToOffsetPosition(elem.syntax.openName);
26 | const issues = map(missingSubElemNames, (_) => {
27 | return {
28 | msg: `Missing Required Sub-Element: <${_}>`,
29 | node: elem,
30 | severity: "error",
31 | position: errPosition,
32 | };
33 | });
34 | return issues;
35 | }
36 |
37 | module.exports = {
38 | validateRequiredSubElements: validateRequiredSubElements,
39 | };
40 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/validators/unknown-sub-elements.js:
--------------------------------------------------------------------------------
1 | const { map, forEach, includes } = require("lodash");
2 | const { tokenToOffsetPosition } = require("./utils");
3 |
4 | /**
5 | * @param {XMLElement} elem
6 | * @param {XSSElement} schema
7 | *
8 | * @returns {ValidationIssue[]}
9 | */
10 | function validateUnknownSubElements(elem, schema) {
11 | // This validation is only relevant if the Schema disallows unknown elements.
12 | if (schema.elementsType !== "closed") {
13 | return [];
14 | }
15 |
16 | const allowedElemNames = map(schema.elements, (_) => _.name);
17 |
18 | const issues = [];
19 | forEach(elem.subElements, (subElem) => {
20 | if (subElem.name !== null) {
21 | if (includes(allowedElemNames, subElem.name) === false) {
22 | issues.push({
23 | msg: `Unknown Sub-Element: <${
24 | subElem.name
25 | }> only [${allowedElemNames.toString()}] Sub-Elements are allowed`,
26 | node: subElem,
27 | severity: "error",
28 | // safe assumption that we have an `openName` (see above condition)
29 | position: tokenToOffsetPosition(subElem.syntax.openName),
30 | });
31 | }
32 | }
33 | });
34 |
35 | return issues;
36 | }
37 |
38 | module.exports = {
39 | validateUnknownSubElements: validateUnknownSubElements,
40 | };
41 |
--------------------------------------------------------------------------------
/packages/parser/scripts/update-snapshots.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | const klawSync = require("klaw-sync");
3 | const { resolve } = require("path");
4 | const { readFileSync, writeFileSync } = require("fs");
5 | const { format } = require("prettier");
6 |
7 | const { transformCstForAssertions } = require("../test/utils");
8 | const { parse } = require("../");
9 |
10 | const testsDir = resolve(__dirname, "..", "test");
11 |
12 | const sampleFiles = klawSync(testsDir, { nodir: true });
13 | const xmlSampleFiles = sampleFiles.filter((fileDesc) => {
14 | if (fileDesc.path.includes("node_modules")) {
15 | return false;
16 | }
17 | return fileDesc.path.endsWith("input.xml");
18 | });
19 |
20 | xmlSampleFiles.forEach((fileDesc) => {
21 | const xmlInput = readFileSync(fileDesc.path, "utf8");
22 | const simpleNewLinesInput = xmlInput.replace(/\r\n/g, "\n");
23 | console.log(`Reading <${fileDesc.path}>`);
24 | const { cst } = parse(simpleNewLinesInput);
25 | transformCstForAssertions(cst);
26 | const snapshotOutput = `module.exports = { cst : ${JSON.stringify(cst)}}`;
27 | const formattedSnapshotOutput = format(snapshotOutput, { parser: "babel" });
28 | const outputFilePath = fileDesc.path.replace(/input.xml$/, "output.js");
29 | console.log(`writing <${outputFilePath}>`);
30 | writeFileSync(outputFilePath, formattedSnapshotOutput);
31 | });
32 |
--------------------------------------------------------------------------------
/packages/constraints/lib/constraints/tag-closing-name-match.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {XMLElement} elem
3 | * @returns {ValidationIssue[]}
4 | */
5 | function validateTagClosingNameMatch(elem) {
6 | const openTagToken = elem.syntax.openName;
7 | const closeTagToken = elem.syntax.closeName;
8 |
9 | // The element tag must have **both** the opening and closing tokens
10 | // to be able to validate.
11 | if (!openTagToken || !closeTagToken) {
12 | return [];
13 | }
14 |
15 | // alles gut
16 | if (openTagToken.image === closeTagToken.image) {
17 | return [];
18 | } else {
19 | return [
20 | {
21 | msg: `tags mismatch: "${openTagToken.image}" must match closing tag: "${closeTagToken.image}"`,
22 | node: elem,
23 | severity: "error",
24 | position: {
25 | startOffset: openTagToken.startOffset,
26 | endOffset: openTagToken.endOffset,
27 | },
28 | },
29 | {
30 | msg: `tags mismatch: "${closeTagToken.image}" must match opening tag: "${openTagToken.image}"`,
31 | node: elem,
32 | severity: "error",
33 | position: {
34 | startOffset: closeTagToken.startOffset,
35 | endOffset: closeTagToken.endOffset,
36 | },
37 | },
38 | ];
39 | }
40 | }
41 |
42 | module.exports = {
43 | validateTagClosingNameMatch,
44 | };
45 |
--------------------------------------------------------------------------------
/packages/language-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@xml-tools/language-server",
3 | "version": "1.1.1",
4 | "description": "XML language server",
5 | "keywords": [
6 | "LSP",
7 | "language-server",
8 | "XML"
9 | ],
10 | "main": "lib/api.js",
11 | "license": "Apache-2.0",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/SAP/xml-tools/"
15 | },
16 | "typings": "./api.d.ts",
17 | "files": [
18 | "dist",
19 | "lib",
20 | ".reuse",
21 | "LICENSES",
22 | "api.d.ts"
23 | ],
24 | "engines": {
25 | "node": ">=10.0.0"
26 | },
27 | "dependencies": {
28 | "@xml-tools/constraints": "^1.1.1",
29 | "@xml-tools/parser": "^1.0.11",
30 | "lodash": "4.17.21",
31 | "vscode-languageserver": "7.0.0",
32 | "vscode-languageserver-textdocument": "1.0.1"
33 | },
34 | "devDependencies": {
35 | "chevrotain": "^7.0.1",
36 | "vscode-languageserver-types": "3.16.0"
37 | },
38 | "scripts": {
39 | "ci": "npm-run-all clean type-check coverage bundle",
40 | "clean": "rimraf ./dist ./coverage ./nyc_output",
41 | "test": "mocha \"./test/**/*spec.js\"",
42 | "coverage": "nyc mocha \"./test/**/*spec.js\"",
43 | "type-check": "tsc api.d.ts",
44 | "bundle": "webpack --mode production"
45 | },
46 | "publishConfig": {
47 | "access": "public"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/ast/scripts/update-snapshots.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | const { parse } = require("@xml-tools/parser");
3 | const { modifyAstForAssertions } = require("../test/utils");
4 | const klawSync = require("klaw-sync");
5 | const { resolve } = require("path");
6 | const { readFileSync, writeFileSync } = require("fs");
7 | const { format } = require("prettier");
8 |
9 | const testsDir = resolve(__dirname, "..", "test");
10 | const { buildAst } = require("../");
11 |
12 | const sampleFiles = klawSync(testsDir, { nodir: true });
13 | const xmlSampleFiles = sampleFiles.filter((fileDesc) => {
14 | if (fileDesc.path.includes("node_modules")) {
15 | return false;
16 | }
17 | return fileDesc.path.endsWith("input.xml");
18 | });
19 |
20 | xmlSampleFiles.forEach((fileDesc) => {
21 | const xmlInput = readFileSync(fileDesc.path, "utf8");
22 | const simpleNewLinesInput = xmlInput.replace(/\r\n/g, "\n");
23 | console.log(`Reading <${fileDesc.path}>`);
24 | const { cst, tokenVector } = parse(simpleNewLinesInput);
25 | const ast = buildAst(cst, tokenVector);
26 | modifyAstForAssertions(ast);
27 | const snapshotOutput = `module.exports = { ast : ${JSON.stringify(ast)}}`;
28 | const formattedSnapshotOutput = format(snapshotOutput, { parser: "babel" });
29 | const outputFilePath = fileDesc.path.replace(/input.xml$/, "output.js");
30 | console.log(`writing <${outputFilePath}>`);
31 | writeFileSync(outputFilePath, formattedSnapshotOutput);
32 | });
33 |
--------------------------------------------------------------------------------
/scripts/merge-coverage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * based on https://github.com/istanbuljs/istanbuljs/blob/1fe490e51909607137ded25b1688581c9fd926cd/monorepo-merge-reports.js
3 | */
4 | const { dirname, basename, join, resolve } = require("path");
5 | const { spawnSync } = require("child_process");
6 |
7 | const rimraf = require("rimraf");
8 | const makeDir = require("make-dir");
9 | const glob = require("glob");
10 |
11 | process.chdir(resolve(__dirname, ".."));
12 | rimraf.sync(".nyc_output");
13 | makeDir.sync(".nyc_output");
14 |
15 | // Merge coverage data from each package so we can generate a complete reports
16 | glob.sync("packages/*/.nyc_output").forEach((nycOutput) => {
17 | const cwd = dirname(nycOutput);
18 | const { status, stderr } = spawnSync(
19 | resolve("node_modules", ".bin", "nyc"),
20 | [
21 | "merge",
22 | ".nyc_output",
23 | join(__dirname, "..", ".nyc_output", basename(cwd) + ".json"),
24 | ],
25 | {
26 | encoding: "utf8",
27 | shell: true,
28 | cwd,
29 | }
30 | );
31 |
32 | if (status !== 0) {
33 | console.error(stderr);
34 | process.exit(status);
35 | }
36 | });
37 |
38 | const { status, stderr } = spawnSync(
39 | resolve("node_modules", ".bin", "nyc"),
40 | ["report", "--reporter=lcov"],
41 | {
42 | encoding: "utf8",
43 | shell: true,
44 | cwd: resolve(__dirname, ".."),
45 | }
46 | );
47 |
48 | if (status !== 0) {
49 | console.error(stderr);
50 | process.exit(status);
51 | }
52 |
--------------------------------------------------------------------------------
/packages/constraints/test/api-spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require("chai");
2 | const { parse } = require("@xml-tools/parser");
3 | const { buildAst } = require("@xml-tools/ast");
4 | const { checkConstraints } = require("../");
5 |
6 | describe("the constraints sub-package apis", () => {
7 | it("can detect validation issues using `checkConstraints` api", () => {
8 | const xmlText = `
9 |
10 | Bill
11 | Tim
12 | `;
13 |
14 | const { cst, tokenVector } = parse(xmlText);
15 | const document = buildAst(cst, tokenVector);
16 | const validationIssues = checkConstraints(document);
17 | const element = document.rootElement;
18 | expect(validationIssues).to.have.lengthOf(2);
19 | expect(validationIssues).to.deep.include.members([
20 | {
21 | msg: 'tags mismatch: "note" must match closing tag: "note-typo"',
22 | severity: "error",
23 | node: element,
24 | position: {
25 | startOffset: element.syntax.openName.startOffset,
26 | endOffset: element.syntax.openName.endOffset,
27 | },
28 | },
29 | {
30 | msg: 'tags mismatch: "note-typo" must match opening tag: "note"',
31 | severity: "error",
32 | node: element,
33 | position: {
34 | startOffset: element.syntax.closeName.startOffset,
35 | endOffset: element.syntax.closeName.endOffset,
36 | },
37 | },
38 | ]);
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/packages/constraints/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/@xml-tools/constraints)
2 |
3 | # @xml-tools/constraints
4 |
5 | Validations for XML constraints.
6 |
7 | The following constraints are currently implemented:
8 |
9 | - Uniqueness of attribute keys inside the same element.
10 | - Element opening tag name must be identical to the element closing tag name.
11 |
12 | ## Installation
13 |
14 | With npm:
15 |
16 | - `npm install @xml-tools/constraints`
17 |
18 | With Yarn
19 |
20 | - `yarn add @xml-tools/constraints`
21 |
22 | ## Usage
23 |
24 | Please see the [TypeScript Definitions](./api.d.ts) for full API details.
25 |
26 | A simple usage example:
27 |
28 | ```javascript
29 | const { parse } = require("@xml-tools/parser");
30 | const { buildAst } = require("@xml-tools/ast");
31 | const { checkConstraints } = require("@xml-tools/constraints");
32 |
33 | const xmlText = `
34 |
35 | Bill
36 | Tim
37 | `;
38 |
39 | const { cst, tokenVector } = parse(xmlText);
40 | const document = buildAst(cst, tokenVector);
41 | const validationIssues = checkConstraints(document);
42 | console.log(validationIssues[0].msg); // --> 'opening tag: "note" must match closing tag: "note-typo"
43 | ```
44 |
45 | ## Support
46 |
47 | Please open [issues](https://github.com/SAP/xml-tols/issues) on github.
48 |
49 | ## Contributing
50 |
51 | See [CONTRIBUTING.md](./CONTRIBUTING.md).
52 |
--------------------------------------------------------------------------------
/packages/ast/test/sample-test.js:
--------------------------------------------------------------------------------
1 | const { expect } = require("chai");
2 | const { readFileSync } = require("fs");
3 | const { resolve, basename } = require("path");
4 | const { parse } = require("@xml-tools/parser");
5 | const { partialRight } = require("lodash");
6 |
7 | const {
8 | modifyAstForAssertions,
9 | assertParentPropsAreValid,
10 | } = require("./utils");
11 | const { buildAst } = require("../");
12 |
13 | function executeSampleTest(dirPath, assertNoErrors) {
14 | it("Can build an AST for a valid XML", () => {
15 | const inputPath = resolve(dirPath, "input.xml");
16 | const inputText = readFileSync(inputPath).toString("utf8");
17 | const simpleNewLinesInput = inputText.replace(/\r\n/g, "\n");
18 | const { cst, tokenVector, lexErrors, parseErrors } = parse(
19 | simpleNewLinesInput
20 | );
21 | if (assertNoErrors === true) {
22 | expect(lexErrors).to.be.empty;
23 | expect(parseErrors).to.be.empty;
24 | }
25 | const ast = buildAst(cst, tokenVector);
26 | assertParentPropsAreValid(ast);
27 | modifyAstForAssertions(ast);
28 | const expectedOutput = require(resolve(dirPath, "output.js")).ast;
29 | expect(ast).to.deep.eql(expectedOutput);
30 | });
31 | }
32 |
33 | function testNameFromDir(dirPath) {
34 | return basename(dirPath);
35 | }
36 |
37 | module.exports = {
38 | executeValidSampleTest: partialRight(executeSampleTest, true),
39 | executeInValidSampleTest: partialRight(executeSampleTest, false),
40 | testNameFromDir: testNameFromDir,
41 | };
42 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/attributes/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [
9 | {
10 | type: "XMLAttribute",
11 | position: { startOffset: 6, endOffset: 16 },
12 | key: "attr1",
13 | value: "666",
14 | syntax: {
15 | key: { image: "attr1", startOffset: 6, endOffset: 10 },
16 | value: { image: '"666"', startOffset: 12, endOffset: 16 },
17 | },
18 | },
19 | {
20 | type: "XMLAttribute",
21 | position: { startOffset: 18, endOffset: 28 },
22 | key: "attr2",
23 | value: "333",
24 | syntax: {
25 | key: { image: "attr2", startOffset: 18, endOffset: 22 },
26 | value: { image: '"333"', startOffset: 24, endOffset: 28 },
27 | },
28 | },
29 | ],
30 | subElements: [],
31 | textContents: [],
32 | position: { startOffset: 0, endOffset: 36 },
33 | syntax: {
34 | openName: { image: "note", startOffset: 1, endOffset: 4 },
35 | closeName: { image: "note", startOffset: 32, endOffset: 35 },
36 | isSelfClosing: false,
37 | openBody: { startOffset: 0, endOffset: 29 },
38 | closeBody: { startOffset: 30, endOffset: 36 },
39 | attributesRange: { startOffset: 6, endOffset: 28 },
40 | },
41 | },
42 | position: { startOffset: 0, endOffset: 37 },
43 | },
44 | };
45 |
--------------------------------------------------------------------------------
/packages/xml-toolkit/README.md:
--------------------------------------------------------------------------------
1 | # XML Toolkit
2 |
3 | The XML toolkit is a VSCode extension that provides XML language editor support.
4 |
5 | ## Features
6 |
7 | ### XML syntax validations
8 |
9 | The syntax validations use a fault-tolerant parser. This enables
10 | the detection of **multiple** syntax errors.
11 |
12 | ### XML Well-formed validations
13 |
14 | - Unique Attribute Names.
15 | - Tag open/close names are aligned.
16 |
17 | #### Preview
18 |
19 | 
20 |
21 | ## Installation
22 |
23 | ### From the VS Code Marketplace
24 |
25 | In the [XML Toolkit](https://marketplace.visualstudio.com/items?itemName=SAPOSS.xml-toolkit)
26 | page, click **Install**.
27 |
28 | ### From Github releases
29 |
30 | 1. Go to [GitHub Releases](https://github.com/sap/xml-tools/releases).
31 | 2. Search for the `.vsix` archive under `xml-toolkit\@x.y.z` releases.
32 | - Replace `x.y.z` with the desired version number.
33 | 3. Follow the instructions for installing an extension from a `.vsix`
34 | file in the [VSCode's guide](https://code.visualstudio.com/docs/editor/extension-gallery#_install-from-a-vsix).
35 |
36 | ## Usage
37 |
38 | The extension is activated when an `.xml` file is opened.
39 | Both lexing and parsing errors will be shown as squiggly red underlines
40 | and in the Problems view.
41 |
42 | ## Support
43 |
44 | You can open [issues](https://github.com/SAP/xml-tools/issues) on GitHub.
45 |
46 | ## Contributing
47 |
48 | See [CONTRIBUTING.md](./CONTRIBUTING.md).
49 |
--------------------------------------------------------------------------------
/packages/common/lib/xml-ns-key.js:
--------------------------------------------------------------------------------
1 | // The xml parser takes care of validating the attribute name.
2 | // If the user started the attribute name with "xmlns:" we can assume that
3 | // they meant for it to be an xml namespace attribute.
4 | // xmlns attributes explicitly can't contain ":" after the "xmlns:" part.
5 | const namespaceRegex = /^xmlns(?:(?[^:]*))?$/;
6 |
7 | /**
8 | * See comment in api.d.ts.
9 | *
10 | * @param {string} key
11 | * @param {boolean} includeEmptyPrefix
12 | * @returns {boolean}
13 | */
14 | function isXMLNamespaceKey({ key, includeEmptyPrefix }) {
15 | if (typeof key !== "string") {
16 | return false;
17 | }
18 | const matchArr = key.match(namespaceRegex);
19 |
20 | // No match - this is not an xmlns key
21 | if (matchArr === null) {
22 | return false;
23 | }
24 |
25 | return !!(
26 | includeEmptyPrefix === true ||
27 | // "xmlns" case
28 | !matchArr.groups.prefixWithColon ||
29 | // "xmlns:" case
30 | matchArr.groups.prefix
31 | );
32 | }
33 |
34 | /**
35 | * See comment in api.d.ts.
36 | *
37 | * @param {string} key
38 | * @returns {string|undefined}
39 | */
40 | function getXMLNamespaceKeyPrefix(key) {
41 | if (typeof key !== "string") {
42 | return undefined;
43 | }
44 | const matchArr = key.match(namespaceRegex);
45 | if (matchArr === null) {
46 | return undefined;
47 | }
48 | return (matchArr.groups && matchArr.groups.prefix) || "";
49 | }
50 |
51 | module.exports = {
52 | isXMLNamespaceKey: isXMLNamespaceKey,
53 | getXMLNamespaceKeyPrefix: getXMLNamespaceKeyPrefix,
54 | };
55 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/validators/duplicate-sub-elements.js:
--------------------------------------------------------------------------------
1 | const { map, forEach, includes, filter, groupBy } = require("lodash");
2 | const { tokenToOffsetPosition } = require("./utils");
3 |
4 | /**
5 | * @param {XMLElement} elem
6 | * @param {XSSElement} schema
7 | *
8 | * @returns {ValidationIssue[]}
9 | */
10 | function validateDuplicateSubElements(elem, schema) {
11 | const allowedDupElem = filter(
12 | schema.elements,
13 | (_) => _.cardinality === "many"
14 | );
15 | const allowedDupElemNames = map(allowedDupElem, (_) => _.name);
16 |
17 | const actualSubElemByName = groupBy(elem.subElements, (_) => _.name);
18 | const issues = [];
19 | forEach(actualSubElemByName, (dupElements, dupElementsName) => {
20 | const allowedDup = includes(allowedDupElemNames, dupElementsName);
21 | const hasConfiguration = schema.elements[dupElementsName] !== undefined;
22 | const hasDuplicates = dupElements.length > 1;
23 |
24 | if (allowedDup === false && hasDuplicates && hasConfiguration) {
25 | forEach(dupElements, (dupElem) => {
26 | issues.push({
27 | msg: `Duplicate Sub-Element: <${dupElem.name}> only a single occurrence of this Sub-Element is allowed here.`,
28 | node: dupElem,
29 | severity: "error",
30 | // safe assumption that we have an `openName` (see above condition)
31 | position: tokenToOffsetPosition(dupElem.syntax.openName),
32 | });
33 | });
34 | }
35 | });
36 |
37 | return issues;
38 | }
39 |
40 | module.exports = {
41 | validateDuplicateSubElements: validateDuplicateSubElements,
42 | };
43 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/validators/unknown-attributes.js:
--------------------------------------------------------------------------------
1 | const { map, includes, forEach } = require("lodash");
2 | const { isXMLNamespaceKey } = require("@xml-tools/common");
3 | const { tokenToOffsetPosition } = require("./utils");
4 |
5 | /**
6 | * @param {XMLElement} elem
7 | * @param {XSSElement} schema
8 | *
9 | * @returns {ValidationIssue[]}
10 | */
11 | function validateUnknownAttributes(elem, schema) {
12 | // This validation is only relevant if the Schema disallows unknown attributes.
13 | if (schema.attributesType !== "closed") {
14 | return [];
15 | }
16 | const allowedAttribNames = map(schema.attributes, (_) => _.key);
17 |
18 | const issues = [];
19 | forEach(elem.attributes, (attrib) => {
20 | /* istanbul ignore else - Defensive programming, but cannot
21 | * reproduce this branch with current error recovery heuristics
22 | */
23 | if (attrib.key !== null) {
24 | if (
25 | includes(allowedAttribNames, attrib.key) === false &&
26 | isXMLNamespaceKey({ key: attrib.key, includeEmptyPrefix: true }) ===
27 | false
28 | ) {
29 | issues.push({
30 | msg: `Unknown Attribute: <${
31 | attrib.key
32 | }> only [${allowedAttribNames.toString()}] attributes are allowed`,
33 | node: attrib,
34 | severity: "error",
35 | // safe assumption that we have a `key` token (see above condition)
36 | position: tokenToOffsetPosition(attrib.syntax.key),
37 | });
38 | }
39 | }
40 | });
41 | return issues;
42 | }
43 |
44 | module.exports = {
45 | validateUnknownAttributes: validateUnknownAttributes,
46 | };
47 |
--------------------------------------------------------------------------------
/packages/ast/lib/visit-ast.js:
--------------------------------------------------------------------------------
1 | const { forEach, isFunction } = require("lodash");
2 | const { getAstChildrenReflective } = require("./utils");
3 |
4 | /**
5 | * @param {XMLAstNode} node
6 | * @param {XMLAstVisitor} visitor
7 | *
8 | * @returns {void}
9 | */
10 | function accept(node, visitor) {
11 | switch (node.type) {
12 | case "XMLDocument": {
13 | if (isFunction(visitor.visitXMLDocument)) {
14 | visitor.visitXMLDocument(node);
15 | }
16 | break;
17 | }
18 | case "XMLProlog": {
19 | if (isFunction(visitor.visitXMLProlog)) {
20 | visitor.visitXMLProlog(node);
21 | }
22 | break;
23 | }
24 | case "XMLPrologAttribute": {
25 | if (isFunction(visitor.visitXMLPrologAttribute)) {
26 | visitor.visitXMLPrologAttribute(node);
27 | }
28 | break;
29 | }
30 | case "XMLElement": {
31 | if (isFunction(visitor.visitXMLElement)) {
32 | visitor.visitXMLElement(node);
33 | }
34 | break;
35 | }
36 | case "XMLAttribute": {
37 | if (isFunction(visitor.visitXMLAttribute)) {
38 | visitor.visitXMLAttribute(node);
39 | }
40 | break;
41 | }
42 | case "XMLTextContent": {
43 | if (isFunction(visitor.visitXMLTextContent)) {
44 | visitor.visitXMLTextContent(node);
45 | }
46 | break;
47 | }
48 | /* istanbul ignore next defensive programming */
49 | default:
50 | throw Error("None Exhaustive Match");
51 | }
52 |
53 | const astChildren = getAstChildrenReflective(node);
54 | forEach(astChildren, (childNode) => {
55 | accept(childNode, visitor);
56 | });
57 | }
58 |
59 | module.exports = {
60 | accept: accept,
61 | };
62 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # sub-packages files for https://reuse.software (avoid duplication)
2 | .reuse
3 | LICENSES
4 | LICENSE
5 |
6 | # root files for https://reuse.software
7 | !/.reuse
8 | !/LICENSES
9 | !/LICENSE
10 |
11 | # Logs
12 | logs
13 | *.log
14 | npm-debug.log*
15 | yarn-debug.log*
16 | yarn-error.log*
17 |
18 | # Runtime data
19 | pids
20 | *.pid
21 | *.seed
22 | *.pid.lock
23 |
24 | # Directory for instrumented libs generated by jscoverage/JSCover
25 | lib-cov
26 |
27 | # Coverage directory used by tools like istanbul
28 | coverage
29 |
30 | # nyc test coverage
31 | .nyc_output
32 |
33 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
34 | .grunt
35 |
36 | # Bower dependency directory (https://bower.io/)
37 | bower_components
38 |
39 | # node-waf configuration
40 | .lock-wscript
41 |
42 | # Compiled binary addons (https://nodejs.org/api/addons.html)
43 | build/Release
44 |
45 | # Dependency directories
46 | node_modules/
47 | jspm_packages/
48 |
49 | # TypeScript v1 declaration files
50 | typings/
51 |
52 | # Optional npm cache directory
53 | .npm
54 |
55 | # Optional eslint cache
56 | .eslintcache
57 |
58 | # Optional REPL history
59 | .node_repl_history
60 |
61 | # Output of 'npm pack'
62 | *.tgz
63 |
64 | # Yarn Integrity file
65 | .yarn-integrity
66 |
67 | # dotenv environment variables file
68 | .env
69 |
70 | # next.js build output
71 | .next
72 |
73 | # IDE
74 | .idea
75 | .vscode
76 |
77 | # vscode-test
78 | .vscode-test
79 |
80 | # VSCode extension build result
81 | *.vsix
82 |
83 | # bundling results
84 | dist
85 |
86 | # NYC
87 | .nyc_output
88 | coverage
89 |
90 | #Output
91 | report
92 | *.map
93 |
94 | # we are using yarn instead of npm
95 | package-lock.json
96 |
97 |
--------------------------------------------------------------------------------
/packages/xml-toolkit/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "xml-toolkit",
3 | "version": "1.2.1",
4 | "private": true,
5 | "displayName": "XML Toolkit",
6 | "description": "Language Support for XML",
7 | "publisher": "SAPOSS",
8 | "icon": "resources/xml-toolkit.png",
9 | "keywords": [
10 | "xml"
11 | ],
12 | "categories": [
13 | "Programming Languages"
14 | ],
15 | "license": "Apache-2.0",
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/SAP/xml-tools/tree/master/packages/xml-toolkit"
19 | },
20 | "engines": {
21 | "vscode": "^1.56.0"
22 | },
23 | "activationEvents": [
24 | "onLanguage:xml"
25 | ],
26 | "main": "./lib/extension",
27 | "scripts": {
28 | "ci": "npm-run-all clean coverage:* bundle package",
29 | "clean": "rimraf ./dist ./coverage ./nyc_output *.vsix NOTICE LICENSE",
30 | "test": "node test/runTest.js",
31 | "coverage:run": "nyc node test/runTest.js",
32 | "coverage:check": "nyc check-coverage --lines 100 --branches 100 --statements 100 --functions 100",
33 | "vsix:prepare": "sh prepare-vsix-package.sh",
34 | "bundle": "webpack --mode production",
35 | "package": "node ./scripts/package-vsix"
36 | },
37 | "dependencies": {
38 | "@xml-tools/language-server": "^1.1.1",
39 | "vscode-languageclient": "6.1.3"
40 | },
41 | "devDependencies": {
42 | "@types/vscode": "1.56.0",
43 | "deep-equal-in-any-order": "1.0.28",
44 | "lodash": "4.17.21",
45 | "proxyquire": "2.1.3",
46 | "vsce": "1.84.0",
47 | "vscode-test": "1.5.2"
48 | },
49 | "nyc": {
50 | "include": [
51 | "lib/**/*.js"
52 | ],
53 | "reporter": [
54 | "text",
55 | "lcov"
56 | ]
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/prolog/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [],
9 | subElements: [],
10 | textContents: [],
11 | position: { startOffset: 39, endOffset: 51 },
12 | syntax: {
13 | openName: { image: "note", startOffset: 40, endOffset: 43 },
14 | closeName: { image: "note", startOffset: 47, endOffset: 50 },
15 | isSelfClosing: false,
16 | openBody: { startOffset: 39, endOffset: 44 },
17 | closeBody: { startOffset: 45, endOffset: 51 },
18 | attributesRange: { startOffset: 45, endOffset: 43 },
19 | },
20 | },
21 | position: { startOffset: 0, endOffset: 52 },
22 | prolog: {
23 | type: "XMLProlog",
24 | attributes: [
25 | {
26 | type: "XMLPrologAttribute",
27 | position: { startOffset: 6, endOffset: 18 },
28 | key: "version",
29 | value: "1.0",
30 | syntax: {
31 | key: { image: "version", startOffset: 6, endOffset: 12 },
32 | value: { image: '"1.0"', startOffset: 14, endOffset: 18 },
33 | },
34 | },
35 | {
36 | type: "XMLPrologAttribute",
37 | position: { startOffset: 20, endOffset: 35 },
38 | key: "encoding",
39 | value: "UTF-8",
40 | syntax: {
41 | key: { image: "encoding", startOffset: 20, endOffset: 27 },
42 | value: { image: '"UTF-8"', startOffset: 29, endOffset: 35 },
43 | },
44 | },
45 | ],
46 | position: { startOffset: 0, endOffset: 37 },
47 | },
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/packages/validation/test/validate-apis-spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require("chai");
2 | const { map } = require("lodash");
3 | const { parse } = require("@xml-tools/parser");
4 | const { buildAst } = require("@xml-tools/ast");
5 | const { validate } = require("../");
6 |
7 | describe("The XML Validations APIs", () => {
8 | it("can validate attributes", () => {
9 | const xmlText = `
10 | Bill
11 | Tim
12 | `;
13 |
14 | const ast = xmlTextToAst(xmlText);
15 |
16 | const issues = validate({
17 | doc: ast,
18 | validators: {
19 | element: [
20 | (node) => {
21 | return [{ msg: node.name }];
22 | },
23 | ],
24 | },
25 | });
26 |
27 | const msgNodeNames = map(issues, (_) => _.msg);
28 | expect(msgNodeNames).to.have.lengthOf(3);
29 | expect(msgNodeNames).to.have.members(["note", "to", "from"]);
30 | });
31 |
32 | it("can validate elements", () => {
33 | const xmlText = `
34 | Bill
35 | Gates
36 | Tim
37 | `;
38 |
39 | const ast = xmlTextToAst(xmlText);
40 |
41 | const issues = validate({
42 | doc: ast,
43 | validators: {
44 | attribute: [
45 | (node) => {
46 | return [{ msg: node.key }];
47 | },
48 | ],
49 | },
50 | });
51 |
52 | const msgNodeNames = map(issues, (_) => _.msg);
53 | expect(msgNodeNames).to.have.lengthOf(2);
54 | expect(msgNodeNames).to.have.members(["clearance", "hidden"]);
55 | });
56 | });
57 |
58 | function xmlTextToAst(text) {
59 | const { cst, tokenVector } = parse(text);
60 | const ast = buildAst(cst, tokenVector);
61 | return ast;
62 | }
63 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-last/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [
9 | {
10 | type: "XMLAttribute",
11 | position: { startOffset: 6, endOffset: 16 },
12 | key: "attr1",
13 | value: "666",
14 | syntax: {
15 | key: { image: "attr1", startOffset: 6, endOffset: 10 },
16 | value: { image: '"666"', startOffset: 12, endOffset: 16 },
17 | },
18 | },
19 | {
20 | type: "XMLAttribute",
21 | position: { startOffset: 18, endOffset: 28 },
22 | key: "attr2",
23 | value: "333",
24 | syntax: {
25 | key: { image: "attr2", startOffset: 18, endOffset: 22 },
26 | value: { image: '"333"', startOffset: 24, endOffset: 28 },
27 | },
28 | },
29 | {
30 | type: "XMLAttribute",
31 | position: { startOffset: 30, endOffset: 35 },
32 | key: "attr3",
33 | value: null,
34 | syntax: { key: { image: "attr3", startOffset: 30, endOffset: 34 } },
35 | },
36 | ],
37 | subElements: [],
38 | textContents: [],
39 | position: { startOffset: 0, endOffset: 44 },
40 | syntax: {
41 | openName: { image: "note", startOffset: 1, endOffset: 4 },
42 | closeName: { image: "note", startOffset: 40, endOffset: 43 },
43 | isSelfClosing: false,
44 | openBody: { startOffset: 0, endOffset: 37 },
45 | closeBody: { startOffset: 38, endOffset: 44 },
46 | attributesRange: { startOffset: 6, endOffset: 36 },
47 | },
48 | },
49 | position: { startOffset: 0, endOffset: 45 },
50 | },
51 | };
52 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-middle/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [
9 | {
10 | type: "XMLAttribute",
11 | position: { startOffset: 6, endOffset: 16 },
12 | key: "attr1",
13 | value: "666",
14 | syntax: {
15 | key: { image: "attr1", startOffset: 6, endOffset: 10 },
16 | value: { image: '"666"', startOffset: 12, endOffset: 16 },
17 | },
18 | },
19 | {
20 | type: "XMLAttribute",
21 | position: { startOffset: 18, endOffset: 23 },
22 | key: "attr2",
23 | value: null,
24 | syntax: { key: { image: "attr2", startOffset: 18, endOffset: 22 } },
25 | },
26 | {
27 | type: "XMLAttribute",
28 | position: { startOffset: 25, endOffset: 35 },
29 | key: "attr3",
30 | value: "111",
31 | syntax: {
32 | key: { image: "attr3", startOffset: 25, endOffset: 29 },
33 | value: { image: '"111"', startOffset: 31, endOffset: 35 },
34 | },
35 | },
36 | ],
37 | subElements: [],
38 | textContents: [],
39 | position: { startOffset: 0, endOffset: 44 },
40 | syntax: {
41 | openName: { image: "note", startOffset: 1, endOffset: 4 },
42 | closeName: { image: "note", startOffset: 40, endOffset: 43 },
43 | isSelfClosing: false,
44 | openBody: { startOffset: 0, endOffset: 37 },
45 | closeBody: { startOffset: 38, endOffset: 44 },
46 | attributesRange: { startOffset: 6, endOffset: 36 },
47 | },
48 | },
49 | position: { startOffset: 0, endOffset: 45 },
50 | },
51 | };
52 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-last/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [
9 | {
10 | type: "XMLAttribute",
11 | position: { startOffset: 6, endOffset: 16 },
12 | key: "attr1",
13 | value: "666",
14 | syntax: {
15 | key: { image: "attr1", startOffset: 6, endOffset: 10 },
16 | value: { image: '"666"', startOffset: 12, endOffset: 16 },
17 | },
18 | },
19 | {
20 | type: "XMLAttribute",
21 | position: { startOffset: 18, endOffset: 28 },
22 | key: "attr2",
23 | value: "333",
24 | syntax: {
25 | key: { image: "attr2", startOffset: 18, endOffset: 22 },
26 | value: { image: '"333"', startOffset: 24, endOffset: 28 },
27 | },
28 | },
29 | {
30 | type: "XMLAttribute",
31 | position: { startOffset: 30, endOffset: 34 },
32 | key: "attr3",
33 | value: null,
34 | syntax: { key: { image: "attr3", startOffset: 30, endOffset: 34 } },
35 | },
36 | ],
37 | subElements: [],
38 | textContents: [],
39 | position: { startOffset: 0, endOffset: 43 },
40 | syntax: {
41 | openName: { image: "note", startOffset: 1, endOffset: 4 },
42 | closeName: { image: "note", startOffset: 39, endOffset: 42 },
43 | isSelfClosing: false,
44 | openBody: { startOffset: 0, endOffset: 36 },
45 | closeBody: { startOffset: 37, endOffset: 43 },
46 | attributesRange: { startOffset: 6, endOffset: 35 },
47 | },
48 | },
49 | position: { startOffset: 0, endOffset: 44 },
50 | },
51 | };
52 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/attributes-no-value-no-eql-middle/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [
9 | {
10 | type: "XMLAttribute",
11 | position: { startOffset: 6, endOffset: 16 },
12 | key: "attr1",
13 | value: "666",
14 | syntax: {
15 | key: { image: "attr1", startOffset: 6, endOffset: 10 },
16 | value: { image: '"666"', startOffset: 12, endOffset: 16 },
17 | },
18 | },
19 | {
20 | type: "XMLAttribute",
21 | position: { startOffset: 18, endOffset: 22 },
22 | key: "attr2",
23 | value: null,
24 | syntax: { key: { image: "attr2", startOffset: 18, endOffset: 22 } },
25 | },
26 | {
27 | type: "XMLAttribute",
28 | position: { startOffset: 24, endOffset: 34 },
29 | key: "attr3",
30 | value: "111",
31 | syntax: {
32 | key: { image: "attr3", startOffset: 24, endOffset: 28 },
33 | value: { image: '"111"', startOffset: 30, endOffset: 34 },
34 | },
35 | },
36 | ],
37 | subElements: [],
38 | textContents: [],
39 | position: { startOffset: 0, endOffset: 43 },
40 | syntax: {
41 | openName: { image: "note", startOffset: 1, endOffset: 4 },
42 | closeName: { image: "note", startOffset: 39, endOffset: 42 },
43 | isSelfClosing: false,
44 | openBody: { startOffset: 0, endOffset: 36 },
45 | closeBody: { startOffset: 37, endOffset: 43 },
46 | attributesRange: { startOffset: 6, endOffset: 35 },
47 | },
48 | },
49 | position: { startOffset: 0, endOffset: 44 },
50 | },
51 | };
52 |
--------------------------------------------------------------------------------
/packages/parser/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/@xml-tools/parser)
2 |
3 | # @xml-tools/parser
4 |
5 | A Fault Tolerant XML Parser which produces a [Concrete Syntax Tree][cst].
6 |
7 | This means that the Parser will **not** stop on the first error and instead attempt to perform automatic error recovery.
8 | This also means that the CST outputted by the Parser may only have **partial** results.
9 | For example, In a valid XML an attribute must always have a value, however in the CST produced
10 | by this parser an attribute's value may be missing as the XML Text input is not necessarily valid.
11 |
12 | The CST produced by this parser is often used as the input for other packages in the xml-tools scope, e.g:
13 |
14 | - [@xml-tools/ast](../ast) As the input for building an XML AST.
15 | - [@xml-tools/content-assist](../content-assist) As part of the input for the content assist APIs.
16 |
17 | ## Installation
18 |
19 | With npm:
20 |
21 | - `npm install @xml-tools/parser`
22 |
23 | With Yarn
24 |
25 | - `yarn add @xml-tools/parser`
26 |
27 | ## Usage
28 |
29 | Please see the [TypeScript Definitions](./api.d.ts) for full API details.
30 |
31 | A simple usage example:
32 |
33 | ```javascript
34 | const { parse } = require("@xml-tools/parser");
35 |
36 | const xmlText = `
37 | Bill
38 | Tim
39 |
40 | `;
41 |
42 | const { cst, lexErrors, parseErrors } = parse(xmlText);
43 | console.log(cst.children["element"][0].children["Name"][0].image); // -> note
44 | ```
45 |
46 | ## Support
47 |
48 | Please open [issues](https://github.com/SAP/xml-tols/issues) on github.
49 |
50 | ## Contributing
51 |
52 | See [CONTRIBUTING.md](./CONTRIBUTING.md).
53 |
54 | [cst]: https://en.wikipedia.org/wiki/Parse_tree
55 |
--------------------------------------------------------------------------------
/packages/xml-toolkit/lib/extension.js:
--------------------------------------------------------------------------------
1 | const { readFile: readFileCallback } = require("fs");
2 | const { resolve } = require("path");
3 | const { promisify } = require("util");
4 | const { workspace } = require("vscode");
5 | const { LanguageClient, TransportKind } = require("vscode-languageclient");
6 | const { SERVER_PATH } = require("@xml-tools/language-server");
7 |
8 | const readFile = promisify(readFileCallback);
9 |
10 | let client;
11 |
12 | async function activate(context) {
13 | const debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] };
14 |
15 | const serverOptions = {
16 | run: { module: SERVER_PATH, transport: TransportKind.ipc },
17 | debug: {
18 | module: SERVER_PATH,
19 | transport: TransportKind.ipc,
20 | options: debugOptions,
21 | },
22 | };
23 |
24 | const meta = JSON.parse(
25 | await readFile(resolve(context.extensionPath, "package.json"), "utf8")
26 | );
27 |
28 | /**
29 | * @type {import("@xml-tools/language-server).ServerInitializationOptions}
30 | */
31 | const initializationOptions = {
32 | consumer: meta.displayName,
33 | };
34 |
35 | let clientOptions = {
36 | documentSelector: [{ scheme: "file", language: "xml" }],
37 | synchronize: {
38 | fileEvents: workspace.createFileSystemWatcher("**/*.xml"),
39 | },
40 | initializationOptions: initializationOptions,
41 | };
42 |
43 | client = new LanguageClient(
44 | "XMLforVSCode",
45 | "XML For VSCode",
46 | serverOptions,
47 | clientOptions
48 | );
49 |
50 | client.start();
51 | }
52 |
53 | function deactivate() {
54 | /* istanbul ignore if - scenario that cannot be reached in productive code */
55 | if (!client) {
56 | return undefined;
57 | }
58 | return client.stop();
59 | }
60 |
61 | module.exports = {
62 | activate: activate,
63 | deactivate: deactivate,
64 | };
65 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/validators/attribute-value.js:
--------------------------------------------------------------------------------
1 | const { isRegExp, isArray, includes, has } = require("lodash");
2 | const { tokenToOffsetPosition } = require("./utils");
3 |
4 | /**
5 | * @param {XMLAttribute} attributeNode
6 | * @param {XSSAttribute }xssAttribute
7 | *
8 | * @returns {ValidationIssue[]}
9 | */
10 | function validateAttributeValue(attributeNode, xssAttribute) {
11 | const issues = [];
12 |
13 | const valueDef = xssAttribute.value;
14 |
15 | // An XSS Attribute definition may not specify any constraints on a value
16 | if (has(xssAttribute, "value") === false) {
17 | return issues;
18 | }
19 |
20 | const actualValue = attributeNode.value;
21 | if (actualValue === null) {
22 | // we cannot validate a partial attribute AST without an actual value...
23 | return issues;
24 | }
25 |
26 | // This is always safe because at this point we know the attribute has a value
27 | const errPosition = tokenToOffsetPosition(attributeNode.syntax.value);
28 | /* istanbul ignore else defensive programming */
29 | if (isRegExp(valueDef)) {
30 | if (valueDef.test(actualValue) === false) {
31 | issues.push({
32 | msg: `Expecting Value matching <${valueDef.toString()}> but found <${actualValue}>`,
33 | node: attributeNode,
34 | severity: "error",
35 | position: errPosition,
36 | });
37 | }
38 | } else if (isArray(valueDef)) {
39 | if (includes(valueDef, actualValue) === false) {
40 | issues.push({
41 | msg: `Expecting one of <${valueDef.toString()}> but found <${actualValue}>`,
42 | node: attributeNode,
43 | severity: "error",
44 | position: errPosition,
45 | });
46 | }
47 | } else {
48 | /* istanbul ignore next defensive programming */
49 | throw Error("None Exhaustive Match");
50 | }
51 |
52 | return issues;
53 | }
54 |
55 | module.exports = {
56 | validateAttributeValue: validateAttributeValue,
57 | };
58 |
--------------------------------------------------------------------------------
/packages/validation/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/@xml-tools/validation)
2 |
3 | # @xml-tools/validation
4 |
5 | APIs which assist in implementing Validations / Diagnostics logic for XML.
6 |
7 | ## Installation
8 |
9 | With npm:
10 |
11 | - `npm install @xml-tools/validation`
12 |
13 | With Yarn
14 |
15 | - `yarn add @xml-tools/validation`
16 |
17 | ## Usage
18 |
19 | Please see the [TypeScript Definitions](./api.d.ts) for full API details.
20 |
21 | A simple usage example:
22 |
23 | ```javascript
24 | const { parse } = require("xml-tools/parser");
25 | const { buildAst } = require("xml-tools/ast");
26 | const { validate } = require("xml-tools/validation");
27 |
28 | const xmlText = `
29 | Bill
30 |
31 | `;
32 |
33 | const { cst, tokenVector } = parse(xmlText);
34 | const xmlDocAst = buildAst(cst, tokenVector);
35 | const issues = validate({
36 | doc: xmlDocAst,
37 | validators: {
38 | element: [
39 | (node) => {
40 | if (node.name === "note") {
41 | const hasFrom = node.subElements.find(
42 | (subNode) => subNode.name === "from"
43 | );
44 | if (hasFrom === undefined) {
45 | return [
46 | {
47 | msg: "A Note Element **must** have a `from` subElement",
48 | node: node,
49 | },
50 | ];
51 | }
52 | }
53 | return [];
54 | },
55 | ],
56 | },
57 | });
58 |
59 | console.log(issues[0].msg); // -> "A Note Element **must** have a `from` subElement"
60 | // Issue position can be extracted from the relevant ASTNode.
61 | console.log(issues[0].node.position.endLine); // -> 3
62 | ```
63 |
64 | ## Support
65 |
66 | Please open [issues](https://github.com/SAP/xml-tols/issues) on github.
67 |
68 | ## Contributing
69 |
70 | See [CONTRIBUTING.md](./CONTRIBUTING.md).
71 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/path.js:
--------------------------------------------------------------------------------
1 | const { drop, map, forEach, first } = require("lodash");
2 |
3 | /**
4 | * @param {XMLAttribute} attribNode
5 | * @param {SimpleSchema} schema
6 | */
7 | function findAttributeXssDef(attribNode, schema) {
8 | const xssElement = findElementXssDef(attribNode.parent, schema);
9 |
10 | let xssAttribute = undefined;
11 | if (xssElement !== undefined) {
12 | const attributeName = attribNode.key;
13 | xssAttribute = xssElement.attributes[attributeName];
14 | }
15 |
16 | return xssAttribute;
17 | }
18 |
19 | /**
20 | * @param {XMLElement} node
21 | * @param {SimpleSchema} schema
22 | */
23 | function findElementXssDef(node, schema) {
24 | const ancestors = getAstNodeAncestors(node);
25 | const elementsPath = map(ancestors, "name");
26 |
27 | const rootElement = first(elementsPath);
28 | // Root Element mis-match, The Schema cannot provide any attribute validations for this XML AST.
29 | if (rootElement !== schema.name) {
30 | return undefined;
31 | }
32 | let xssElement = schema;
33 | forEach(drop(elementsPath), (elemName) => {
34 | // traverse subElements
35 | xssElement = xssElement.elements[elemName];
36 | if (xssElement === undefined) {
37 | return false;
38 | }
39 | });
40 |
41 | return xssElement;
42 | }
43 |
44 | /**
45 | * @param {XMLAstNode} node
46 | *
47 | * @returns {XMLAstNode[]} - The Ancestors do not include the XMLDocument
48 | */
49 | function getAstNodeAncestors(node) {
50 | const ancestors = [];
51 | ancestors.push(node);
52 | let currAncestor = node.parent;
53 | while (
54 | currAncestor !== undefined &&
55 | // The Simple Schema only starts at the root Element (not the Root Document).
56 | currAncestor.type !== "XMLDocument"
57 | ) {
58 | ancestors.push(currAncestor);
59 | currAncestor = currAncestor.parent;
60 | }
61 | ancestors.reverse();
62 |
63 | return ancestors;
64 | }
65 |
66 | module.exports = {
67 | findAttributeXssDef: findAttributeXssDef,
68 | findElementXssDef: findElementXssDef,
69 | };
70 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/get-content-assist.js:
--------------------------------------------------------------------------------
1 | const { attributeNameCompletion } = require("./content-assist/attribute-name");
2 | const {
3 | attributeValueCompletion,
4 | } = require("./content-assist/attribute-value");
5 | const { elementNameCompletion } = require("./content-assist/element-name");
6 | const { findElementXssDef, findAttributeXssDef } = require("./path");
7 |
8 | function getSchemaSuggestionsProviders(schema) {
9 | const attributeNameProvider = buildAttributeNameProvider(schema);
10 | const attributeValueProvider = buildAttributeValueProvider(schema);
11 | const elementNameProvider = buildElementNameProvider(schema);
12 |
13 | return {
14 | schemaElementNameCompletion: elementNameProvider,
15 | schemaAttributeNameCompletion: attributeNameProvider,
16 | schemaAttributeValueCompletion: attributeValueProvider,
17 | };
18 | }
19 |
20 | /**
21 | * @param {SimpleSchema} schema
22 | */
23 | function buildAttributeNameProvider(schema) {
24 | return ({ element, prefix }) => {
25 | const xssElementDef = findElementXssDef(element, schema);
26 | if (xssElementDef !== undefined) {
27 | return attributeNameCompletion(element, xssElementDef, prefix);
28 | } else {
29 | return [];
30 | }
31 | };
32 | }
33 |
34 | /**
35 | * @param {SimpleSchema} schema
36 | */
37 | function buildElementNameProvider(schema) {
38 | return ({ element, prefix }) => {
39 | // Note we are finding the definition for the element's parent
40 | // Because the information on possible sibling elements exists there...
41 | const xssElementDef = findElementXssDef(element.parent, schema);
42 | if (xssElementDef !== undefined) {
43 | return elementNameCompletion(element.parent, xssElementDef, prefix);
44 | } else {
45 | return [];
46 | }
47 | };
48 | }
49 |
50 | /**
51 | * @param {SimpleSchema} schema
52 | */
53 | function buildAttributeValueProvider(schema) {
54 | return ({ attribute, prefix }) => {
55 | const attributeXssDef = findAttributeXssDef(attribute, schema);
56 | return attributeValueCompletion(attribute, attributeXssDef, prefix);
57 | };
58 | }
59 |
60 | module.exports = {
61 | getSchemaSuggestionsProviders: getSchemaSuggestionsProviders,
62 | };
63 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/invalid/element-closing-name/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | cst: {
3 | name: "document",
4 | children: {
5 | element: [
6 | {
7 | name: "element",
8 | children: {
9 | OPEN: [
10 | { image: "<", startOffset: 0, endOffset: 0, tokenType: "OPEN" },
11 | ],
12 | Name: [
13 | {
14 | image: "note",
15 | startOffset: 1,
16 | endOffset: 4,
17 | tokenType: "Name",
18 | },
19 | ],
20 | START_CLOSE: [
21 | { image: ">", startOffset: 5, endOffset: 5, tokenType: "CLOSE" },
22 | ],
23 | content: [
24 | {
25 | name: "content",
26 | children: {},
27 | location: { startOffset: null, endOffset: null },
28 | },
29 | ],
30 | SLASH_OPEN: [
31 | {
32 | image: "",
33 | startOffset: 6,
34 | endOffset: 7,
35 | tokenType: "SLASH_OPEN",
36 | },
37 | ],
38 | END_NAME: [
39 | {
40 | image: "note",
41 | startOffset: 9,
42 | endOffset: 12,
43 | tokenType: "Name",
44 | },
45 | ],
46 | END: [
47 | {
48 | image: ">",
49 | startOffset: 18,
50 | endOffset: 18,
51 | tokenType: "CLOSE",
52 | },
53 | ],
54 | },
55 | location: { startOffset: 0, endOffset: 18 },
56 | },
57 | ],
58 | misc: [
59 | {
60 | name: "misc",
61 | children: {
62 | SEA_WS: [
63 | {
64 | image: "\n",
65 | startOffset: 19,
66 | endOffset: 19,
67 | tokenType: "SEA_WS",
68 | },
69 | ],
70 | },
71 | location: { startOffset: 19, endOffset: 19 },
72 | },
73 | ],
74 | },
75 | location: { startOffset: 0, endOffset: 19 },
76 | },
77 | };
78 |
--------------------------------------------------------------------------------
/scripts/trigger-release.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require("path");
2 | const { includes } = require("lodash");
3 | const simpleGit = require("simple-git/promise");
4 |
5 | const rootDir = resolve(__dirname, "..");
6 | const git = simpleGit(rootDir);
7 |
8 | /**
9 | * Github Releases are triggered when a new version (tag) of the VSCode extension
10 | * is pushed. **however** there seems to be a bug in either CircleCI or github.com
11 | * Which causes circleCI to **not** trigger tag builds when many tags are pushed at once.
12 | * Therefore we are manually deleting and re-pushing the VSCode extension tag
13 | */
14 | async function triggerGHReleasesPublish() {
15 | const log = await git.log();
16 | const latestCommit = log.latest;
17 | const vscodeExtResult = /xml-toolkit@\d+(?:\.\d+)*/.exec(latestCommit.body);
18 | if (vscodeExtResult !== null) {
19 | const vscodeExtTag = vscodeExtResult[0];
20 | console.log(`deleting remote tag ${vscodeExtTag}`);
21 | await git.push(["--delete", "origin", vscodeExtTag]);
22 | console.log(`pushing tag ${vscodeExtTag}`);
23 | await git.push("origin", vscodeExtTag);
24 | }
25 | }
26 |
27 | /**
28 | * `lerna version` pushes a tag per each new package version.
29 | * However we need a single unique tag to trigger the `lerna publish --from-git` on circle-CI
30 | * This is why we delete and push the `RELEASE` as part of our release process.
31 | */
32 | async function triggerNPMPublish() {
33 | const allTags = (await git.tags()).all;
34 | if (includes(allTags, "RELEASE")) {
35 | console.log("deleting old local Tag");
36 | await git.tag(["-d", "RELEASE"]);
37 | }
38 | console.log("creating new local Tag");
39 | await git.tag(["RELEASE"]);
40 |
41 | try {
42 | console.log("trying to delete old remote Tag");
43 | await git.push(["--delete", "origin", "RELEASE"]);
44 | } catch (e) {
45 | console.log(e.message);
46 | }
47 |
48 | console.log("pushing new remote Tag");
49 | await git.push("origin", "RELEASE");
50 | }
51 |
52 | async function main() {
53 | await triggerNPMPublish();
54 | // push tags slowly and one at a time to ensure CircleCi recognizes them.
55 | await new Promise((r) => setTimeout(r, 1000));
56 | await triggerGHReleasesPublish();
57 | }
58 |
59 | main();
60 |
--------------------------------------------------------------------------------
/packages/ast-position/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | ## [2.0.7](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.6...@xml-tools/ast-position@2.0.7) (2021-06-03)
7 |
8 | **Note:** Version bump only for package @xml-tools/ast-position
9 |
10 | ## [2.0.6](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.5...@xml-tools/ast-position@2.0.6) (2021-02-09)
11 |
12 | **Note:** Version bump only for package @xml-tools/ast-position
13 |
14 | ## [2.0.5](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.4...@xml-tools/ast-position@2.0.5) (2021-01-21)
15 |
16 | **Note:** Version bump only for package @xml-tools/ast-position
17 |
18 | ## [2.0.4](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.3...@xml-tools/ast-position@2.0.4) (2021-01-20)
19 |
20 | ### Bug Fixes
21 |
22 | - missing lib folder in npm packages ([ffed3c2](https://github.com/sap/xml-tools/commit/ffed3c2c54c70aea8b9ded0d53786382bc190cc5))
23 |
24 | ## [2.0.3](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.2...@xml-tools/ast-position@2.0.3) (2021-01-20)
25 |
26 | **Note:** Version bump only for package @xml-tools/ast-position
27 |
28 | ## [2.0.2](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.1...@xml-tools/ast-position@2.0.2) (2020-06-29)
29 |
30 | **Note:** Version bump only for package @xml-tools/ast-position
31 |
32 | ## [2.0.1](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@2.0.0...@xml-tools/ast-position@2.0.1) (2020-06-18)
33 |
34 | **Note:** Version bump only for package @xml-tools/ast-position
35 |
36 | # [2.0.0](https://github.com/sap/xml-tools/compare/@xml-tools/ast-position@1.1.0...@xml-tools/ast-position@2.0.0) (2020-05-21)
37 |
38 | ### Features
39 |
40 | - **ast-position:** rename `getAstNodeInPosition` -> astPositionAtOffset ([585253f](https://github.com/sap/xml-tools/commit/585253f))
41 |
42 | ### BREAKING CHANGES
43 |
44 | - **ast-position:** rename `getAstNodeInPosition` -> astPositionAtOffset
45 |
46 | # 1.1.0 (2020-05-18)
47 |
48 | ### Features
49 |
50 | - **ast-position:** add AST position visitor ([#175](https://github.com/sap/xml-tools/issues/175)) ([9eb1253](https://github.com/sap/xml-tools/commit/9eb1253))
51 |
--------------------------------------------------------------------------------
/packages/ast/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/@xml-tools/ast)
2 |
3 | # @xml-tools/ast
4 |
5 | Utilities for building and traversing an XML **A**bstract **S**yntax **T**ree ([AST][ast]).
6 |
7 | There are two things which distinguish this AST from most others ASTs:
8 |
9 | 1. This AST can represent a **partially valid** XML, in practice this means virtually all properties on
10 | the AST are optional and may have undefined values.
11 | 2. This AST contains **additional syntactic information** to enable additional linting & formatting flows, for example:
12 | - the original position and value of an attribute's value (including the quotes).
13 | - The original positions and values of an XMLElement's open/close names.
14 |
15 | The input for constructing the AST is a CST which is created by the [@xml-tools/parser](../parser) package.
16 |
17 | The AST structure is used as part of the input for the [@xml-tools/content-assist](../content-assist) APIs.
18 |
19 | ## Installation
20 |
21 | With npm:
22 |
23 | - `npm install @xml-tools/ast`
24 |
25 | With Yarn
26 |
27 | - `yarn add @xml-tools/ast`
28 |
29 | ## Usage
30 |
31 | Please see the [TypeScript Definitions](./api.d.ts) for full API details.
32 |
33 | A simple usage example:
34 |
35 | ```javascript
36 | const { parse } = require("@xml-tools/parser");
37 | const { buildAst, accept } = require("@xml-tools/ast");
38 |
39 | const xmlText = `
40 | Bill
41 | Tim
42 |
43 | `;
44 |
45 | const { cst, tokenVector } = parse(xmlText);
46 | const xmlDocAst = buildAst(cst, tokenVector);
47 | console.log(xmlDocAst.rootElement.name); // -> note
48 |
49 | // A Visitor allows us to invoke actions on the XML ASTNodes without worrying about
50 | // The XML AST structure / traversal method.
51 | const printVisitor = {
52 | // Will be invoked once for each Element node in the AST.
53 | visitXMLElement: function (node) {
54 | console.log(node.name);
55 | },
56 |
57 | // An XML AST Visitor may have other methods as well, see the api.d.ts file/
58 | };
59 |
60 | // Invoking the Visitor
61 | accept(xmlDocAst, printVisitor); // -> note, Bill, Tim
62 | ```
63 |
64 | ## Support
65 |
66 | Please open [issues](https://github.com/SAP/xml-tols/issues) on github.
67 |
68 | ## Contributing
69 |
70 | See [CONTRIBUTING.md](./CONTRIBUTING.md).
71 |
--------------------------------------------------------------------------------
/packages/simple-schema/api.d.ts:
--------------------------------------------------------------------------------
1 | import { XMLAstNode, XMLAttribute, XMLElement } from "@xml-tools/ast";
2 | import {
3 | ValidationIssue,
4 | AttributeValidator,
5 | ElementValidator,
6 | } from "@xml-tools/validation";
7 | import {
8 | AttributeNameCompletion,
9 | AttributeValueCompletion,
10 | ElementContentCompletion,
11 | ElementNameCompletion,
12 | } from "@xml-tools/content-assist";
13 |
14 | // Represents the root element in an XML document
15 | declare type SimpleSchema = XSSElement & {
16 | // There may only be a single top level element in XML
17 | cardinality: "single";
18 | // The top level element in mandatory in XML
19 | require: true;
20 | };
21 |
22 | declare type XSSElement = {
23 | required: boolean;
24 | cardinality: "many" | "single";
25 | name: string;
26 | namespace?: string;
27 |
28 | attributesType?: "open" | "closed";
29 | attributes: Record;
30 |
31 | elementsType?: "open" | "closed";
32 | elements: Record;
33 | // TODO: textValue definition (for simple pure text only?)
34 | };
35 |
36 | declare type XSSAttribute = {
37 | required: boolean;
38 | key: string;
39 | value?: XSSValue;
40 | };
41 |
42 | // TODO: could be expended to support more primitive types and also complex types such as `union type`
43 | declare type XSSValue = RegExp | XSSValueEnum;
44 |
45 | declare type XSSValueEnum = string[];
46 |
47 | declare function getSchemaValidators(
48 | schema: SimpleSchema
49 | ): {
50 | attribute: AttributeValidator;
51 | element: ElementValidator;
52 | };
53 |
54 | interface CompletionSuggestion {
55 | text: string;
56 | label?: string;
57 | docs?: string;
58 | commitCharacter?: string;
59 | isNamespace?: boolean;
60 | /**
61 | * A measure of how certain we are about this suggestion's relevance.
62 | * This value could be used to:
63 | * - Filter out less relevant suggestions when there are too many possible suggestions.
64 | * - Sort the suggestions by relevance.
65 | */
66 | confidence?: number;
67 | }
68 |
69 | declare function getSchemaSuggestionsProviders(
70 | schema: SimpleSchema
71 | ): {
72 | // TBD in the future...
73 | // schemaElementContentCompletion: ElementContentCompletion;
74 | schemaElementNameCompletion: ElementNameCompletion;
75 | schemaAttributeNameCompletion: AttributeNameCompletion;
76 | schemaAttributeValueCompletion: AttributeValueCompletion;
77 | };
78 |
--------------------------------------------------------------------------------
/packages/constraints/test/constraints/unique-attribute-keys-spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require("chai");
2 | const { parse } = require("@xml-tools/parser");
3 | const { buildAst } = require("@xml-tools/ast");
4 | const {
5 | validateUniqueAttributeKeys,
6 | } = require("../../lib/constraints/unique-attribute-keys");
7 |
8 | describe("unique attribute keys constraint", () => {
9 | context("no issues", () => {
10 | it("will not detect any issues for a valid XML with attributes", () => {
11 | const xmlText = ``;
12 | const { cst, tokenVector } = parse(xmlText);
13 | const document = buildAst(cst, tokenVector);
14 | const element = document.rootElement;
15 | const result = validateUniqueAttributeKeys(element);
16 |
17 | expect(result).to.be.empty;
18 | });
19 |
20 | it("will not detect any issues for a valid XML without attributes", () => {
21 | const xmlText = ``;
22 | const { cst, tokenVector } = parse(xmlText);
23 | const document = buildAst(cst, tokenVector);
24 | const element = document.rootElement;
25 | const result = validateUniqueAttributeKeys(element);
26 |
27 | expect(result).to.be.empty;
28 | });
29 | });
30 |
31 | context("positive constraint checks", () => {
32 | it("detects a duplicate attribute keys", () => {
33 | const xmlText = ``;
34 |
35 | const { cst, tokenVector } = parse(xmlText);
36 | const document = buildAst(cst, tokenVector);
37 | const element = document.rootElement;
38 | const result = validateUniqueAttributeKeys(element);
39 |
40 | const donaldNameAttrib = element.attributes[0];
41 | const bambaNameAttrib = element.attributes[3];
42 |
43 | expect(result).to.have.lengthOf(2);
44 | expect(result).to.deep.include.members([
45 | {
46 | msg: 'duplicate attribute: "name"',
47 | severity: "error",
48 | node: donaldNameAttrib,
49 | position: {
50 | startOffset: donaldNameAttrib.syntax.key.startOffset,
51 | endOffset: donaldNameAttrib.syntax.key.endOffset,
52 | },
53 | },
54 | {
55 | msg: 'duplicate attribute: "name"',
56 | severity: "error",
57 | node: bambaNameAttrib,
58 | position: {
59 | startOffset: bambaNameAttrib.syntax.key.startOffset,
60 | endOffset: bambaNameAttrib.syntax.key.endOffset,
61 | },
62 | },
63 | ]);
64 | });
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/packages/common/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | ## [0.1.6](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.1.5...@xml-tools/common@0.1.6) (2021-06-03)
7 |
8 | **Note:** Version bump only for package @xml-tools/common
9 |
10 | ## [0.1.5](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.1.4...@xml-tools/common@0.1.5) (2021-02-09)
11 |
12 | **Note:** Version bump only for package @xml-tools/common
13 |
14 | ## [0.1.4](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.1.3...@xml-tools/common@0.1.4) (2021-01-20)
15 |
16 | ### Bug Fixes
17 |
18 | - missing lib folder in npm packages ([ffed3c2](https://github.com/sap/xml-tools/commit/ffed3c2c54c70aea8b9ded0d53786382bc190cc5))
19 |
20 | ## [0.1.3](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.1.2...@xml-tools/common@0.1.3) (2021-01-20)
21 |
22 | **Note:** Version bump only for package @xml-tools/common
23 |
24 | ## [0.1.2](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.1.1...@xml-tools/common@0.1.2) (2020-06-29)
25 |
26 | **Note:** Version bump only for package @xml-tools/common
27 |
28 | ## [0.1.1](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.1.0...@xml-tools/common@0.1.1) (2020-06-18)
29 |
30 | **Note:** Version bump only for package @xml-tools/common
31 |
32 | # [0.1.0](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.0.6...@xml-tools/common@0.1.0) (2020-05-17)
33 |
34 | ### Features
35 |
36 | - **common:** add utility functions for xmlns attributes ([#173](https://github.com/sap/xml-tools/issues/173)) ([20d6c09](https://github.com/sap/xml-tools/commit/20d6c09))
37 |
38 | ## [0.0.6](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.0.5...@xml-tools/common@0.0.6) (2020-04-02)
39 |
40 | **Note:** Version bump only for package @xml-tools/common
41 |
42 | ## [0.0.5](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.0.4...@xml-tools/common@0.0.5) (2020-02-19)
43 |
44 | ### Bug Fixes
45 |
46 | - add npmignore file to each package ([5bbf209](https://github.com/sap/xml-tools/commit/5bbf209))
47 |
48 | ## [0.0.4](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.0.3...@xml-tools/common@0.0.4) (2020-02-11)
49 |
50 | **Note:** Version bump only for package @xml-tools/common
51 |
52 | ## [0.0.3](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.0.2...@xml-tools/common@0.0.3) (2020-01-28)
53 |
54 | **Note:** Version bump only for package @xml-tools/common
55 |
56 | ## [0.0.2](https://github.com/sap/xml-tools/compare/@xml-tools/common@0.0.1...@xml-tools/common@0.0.2) (2020-01-20)
57 |
58 | **Note:** Version bump only for package @xml-tools/common
59 |
60 | ## 0.0.1 (2019-12-05)
61 |
62 | **Note:** Version bump only for package @xml-tools/common
63 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "private": true,
4 | "workspaces": {
5 | "packages": [
6 | "packages/*"
7 | ],
8 | "nohoist": [
9 | "xml-toolkit/@xml-tools/language-server",
10 | "xml-toolkit/@xml-tools/language-server/**"
11 | ]
12 | },
13 | "scripts": {
14 | "release:version": "lerna version && yarn run release:trigger",
15 | "release:trigger": "node ./scripts/trigger-release",
16 | "release:publish": "lerna publish from-git --yes",
17 | "ci": "npm-run-all format:validate lint:validate ci:subpackages coverage:merge legal:*",
18 | "format:fix": "prettier --write --ignore-path .gitignore \"**/*.@(ts|js|json|md)\"",
19 | "format:validate": "prettier --check --ignore-path .gitignore \"**/*.@(ts|js|json|md)\"",
20 | "lint:fix": "eslint . --ext=js --fix --max-warnings=0 --ignore-path=.gitignore",
21 | "lint:validate": "eslint . --ext=js --max-warnings=0 --ignore-path=.gitignore",
22 | "ci:subpackages": "lerna run ci",
23 | "test": "lerna run test",
24 | "coverage": "lerna run coverage",
25 | "coverage:merge": "node ./scripts/merge-coverage",
26 | "clean": "lerna run clean",
27 | "legal:delete": "lerna exec \"shx rm -rf .reuse LICENSES\" || true",
28 | "legal:copy": "lerna exec \"shx cp -r ../../.reuse .reuse && shx cp -r ../../LICENSES LICENSES\""
29 | },
30 | "devDependencies": {
31 | "@types/chai": "4.2.14",
32 | "@types/mocha": "8.2.0",
33 | "@types/node": "14.14.19",
34 | "eslint": "7.27.0",
35 | "eslint-config-prettier": "7.2.0",
36 | "eslint-plugin-eslint-comments": "3.2.0",
37 | "coveralls": "3.1.0",
38 | "make-dir": "3.1.0",
39 | "glob": "7.1.6",
40 | "chai": "4.3.4",
41 | "husky": "4.3.8",
42 | "lerna": "4.0.0",
43 | "lint-staged": "11.0.0",
44 | "mocha": "7.2.0",
45 | "npm-run-all": "4.1.5",
46 | "nyc": "15.1.0",
47 | "prettier": "2.2.1",
48 | "typescript": "4.1.3",
49 | "cz-conventional-changelog": "3.3.0",
50 | "@commitlint/cli": "11.0.0",
51 | "@commitlint/config-conventional": "11.0.0",
52 | "rimraf": "3.0.2",
53 | "simple-git": "2.31.0",
54 | "fs-extra": "9.1.0",
55 | "webpack": "^5.61.0",
56 | "webpack-cli": "^4.9.1",
57 | "shx": "0.3.3"
58 | },
59 | "prettier": {
60 | "plugins": [],
61 | "pluginSearchDirs": []
62 | },
63 | "husky": {
64 | "hooks": {
65 | "pre-commit": "lint-staged",
66 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
67 | }
68 | },
69 | "lint-staged": {
70 | "*.{ts,js,md,json}": [
71 | "prettier --write"
72 | ],
73 | "*.{js}": [
74 | "eslint --fix --max-warnings=0 --ignore-pattern=!.*"
75 | ]
76 | },
77 | "config": {
78 | "commitizen": {
79 | "path": "./node_modules/cz-conventional-changelog"
80 | }
81 | },
82 | "commitlint": {
83 | "extends": [
84 | "@commitlint/config-conventional"
85 | ]
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/packages/content-assist/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/@xml-tools/content-assist)
2 |
3 | # @xml-tools/content-assist
4 |
5 | APIs which assist in implementing content assist (auto-complete) logic for XML.
6 | This package handles the identification of the specific content assist **scenario and context**.
7 | Given this scenario & context information a user of this package can then implement
8 | their custom content assist logic.
9 |
10 | For example:
11 |
12 | Given the following XML and a content assist request at the `⇶` sign:
13 |
14 | ```xml
15 |
16 |
17 |
18 |
19 |
20 | ```
21 |
22 | A **Attribute Value** content assist scenario would be identified.
23 | With a **prefix** `New` and references to the ASTNodes
24 | of the relevant attribute(city) and the containing element (address).
25 |
26 | - Note that an ASTNode possesses a `parent` property, so the full structure is accessible.
27 |
28 | ## Installation
29 |
30 | With npm:
31 |
32 | - `npm install @xml-tools/content-assist`
33 |
34 | With Yarn
35 |
36 | - `yarn add @xml-tools/content-assist`
37 |
38 | ## Usage
39 |
40 | Please see the [TypeScript Definitions](./api.d.ts) for full API details.
41 |
42 | A simple usage example:
43 |
44 | ```javascript
45 | const { parse } = require("@xml-tools/parser");
46 | const { buildAst } = require("@xml-tools/ast");
47 | const { getSuggestions } = require("@xml-tools/content-assist");
48 |
49 | const xmlText = `
50 | Bill
51 | Bill
53 |
54 | `;
55 |
56 | // A-lot of contextual information is needed to compute the content assist context.
57 | const { cst, tokenVector } = parse(xmlText);
58 | const xmlDocAst = buildAst(cst, tokenVector);
59 |
60 | const suggestions = getSuggestions({
61 | ast: xmlDocAst,
62 | cst: cst,
63 | tokVector: tokenVector,
64 | offset: 66, // Right after the ' {
70 | const suggestions = [];
71 | if (element.parent.name === "note" && "address".startsWith(prefix)) {
72 | suggestions.push({
73 | text: "address".slice(prefix.length),
74 | label: "address",
75 | });
76 | }
77 | return suggestions;
78 | },
79 | ],
80 | },
81 | });
82 |
83 | console.log(suggestions[0].text); // -> dress
84 | ```
85 |
86 | ## Support
87 |
88 | Please open [issues](https://github.com/SAP/xml-tols/issues) on github.
89 |
90 | ## Contributing
91 |
92 | See [CONTRIBUTING.md](./CONTRIBUTING.md).
93 |
94 | [ast]: https://en.wikipedia.org/wiki/Abstract_syntax_tree
95 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/get-validators.js:
--------------------------------------------------------------------------------
1 | const { validateAttributeValue } = require("./validators/attribute-value");
2 |
3 | const {
4 | validateDuplicateSubElements,
5 | } = require("./validators/duplicate-sub-elements");
6 |
7 | const {
8 | validateRequiredAttributes,
9 | } = require("./validators/required-attributes");
10 |
11 | const {
12 | validateRequiredSubElements,
13 | } = require("./validators/required-sub-elements");
14 |
15 | const {
16 | validateUnknownAttributes,
17 | } = require("./validators/unknown-attributes");
18 |
19 | const {
20 | validateUnknownSubElements,
21 | } = require("./validators/unknown-sub-elements");
22 |
23 | const { findAttributeXssDef, findElementXssDef } = require("./path");
24 |
25 | function getSchemaValidators(schema) {
26 | const attributeValidator = buildAttributeValidator(schema);
27 | const elementValidator = buildElementValidator(schema);
28 |
29 | return {
30 | attribute: attributeValidator,
31 | element: elementValidator,
32 | };
33 | }
34 |
35 | /**
36 | * @param {SimpleSchema} schema
37 | */
38 | function buildAttributeValidator(schema) {
39 | return (attributeNode) => {
40 | let issues = [];
41 | const xssAttributeDef = findAttributeXssDef(attributeNode, schema);
42 |
43 | if (xssAttributeDef !== undefined) {
44 | const attributeValueIssues = validateAttributeValue(
45 | attributeNode,
46 | xssAttributeDef
47 | );
48 | issues = issues.concat(attributeValueIssues);
49 | }
50 |
51 | return issues;
52 | };
53 | }
54 |
55 | /**
56 | * @param {SimpleSchema} schema
57 | */
58 | function buildElementValidator(schema) {
59 | return (elementNode) => {
60 | let issues = [];
61 | const xssElementDef = findElementXssDef(elementNode, schema);
62 |
63 | if (xssElementDef !== undefined) {
64 | const duplicateElementsIssues = validateDuplicateSubElements(
65 | elementNode,
66 | xssElementDef
67 | );
68 | const requiredAttributesIssues = validateRequiredAttributes(
69 | elementNode,
70 | xssElementDef
71 | );
72 | const requiredSubElementsIssues = validateRequiredSubElements(
73 | elementNode,
74 | xssElementDef
75 | );
76 | const unknownAttributesIssues = validateUnknownAttributes(
77 | elementNode,
78 | xssElementDef
79 | );
80 | const unknownSubElementsIssues = validateUnknownSubElements(
81 | elementNode,
82 | xssElementDef
83 | );
84 | issues = issues.concat(
85 | duplicateElementsIssues,
86 | requiredAttributesIssues,
87 | requiredSubElementsIssues,
88 | unknownAttributesIssues,
89 | unknownSubElementsIssues
90 | );
91 | }
92 |
93 | return issues;
94 | };
95 | }
96 |
97 | module.exports = {
98 | getSchemaValidators: getSchemaValidators,
99 | };
100 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/elements/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [],
9 | subElements: [
10 | {
11 | type: "XMLElement",
12 | namespaces: {},
13 | name: "to",
14 | attributes: [],
15 | subElements: [],
16 | textContents: [
17 | {
18 | type: "XMLTextContent",
19 | position: { startOffset: 15, endOffset: 18 },
20 | text: "Bill",
21 | },
22 | ],
23 | position: { startOffset: 11, endOffset: 23 },
24 | syntax: {
25 | openName: { image: "to", startOffset: 12, endOffset: 13 },
26 | closeName: { image: "to", startOffset: 21, endOffset: 22 },
27 | isSelfClosing: false,
28 | openBody: { startOffset: 11, endOffset: 14 },
29 | closeBody: { startOffset: 19, endOffset: 23 },
30 | attributesRange: { startOffset: 15, endOffset: 13 },
31 | },
32 | },
33 | {
34 | type: "XMLElement",
35 | namespaces: {},
36 | name: "from",
37 | attributes: [],
38 | subElements: [],
39 | textContents: [
40 | {
41 | type: "XMLTextContent",
42 | position: { startOffset: 35, endOffset: 37 },
43 | text: "Tim",
44 | },
45 | ],
46 | position: { startOffset: 29, endOffset: 44 },
47 | syntax: {
48 | openName: { image: "from", startOffset: 30, endOffset: 33 },
49 | closeName: { image: "from", startOffset: 40, endOffset: 43 },
50 | isSelfClosing: false,
51 | openBody: { startOffset: 29, endOffset: 34 },
52 | closeBody: { startOffset: 38, endOffset: 44 },
53 | attributesRange: { startOffset: 35, endOffset: 33 },
54 | },
55 | },
56 | ],
57 | textContents: [
58 | {
59 | type: "XMLTextContent",
60 | position: { startOffset: 6, endOffset: 10 },
61 | text: "\n ",
62 | },
63 | {
64 | type: "XMLTextContent",
65 | position: { startOffset: 24, endOffset: 28 },
66 | text: "\n ",
67 | },
68 | {
69 | type: "XMLTextContent",
70 | position: { startOffset: 45, endOffset: 45 },
71 | text: "\n",
72 | },
73 | ],
74 | position: { startOffset: 0, endOffset: 52 },
75 | syntax: {
76 | openName: { image: "note", startOffset: 1, endOffset: 4 },
77 | closeName: { image: "note", startOffset: 48, endOffset: 51 },
78 | isSelfClosing: false,
79 | openBody: { startOffset: 0, endOffset: 5 },
80 | closeBody: { startOffset: 46, endOffset: 52 },
81 | attributesRange: { startOffset: 6, endOffset: 4 },
82 | },
83 | },
84 | position: { startOffset: 0, endOffset: 53 },
85 | },
86 | };
87 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/elements-short-form/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [],
9 | subElements: [
10 | {
11 | type: "XMLElement",
12 | namespaces: {},
13 | name: "to",
14 | attributes: [],
15 | subElements: [],
16 | textContents: [
17 | {
18 | type: "XMLTextContent",
19 | position: { startOffset: 15, endOffset: 18 },
20 | text: "Bill",
21 | },
22 | ],
23 | position: { startOffset: 11, endOffset: 23 },
24 | syntax: {
25 | openName: { image: "to", startOffset: 12, endOffset: 13 },
26 | closeName: { image: "to", startOffset: 21, endOffset: 22 },
27 | isSelfClosing: false,
28 | openBody: { startOffset: 11, endOffset: 14 },
29 | closeBody: { startOffset: 19, endOffset: 23 },
30 | attributesRange: { startOffset: 15, endOffset: 13 },
31 | },
32 | },
33 | {
34 | type: "XMLElement",
35 | namespaces: {},
36 | name: "from",
37 | attributes: [
38 | {
39 | type: "XMLAttribute",
40 | position: { startOffset: 35, endOffset: 44 },
41 | key: "name",
42 | value: "tim",
43 | syntax: {
44 | key: { image: "name", startOffset: 35, endOffset: 38 },
45 | value: { image: '"tim"', startOffset: 40, endOffset: 44 },
46 | },
47 | },
48 | ],
49 | subElements: [],
50 | textContents: [],
51 | position: { startOffset: 29, endOffset: 46 },
52 | syntax: {
53 | openName: { image: "from", startOffset: 30, endOffset: 33 },
54 | isSelfClosing: true,
55 | openBody: { startOffset: 29, endOffset: 46 },
56 | attributesRange: { startOffset: 35, endOffset: 44 },
57 | },
58 | },
59 | ],
60 | textContents: [
61 | {
62 | type: "XMLTextContent",
63 | position: { startOffset: 6, endOffset: 10 },
64 | text: "\n ",
65 | },
66 | {
67 | type: "XMLTextContent",
68 | position: { startOffset: 24, endOffset: 28 },
69 | text: "\n ",
70 | },
71 | {
72 | type: "XMLTextContent",
73 | position: { startOffset: 47, endOffset: 47 },
74 | text: "\n",
75 | },
76 | ],
77 | position: { startOffset: 0, endOffset: 54 },
78 | syntax: {
79 | openName: { image: "note", startOffset: 1, endOffset: 4 },
80 | closeName: { image: "note", startOffset: 50, endOffset: 53 },
81 | isSelfClosing: false,
82 | openBody: { startOffset: 0, endOffset: 5 },
83 | closeBody: { startOffset: 48, endOffset: 54 },
84 | attributesRange: { startOffset: 6, endOffset: 4 },
85 | },
86 | },
87 | position: { startOffset: 0, endOffset: 55 },
88 | },
89 | };
90 |
--------------------------------------------------------------------------------
/packages/ast/test/utils.js:
--------------------------------------------------------------------------------
1 | const { forEach } = require("lodash");
2 | const { expect } = require("chai");
3 |
4 | const { accept } = require("../");
5 |
6 | function modifyAstForAssertions(astNode) {
7 | // Avoid comparing cyclic structures
8 | accept(astNode, parentRemoverVisitor);
9 | // Reduce verbosity of assertions
10 | accept(astNode, positionReducerVisitor);
11 | }
12 |
13 | /**
14 | * @type {XMLAstVisitor}
15 | */
16 | const parentRemoverVisitor = {
17 | /* eslint-disable-next-line no-unused-vars -- consistent signatures in visitor methods even if they are empty placeholders */
18 | visitXMLDocument: (node) => {
19 | // top level XML Doc does not have a parent...
20 | },
21 | visitXMLProlog: (node) => {
22 | delete node.parent;
23 | },
24 | visitXMLPrologAttribute: (node) => {
25 | delete node.parent;
26 | },
27 | visitXMLElement: (node) => {
28 | delete node.parent;
29 | },
30 | visitXMLAttribute: (node) => {
31 | delete node.parent;
32 | },
33 | visitXMLTextContent: (node) => {
34 | delete node.parent;
35 | },
36 | };
37 |
38 | /**
39 | * @type {XMLAstVisitor}
40 | */
41 | const positionReducerVisitor = {
42 | visitXMLDocument: (node) => {
43 | reduceNodePoseInfo(node);
44 | },
45 | visitXMLProlog: (node) => {
46 | reduceNodePoseInfo(node);
47 | },
48 | visitXMLPrologAttribute: (node) => {
49 | reduceNodePoseInfo(node);
50 | },
51 | visitXMLElement: (node) => {
52 | reduceNodePoseInfo(node);
53 | },
54 | visitXMLAttribute: (node) => {
55 | reduceNodePoseInfo(node);
56 | },
57 | visitXMLTextContent: (node) => {
58 | reduceNodePoseInfo(node);
59 | },
60 | };
61 |
62 | function reduceNodePoseInfo(node) {
63 | reducePositionInfo(node.position);
64 |
65 | forEach(node.syntax, (tok) => {
66 | reducePositionInfo(tok);
67 | });
68 | }
69 |
70 | function reducePositionInfo(pos) {
71 | delete pos.startColumn;
72 | delete pos.endColumn;
73 | delete pos.startLine;
74 | delete pos.endLine;
75 | }
76 |
77 | function assertParentPropsAreValid(astNode) {
78 | accept(astNode, parentPropsValidatorVisitor);
79 | }
80 |
81 | /**
82 | * @type {XMLAstVisitor}
83 | */
84 | const parentPropsValidatorVisitor = {
85 | /* eslint-disable-next-line no-unused-vars -- consistent signatures in visitor methods even if they are empty placeholders */
86 | visitXMLDocument: (node) => {
87 | // top level XML Doc does not have a parent...
88 | },
89 | visitXMLProlog: (node) => {
90 | expect(node.parent.prolog).to.eql(node);
91 | },
92 | visitXMLElement: (node) => {
93 | const parent = node.parent;
94 | if (parent.type === "XMLDocument") {
95 | expect(parent.rootElement).to.eql(node);
96 | } else {
97 | expect(parent.subElements).to.include(node);
98 | }
99 | },
100 | visitXMLAttribute: (node) => {
101 | expect(node.parent.attributes).to.include(node);
102 | },
103 | visitXMLTextContent: (node) => {
104 | expect(node.parent.textContents).to.include(node);
105 | },
106 | };
107 |
108 | module.exports = {
109 | modifyAstForAssertions: modifyAstForAssertions,
110 | positionReducerVisitor: positionReducerVisitor,
111 | assertParentPropsAreValid: assertParentPropsAreValid,
112 | };
113 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-empty-last/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [],
9 | subElements: [
10 | {
11 | type: "XMLElement",
12 | namespaces: {},
13 | name: "to",
14 | attributes: [],
15 | subElements: [],
16 | textContents: [
17 | {
18 | type: "XMLTextContent",
19 | position: { startOffset: 15, endOffset: 18 },
20 | text: "bobi",
21 | },
22 | ],
23 | position: { startOffset: 11, endOffset: 23 },
24 | syntax: {
25 | openName: { image: "to", startOffset: 12, endOffset: 13 },
26 | closeName: { image: "to", startOffset: 21, endOffset: 22 },
27 | isSelfClosing: false,
28 | openBody: { startOffset: 11, endOffset: 14 },
29 | closeBody: { startOffset: 19, endOffset: 23 },
30 | attributesRange: { startOffset: 15, endOffset: 13 },
31 | },
32 | },
33 | {
34 | type: "XMLElement",
35 | namespaces: {},
36 | name: "from",
37 | attributes: [],
38 | subElements: [],
39 | textContents: [
40 | {
41 | type: "XMLTextContent",
42 | position: { startOffset: 35, endOffset: 38 },
43 | text: "john",
44 | },
45 | ],
46 | position: { startOffset: 29, endOffset: 45 },
47 | syntax: {
48 | openName: { image: "from", startOffset: 30, endOffset: 33 },
49 | closeName: { image: "from", startOffset: 41, endOffset: 44 },
50 | isSelfClosing: false,
51 | openBody: { startOffset: 29, endOffset: 34 },
52 | closeBody: { startOffset: 39, endOffset: 45 },
53 | attributesRange: { startOffset: 35, endOffset: 33 },
54 | },
55 | },
56 | {
57 | type: "XMLElement",
58 | namespaces: {},
59 | name: null,
60 | attributes: [],
61 | subElements: [],
62 | textContents: [],
63 | position: { startOffset: 51, endOffset: 51 },
64 | syntax: {},
65 | },
66 | ],
67 | textContents: [
68 | {
69 | type: "XMLTextContent",
70 | position: { startOffset: 6, endOffset: 10 },
71 | text: "\n ",
72 | },
73 | {
74 | type: "XMLTextContent",
75 | position: { startOffset: 24, endOffset: 28 },
76 | text: "\n ",
77 | },
78 | {
79 | type: "XMLTextContent",
80 | position: { startOffset: 46, endOffset: 50 },
81 | text: "\n ",
82 | },
83 | ],
84 | position: { startOffset: 0, endOffset: 59 },
85 | syntax: {
86 | openName: { image: "note", startOffset: 1, endOffset: 4 },
87 | closeName: { image: "note", startOffset: 55, endOffset: 58 },
88 | isSelfClosing: false,
89 | openBody: { startOffset: 0, endOffset: 5 },
90 | closeBody: { startOffset: 53, endOffset: 59 },
91 | attributesRange: { startOffset: 6, endOffset: 4 },
92 | },
93 | },
94 | position: { startOffset: 0, endOffset: 60 },
95 | },
96 | };
97 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-empty-middle/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [],
9 | subElements: [
10 | {
11 | type: "XMLElement",
12 | namespaces: {},
13 | name: "to",
14 | attributes: [],
15 | subElements: [],
16 | textContents: [
17 | {
18 | type: "XMLTextContent",
19 | position: { startOffset: 15, endOffset: 18 },
20 | text: "bobi",
21 | },
22 | ],
23 | position: { startOffset: 11, endOffset: 23 },
24 | syntax: {
25 | openName: { image: "to", startOffset: 12, endOffset: 13 },
26 | closeName: { image: "to", startOffset: 21, endOffset: 22 },
27 | isSelfClosing: false,
28 | openBody: { startOffset: 11, endOffset: 14 },
29 | closeBody: { startOffset: 19, endOffset: 23 },
30 | attributesRange: { startOffset: 15, endOffset: 13 },
31 | },
32 | },
33 | {
34 | type: "XMLElement",
35 | namespaces: {},
36 | name: null,
37 | attributes: [],
38 | subElements: [],
39 | textContents: [],
40 | position: { startOffset: 29, endOffset: 29 },
41 | syntax: {},
42 | },
43 | {
44 | type: "XMLElement",
45 | namespaces: {},
46 | name: "from",
47 | attributes: [],
48 | subElements: [],
49 | textContents: [
50 | {
51 | type: "XMLTextContent",
52 | position: { startOffset: 41, endOffset: 44 },
53 | text: "john",
54 | },
55 | ],
56 | position: { startOffset: 35, endOffset: 51 },
57 | syntax: {
58 | openName: { image: "from", startOffset: 36, endOffset: 39 },
59 | closeName: { image: "from", startOffset: 47, endOffset: 50 },
60 | isSelfClosing: false,
61 | openBody: { startOffset: 35, endOffset: 40 },
62 | closeBody: { startOffset: 45, endOffset: 51 },
63 | attributesRange: { startOffset: 41, endOffset: 39 },
64 | },
65 | },
66 | ],
67 | textContents: [
68 | {
69 | type: "XMLTextContent",
70 | position: { startOffset: 6, endOffset: 10 },
71 | text: "\n ",
72 | },
73 | {
74 | type: "XMLTextContent",
75 | position: { startOffset: 24, endOffset: 28 },
76 | text: "\n ",
77 | },
78 | {
79 | type: "XMLTextContent",
80 | position: { startOffset: 52, endOffset: 52 },
81 | text: "\n",
82 | },
83 | ],
84 | position: { startOffset: 0, endOffset: 59 },
85 | syntax: {
86 | openName: { image: "note", startOffset: 1, endOffset: 4 },
87 | closeName: { image: "note", startOffset: 55, endOffset: 58 },
88 | isSelfClosing: false,
89 | openBody: { startOffset: 0, endOffset: 5 },
90 | closeBody: { startOffset: 53, endOffset: 59 },
91 | attributesRange: { startOffset: 6, endOffset: 4 },
92 | },
93 | },
94 | position: { startOffset: 0, endOffset: 60 },
95 | },
96 | };
97 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-empty-middle-comment/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [],
9 | subElements: [
10 | {
11 | type: "XMLElement",
12 | namespaces: {},
13 | name: "to",
14 | attributes: [],
15 | subElements: [],
16 | textContents: [
17 | {
18 | type: "XMLTextContent",
19 | position: { startOffset: 15, endOffset: 18 },
20 | text: "bobi",
21 | },
22 | ],
23 | position: { startOffset: 11, endOffset: 23 },
24 | syntax: {
25 | openName: { image: "to", startOffset: 12, endOffset: 13 },
26 | closeName: { image: "to", startOffset: 21, endOffset: 22 },
27 | isSelfClosing: false,
28 | openBody: { startOffset: 11, endOffset: 14 },
29 | closeBody: { startOffset: 19, endOffset: 23 },
30 | attributesRange: { startOffset: 15, endOffset: 13 },
31 | },
32 | },
33 | {
34 | type: "XMLElement",
35 | namespaces: {},
36 | name: null,
37 | attributes: [],
38 | subElements: [],
39 | textContents: [],
40 | position: { startOffset: 29, endOffset: 29 },
41 | syntax: {},
42 | },
43 | {
44 | type: "XMLElement",
45 | namespaces: {},
46 | name: "from",
47 | attributes: [],
48 | subElements: [],
49 | textContents: [
50 | {
51 | type: "XMLTextContent",
52 | position: { startOffset: 70, endOffset: 73 },
53 | text: "john",
54 | },
55 | ],
56 | position: { startOffset: 64, endOffset: 80 },
57 | syntax: {
58 | openName: { image: "from", startOffset: 65, endOffset: 68 },
59 | closeName: { image: "from", startOffset: 76, endOffset: 79 },
60 | isSelfClosing: false,
61 | openBody: { startOffset: 64, endOffset: 69 },
62 | closeBody: { startOffset: 74, endOffset: 80 },
63 | attributesRange: { startOffset: 70, endOffset: 68 },
64 | },
65 | },
66 | ],
67 | textContents: [
68 | {
69 | type: "XMLTextContent",
70 | position: { startOffset: 6, endOffset: 10 },
71 | text: "\n ",
72 | },
73 | {
74 | type: "XMLTextContent",
75 | position: { startOffset: 24, endOffset: 28 },
76 | text: "\n ",
77 | },
78 | {
79 | type: "XMLTextContent",
80 | position: { startOffset: 81, endOffset: 81 },
81 | text: "\n",
82 | },
83 | ],
84 | position: { startOffset: 0, endOffset: 88 },
85 | syntax: {
86 | openName: { image: "note", startOffset: 1, endOffset: 4 },
87 | closeName: { image: "note", startOffset: 84, endOffset: 87 },
88 | isSelfClosing: false,
89 | openBody: { startOffset: 0, endOffset: 5 },
90 | closeBody: { startOffset: 82, endOffset: 88 },
91 | attributesRange: { startOffset: 6, endOffset: 4 },
92 | },
93 | },
94 | position: { startOffset: 0, endOffset: 89 },
95 | },
96 | };
97 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | jobs:
3 | build-node18:
4 | docker:
5 | - image: cimg/node:18.20-browsers
6 | working_directory: ~/repo
7 | steps:
8 | - run: sudo apt-get install libxss1
9 | - checkout
10 | - run: yarn
11 | - run: yarn run ci
12 | - run:
13 | command: cat ./coverage/lcov.info | ./node_modules/.bin/coveralls
14 | environment:
15 | COVERALLS_SERVICE_NAME: circle-ci
16 | COVERALLS_GIT_BRANCH: ${CIRCLE_BRANCH}
17 |
18 | build-node20:
19 | docker:
20 | - image: cimg/node:20.11-browsers
21 | working_directory: ~/repo
22 | steps:
23 | - run: sudo apt-get install libxss1
24 | - checkout
25 | - run: yarn
26 | - run: yarn run ci
27 |
28 | compliance:
29 | docker:
30 | - image: cimg/python:3.10
31 | working_directory: ~/workdir
32 | steps:
33 | - checkout
34 | - run:
35 | name: upgrade pip
36 | command: python -m pip install --upgrade pip
37 | - run:
38 | name: compliance check
39 | command: |
40 | pip install --user reuse
41 | ~/.local/bin/reuse lint
42 |
43 | deploy-npm:
44 | docker:
45 | - image: cimg/node:18.20-browsers
46 | working_directory: ~/repo
47 | steps:
48 | - run: sudo apt-get install libxss1
49 | - checkout
50 | - run: yarn --pure-lockfile
51 | - run: yarn run ci
52 | - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
53 | # To help debug when lerna detects changes to the working tree and fails the publish
54 | - run: git status
55 | - run: yarn run release:publish --no-verify-access
56 |
57 | prepare-vsix:
58 | docker:
59 | - image: cimg/node:18.20-browsers
60 | working_directory: ~/repo
61 | steps:
62 | - run: sudo apt-get install libxss1
63 | - checkout
64 | - run: yarn
65 | - run: yarn run ci
66 | - persist_to_workspace:
67 | root: "./packages/xml-toolkit"
68 | paths:
69 | # https://golang.org/pkg/path/filepath/#Match
70 | - xml-toolkit-*
71 |
72 | deploy-gh-releases:
73 | docker:
74 | - image: cimg/go:1.21
75 | steps:
76 | - attach_workspace:
77 | at: ./dist
78 | - run:
79 | name: "Publish Release on GitHub"
80 | command: |
81 | export GO111MODULE=on
82 | go install github.com/tcnksm/ghr@latest
83 | VERSION=${CIRCLE_TAG}
84 | ~/go/bin/ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION} ./dist/
85 |
86 | workflows:
87 | version: 2
88 | voter:
89 | jobs:
90 | - build-node18
91 | - build-node20
92 | - compliance
93 | release:
94 | jobs:
95 | - deploy-npm:
96 | filters:
97 | tags:
98 | only: /^RELEASE/
99 | branches:
100 | ignore: /.*/
101 | - prepare-vsix:
102 | filters:
103 | tags:
104 | only: /^xml-toolkit@[0-9]+(.[0-9]+)*/
105 | branches:
106 | ignore: /.*/
107 | - deploy-gh-releases:
108 | requires:
109 | - prepare-vsix
110 | filters:
111 | tags:
112 | only: /^xml-toolkit@[0-9]+(.[0-9]+)*/
113 | branches:
114 | ignore: /.*/
115 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/complex-contents/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | cst: {
3 | name: "document",
4 | children: {
5 | prolog: [
6 | {
7 | name: "prolog",
8 | children: {
9 | XMLDeclOpen: [
10 | {
11 | image: "",
20 | startOffset: 6,
21 | endOffset: 7,
22 | tokenType: "SPECIAL_CLOSE",
23 | },
24 | ],
25 | },
26 | location: { startOffset: 0, endOffset: 7 },
27 | },
28 | ],
29 | misc: [
30 | {
31 | name: "misc",
32 | children: {
33 | SEA_WS: [
34 | {
35 | image: "\n",
36 | startOffset: 8,
37 | endOffset: 8,
38 | tokenType: "SEA_WS",
39 | },
40 | ],
41 | },
42 | location: { startOffset: 8, endOffset: 8 },
43 | },
44 | {
45 | name: "misc",
46 | children: {
47 | SEA_WS: [
48 | {
49 | image: "\n",
50 | startOffset: 22,
51 | endOffset: 22,
52 | tokenType: "SEA_WS",
53 | },
54 | ],
55 | },
56 | location: { startOffset: 22, endOffset: 22 },
57 | },
58 | ],
59 | element: [
60 | {
61 | name: "element",
62 | children: {
63 | OPEN: [
64 | { image: "<", startOffset: 9, endOffset: 9, tokenType: "OPEN" },
65 | ],
66 | Name: [
67 | {
68 | image: "note",
69 | startOffset: 10,
70 | endOffset: 13,
71 | tokenType: "Name",
72 | },
73 | ],
74 | START_CLOSE: [
75 | {
76 | image: ">",
77 | startOffset: 14,
78 | endOffset: 14,
79 | tokenType: "CLOSE",
80 | },
81 | ],
82 | content: [
83 | {
84 | name: "content",
85 | children: {},
86 | location: { startOffset: null, endOffset: null },
87 | },
88 | ],
89 | SLASH_OPEN: [
90 | {
91 | image: "",
92 | startOffset: 15,
93 | endOffset: 16,
94 | tokenType: "SLASH_OPEN",
95 | },
96 | ],
97 | END_NAME: [
98 | {
99 | image: "note",
100 | startOffset: 17,
101 | endOffset: 20,
102 | tokenType: "Name",
103 | },
104 | ],
105 | END: [
106 | {
107 | image: ">",
108 | startOffset: 21,
109 | endOffset: 21,
110 | tokenType: "CLOSE",
111 | },
112 | ],
113 | },
114 | location: { startOffset: 9, endOffset: 21 },
115 | },
116 | ],
117 | },
118 | location: { startOffset: 0, endOffset: 22 },
119 | },
120 | };
121 |
--------------------------------------------------------------------------------
/packages/simple-schema/lib/content-assist/element-name.js:
--------------------------------------------------------------------------------
1 | const { difference, map, filter, has, pickBy } = require("lodash");
2 | const { DEFAULT_NS } = require("@xml-tools/ast");
3 |
4 | // https://www.w3.org/TR/2009/REC-xml-names-20091208/#NT-PrefixedName
5 | const NAMESPACE_PATTERN = /^(?:([^:]*):)?([^:]*)$/;
6 | /**
7 | *
8 | * Note that the Element (XML/XSS) are of the parent node of the element
9 | * in which content assist was requested.
10 | *
11 | * @param {XMLElement} elementNode
12 | * @param {XSSElement} xssElement
13 | *
14 | * @returns {CompletionSuggestion[]}
15 | */
16 | function elementNameCompletion(elementNode, xssElement, prefix = "") {
17 | const match = prefix.match(NAMESPACE_PATTERN);
18 | if (match === null) {
19 | return [];
20 | }
21 | // If there is no prefix, use the default namespace prefix
22 | const namespacePrefix = match[1] ? match[1] : DEFAULT_NS;
23 | const elementNamespaceUri = elementNode.namespaces[namespacePrefix];
24 | const possibleElements = filter(
25 | xssElement.elements,
26 | (_) =>
27 | has(_, "namespace") === false ||
28 | (_.namespace && _.namespace === elementNamespaceUri)
29 | );
30 | const possibleSuggestionsWithoutExistingSingular = applicableElements(
31 | xssElement.elements,
32 | elementNode.subElements,
33 | possibleElements
34 | );
35 |
36 | const suggestions = map(possibleSuggestionsWithoutExistingSingular, (_) => {
37 | return {
38 | text: _,
39 | label: _,
40 | };
41 | });
42 |
43 | if (namespacePrefix === undefined || namespacePrefix === DEFAULT_NS) {
44 | // Can't really suggest anything for the `implicit` default namespace...
45 | const namespacesWithoutDefault = pickBy(
46 | elementNode.namespaces,
47 | (uri, prefix) => prefix !== DEFAULT_NS
48 | );
49 | const applicableNamespaces = pickBy(namespacesWithoutDefault, (uri) => {
50 | const possibleElements = filter(
51 | xssElement.elements,
52 | (element) =>
53 | has(element, "namespace") === true && element.namespace === uri
54 | );
55 | const possibleSuggestionsWithoutExistingSingular = applicableElements(
56 | xssElement.elements,
57 | elementNode.subElements,
58 | possibleElements
59 | );
60 | const namespaceHasApplicableElements =
61 | possibleSuggestionsWithoutExistingSingular.length > 0;
62 | return namespaceHasApplicableElements;
63 | });
64 | const namespaceSuggestions = map(applicableNamespaces, (uri, prefix) => ({
65 | text: prefix,
66 | label: prefix,
67 | commitCharacter: ":",
68 | isNamespace: true,
69 | }));
70 | return [...namespaceSuggestions, ...suggestions];
71 | }
72 | return suggestions;
73 | }
74 |
75 | function applicableElements(xssElements, subElements, possibleElements) {
76 | const allPossibleSuggestions = map(
77 | possibleElements,
78 | (element) => element.name
79 | );
80 | const notSingularElem = filter(
81 | xssElements,
82 | (element) => element.cardinality === "many"
83 | );
84 | const notSingularElemNames = map(notSingularElem, (element) => element.name);
85 | const existingElemNames = map(subElements, (element) => element.name);
86 | const existingSingular = difference(existingElemNames, notSingularElemNames);
87 | const possibleSuggestionsWithoutExistingSingular = difference(
88 | allPossibleSuggestions,
89 | existingSingular
90 | );
91 | return possibleSuggestionsWithoutExistingSingular;
92 | }
93 |
94 | module.exports = {
95 | elementNameCompletion: elementNameCompletion,
96 | };
97 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/invalid/element-name-last/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [],
9 | subElements: [
10 | {
11 | type: "XMLElement",
12 | namespaces: {},
13 | name: "to",
14 | attributes: [],
15 | subElements: [],
16 | textContents: [
17 | {
18 | type: "XMLTextContent",
19 | position: { startOffset: 15, endOffset: 18 },
20 | text: "bobi",
21 | },
22 | ],
23 | position: { startOffset: 11, endOffset: 23 },
24 | syntax: {
25 | openName: { image: "to", startOffset: 12, endOffset: 13 },
26 | closeName: { image: "to", startOffset: 21, endOffset: 22 },
27 | isSelfClosing: false,
28 | openBody: { startOffset: 11, endOffset: 14 },
29 | closeBody: { startOffset: 19, endOffset: 23 },
30 | attributesRange: { startOffset: 15, endOffset: 13 },
31 | },
32 | },
33 | {
34 | type: "XMLElement",
35 | namespaces: {},
36 | name: "from",
37 | attributes: [],
38 | subElements: [],
39 | textContents: [
40 | {
41 | type: "XMLTextContent",
42 | position: { startOffset: 35, endOffset: 38 },
43 | text: "john",
44 | },
45 | ],
46 | position: { startOffset: 29, endOffset: 45 },
47 | syntax: {
48 | openName: { image: "from", startOffset: 30, endOffset: 33 },
49 | closeName: { image: "from", startOffset: 41, endOffset: 44 },
50 | isSelfClosing: false,
51 | openBody: { startOffset: 29, endOffset: 34 },
52 | closeBody: { startOffset: 39, endOffset: 45 },
53 | attributesRange: { startOffset: 35, endOffset: 33 },
54 | },
55 | },
56 | {
57 | type: "XMLElement",
58 | namespaces: {},
59 | name: "ad",
60 | attributes: [],
61 | subElements: [],
62 | textContents: [],
63 | position: { startOffset: 51, endOffset: 53 },
64 | syntax: {
65 | openName: { image: "ad", startOffset: 52, endOffset: 53 },
66 | guessedAttributesRange: { startOffset: 55, endOffset: 55 },
67 | },
68 | },
69 | ],
70 | textContents: [
71 | {
72 | type: "XMLTextContent",
73 | position: { startOffset: 6, endOffset: 10 },
74 | text: "\n ",
75 | },
76 | {
77 | type: "XMLTextContent",
78 | position: { startOffset: 24, endOffset: 28 },
79 | text: "\n ",
80 | },
81 | {
82 | type: "XMLTextContent",
83 | position: { startOffset: 46, endOffset: 50 },
84 | text: "\n ",
85 | },
86 | ],
87 | position: { startOffset: 0, endOffset: 61 },
88 | syntax: {
89 | openName: { image: "note", startOffset: 1, endOffset: 4 },
90 | closeName: { image: "note", startOffset: 57, endOffset: 60 },
91 | isSelfClosing: false,
92 | openBody: { startOffset: 0, endOffset: 5 },
93 | closeBody: { startOffset: 55, endOffset: 61 },
94 | attributesRange: { startOffset: 6, endOffset: 4 },
95 | },
96 | },
97 | position: { startOffset: 0, endOffset: 62 },
98 | },
99 | };
100 |
--------------------------------------------------------------------------------
/packages/ast/test/snapshots/valid/prolog-no-attriibs/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ast: {
3 | type: "XMLDocument",
4 | rootElement: {
5 | type: "XMLElement",
6 | namespaces: {},
7 | name: "note",
8 | attributes: [],
9 | subElements: [
10 | {
11 | type: "XMLElement",
12 | namespaces: {},
13 | name: "to",
14 | attributes: [],
15 | subElements: [],
16 | textContents: [
17 | {
18 | type: "XMLTextContent",
19 | position: { startOffset: 39, endOffset: 42 },
20 | text: "Bill",
21 | },
22 | ],
23 | position: { startOffset: 35, endOffset: 47 },
24 | syntax: {
25 | openName: { image: "to", startOffset: 36, endOffset: 37 },
26 | closeName: { image: "to", startOffset: 45, endOffset: 46 },
27 | isSelfClosing: false,
28 | openBody: { startOffset: 35, endOffset: 38 },
29 | closeBody: { startOffset: 43, endOffset: 47 },
30 | attributesRange: { startOffset: 39, endOffset: 37 },
31 | },
32 | },
33 | {
34 | type: "XMLElement",
35 | namespaces: {},
36 | name: "from",
37 | attributes: [],
38 | subElements: [],
39 | textContents: [
40 | {
41 | type: "XMLTextContent",
42 | position: { startOffset: 69, endOffset: 71 },
43 | text: "Tim",
44 | },
45 | ],
46 | position: { startOffset: 63, endOffset: 78 },
47 | syntax: {
48 | openName: { image: "from", startOffset: 64, endOffset: 67 },
49 | closeName: { image: "from", startOffset: 74, endOffset: 77 },
50 | isSelfClosing: false,
51 | openBody: { startOffset: 63, endOffset: 68 },
52 | closeBody: { startOffset: 72, endOffset: 78 },
53 | attributesRange: { startOffset: 69, endOffset: 67 },
54 | },
55 | },
56 | ],
57 | textContents: [
58 | {
59 | type: "XMLTextContent",
60 | position: { startOffset: 6, endOffset: 10 },
61 | text: "\n ",
62 | },
63 | {
64 | type: "XMLTextContent",
65 | position: { startOffset: 30, endOffset: 34 },
66 | text: "\n ",
67 | },
68 | {
69 | type: "XMLTextContent",
70 | position: { startOffset: 48, endOffset: 52 },
71 | text: "\n ",
72 | },
73 | {
74 | type: "XMLTextContent",
75 | position: { startOffset: 53, endOffset: 62 },
76 | text: "hello\n ",
77 | },
78 | {
79 | type: "XMLTextContent",
80 | position: { startOffset: 79, endOffset: 83 },
81 | text: "\n ",
82 | },
83 | {
84 | type: "XMLTextContent",
85 | position: { startOffset: 84, endOffset: 89 },
86 | text: "world\n",
87 | },
88 | ],
89 | position: { startOffset: 0, endOffset: 96 },
90 | syntax: {
91 | openName: { image: "note", startOffset: 1, endOffset: 4 },
92 | closeName: { image: "note", startOffset: 92, endOffset: 95 },
93 | isSelfClosing: false,
94 | openBody: { startOffset: 0, endOffset: 5 },
95 | closeBody: { startOffset: 90, endOffset: 96 },
96 | attributesRange: { startOffset: 6, endOffset: 4 },
97 | },
98 | },
99 | position: { startOffset: 0, endOffset: 97 },
100 | },
101 | };
102 |
--------------------------------------------------------------------------------
/packages/language-server/lib/language-services.js:
--------------------------------------------------------------------------------
1 | const { DiagnosticSeverity, Range } = require("vscode-languageserver");
2 | const { map, forEach } = require("lodash");
3 | const { parse } = require("@xml-tools/parser");
4 | const { buildAst } = require("@xml-tools/ast");
5 | const { checkConstraints } = require("@xml-tools/constraints");
6 |
7 | /**
8 | * @param {TextDocument} document
9 | * @param {import("chevrotain").ILexingError} error
10 | * @returns {import("vscode-languageserver-types").Diagnostic}
11 | */
12 | function lexingErrorToDiagnostic(document, error) {
13 | return {
14 | message: error.message,
15 | range: Range.create(
16 | document.positionAt(error.offset),
17 | document.positionAt(error.offset + error.length)
18 | ),
19 | severity: DiagnosticSeverity.Error,
20 | };
21 | }
22 |
23 | /**
24 | * @param {TextDocument} document
25 | * @param {import("chevrotain").ILexingError} error
26 | * @returns {import("vscode-languageserver-types").Diagnostic}
27 | */
28 | function parsingErrorToDiagnostic(document, error) {
29 | return {
30 | message: error.message,
31 | range: {
32 | start: document.positionAt(error.token.startOffset),
33 | end: document.positionAt(
34 | error.token.endOffset ? error.token.endOffset : 0
35 | ),
36 | },
37 | severity: DiagnosticSeverity.Error,
38 | };
39 | }
40 |
41 | /**
42 | * @param {TextDocument} document
43 | * @param {import("@xml-tools/validation").ValidationIssue} issue
44 | * @returns {import("vscode-languageserver-types").Diagnostic}
45 | */
46 | function constraintIssueToDiagnostic(document, issue) {
47 | return {
48 | message: issue.msg,
49 | range: {
50 | start: document.positionAt(issue.position.startOffset),
51 | // Chevrotain Token positions are non-inclusive for endOffsets
52 | end: document.positionAt(issue.position.endOffset + 1),
53 | },
54 | severity: toDiagnosticSeverity(issue.severity),
55 | };
56 | }
57 |
58 | /**
59 | * @param {"info" | "warning" | "error"} issueSeverity
60 | * @returns {import("vscode-languageserver-types").DiagnosticSeverity}
61 | */
62 | function toDiagnosticSeverity(issueSeverity) {
63 | switch (issueSeverity) {
64 | case "error":
65 | return DiagnosticSeverity.Error;
66 | case "warning":
67 | return DiagnosticSeverity.Warning;
68 | case "info":
69 | return DiagnosticSeverity.Information;
70 | /* istanbul ignore next -- runtime check */
71 | default:
72 | throw Error("Non Exhaustive Match");
73 | }
74 | }
75 |
76 | /**
77 | * @param {TextDocument} document
78 | * @param {object} opts
79 | * @param {string} opts.consumer
80 | *
81 | * @returns {import("vscode-languageserver-types").Diagnostic[]}
82 | */
83 | async function validateDocument(document, opts) {
84 | let diagnostics = [];
85 | if (document.languageId === "xml") {
86 | const { cst, tokenVector, lexErrors, parseErrors } = parse(
87 | document.getText()
88 | );
89 | const ast = buildAst(cst, tokenVector);
90 | const constraintsIssues = checkConstraints(ast);
91 |
92 | diagnostics = [
93 | ...map(lexErrors, (_) => lexingErrorToDiagnostic(document, _)),
94 | ...map(parseErrors, (_) => parsingErrorToDiagnostic(document, _)),
95 | ...map(constraintsIssues, (_) =>
96 | constraintIssueToDiagnostic(document, _)
97 | ),
98 | ];
99 | }
100 |
101 | forEach(diagnostics, (_) => (_.source = opts.consumer));
102 |
103 | return diagnostics;
104 | }
105 |
106 | module.exports = {
107 | validateDocument: validateDocument,
108 | toDiagnosticSeverity: toDiagnosticSeverity,
109 | };
110 |
--------------------------------------------------------------------------------
/packages/constraints/test/constraints/tag-closing-name-match-spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require("chai");
2 | const { parse } = require("@xml-tools/parser");
3 | const { buildAst } = require("@xml-tools/ast");
4 | const {
5 | validateTagClosingNameMatch,
6 | } = require("../../lib/constraints/tag-closing-name-match");
7 |
8 | describe("tag closing name constraint", () => {
9 | context("no issues", () => {
10 | it("will not detect any issues for valid XML", () => {
11 | const xmlText = `
12 |
13 |
14 | Tim
15 | `;
16 |
17 | const { cst, tokenVector } = parse(xmlText);
18 | const document = buildAst(cst, tokenVector);
19 | const element = document.rootElement;
20 | const result = validateTagClosingNameMatch(element);
21 |
22 | expect(result).to.be.empty;
23 | });
24 | });
25 |
26 | context("unable to validate", () => {
27 | it("will not detect any issues when the opening tag name is missing", () => {
28 | const xmlText = `
29 | <>
30 | Bill
31 | Tim
32 | `;
33 |
34 | const { cst, tokenVector } = parse(xmlText);
35 | const document = buildAst(cst, tokenVector);
36 | const element = document.rootElement;
37 | const result = validateTagClosingNameMatch(element);
38 |
39 | expect(result).to.be.empty;
40 | });
41 |
42 | it("will not detect any issues when the closing tag name is missing", () => {
43 | const xmlText = `
44 |
45 | Bill
46 | Tim
47 | >`;
48 |
49 | const { cst, tokenVector } = parse(xmlText);
50 | const document = buildAst(cst, tokenVector);
51 | const element = document.rootElement;
52 | const result = validateTagClosingNameMatch(element);
53 |
54 | expect(result).to.be.empty;
55 | });
56 |
57 | it("will not detect any issues when both tag names are missing", () => {
58 | const xmlText = `
59 | <>
60 | Bill
61 | Tim
62 | >`;
63 |
64 | const { cst, tokenVector } = parse(xmlText);
65 | const document = buildAst(cst, tokenVector);
66 | const element = document.rootElement;
67 | const result = validateTagClosingNameMatch(element);
68 |
69 | expect(result).to.be.empty;
70 | });
71 | });
72 |
73 | context("positive constraint checks", () => {
74 | it("detects a mis-aligned closing tag", () => {
75 | const xmlText = `
76 |
77 | Bill
78 | Tim
79 | `;
80 |
81 | const { cst, tokenVector } = parse(xmlText);
82 | const document = buildAst(cst, tokenVector);
83 | const element = document.rootElement;
84 | const result = validateTagClosingNameMatch(element);
85 |
86 | expect(result).to.have.lengthOf(2);
87 | expect(result).to.deep.include.members([
88 | {
89 | msg: 'tags mismatch: "note" must match closing tag: "note-typo"',
90 | severity: "error",
91 | node: element,
92 | position: {
93 | startOffset: element.syntax.openName.startOffset,
94 | endOffset: element.syntax.openName.endOffset,
95 | },
96 | },
97 | {
98 | msg: 'tags mismatch: "note-typo" must match opening tag: "note"',
99 | severity: "error",
100 | node: element,
101 | position: {
102 | startOffset: element.syntax.closeName.startOffset,
103 | endOffset: element.syntax.closeName.endOffset,
104 | },
105 | },
106 | ]);
107 | });
108 | });
109 | });
110 |
--------------------------------------------------------------------------------
/packages/parser/test/snapshots/valid/cdata/output.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | cst: {
3 | name: "document",
4 | children: {
5 | element: [
6 | {
7 | name: "element",
8 | children: {
9 | OPEN: [
10 | { image: "<", startOffset: 0, endOffset: 0, tokenType: "OPEN" },
11 | ],
12 | Name: [
13 | {
14 | image: "note",
15 | startOffset: 1,
16 | endOffset: 4,
17 | tokenType: "Name",
18 | },
19 | ],
20 | START_CLOSE: [
21 | { image: ">", startOffset: 5, endOffset: 5, tokenType: "CLOSE" },
22 | ],
23 | content: [
24 | {
25 | name: "content",
26 | children: {
27 | chardata: [
28 | {
29 | name: "chardata",
30 | children: {
31 | SEA_WS: [
32 | {
33 | image: "\n ",
34 | startOffset: 6,
35 | endOffset: 10,
36 | tokenType: "SEA_WS",
37 | },
38 | ],
39 | },
40 | location: { startOffset: 6, endOffset: 10 },
41 | },
42 | {
43 | name: "chardata",
44 | children: {
45 | SEA_WS: [
46 | {
47 | image: "\n",
48 | startOffset: 57,
49 | endOffset: 57,
50 | tokenType: "SEA_WS",
51 | },
52 | ],
53 | },
54 | location: { startOffset: 57, endOffset: 57 },
55 | },
56 | ],
57 | CData: [
58 | {
59 | image: "Hello, world!]]>",
60 | startOffset: 11,
61 | endOffset: 56,
62 | tokenType: "CData",
63 | },
64 | ],
65 | },
66 | location: { startOffset: 6, endOffset: 57 },
67 | },
68 | ],
69 | SLASH_OPEN: [
70 | {
71 | image: "",
72 | startOffset: 58,
73 | endOffset: 59,
74 | tokenType: "SLASH_OPEN",
75 | },
76 | ],
77 | END_NAME: [
78 | {
79 | image: "note",
80 | startOffset: 60,
81 | endOffset: 63,
82 | tokenType: "Name",
83 | },
84 | ],
85 | END: [
86 | {
87 | image: ">",
88 | startOffset: 64,
89 | endOffset: 64,
90 | tokenType: "CLOSE",
91 | },
92 | ],
93 | },
94 | location: { startOffset: 0, endOffset: 64 },
95 | },
96 | ],
97 | misc: [
98 | {
99 | name: "misc",
100 | children: {
101 | SEA_WS: [
102 | {
103 | image: "\n",
104 | startOffset: 65,
105 | endOffset: 65,
106 | tokenType: "SEA_WS",
107 | },
108 | ],
109 | },
110 | location: { startOffset: 65, endOffset: 65 },
111 | },
112 | ],
113 | },
114 | location: { startOffset: 0, endOffset: 65 },
115 | },
116 | };
117 |
--------------------------------------------------------------------------------
/packages/language-server/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | ## [1.1.1](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.1.0...@xml-tools/language-server@1.1.1) (2021-06-03)
7 |
8 | **Note:** Version bump only for package @xml-tools/language-server
9 |
10 | # [1.1.0](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.12...@xml-tools/language-server@1.1.0) (2021-02-09)
11 |
12 | ### Bug Fixes
13 |
14 | - use extension name as diagnostic source ([#297](https://github.com/SAP/xml-tools/issues/297)) ([f6d7dc4](https://github.com/SAP/xml-tools/commit/f6d7dc42a05c4e1d7c9ab3f1fbe35ecb1eb820fa))
15 |
16 | ### Features
17 |
18 | - well formed constraints validations ([#293](https://github.com/SAP/xml-tools/issues/293)) ([8a9d535](https://github.com/SAP/xml-tools/commit/8a9d535e95a52b9dfc1068d2e203b09bd08e1066)), closes [#13](https://github.com/SAP/xml-tools/issues/13) [#14](https://github.com/SAP/xml-tools/issues/14)
19 |
20 | ## [1.0.12](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.11...@xml-tools/language-server@1.0.12) (2021-01-20)
21 |
22 | ### Bug Fixes
23 |
24 | - missing lib folder in npm packages ([ffed3c2](https://github.com/SAP/xml-tools/commit/ffed3c2c54c70aea8b9ded0d53786382bc190cc5))
25 |
26 | ## [1.0.11](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.10...@xml-tools/language-server@1.0.11) (2021-01-20)
27 |
28 | **Note:** Version bump only for package @xml-tools/language-server
29 |
30 | ## [1.0.10](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.9...@xml-tools/language-server@1.0.10) (2020-06-29)
31 |
32 | **Note:** Version bump only for package @xml-tools/language-server
33 |
34 | ## [1.0.9](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.8...@xml-tools/language-server@1.0.9) (2020-06-18)
35 |
36 | **Note:** Version bump only for package @xml-tools/language-server
37 |
38 | ## [1.0.8](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.7...@xml-tools/language-server@1.0.8) (2020-05-21)
39 |
40 | **Note:** Version bump only for package @xml-tools/language-server
41 |
42 | ## [1.0.7](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.6...@xml-tools/language-server@1.0.7) (2020-05-17)
43 |
44 | **Note:** Version bump only for package @xml-tools/language-server
45 |
46 | ## [1.0.6](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.5...@xml-tools/language-server@1.0.6) (2020-04-02)
47 |
48 | **Note:** Version bump only for package @xml-tools/language-server
49 |
50 | ## [1.0.5](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.4...@xml-tools/language-server@1.0.5) (2020-03-08)
51 |
52 | **Note:** Version bump only for package @xml-tools/language-server
53 |
54 | ## [1.0.4](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.2...@xml-tools/language-server@1.0.4) (2020-03-08)
55 |
56 | ### Bug Fixes
57 |
58 | - fix README.md ([472301d](https://github.com/SAP/xml-tools/commit/472301d))
59 |
60 | ## [1.0.3](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.2...@xml-tools/language-server@1.0.3) (2020-03-08)
61 |
62 | ### Bug Fixes
63 |
64 | - fix README.md ([472301d](https://github.com/SAP/xml-tools/commit/472301d))
65 |
66 | ## [1.0.2](https://github.com/SAP/xml-tools/compare/@xml-tools/language-server@1.0.1...@xml-tools/language-server@1.0.2) (2020-02-19)
67 |
68 | ### Bug Fixes
69 |
70 | - add npmignore file to each package ([5bbf209](https://github.com/SAP/xml-tools/commit/5bbf209))
71 | - missing import ([2a51ab9](https://github.com/SAP/xml-tools/commit/2a51ab9))
72 |
73 | ## 1.0.1 (2020-02-19)
74 |
75 | **Note:** Version bump only for package @xml-tools/language-server
76 |
--------------------------------------------------------------------------------
/packages/simple-schema/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/@xml-tools/simple-schema)
2 |
3 | # @xml-tools/simple-schema
4 |
5 | This package includes three elements:
6 |
7 | - Data structure definition for a basic XML schema represented as a JavaScript object literal.
8 | - Validation logic to inspect a given XML conforms to a Schema.
9 | - Content Assist logic that provides suggestions using the Schema information.
10 |
11 | ## Installation
12 |
13 | With npm:
14 |
15 | - `npm install @xml-tools/simple-schema`
16 |
17 | With Yarn
18 |
19 | - `yarn add @xml-tools/simple-schema`
20 |
21 | ## Usage
22 |
23 | Please see the [TypeScript Definitions](./api.d.ts) for full API details.
24 |
25 | - Defining a Schema:
26 | ```javascript
27 | const schema = {
28 | name: "people",
29 | required: true,
30 | cardinality: "single",
31 | attributes: {},
32 | elements: {
33 | person: {
34 | name: "person",
35 | required: false,
36 | cardinality: "many",
37 | attributes: {
38 | eyeColor: {
39 | key: "eyeColor",
40 | required: false,
41 | value: ["grey", "blue", "green", "red"],
42 | },
43 | },
44 | elements: {
45 | name: {
46 | cardinality: "single",
47 | required: true,
48 | name: "name",
49 | attributes: {},
50 | elements: {},
51 | },
52 | },
53 | },
54 | },
55 | };
56 | ```
57 | - A Simple XML Text
58 | ```javascript
59 | // A Sample XML Text
60 | const xmlText = `
61 |
62 | Daenerys Targaryen
63 |
64 | `;
65 | ```
66 | - Performing Validations
67 |
68 | ```javascript
69 | const { parse } = require("@xml-tools/parser");
70 | const { buildAst } = require("@xml-tools/ast");
71 | const { validate } = require("@xml-tools/validation");
72 | const { getSchemaValidators } = require("@xml-tools/simple-schema");
73 |
74 | const { cst, tokenVector } = parse(xmlText);
75 | const xmlDoc = buildAst(cst, tokenVector);
76 | const schemaValidators = getSchemaValidators(schema);
77 | const issues = validate({
78 | doc: xmlDoc,
79 | validators: {
80 | attribute: [schemaValidators.attribute],
81 | element: [schemaValidators.element],
82 | },
83 | });
84 |
85 | console.log(issues[0].msg); // Expecting one of but found
86 | console.log(issues[0].position); // { startOffset: 46, endOffset: 53 }
87 | ```
88 |
89 | - Using Content Assist APIs
90 |
91 | ```javascript
92 | const { getSuggestions } = require("@xml-tools/content-assist");
93 | const { getSchemaSuggestionsProviders } = require("@xml-tools/simple-schema");
94 |
95 | const schemaSuggestionsProviders = getSchemaSuggestionsProviders(schema);
96 |
97 | const suggestions = getSuggestions({
98 | text: xmlText,
99 | offset: 47, // within the eyeColor quoted value
100 | providers: {
101 | attributeValue: [
102 | schemaSuggestionsProviders.schemaAttributeValueCompletion,
103 | ],
104 | attributeName: [schemaSuggestionsProviders.schemaAttributeNameCompletion],
105 | elementName: [schemaSuggestionsProviders.schemaElementNameCompletion],
106 | },
107 | });
108 |
109 | console.log(suggestions[0]); // { text: 'grey', label: 'grey' }
110 | console.log(suggestions[1]); // { text: 'blue', label: 'blue' }
111 | ```
112 |
113 | ## Support
114 |
115 | Please open [issues](https://github.com/SAP/xml-tols/issues) on github.
116 |
117 | ## Contributing
118 |
119 | See [CONTRIBUTING.md](./CONTRIBUTING.md).
120 |
--------------------------------------------------------------------------------
/packages/ast/test/visitor/visitor-spec.js:
--------------------------------------------------------------------------------
1 | const { expect } = require("chai");
2 | const { parse } = require("@xml-tools/parser");
3 | const { buildAst, accept } = require("../../");
4 |
5 | describe("The XML AST Visitor", () => {
6 | it("can traverse AST Elements", () => {
7 | const inputText = `
8 | Bill
9 | Tim
10 |
11 | `;
12 |
13 | let visitedCounter = 0;
14 | const visitor = {
15 | visitXMLElement: function (node) {
16 | expect(["to", "from", "note"]).to.include(node.name);
17 | visitedCounter++;
18 | },
19 | };
20 |
21 | const astNode = getAst(inputText);
22 | accept(astNode, visitor);
23 | expect(visitedCounter).to.eql(3);
24 | });
25 |
26 | it("can traverse AST TextContents", () => {
27 | const inputText = `
28 | Bill
29 | Tim
30 |
31 | `;
32 |
33 | let visitedCounter = 0;
34 | const visitor = {
35 | visitXMLTextContent: function (node) {
36 | // ignore whitespace TextNodes
37 | if (/^\s+$/.test(node.text) === false) {
38 | expect(["Bill", "Tim", "note"]).to.include(node.text);
39 | visitedCounter++;
40 | }
41 | },
42 | };
43 |
44 | const astNode = getAst(inputText);
45 | accept(astNode, visitor);
46 | expect(visitedCounter).to.eql(2);
47 | });
48 |
49 | it("can traverse AST Attributes", () => {
50 | const inputText = `
51 |
52 | Bill
53 | Tim
54 |
55 | `;
56 |
57 | let visitedCounter = 0;
58 | const visitor = {
59 | visitXMLAttribute: function (node) {
60 | expect(["foo", "bar"]).to.include(node.key);
61 | visitedCounter++;
62 | },
63 | };
64 |
65 | const astNode = getAst(inputText);
66 | accept(astNode, visitor);
67 | expect(visitedCounter).to.eql(2);
68 | });
69 |
70 | it("can traverse AST Prolog Attributes", () => {
71 | const inputText = `
72 |
73 | Bill
74 | Tim
75 |
76 | `;
77 |
78 | let visitedCounter = 0;
79 | const visitor = {
80 | visitXMLPrologAttribute: function (node) {
81 | expect(["version", "encoding"]).to.include(node.key);
82 | visitedCounter++;
83 | },
84 | };
85 |
86 | const astNode = getAst(inputText);
87 | accept(astNode, visitor);
88 | expect(visitedCounter).to.eql(2);
89 | });
90 |
91 | it("can traverse AST Prolog", () => {
92 | const inputText = `
93 |
94 |
95 | `;
96 |
97 | let visitedCounter = 0;
98 | const visitor = {
99 | visitXMLProlog: function (node) {
100 | expect(node.attributes).to.have.lengthOf(2);
101 | expect(node.attributes[0].key).to.eql("version");
102 | expect(node.attributes[1].key).to.eql("encoding");
103 | visitedCounter++;
104 | },
105 | };
106 |
107 | const astNode = getAst(inputText);
108 | accept(astNode, visitor);
109 | expect(visitedCounter).to.eql(1);
110 | });
111 |
112 | it("can traverse XML Document AST", () => {
113 | const inputText = `
114 | foo
115 | bar
116 |
117 | `;
118 |
119 | let visitedCounter = 0;
120 | const visitor = {
121 | visitXMLDocument: function (node) {
122 | expect(node.rootElement.name).to.eql("note");
123 | visitedCounter++;
124 | },
125 | };
126 |
127 | const astNode = getAst(inputText);
128 | accept(astNode, visitor);
129 | expect(visitedCounter).to.eql(1);
130 | });
131 | });
132 |
133 | function getAst(text) {
134 | const { cst, tokenVector } = parse(text);
135 | const ast = buildAst(cst, tokenVector);
136 | return ast;
137 | }
138 |
--------------------------------------------------------------------------------