├── .github
└── workflows
│ ├── CI.yml
│ └── CODE_SCANNING.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
└── descriptor-xml.md
├── eslint.config.js
├── lib
├── common.js
├── index.js
├── read.js
└── write.js
├── package-lock.json
├── package.json
├── rollup.config.js
└── test
├── expect.js
├── fixtures
├── error
│ ├── binary.png
│ └── no-xml.txt
├── model
│ ├── attr-child-conflict.json
│ ├── datatype-aliased.json
│ ├── datatype-external.json
│ ├── datatype.json
│ ├── extension
│ │ ├── base.json
│ │ └── custom.json
│ ├── extensions.json
│ ├── fake-id.json
│ ├── noalias.json
│ ├── properties-extended.json
│ ├── properties.json
│ ├── redefine.json
│ ├── replace.json
│ ├── virtual.json
│ └── xmi.json
└── xml
│ └── UML.xmi
├── helper.js
├── integration
└── distro.cjs
├── matchers.js
└── spec
├── reader.js
├── roundtrip.uml.js
├── rountrip.js
└── writer.js
/.github/workflows/CI.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [ push, pull_request ]
3 | jobs:
4 | build:
5 |
6 | strategy:
7 | matrix:
8 | os: [ ubuntu-latest ]
9 | node-version: [ 20 ]
10 |
11 | runs-on: ${{ matrix.os }}
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | - name: Use Node.js
17 | uses: actions/setup-node@v4
18 | with:
19 | node-version: ${{ matrix.node-version }}
20 | cache: 'npm'
21 | - name: Install dependencies
22 | run: npm ci
23 | - name: Build
24 | run: npm run all
25 |
--------------------------------------------------------------------------------
/.github/workflows/CODE_SCANNING.yml:
--------------------------------------------------------------------------------
1 | name: CODE_SCANNING
2 |
3 | on:
4 | push:
5 | branches: [ master, develop ]
6 | pull_request:
7 | branches: [ master, develop ]
8 | paths-ignore:
9 | - '**/*.md'
10 |
11 | jobs:
12 | codeql_build:
13 | # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
14 | runs-on: ubuntu-latest
15 |
16 | permissions:
17 | # required for all workflows
18 | security-events: write
19 |
20 | steps:
21 | - name: Checkout repository
22 | uses: actions/checkout@v3
23 |
24 | # Initializes the CodeQL tools for scanning.
25 | - name: Initialize CodeQL
26 | uses: github/codeql-action/init@v2
27 | with:
28 | languages: javascript
29 | config: |
30 | paths-ignore:
31 | - '**/test'
32 |
33 | - name: Perform CodeQL Analysis
34 | uses: github/codeql-action/analyze@v2
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | tmp/
3 | dist/
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to [moddle-xml](https://github.com/bpmn-io/moddle-xml) are documented here. We use [semantic versioning](http://semver.org/) for releases.
4 |
5 | ## Unreleased
6 |
7 | ___Note:__ Yet to be released changes appear here._
8 |
9 | ## 11.0.0
10 |
11 | * `FEAT`: add package `exports` ([#73](https://github.com/bpmn-io/moddle-xml/pull/73))
12 | * `FEAT`: access `:xmlns` as a global name ([#69](https://github.com/bpmn-io/moddle-xml/pull/69), [`bdb5824`](https://github.com/bpmn-io/moddle-xml/pull/69/commits/bdb5824d8fc7727c20de40f5460f0f2b32b0d0c1))
13 | * `FEAT`: allow to configure custom namespace map ([#69](https://github.com/bpmn-io/moddle-xml/pull/69), [`17d8cb0`](https://github.com/bpmn-io/moddle-xml/pull/69/commits/17d8cb0a737551c738b77c7e42bb7f3b56ab2fdb))
14 | * `FEAT`: support alternative serialization methods ([#69](https://github.com/bpmn-io/moddle-xml/pull/69), [`2cb16b2`](https://github.com/bpmn-io/moddle-xml/pull/69/commits/2cb16b277c710ff1d4a53acfa78e243de898d0a5))
15 | * `FIX`: correct export of generic element ([#69](https://github.com/bpmn-io/moddle-xml/pull/69), [`235504f`](https://github.com/bpmn-io/moddle-xml/pull/69/commits/235504f98488fced305e13a5b8a7e9f157f80232))
16 | * `FIX`: remove broken `main` configuration ([#73](https://github.com/bpmn-io/moddle-xml/pull/73))
17 | * `CHORE`: drop `UMD` distribution ([#73](https://github.com/bpmn-io/moddle-xml/pull/73))
18 | * `DEPS`: update to `min-dash@4.2.1`
19 | * `DEPS`: update to `min-dash@4.2.1`
20 | * `DEPS`: update to `saxen@10`
21 | * `DEPS`: update to `moddle@7`
22 |
23 | ### Breaking Changes
24 |
25 | * UMD distribution no longer bundled. The module is now available as an ES module.
26 |
27 | ## 10.1.0
28 |
29 | * `FEAT`: generate sourcemaps
30 |
31 | ## 10.0.0
32 |
33 | * `DEPS`: update to `min-dash@4`
34 | * `DEPS`: update to `moddle@6`
35 | * `CHORE`: turn into ES module
36 |
37 | ## 9.0.6
38 |
39 | * `FIX`: correctly handle duplicated attributes ([#66](https://github.com/bpmn-io/moddle-xml/issues/66))
40 |
41 | ## 9.0.5
42 |
43 | * `FIX`: correct serialization of `xml` namespace attributes on `Any` elements ([#60](https://github.com/bpmn-io/moddle-xml/issues/60))
44 | * `FIX`: do not trim non-empty element text ([#58](https://github.com/bpmn-io/moddle-xml/issues/58))
45 |
46 | ## 9.0.4
47 |
48 | * `FIX`: make hasOwnProperty check safe ([#54](https://github.com/bpmn-io/moddle-xml/pull/54))
49 |
50 | ## 9.0.3
51 |
52 | * `FIX`: handle default `xml` namespace ([#50](https://github.com/bpmn-io/moddle-xml/issues/50))
53 |
54 | ## 8.0.8
55 |
56 | * `FIX`: handle default `xml` namespace
57 |
58 | ## 9.0.2
59 |
60 | * `FIX`: recursively log namespace as used ([#49](https://github.com/bpmn-io/moddle-xml/pull/49))
61 |
62 | ## 8.0.7
63 |
64 | * `FIX`: recursively log namespace as used ([#49](https://github.com/bpmn-io/moddle-xml/pull/49))
65 |
66 | ## 9.0.1
67 |
68 | * `FIX`: correctly serialize nested local namespaced elements ([#47](https://github.com/bpmn-io/moddle-xml/pull/47))
69 |
70 | ## 8.0.6
71 |
72 | * `FIX`: correctly serialize nested local namespaced elements ([#48](https://github.com/bpmn-io/moddle-xml/pull/48))
73 |
74 | ## 9.0.0
75 |
76 | * `FEAT`: promisify `Reader#fromXML` ([#45](https://github.com/bpmn-io/moddle-xml/pull/45))
77 |
78 | ### Breaking Changes
79 |
80 | * `Reader#fromXML` API now returns a Promise. Support for callbacks is dropped. Refer to the [documentation](https://github.com/bpmn-io/moddle-xml#read-xml) for updated usage information.
81 |
82 | ## 8.0.5
83 |
84 | _Republish of `v8.0.4`._
85 |
86 | ## 8.0.4
87 |
88 | * `CHORE`: bump to `saxen@8.1.2`
89 |
90 | ## 8.0.3
91 |
92 | * `CHORE`: bump to `saxen@8.1.1`
93 |
94 | ## 8.0.2
95 |
96 | * `FIX`: read element as type if conflicting named propery is defines an attribute ([#43](https://github.com/bpmn-io/moddle-xml/issues/43))
97 |
98 | ## 8.0.1
99 |
100 | * `DOCS`: update documentation
101 |
102 | ## 8.0.0
103 |
104 | * `FEAT`: provide pre-packaged distribution
105 | * `CHORE`: bump to `moddle@5`
106 |
107 | ## 7.5.0
108 |
109 | * `FEAT`: validate ID attributes are [QNames](http://www.w3.org/TR/REC-xml/#NT-NameChar)
110 |
111 | ## 7.4.1
112 |
113 | * `FIX`: make ES5 compliant
114 |
115 | ## 7.4.0
116 |
117 | * `CHORE`: get rid of `tiny-stack` as a dependency ([#38](https://github.com/bpmn-io/moddle-xml/pull/38))
118 |
119 | ## 7.3.0
120 |
121 | * `FEAT`: warn on unexpected body text
122 | * `FEAT`: warn on text outside root node
123 | * `CHORE`: remove `console.log` during import ([#28](https://github.com/bpmn-io/moddle-xml/issues/28))
124 | * `CHORE`: bump to [`saxen@8.1.0`](https://github.com/nikku/saxen/blob/master/CHANGELOG.md#810)
125 |
126 | ## 7.2.3
127 |
128 | * `FIX`: correctly serialize extension attributes along with typed elements
129 |
130 | ## 7.2.0
131 |
132 | * `FEAT`: warn on invalid attributes under well-known namespaces ([#32](https://github.com/bpmn-io/moddle-xml/issues/32))
133 |
134 | ## 7.1.0
135 |
136 | * `CHORE`: bump dependency versions
137 |
138 | ## 7.0.0
139 |
140 | ### Breaking Changes
141 |
142 | * `FEAT`: migrate to ES modules. Use `esm` or a ES module aware transpiler to consume this library.
143 |
144 | ## 6.0.0
145 |
146 | * `FEAT`: encode entities in body properties (instead of escaping via `CDATA`) ([`5645b582`](https://github.com/bpmn-io/moddle-xml/commit/5645b5822644a461eba9f3da481362475f040984))
147 |
148 | ## 5.0.2
149 |
150 | * `FIX`: properly handle `.` in attribute names
151 |
152 | ## 5.0.1
153 |
154 | * `FIX`: decode entities in `text` nodes
155 |
156 | ## 5.0.0
157 |
158 | * `FEAT`: replace lodash with [min-dash](https://github.com/bpmn-io/min-dash)
159 | * `FEAT`: don't bail out from attribute parsing on parse errors ([`fd0c8b40`](https://github.com/bpmn-io/moddle-xml/commit/fd0c8b4084b4d92565dd7d3099e283fbb98f1dd0))
160 |
161 | ## ...
162 |
163 | Check `git log` for earlier history.
164 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-present Camunda Services GmbH
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # moddle-xml
2 |
3 | [](https://github.com/bpmn-io/moddle-xml/actions?query=workflow%3ACI)
4 |
5 | Read and write XML documents described with [moddle](https://github.com/bpmn-io/moddle).
6 |
7 |
8 | ## Usage
9 |
10 | Get the libray via [npm](http://npmjs.org)
11 |
12 | ```
13 | npm install --save moddle-xml
14 | ```
15 |
16 |
17 | #### Bootstrap
18 |
19 | Create a [moddle instance](https://github.com/bpmn-io/moddle)
20 |
21 | ```javascript
22 | import { Moddle } from 'moddle';
23 | import {
24 | Reader,
25 | Writer
26 | } from 'moddle-xml';
27 |
28 | const model = new Moddle([ myPackage ]);
29 | ```
30 |
31 |
32 | #### Read XML
33 |
34 | Use the reader to parse XML into an easily accessible object tree:
35 |
36 | ```javascript
37 | const model; // previously created
38 |
39 | const xml =
40 | '' +
41 | '' +
42 | '' +
43 | '' +
44 | '';
45 |
46 | const reader = new Reader(model);
47 | const rootHandler = reader.handler('my:Root');
48 |
49 | // when
50 | try {
51 | const {
52 | rootElement: cars,
53 | warnings
54 | } = await reader.fromXML(xml, rootHandler);
55 |
56 | if (warnings.length) {
57 | console.log('import warnings', warnings);
58 | }
59 |
60 | console.log(cars);
61 |
62 | // {
63 | // $type: 'my:Root',
64 | // cars: [
65 | // {
66 | // $type: 'my:Car',
67 | // id: 'Car_1',
68 | // engine: [
69 | // { $type: 'my:Engine', powser: 121, fuelConsumption: 10 }
70 | // ]
71 | // }
72 | // ]
73 | // }
74 |
75 | } catch (err) {
76 | console.log('import error', err, err.warnings);
77 | }
78 | ```
79 |
80 |
81 | #### Write XML
82 |
83 | Use the writer to serialize the object tree back to XML:
84 |
85 | ```javascript
86 | var model; // previously created
87 |
88 | var cars = model.create('my:Root');
89 | cars.get('cars').push(model.create('my:Car', { power: 10 }));
90 |
91 | var options = { format: false, preamble: false };
92 | var writer = new Writer(options);
93 |
94 | var xml = writer.toXML(bar);
95 |
96 | console.log(xml); // ...
97 | ```
98 |
99 | ## License
100 |
101 | MIT
102 |
--------------------------------------------------------------------------------
/docs/descriptor-xml.md:
--------------------------------------------------------------------------------
1 | # moddle descriptor - XML Extensions
2 |
3 | When reading from / writing to XML, additional meta-data is necessary to correctly map the data model to a XML document.
4 |
5 | This document contains the list of supported extensions that are understood by [moddle-xml](https://github.com/bpmn-io/moddle-xml).
6 |
7 |
8 | ## Package Definition
9 |
10 | Extensions to the package definition allows you to configure namespacing and element serialization.
11 |
12 |
13 | ### Namespace URI
14 |
15 | Specify the `uri` field in a package definition to define the associated XML namespace URI.
16 |
17 | ```json
18 | {
19 | "prefix": "s",
20 | "uri": "http://sample"
21 | }
22 | ```
23 |
24 | This results in
25 |
26 | ```xml
27 |
28 | ```
29 |
30 | ### Element Name Serialization
31 |
32 | Specify `alias=lowerCase` to map elements to their lower case names in xml.
33 |
34 | The above output becomes
35 |
36 | ```xml
37 |
38 | ```
39 |
40 | when this property is specified.
41 |
42 |
43 | ## Property definition
44 |
45 | XML distinguishes between child elements, body text and attributes. moddle allows you to map your data to these places via special qualifiers.
46 |
47 |
48 | ### Qualifiers
49 |
50 | Use any of the following qualifiers to configure how a property is mapped to XML.
51 |
52 | | Qualifier | Values | Description |
53 | | ------------- | ------------- | ----- |
54 | | `isAttr=false` | `Boolean` | serializes as an attribute |
55 | | `isBody=false` | `Boolean` | serializes as the body of the element |
56 | | `serialize` | `String` | adds additional notes on how to serialize. Supported value(s): `xsi:type` serializes as data type rather than element |
57 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import bpmnIoPlugin from 'eslint-plugin-bpmn-io';
2 |
3 | const files = {
4 | lib: [
5 | 'lib/**/*.js'
6 | ],
7 | test: [
8 | 'test/**/*.js',
9 | 'test/**/*.cjs'
10 | ],
11 | ignored: [
12 | 'dist'
13 | ]
14 | };
15 |
16 | export default [
17 | {
18 | 'ignores': files.ignored
19 | },
20 |
21 | // build
22 | ...bpmnIoPlugin.configs.node.map(config => {
23 |
24 | return {
25 | ...config,
26 | ignores: files.lib
27 | };
28 | }),
29 |
30 | // lib + test
31 | ...bpmnIoPlugin.configs.recommended.map(config => {
32 |
33 | return {
34 | ...config,
35 | files: files.lib
36 | };
37 | }),
38 |
39 | // test
40 | ...bpmnIoPlugin.configs.mocha.map(config => {
41 |
42 | return {
43 | ...config,
44 | files: files.test
45 | };
46 | })
47 | ];
--------------------------------------------------------------------------------
/lib/common.js:
--------------------------------------------------------------------------------
1 | export function hasLowerCaseAlias(pkg) {
2 | return pkg.xml && pkg.xml.tagAlias === 'lowerCase';
3 | }
4 |
5 | export var DEFAULT_NS_MAP = {
6 | 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
7 | 'xml': 'http://www.w3.org/XML/1998/namespace'
8 | };
9 |
10 | export var SERIALIZE_PROPERTY = 'property';
11 |
12 | export function getSerialization(element) {
13 | return element.xml && element.xml.serialize;
14 | }
15 |
16 | export function getSerializationType(element) {
17 | const type = getSerialization(element);
18 |
19 | return type !== SERIALIZE_PROPERTY && (type || null);
20 | }
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | Reader
3 | } from './read.js';
4 |
5 | export {
6 | Writer
7 | } from './write.js';
--------------------------------------------------------------------------------
/lib/read.js:
--------------------------------------------------------------------------------
1 | import {
2 | forEach,
3 | find,
4 | assign
5 | } from 'min-dash';
6 |
7 | import {
8 | Parser as SaxParser
9 | } from 'saxen';
10 |
11 | import {
12 | Moddle,
13 | parseNameNS,
14 | coerceType,
15 | isSimpleType
16 | } from 'moddle';
17 |
18 | import {
19 | DEFAULT_NS_MAP,
20 | getSerializationType,
21 | hasLowerCaseAlias
22 | } from './common.js';
23 |
24 | function capitalize(str) {
25 | return str.charAt(0).toUpperCase() + str.slice(1);
26 | }
27 |
28 | function aliasToName(aliasNs, pkg) {
29 |
30 | if (!hasLowerCaseAlias(pkg)) {
31 | return aliasNs.name;
32 | }
33 |
34 | return aliasNs.prefix + ':' + capitalize(aliasNs.localName);
35 | }
36 |
37 | /**
38 | * Un-prefix a potentially prefixed type name.
39 | *
40 | * @param {NsName} nameNs
41 | * @param {Object} [pkg]
42 | *
43 | * @return {string}
44 | */
45 | function prefixedToName(nameNs, pkg) {
46 |
47 | var name = nameNs.name,
48 | localName = nameNs.localName;
49 |
50 | var typePrefix = pkg && pkg.xml && pkg.xml.typePrefix;
51 |
52 | if (typePrefix && localName.indexOf(typePrefix) === 0) {
53 | return nameNs.prefix + ':' + localName.slice(typePrefix.length);
54 | } else {
55 | return name;
56 | }
57 | }
58 |
59 | function normalizeTypeName(name, nsMap, model) {
60 |
61 | // normalize against actual NS
62 | const nameNs = parseNameNS(name, nsMap.xmlns);
63 |
64 | const normalizedName = `${ nsMap[nameNs.prefix] || nameNs.prefix }:${ nameNs.localName }`;
65 |
66 | const normalizedNameNs = parseNameNS(normalizedName);
67 |
68 | // determine actual type name, based on package-defined prefix
69 | var pkg = model.getPackage(normalizedNameNs.prefix);
70 |
71 | return prefixedToName(normalizedNameNs, pkg);
72 | }
73 |
74 | function error(message) {
75 | return new Error(message);
76 | }
77 |
78 | /**
79 | * Get the moddle descriptor for a given instance or type.
80 | *
81 | * @param {ModdleElement|Function} element
82 | *
83 | * @return {Object} the moddle descriptor
84 | */
85 | function getModdleDescriptor(element) {
86 | return element.$descriptor;
87 | }
88 |
89 |
90 | /**
91 | * A parse context.
92 | *
93 | * @class
94 | *
95 | * @param {Object} options
96 | * @param {ElementHandler} options.rootHandler the root handler for parsing a document
97 | * @param {boolean} [options.lax=false] whether or not to ignore invalid elements
98 | */
99 | export function Context(options) {
100 |
101 | /**
102 | * @property {ElementHandler} rootHandler
103 | */
104 |
105 | /**
106 | * @property {Boolean} lax
107 | */
108 |
109 | assign(this, options);
110 |
111 | this.elementsById = {};
112 | this.references = [];
113 | this.warnings = [];
114 |
115 | /**
116 | * Add an unresolved reference.
117 | *
118 | * @param {Object} reference
119 | */
120 | this.addReference = function(reference) {
121 | this.references.push(reference);
122 | };
123 |
124 | /**
125 | * Add a processed element.
126 | *
127 | * @param {ModdleElement} element
128 | */
129 | this.addElement = function(element) {
130 |
131 | if (!element) {
132 | throw error('expected element');
133 | }
134 |
135 | var elementsById = this.elementsById;
136 |
137 | var descriptor = getModdleDescriptor(element);
138 |
139 | var idProperty = descriptor.idProperty,
140 | id;
141 |
142 | if (idProperty) {
143 | id = element.get(idProperty.name);
144 |
145 | if (id) {
146 |
147 | // for QName validation as per http://www.w3.org/TR/REC-xml/#NT-NameChar
148 | if (!/^([a-z][\w-.]*:)?[a-z_][\w-.]*$/i.test(id)) {
149 | throw new Error('illegal ID <' + id + '>');
150 | }
151 |
152 | if (elementsById[id]) {
153 | throw error('duplicate ID <' + id + '>');
154 | }
155 |
156 | elementsById[id] = element;
157 | }
158 | }
159 | };
160 |
161 | /**
162 | * Add an import warning.
163 | *
164 | * @param {Object} warning
165 | * @param {String} warning.message
166 | * @param {Error} [warning.error]
167 | */
168 | this.addWarning = function(warning) {
169 | this.warnings.push(warning);
170 | };
171 | }
172 |
173 | function BaseHandler() {}
174 |
175 | BaseHandler.prototype.handleEnd = function() {};
176 | BaseHandler.prototype.handleText = function() {};
177 | BaseHandler.prototype.handleNode = function() {};
178 |
179 |
180 | /**
181 | * A simple pass through handler that does nothing except for
182 | * ignoring all input it receives.
183 | *
184 | * This is used to ignore unknown elements and
185 | * attributes.
186 | */
187 | function NoopHandler() { }
188 |
189 | NoopHandler.prototype = Object.create(BaseHandler.prototype);
190 |
191 | NoopHandler.prototype.handleNode = function() {
192 | return this;
193 | };
194 |
195 | function BodyHandler() {}
196 |
197 | BodyHandler.prototype = Object.create(BaseHandler.prototype);
198 |
199 | BodyHandler.prototype.handleText = function(text) {
200 | this.body = (this.body || '') + text;
201 | };
202 |
203 | function ReferenceHandler(property, context) {
204 | this.property = property;
205 | this.context = context;
206 | }
207 |
208 | ReferenceHandler.prototype = Object.create(BodyHandler.prototype);
209 |
210 | ReferenceHandler.prototype.handleNode = function(node) {
211 |
212 | if (this.element) {
213 | throw error('expected no sub nodes');
214 | } else {
215 | this.element = this.createReference(node);
216 | }
217 |
218 | return this;
219 | };
220 |
221 | ReferenceHandler.prototype.handleEnd = function() {
222 | this.element.id = this.body;
223 | };
224 |
225 | ReferenceHandler.prototype.createReference = function(node) {
226 | return {
227 | property: this.property.ns.name,
228 | id: ''
229 | };
230 | };
231 |
232 | function ValueHandler(propertyDesc, element) {
233 | this.element = element;
234 | this.propertyDesc = propertyDesc;
235 | }
236 |
237 | ValueHandler.prototype = Object.create(BodyHandler.prototype);
238 |
239 | ValueHandler.prototype.handleEnd = function() {
240 |
241 | var value = this.body || '',
242 | element = this.element,
243 | propertyDesc = this.propertyDesc;
244 |
245 | value = coerceType(propertyDesc.type, value);
246 |
247 | if (propertyDesc.isMany) {
248 | element.get(propertyDesc.name).push(value);
249 | } else {
250 | element.set(propertyDesc.name, value);
251 | }
252 | };
253 |
254 |
255 | function BaseElementHandler() {}
256 |
257 | BaseElementHandler.prototype = Object.create(BodyHandler.prototype);
258 |
259 | BaseElementHandler.prototype.handleNode = function(node) {
260 | var parser = this,
261 | element = this.element;
262 |
263 | if (!element) {
264 | element = this.element = this.createElement(node);
265 |
266 | this.context.addElement(element);
267 | } else {
268 | parser = this.handleChild(node);
269 | }
270 |
271 | return parser;
272 | };
273 |
274 | /**
275 | * @class Reader.ElementHandler
276 | *
277 | */
278 | export function ElementHandler(model, typeName, context) {
279 | this.model = model;
280 | this.type = model.getType(typeName);
281 | this.context = context;
282 | }
283 |
284 | ElementHandler.prototype = Object.create(BaseElementHandler.prototype);
285 |
286 | ElementHandler.prototype.addReference = function(reference) {
287 | this.context.addReference(reference);
288 | };
289 |
290 | ElementHandler.prototype.handleText = function(text) {
291 |
292 | var element = this.element,
293 | descriptor = getModdleDescriptor(element),
294 | bodyProperty = descriptor.bodyProperty;
295 |
296 | if (!bodyProperty) {
297 | throw error('unexpected body text <' + text + '>');
298 | }
299 |
300 | BodyHandler.prototype.handleText.call(this, text);
301 | };
302 |
303 | ElementHandler.prototype.handleEnd = function() {
304 |
305 | var value = this.body,
306 | element = this.element,
307 | descriptor = getModdleDescriptor(element),
308 | bodyProperty = descriptor.bodyProperty;
309 |
310 | if (bodyProperty && value !== undefined) {
311 | value = coerceType(bodyProperty.type, value);
312 | element.set(bodyProperty.name, value);
313 | }
314 | };
315 |
316 | /**
317 | * Create an instance of the model from the given node.
318 | *
319 | * @param {Element} node the xml node
320 | */
321 | ElementHandler.prototype.createElement = function(node) {
322 | var attributes = node.attributes,
323 | Type = this.type,
324 | descriptor = getModdleDescriptor(Type),
325 | context = this.context,
326 | instance = new Type({}),
327 | model = this.model,
328 | propNameNs;
329 |
330 | forEach(attributes, function(value, name) {
331 |
332 | var prop = descriptor.propertiesByName[name],
333 | values;
334 |
335 | if (prop && prop.isReference) {
336 |
337 | if (!prop.isMany) {
338 | context.addReference({
339 | element: instance,
340 | property: prop.ns.name,
341 | id: value
342 | });
343 | } else {
344 |
345 | // IDREFS: parse references as whitespace-separated list
346 | values = value.split(' ');
347 |
348 | forEach(values, function(v) {
349 | context.addReference({
350 | element: instance,
351 | property: prop.ns.name,
352 | id: v
353 | });
354 | });
355 | }
356 |
357 | } else {
358 | if (prop) {
359 | value = coerceType(prop.type, value);
360 | } else if (name === 'xmlns') {
361 | name = ':' + name;
362 | } else {
363 | propNameNs = parseNameNS(name, descriptor.ns.prefix);
364 |
365 | // check whether attribute is defined in a well-known namespace
366 | // if that is the case we emit a warning to indicate potential misuse
367 | if (model.getPackage(propNameNs.prefix)) {
368 |
369 | context.addWarning({
370 | message: 'unknown attribute <' + name + '>',
371 | element: instance,
372 | property: name,
373 | value: value
374 | });
375 | }
376 | }
377 |
378 | instance.set(name, value);
379 | }
380 | });
381 |
382 | return instance;
383 | };
384 |
385 | ElementHandler.prototype.getPropertyForNode = function(node) {
386 |
387 | var name = node.name;
388 | var nameNs = parseNameNS(name);
389 |
390 | var type = this.type,
391 | model = this.model,
392 | descriptor = getModdleDescriptor(type);
393 |
394 | var propertyName = nameNs.name,
395 | property = descriptor.propertiesByName[propertyName];
396 |
397 | // search for properties by name first
398 |
399 | if (property && !property.isAttr) {
400 |
401 | const serializationType = getSerializationType(property);
402 |
403 | if (serializationType) {
404 | const elementTypeName = node.attributes[serializationType];
405 |
406 | // type is optional, if it does not exists the
407 | // default type is assumed
408 | if (elementTypeName) {
409 |
410 | // convert the prefix used to the mapped form, but also
411 | // take possible type prefixes from XML
412 | // into account, i.e.: xsi:type="t{ActualType}",
413 | const normalizedTypeName = normalizeTypeName(elementTypeName, node.ns, model);
414 |
415 | const elementType = model.getType(normalizedTypeName);
416 |
417 | return assign({}, property, {
418 | effectiveType: getModdleDescriptor(elementType).name
419 | });
420 | }
421 | }
422 |
423 | // search for properties by name first
424 | return property;
425 | }
426 |
427 | var pkg = model.getPackage(nameNs.prefix);
428 |
429 | if (pkg) {
430 | const elementTypeName = aliasToName(nameNs, pkg);
431 | const elementType = model.getType(elementTypeName);
432 |
433 | // search for collection members later
434 | property = find(descriptor.properties, function(p) {
435 | return !p.isVirtual && !p.isReference && !p.isAttribute && elementType.hasType(p.type);
436 | });
437 |
438 | if (property) {
439 | return assign({}, property, {
440 | effectiveType: getModdleDescriptor(elementType).name
441 | });
442 | }
443 | } else {
444 |
445 | // parse unknown element (maybe extension)
446 | property = find(descriptor.properties, function(p) {
447 | return !p.isReference && !p.isAttribute && p.type === 'Element';
448 | });
449 |
450 | if (property) {
451 | return property;
452 | }
453 | }
454 |
455 | throw error('unrecognized element <' + nameNs.name + '>');
456 | };
457 |
458 | ElementHandler.prototype.toString = function() {
459 | return 'ElementDescriptor[' + getModdleDescriptor(this.type).name + ']';
460 | };
461 |
462 | ElementHandler.prototype.valueHandler = function(propertyDesc, element) {
463 | return new ValueHandler(propertyDesc, element);
464 | };
465 |
466 | ElementHandler.prototype.referenceHandler = function(propertyDesc) {
467 | return new ReferenceHandler(propertyDesc, this.context);
468 | };
469 |
470 | ElementHandler.prototype.handler = function(type) {
471 | if (type === 'Element') {
472 | return new GenericElementHandler(this.model, type, this.context);
473 | } else {
474 | return new ElementHandler(this.model, type, this.context);
475 | }
476 | };
477 |
478 | /**
479 | * Handle the child element parsing
480 | *
481 | * @param {Element} node the xml node
482 | */
483 | ElementHandler.prototype.handleChild = function(node) {
484 | var propertyDesc, type, element, childHandler;
485 |
486 | propertyDesc = this.getPropertyForNode(node);
487 | element = this.element;
488 |
489 | type = propertyDesc.effectiveType || propertyDesc.type;
490 |
491 | if (isSimpleType(type)) {
492 | return this.valueHandler(propertyDesc, element);
493 | }
494 |
495 | if (propertyDesc.isReference) {
496 | childHandler = this.referenceHandler(propertyDesc).handleNode(node);
497 | } else {
498 | childHandler = this.handler(type).handleNode(node);
499 | }
500 |
501 | var newElement = childHandler.element;
502 |
503 | // child handles may decide to skip elements
504 | // by not returning anything
505 | if (newElement !== undefined) {
506 |
507 | if (propertyDesc.isMany) {
508 | element.get(propertyDesc.name).push(newElement);
509 | } else {
510 | element.set(propertyDesc.name, newElement);
511 | }
512 |
513 | if (propertyDesc.isReference) {
514 | assign(newElement, {
515 | element: element
516 | });
517 |
518 | this.context.addReference(newElement);
519 | } else {
520 |
521 | // establish child -> parent relationship
522 | newElement.$parent = element;
523 | }
524 | }
525 |
526 | return childHandler;
527 | };
528 |
529 | /**
530 | * An element handler that performs special validation
531 | * to ensure the node it gets initialized with matches
532 | * the handlers type (namespace wise).
533 | *
534 | * @param {Moddle} model
535 | * @param {String} typeName
536 | * @param {Context} context
537 | */
538 | function RootElementHandler(model, typeName, context) {
539 | ElementHandler.call(this, model, typeName, context);
540 | }
541 |
542 | RootElementHandler.prototype = Object.create(ElementHandler.prototype);
543 |
544 | RootElementHandler.prototype.createElement = function(node) {
545 |
546 | var name = node.name,
547 | nameNs = parseNameNS(name),
548 | model = this.model,
549 | type = this.type,
550 | pkg = model.getPackage(nameNs.prefix),
551 | typeName = pkg && aliasToName(nameNs, pkg) || name;
552 |
553 | // verify the correct namespace if we parse
554 | // the first element in the handler tree
555 | //
556 | // this ensures we don't mistakenly import wrong namespace elements
557 | if (!type.hasType(typeName)) {
558 | throw error('unexpected element <' + node.originalName + '>');
559 | }
560 |
561 | return ElementHandler.prototype.createElement.call(this, node);
562 | };
563 |
564 |
565 | function GenericElementHandler(model, typeName, context) {
566 | this.model = model;
567 | this.context = context;
568 | }
569 |
570 | GenericElementHandler.prototype = Object.create(BaseElementHandler.prototype);
571 |
572 | GenericElementHandler.prototype.createElement = function(node) {
573 |
574 | var name = node.name,
575 | ns = parseNameNS(name),
576 | prefix = ns.prefix,
577 | uri = node.ns[prefix + '$uri'],
578 | attributes = node.attributes;
579 |
580 | return this.model.createAny(name, uri, attributes);
581 | };
582 |
583 | GenericElementHandler.prototype.handleChild = function(node) {
584 |
585 | var handler = new GenericElementHandler(this.model, 'Element', this.context).handleNode(node),
586 | element = this.element;
587 |
588 | var newElement = handler.element,
589 | children;
590 |
591 | if (newElement !== undefined) {
592 | children = element.$children = element.$children || [];
593 | children.push(newElement);
594 |
595 | // establish child -> parent relationship
596 | newElement.$parent = element;
597 | }
598 |
599 | return handler;
600 | };
601 |
602 | GenericElementHandler.prototype.handleEnd = function() {
603 | if (this.body) {
604 | this.element.$body = this.body;
605 | }
606 | };
607 |
608 | /**
609 | * A reader for a meta-model
610 | *
611 | * @param {Object} options
612 | * @param {Model} options.model used to read xml files
613 | * @param {Boolean} options.lax whether to make parse errors warnings
614 | */
615 | export function Reader(options) {
616 |
617 | if (options instanceof Moddle) {
618 | options = {
619 | model: options
620 | };
621 | }
622 |
623 | assign(this, { lax: false }, options);
624 | }
625 |
626 | /**
627 | * The fromXML result.
628 | *
629 | * @typedef {Object} ParseResult
630 | *
631 | * @property {ModdleElement} rootElement
632 | * @property {Array