├── lib
├── index.js
├── simple.js
└── dmn-moddle.js
├── .gitignore
├── test
├── .eslintrc
├── fixtures
│ └── dmn
│ │ ├── dmn
│ │ ├── decision-extensionAttributes.part.dmn
│ │ ├── unary-tests.part.dmn
│ │ ├── definitions-extensionAttributes.dmn
│ │ ├── decision-decisionLogic.part.dmn
│ │ ├── decision-extensionElements.part.dmn
│ │ ├── list.part.dmn
│ │ ├── definitions-import.dmn
│ │ ├── function-definition.part.dmn
│ │ ├── decision.dmn
│ │ ├── definitions-extensionElements.dmn
│ │ ├── input-data.dmn
│ │ ├── business-knowledge-model.part.dmn
│ │ ├── information-requirement.dmn
│ │ └── context.part.dmn
│ │ ├── dmndi
│ │ ├── decision.dmn
│ │ ├── input-data.dmn
│ │ ├── extension.dmn
│ │ ├── text-annotation.dmn
│ │ ├── label.dmn
│ │ └── size.dmn
│ │ └── biodi
│ │ └── biodi.dmn
├── expect.js
├── distro
│ └── dmn-moddle.js
├── spec
│ ├── dmn-moddle.js
│ └── xml
│ │ ├── roundtrip.js
│ │ ├── edit.js
│ │ ├── write.js
│ │ └── read.js
└── integration
│ └── tck.js
├── tasks
├── transforms
│ ├── index.cjs
│ ├── transformDC.cjs
│ ├── transformDMNDI.cjs
│ ├── transformDI.cjs
│ ├── helper.cjs
│ └── transformDMN.cjs
└── generate-schema.cjs
├── .github
└── workflows
│ └── CI.yml
├── rollup.config.js
├── eslint.config.js
├── README.md
├── LICENSE
├── resources
└── dmn
│ ├── bpmn-io
│ └── biodi.json
│ ├── json
│ ├── dc.json
│ ├── di.json
│ ├── dmndi13.json
│ └── dmn13.json
│ └── xsd
│ ├── DMNDI13.xsd
│ ├── DI.xsd
│ ├── DC.xsd
│ └── DMN13.xsd
├── package.json
└── CHANGELOG.md
/lib/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | default
3 | } from './simple.js';
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | tmp/
4 | .idea
5 | *.iml
6 | npm-debug.log
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "plugin:bpmn-io/mocha",
4 | "plugin:bpmn-io/node"
5 | ]
6 | }
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/decision-extensionAttributes.part.dmn:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/unary-tests.part.dmn:
--------------------------------------------------------------------------------
1 |
2 | FOO
3 |
4 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/definitions-extensionAttributes.dmn:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/decision-decisionLogic.part.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/decision-extensionElements.part.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/list.part.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 | FOO
4 |
5 |
6 | BAR
7 |
8 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/definitions-import.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/function-definition.part.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/decision.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/definitions-extensionElements.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/input-data.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/tasks/transforms/index.cjs:
--------------------------------------------------------------------------------
1 | const transformDC = require('./transformDC.cjs'),
2 | transformDI = require('./transformDI.cjs'),
3 | transformDMN = require('./transformDMN.cjs'),
4 | transformDMNDI = require('./transformDMNDI.cjs');
5 |
6 | module.exports = {
7 | transformDC,
8 | transformDI,
9 | transformDMN,
10 | transformDMNDI
11 | };
12 |
--------------------------------------------------------------------------------
/.github/workflows/CI.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [ push, pull_request ]
3 | jobs:
4 | Build:
5 | runs-on: ubuntu-latest
6 |
7 | steps:
8 | - name: Checkout
9 | uses: actions/checkout@v4
10 | - name: Use Node.js
11 | uses: actions/setup-node@v4
12 | with:
13 | node-version: 20
14 | cache: 'npm'
15 | - name: Install dependencies
16 | run: npm ci
17 | - name: Build
18 | run: npm run all
19 | - name: Integration test
20 | run: npm run test:integration
21 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/business-knowledge-model.part.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/expect.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai';
2 |
3 | chai.use(function(chai, utils) {
4 | utils.addMethod(chai.Assertion.prototype, 'jsonEqual', function(comparison) {
5 | const actual = JSON.stringify(this._obj);
6 | const expected = JSON.stringify(comparison);
7 |
8 | this.assert(
9 | actual == expected,
10 | 'expected #{this} to deeply equal #{act}',
11 | 'expected #{this} not to deeply equal #{act}',
12 | comparison,
13 | this._obj,
14 | true
15 | );
16 | });
17 | });
18 |
19 | export { expect as default } from 'chai';
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import json from '@rollup/plugin-json';
2 |
3 | import pkg from './package.json' with { type: 'json' };
4 |
5 | function pgl(plugins = []) {
6 | return [
7 | json(),
8 | ...plugins
9 | ];
10 | }
11 |
12 | const srcEntry = 'lib/index.js';
13 |
14 | export default [
15 | {
16 | input: srcEntry,
17 | output: [
18 | { file: pkg.exports['.'].require, format: 'cjs' },
19 | { file: pkg.exports['.'].import, format: 'es' }
20 | ],
21 | external: [
22 | 'min-dash',
23 | 'moddle',
24 | 'moddle-xml'
25 | ],
26 | plugins: pgl()
27 | }
28 | ];
29 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/information-requirement.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmndi/decision.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmndi/input-data.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/distro/dmn-moddle.js:
--------------------------------------------------------------------------------
1 | import { createRequire } from 'node:module';
2 |
3 | import { expect } from 'chai';
4 |
5 | import pkg from '../../package.json' with { type: 'json' };
6 |
7 | const pkgExports = pkg.exports['.'];
8 |
9 |
10 | describe('dmn-moddle', function() {
11 |
12 | it('should expose ESM bundle', async function() {
13 | const DmnModdle = await import('dmn-moddle').then(m => m.default);
14 |
15 | expect(new DmnModdle()).to.exist;
16 | });
17 |
18 | it('should export CJS bundle', function() {
19 | const require = createRequire(import.meta.url);
20 |
21 | const DmnModdle = require('../../' + pkgExports.require);
22 |
23 | expect(new DmnModdle()).to.exist;
24 | });
25 |
26 | });
27 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmndi/extension.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/biodi/biodi.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmn/context.part.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | if ProductType ="STANDARD LOAN" then 20.00 else if ProductType ="SPECIAL LOAN" then 25.00 else null
6 |
7 |
8 |
9 |
10 |
11 | (Amount *Rate/12) / (1 - (1 + Rate/12)**-Term)
12 |
13 |
14 |
15 |
16 | MonthlyRepayment+MonthlyFee
17 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/simple.js:
--------------------------------------------------------------------------------
1 | import {
2 | assign
3 | } from 'min-dash';
4 |
5 | import DmnModdle from './dmn-moddle.js';
6 |
7 | import DcPackage from '../resources/dmn/json/dc.json' with { type: 'json' };
8 | import DiPackage from '../resources/dmn/json/di.json' with { type: 'json' };
9 | import DmnPackage from '../resources/dmn/json/dmn13.json' with { type: 'json' };
10 | import DmnDiPackage from '../resources/dmn/json/dmndi13.json' with { type: 'json' };
11 | import BioDiPackage from '../resources/dmn/bpmn-io/biodi.json' with { type: 'json' };
12 |
13 | var packages = {
14 | dc: DcPackage,
15 | di: DiPackage,
16 | dmn: DmnPackage,
17 | dmndi: DmnDiPackage,
18 | biodi: BioDiPackage
19 | };
20 |
21 | export default function(additionalPackages, options) {
22 | var pks = assign({}, packages, additionalPackages);
23 |
24 | return new DmnModdle(pks, options);
25 | }
26 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import bpmnIoPlugin from 'eslint-plugin-bpmn-io';
2 |
3 | const files = {
4 | build: [
5 | 'tasks/**/*.cjs',
6 | '*.js'
7 | ],
8 | test: [
9 | 'test/**/*.js',
10 | 'test/**/*.cjs'
11 | ],
12 | ignored: [
13 | 'dist',
14 | 'tmp'
15 | ]
16 | };
17 |
18 | export default [
19 | {
20 | 'ignores': files.ignored
21 | },
22 |
23 | // build
24 | ...bpmnIoPlugin.configs.node.map(config => {
25 |
26 | return {
27 | ...config,
28 | files: files.build
29 | };
30 | }),
31 |
32 | // lib + test
33 | ...bpmnIoPlugin.configs.recommended.map(config => {
34 |
35 | return {
36 | ...config,
37 | ignores: files.build
38 | };
39 | }),
40 |
41 | // test
42 | ...bpmnIoPlugin.configs.mocha.map(config => {
43 |
44 | return {
45 | ...config,
46 | files: files.test
47 | };
48 | }),
49 |
50 | // support "with" import
51 | {
52 | languageOptions: {
53 | ecmaVersion: 2025
54 | }
55 | }
56 | ];
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # dmn-moddle
2 |
3 | [](https://github.com/bpmn-io/dmn-moddle/actions?query=workflow%3ACI)
4 |
5 | Read and write DMN 1.3 files in NodeJS and the browser.
6 |
7 | __dmn-moddle__ uses the [DMN specification](http://www.omg.org/spec/DMN/1.3/) to validate the input and produce correct DMN XML. The library is built on top of [moddle](https://github.com/bpmn-io/moddle) and [moddle-xml](https://github.com/bpmn-io/moddle-xml).
8 |
9 |
10 | ## Resources
11 |
12 | * [Issues](https://github.com/bpmn-io/dmn-moddle/issues)
13 | * [Examples](https://github.com/bpmn-io/dmn-moddle/tree/master/test/spec/xml)
14 |
15 |
16 | ## Building the Project
17 |
18 | To run the test suite that includes XSD schema validation you must have a Java JDK installed and properly exposed through the `JAVA_HOME` variable.
19 |
20 | Execute the test via
21 |
22 | ```
23 | npm test
24 | ```
25 |
26 | Perform a complete build of the library via
27 |
28 | ```
29 | npm run all
30 | ```
31 |
32 |
33 | ## License
34 |
35 | Use under the terms of the [MIT license](http://opensource.org/licenses/MIT).
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-2017 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.
22 |
--------------------------------------------------------------------------------
/resources/dmn/bpmn-io/biodi.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bpmn.io DI for DMN",
3 | "uri": "http://bpmn.io/schema/dmn/biodi/2.0",
4 | "prefix": "biodi",
5 | "xml": {
6 | "tagAlias": "lowerCase"
7 | },
8 | "types": [
9 | {
10 | "name": "DecisionTable",
11 | "isAbstract": true,
12 | "extends": [
13 | "dmn:DecisionTable"
14 | ],
15 | "properties": [
16 | {
17 | "name": "annotationsWidth",
18 | "isAttr": true,
19 | "type": "Integer"
20 | }
21 | ]
22 | },
23 | {
24 | "name": "OutputClause",
25 | "isAbstract": true,
26 | "extends": [
27 | "dmn:OutputClause"
28 | ],
29 | "properties": [
30 | {
31 | "name": "width",
32 | "isAttr": true,
33 | "type": "Integer"
34 | }
35 | ]
36 | },
37 | {
38 | "name": "InputClause",
39 | "isAbstract": true,
40 | "extends": [
41 | "dmn:InputClause"
42 | ],
43 | "properties": [
44 | {
45 | "name": "width",
46 | "isAttr": true,
47 | "type": "Integer"
48 | }
49 | ]
50 | }
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmndi/text-annotation.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | TextAnnotation_1
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tasks/transforms/transformDC.cjs:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs');
2 |
3 | const {
4 | fixSequence,
5 | parseXML,
6 | removePrefixes,
7 | removeWhitespace
8 | } = require('./helper.cjs');
9 |
10 | module.exports = async function(results) {
11 | const { elementsByType } = results;
12 |
13 | let model = elementsByType[ 'uml:Package' ][ 0 ];
14 |
15 | model.name = 'DC';
16 |
17 | const prefix = model.prefix = 'dc';
18 |
19 | // remove properties without name
20 | model.types.forEach(type => {
21 | if (type.properties) {
22 | type.properties = type.properties.filter(property => !!property.name);
23 | }
24 | });
25 |
26 | // remove associations
27 | model.associations = [];
28 |
29 | // filter DC
30 | model.types = model.types.filter(({ name }) => {
31 | return name && name.includes('dc:');
32 | });
33 |
34 | model.enumerations = model.enumerations.filter(({ name }) => {
35 | return name && name.includes('dc:');
36 | });
37 |
38 | // remove dc:Style (belongs to DI, not DC)
39 | model.types = model.types.filter(({ name }) => {
40 | return name !== 'dc:Style';
41 | });
42 |
43 | model = removePrefixes(model, prefix);
44 |
45 | model = removeWhitespace(model);
46 |
47 | const file = fs.readFileSync('resources/dmn/xsd/DC.xsd', 'utf8');
48 |
49 | const xsd = await parseXML(file);
50 |
51 | model = fixSequence(model, xsd);
52 |
53 | // set uri
54 | model.uri = xsd.elementsByTagName[ 'xsd:schema' ][ 0 ].targetNamespace;
55 |
56 | return model;
57 | };
58 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmndi/label.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Decision_1
15 |
16 |
17 |
18 |
19 |
20 | InputData_1
21 |
22 |
23 |
24 |
25 |
26 |
27 | InformationRequirement_1
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/test/fixtures/dmn/dmndi/size.dmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | "Hello " + Full Name
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/test/spec/dmn-moddle.js:
--------------------------------------------------------------------------------
1 | import expect from '../expect.js';
2 |
3 | import DmnModdle from '../../lib/index.js';
4 |
5 |
6 | describe('dmn-moddle', function() {
7 |
8 | let moddle;
9 |
10 | beforeEach(function() {
11 | moddle = new DmnModdle();
12 | });
13 |
14 |
15 | describe('#getType', function() {
16 |
17 | it('should get type of Decision', function() {
18 |
19 | // when
20 | const type = moddle.getType('dmn:Decision');
21 |
22 | // then
23 | expect(type).to.exist;
24 | expect(type.$descriptor).to.exist;
25 | });
26 |
27 | });
28 |
29 |
30 | describe('#create', function() {
31 |
32 | it('should create DMNElement', function() {
33 |
34 | // when
35 | const dmnElement = moddle.create('dmn:DMNElement');
36 |
37 | // then
38 | expect(dmnElement.$type).to.eql('dmn:DMNElement');
39 | });
40 |
41 |
42 | it('should create Definitions', function() {
43 |
44 | // when
45 | const definitions = moddle.create('dmn:Definitions');
46 |
47 | // then
48 | expect(definitions.$type).to.eql('dmn:Definitions');
49 | });
50 |
51 |
52 | it('should create Decision', function() {
53 |
54 | // when
55 | var decision = moddle.create('dmn:Decision');
56 |
57 | // then
58 | expect(decision.$type).to.eql('dmn:Decision');
59 | expect(decision.$instanceOf('dmn:DRGElement')).to.be.true;
60 | });
61 |
62 |
63 | it('should create DecisionTable', function() {
64 |
65 | // when
66 | const decisionTable = moddle.create('dmn:DecisionTable');
67 |
68 | // then
69 | expect(decisionTable.$type).to.eql('dmn:DecisionTable');
70 | expect(decisionTable.$instanceOf('dmn:Expression')).to.be.true;
71 | });
72 |
73 |
74 | it('should create TextAnnotation', function() {
75 |
76 | // when
77 | const annotation = moddle.create('dmn:TextAnnotation');
78 |
79 | // then
80 | expect(annotation.textFormat).to.eql('text/plain');
81 | });
82 |
83 | });
84 |
85 | });
86 |
--------------------------------------------------------------------------------
/tasks/generate-schema.cjs:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs');
2 |
3 | const parseFile = require('cmof-parser');
4 |
5 | const {
6 | transformDC,
7 | transformDI,
8 | transformDMN,
9 | transformDMNDI
10 | } = require('./transforms/index.cjs');
11 |
12 | async function generateSchema(files) {
13 | files.forEach(async file => {
14 | const {
15 | options,
16 | source,
17 | target,
18 | transform
19 | } = file;
20 |
21 | const parsed = await parseFile(fs.readFileSync(source, 'utf8'), options);
22 |
23 | const transformed = await transform(parsed);
24 |
25 | fs.writeFileSync(target, JSON.stringify(transformed, null, 2));
26 | });
27 | }
28 |
29 | generateSchema([
30 | {
31 | source: 'resources/dmn/xmi/DMN13.xmi',
32 | target: 'resources/dmn/json/dmn13.json',
33 | transform: transformDMN,
34 | options: {
35 | clean: true,
36 | prefixNamespaces: {
37 | 'DC': 'dc',
38 | 'DI': 'di',
39 | 'http://www.omg.org/spec/BMM/20130801/BMM.xmi': 'bmm',
40 | 'http://www.omg.org/spec/BPMN/20100501/BPMN20.cmof': 'bpmn',
41 | 'https://www.omg.org/spec/DMN/20191111/DMNDI13.xmi': 'dmndi'
42 | }
43 | }
44 | },
45 | {
46 | source: 'resources/dmn/xmi/DMNDI13.xmi',
47 | target: 'resources/dmn/json/dmndi13.json',
48 | transform: transformDMNDI,
49 | options: {
50 | clean: true,
51 | prefixNamespaces: {
52 | 'DC': 'dc',
53 | 'DI': 'di'
54 | }
55 | }
56 | },
57 | {
58 | source: 'resources/dmn/xmi/DMNDI13.xmi',
59 | target: 'resources/dmn/json/dc.json',
60 | transform: transformDC,
61 | options: {
62 | clean: true,
63 | prefixNamespaces: {
64 | 'DC': 'dc',
65 | 'DI': 'di'
66 | }
67 | }
68 | },
69 | {
70 | source: 'resources/dmn/xmi/DMNDI13.xmi',
71 | target: 'resources/dmn/json/di.json',
72 | transform: transformDI,
73 | options: {
74 | clean: true,
75 | prefixNamespaces: {
76 | 'DC': 'dc',
77 | 'DI': 'di'
78 | }
79 | }
80 | }
81 | ]);
82 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dmn-moddle",
3 | "version": "11.0.0",
4 | "description": "A moddle wrapper for DMN 1.3",
5 | "scripts": {
6 | "all": "run-s generate-schema lint test distro",
7 | "lint": "eslint .",
8 | "dev": "npm test -- --watch",
9 | "test": "mocha --reporter=spec --recursive test/spec",
10 | "distro": "run-s build test:build",
11 | "build": "rollup -c",
12 | "test:build": "mocha --reporter=spec --recursive test/distro",
13 | "prepublishOnly": "run-s distro",
14 | "generate-schema": "node tasks/generate-schema.cjs",
15 | "test:tck": "npm run test:integration",
16 | "test:integration": "mocha --reporter=spec test/integration/*.js"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/bpmn-io/dmn-moddle"
21 | },
22 | "type": "module",
23 | "exports": {
24 | ".": {
25 | "import": "./dist/index.js",
26 | "require": "./dist/index.cjs"
27 | },
28 | "./resources/*": "./resources/*",
29 | "./package.json": "./package.json"
30 | },
31 | "engines": {
32 | "node": ">= 18"
33 | },
34 | "keywords": [
35 | "dmn",
36 | "moddle",
37 | "meta-model"
38 | ],
39 | "author": {
40 | "name": "Sebastian Stamm",
41 | "url": "https://github.com/SebastianStamm"
42 | },
43 | "contributors": [
44 | {
45 | "name": "bpmn.io contributors",
46 | "url": "https://github.com/bpmn-io"
47 | }
48 | ],
49 | "license": "MIT",
50 | "sideEffects": false,
51 | "devDependencies": {
52 | "@rollup/plugin-json": "^6.1.0",
53 | "chai": "^4.4.1",
54 | "cmof-parser": "^0.5.1",
55 | "eslint": "^9.31.0",
56 | "eslint-plugin-bpmn-io": "^2.2.0",
57 | "glob": "^11.0.3",
58 | "jsondiffpatch": "^0.7.3",
59 | "mocha": "^10.3.0",
60 | "npm-run-all2": "^8.0.0",
61 | "rollup": "^4.12.1",
62 | "sax": "^1.2.4",
63 | "tiny-stack": "^2.0.1",
64 | "xsd-schema-validator": "^0.10.0"
65 | },
66 | "dependencies": {
67 | "min-dash": "^4.0.0",
68 | "moddle": "^7.0.0",
69 | "moddle-xml": "^11.0.0"
70 | },
71 | "files": [
72 | "dist",
73 | "resources",
74 | "!resources/dmn/xmi"
75 | ]
76 | }
77 |
--------------------------------------------------------------------------------
/resources/dmn/json/dc.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "DC",
3 | "prefix": "dc",
4 | "uri": "http://www.omg.org/spec/DMN/20180521/DC/",
5 | "types": [
6 | {
7 | "name": "Dimension",
8 | "properties": [
9 | {
10 | "name": "width",
11 | "isAttr": true,
12 | "type": "Real"
13 | },
14 | {
15 | "name": "height",
16 | "isAttr": true,
17 | "type": "Real"
18 | }
19 | ]
20 | },
21 | {
22 | "name": "Bounds",
23 | "properties": [
24 | {
25 | "name": "height",
26 | "isAttr": true,
27 | "type": "Real"
28 | },
29 | {
30 | "name": "width",
31 | "isAttr": true,
32 | "type": "Real"
33 | },
34 | {
35 | "name": "x",
36 | "isAttr": true,
37 | "type": "Real"
38 | },
39 | {
40 | "name": "y",
41 | "isAttr": true,
42 | "type": "Real"
43 | }
44 | ]
45 | },
46 | {
47 | "name": "Point",
48 | "properties": [
49 | {
50 | "name": "x",
51 | "isAttr": true,
52 | "type": "Real"
53 | },
54 | {
55 | "name": "y",
56 | "isAttr": true,
57 | "type": "Real"
58 | }
59 | ]
60 | },
61 | {
62 | "name": "Color",
63 | "properties": [
64 | {
65 | "name": "red",
66 | "type": "UML_Standard_Profile.mdzip:eee_1045467100323_917313_65"
67 | },
68 | {
69 | "name": "green",
70 | "type": "UML_Standard_Profile.mdzip:eee_1045467100323_917313_65"
71 | },
72 | {
73 | "name": "blue",
74 | "type": "UML_Standard_Profile.mdzip:eee_1045467100323_917313_65"
75 | }
76 | ]
77 | }
78 | ],
79 | "associations": [],
80 | "enumerations": [
81 | {
82 | "name": "AlignmentKind",
83 | "literalValues": [
84 | {
85 | "name": "start"
86 | },
87 | {
88 | "name": "center"
89 | },
90 | {
91 | "name": "end"
92 | }
93 | ]
94 | }
95 | ]
96 | }
--------------------------------------------------------------------------------
/test/spec/xml/roundtrip.js:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs';
2 | import process from 'node:process';
3 |
4 | import { validateXML } from 'xsd-schema-validator';
5 |
6 | import DmnModdle from '../../../lib/index.js';
7 |
8 | import { expect } from 'chai';
9 |
10 | const xsd = 'resources/dmn/xsd/DMN13.xsd';
11 |
12 |
13 | describe('dmn-moddle - roundtrip', function() {
14 |
15 | this.timeout(30000);
16 |
17 |
18 | describe('dmn', function() {
19 |
20 | it('dmn:Decision', roundtrip('test/fixtures/dmn/dmn/decision.dmn'));
21 |
22 |
23 | it('dmn:InputData', roundtrip('test/fixtures/dmn/dmn/input-data.dmn'));
24 |
25 |
26 | it('dmn:Context', roundtrip('test/fixtures/dmn/dmn/context.dmn'));
27 |
28 |
29 | it('dmn:Import', roundtrip('test/fixtures/dmn/dmn/definitions-import.dmn'));
30 |
31 | });
32 |
33 |
34 | describe('di', function() {
35 |
36 | it('dmn:Decision + DI', roundtrip('test/fixtures/dmn/dmndi/decision.dmn'));
37 |
38 |
39 | it('dmn:InputData + DI', roundtrip('test/fixtures/dmn/dmndi/input-data.dmn'));
40 |
41 |
42 | it('dmn:TextAnnotation + DI', roundtrip('test/fixtures/dmn/dmndi/text-annotation.dmn'));
43 |
44 |
45 | it('with Label', roundtrip('test/fixtures/dmn/dmndi/label.dmn'));
46 |
47 |
48 | it('with Size', roundtrip('test/fixtures/dmn/dmndi/size.dmn'));
49 |
50 |
51 | it('with di:Extension', roundtrip('test/fixtures/dmn/dmndi/extension.dmn'));
52 |
53 | });
54 |
55 |
56 | describe('biodi', function() {
57 |
58 | it('biodi', roundtrip('test/fixtures/dmn/biodi/biodi.dmn'));
59 |
60 | });
61 | });
62 |
63 |
64 | // helpers ////////////////
65 |
66 | function roundtrip(fileName) {
67 | return async function() {
68 | const moddle = new DmnModdle();
69 |
70 | const file = fs.readFileSync(fileName, 'utf8');
71 |
72 | const {
73 | rootElement: definitions,
74 | warnings
75 | } = await moddle.fromXML(file, 'dmn:Definitions');
76 |
77 | if (process.env.VERBOSE && warnings.length > 0) {
78 | console.log('import warnings', warnings);
79 | }
80 |
81 | expect(warnings).to.be.empty;
82 |
83 | const { xml } = await moddle.toXML(definitions, { format: true });
84 |
85 | await validateXML(xml, xsd);
86 | };
87 | }
88 |
--------------------------------------------------------------------------------
/tasks/transforms/transformDMNDI.cjs:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs');
2 |
3 | const {
4 | findProperty,
5 | findType,
6 | fixSequence,
7 | parseXML,
8 | removeProperty,
9 | removeWhitespace
10 | } = require('./helper.cjs');
11 |
12 | module.exports = async function(results) {
13 | const { elementsByType } = results;
14 |
15 | let model = elementsByType[ 'uml:Package' ][ 0 ];
16 |
17 | // remove associations
18 | model.associations = [];
19 |
20 | // remove DC and DI
21 | model.types = model.types.filter(({ name }) => {
22 | return name && !name.includes(':');
23 | });
24 |
25 | model.enumerations = model.enumerations.filter(({ name }) => {
26 | return name && !name.includes(':');
27 | });
28 |
29 | // fix super class of DMNStyle
30 | findType('DMNStyle', model).superClass = [ 'di:Style' ];
31 |
32 | // reverse order of DMNEdge superclasses
33 | findType('DMNEdge', model).superClass.reverse();
34 |
35 | // fix DMNLabel
36 | const text = findProperty('DMNLabel#text', model);
37 |
38 | delete text.isAttr;
39 |
40 | text.type = 'Text';
41 |
42 | model.types.push({
43 | name: 'Text',
44 | properties: [ {
45 | name: 'text',
46 | isBody: true,
47 | type: 'String'
48 | } ]
49 | });
50 |
51 | removeProperty('DMNStyle#id', model);
52 |
53 | // fix dmndi:DMNDiagram#sharedStyle and dmndi:DMNDiagramElement#sharedStyle
54 | // by redefining di:DiagramElement#sharedStyle
55 | findProperty('DMNDiagram#sharedStyle', model).redefines = 'di:DiagramElement#sharedStyle';
56 | findProperty('DMNDiagramElement#sharedStyle', model).redefines = 'di:DiagramElement#sharedStyle';
57 |
58 | // fix dmndi:DMNDiagram#dmnElementRef and dmndi:DMNDiagramElement#dmnElementRef type prefix
59 | findProperty('DMNDiagram#dmnElementRef', model).type = 'dmn:DMNElement';
60 | findProperty('DMNDiagramElement#dmnElementRef', model).type = 'dmn:DMNElement';
61 |
62 | // add dmndi:Size and change dmndi:DMNDiagram#size type to that
63 | model.types.push({
64 | name: 'Size',
65 | superClass: [
66 | 'dc:Dimension'
67 | ]
68 | });
69 | findProperty('DMNDiagram#size', model).type = 'Size';
70 |
71 | model = removeWhitespace(model);
72 |
73 | const file = fs.readFileSync('resources/dmn/xsd/DMNDI13.xsd', 'utf8');
74 |
75 | const xsd = await parseXML(file);
76 |
77 | model = fixSequence(model, xsd);
78 |
79 | // set uri
80 | model.uri = xsd.elementsByTagName[ 'xsd:schema' ][ 0 ].targetNamespace;
81 |
82 | return model;
83 | };
84 |
--------------------------------------------------------------------------------
/lib/dmn-moddle.js:
--------------------------------------------------------------------------------
1 | import {
2 | isString,
3 | assign
4 | } from 'min-dash';
5 |
6 | import {
7 | Moddle
8 | } from 'moddle';
9 |
10 | import {
11 | Reader,
12 | Writer
13 | } from 'moddle-xml';
14 |
15 |
16 | /**
17 | * A sub class of {@link Moddle} with support for import and export of DMN xml files.
18 | *
19 | * @class DmnModdle
20 | * @extends Moddle
21 | *
22 | * @param {Object|Array} packages to use for instantiating the model
23 | * @param {Object} [options] additional options to pass over
24 | */
25 | export default function DmnModdle(packages, options) {
26 | Moddle.call(this, packages, options);
27 | }
28 |
29 | DmnModdle.prototype = Object.create(Moddle.prototype);
30 |
31 | /**
32 | * The fromXML result.
33 | *
34 | * @typedef {Object} ParseResult
35 | *
36 | * @property {ModdleElement} rootElement
37 | * @property {Array