├── 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 | [![CI](https://github.com/bpmn-io/dmn-moddle/workflows/CI/badge.svg)](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} references 38 | * @property {Array} warnings 39 | * @property {Object} elementsById - a mapping containing each ID -> ModdleElement 40 | */ 41 | 42 | /** 43 | * The fromXML error. 44 | * 45 | * @typedef {Error} ParseError 46 | * 47 | * @property {Array} warnings 48 | */ 49 | 50 | /** 51 | * Instantiates a DMN model tree from a given xml string. 52 | * 53 | * @param {String} xmlStr 54 | * @param {String} [typeName='dmn:Definitions'] name of the root element 55 | * @param {Object} [options] options to pass to the underlying reader 56 | * 57 | * @returns {Promise} 58 | */ 59 | DmnModdle.prototype.fromXML = function(xmlStr, typeName, options) { 60 | 61 | if (!isString(typeName)) { 62 | options = typeName; 63 | typeName = 'dmn:Definitions'; 64 | } 65 | 66 | var reader = new Reader(assign({ model: this, lax: true }, options)); 67 | var rootHandler = reader.handler(typeName); 68 | 69 | return reader.fromXML(xmlStr, rootHandler); 70 | }; 71 | 72 | /** 73 | * The toXML result. 74 | * 75 | * @typedef {Object} SerializationResult 76 | * 77 | * @property {String} xml 78 | */ 79 | 80 | /** 81 | * Serializes a DMN object tree to XML. 82 | * 83 | * @param {String} element the root element, typically an instance of `Definitions` 84 | * @param {Object} [options] to pass to the underlying writer 85 | * 86 | * @returns {Promise} 87 | */ 88 | DmnModdle.prototype.toXML = function(element, options) { 89 | 90 | var writer = new Writer(options); 91 | 92 | return new Promise(function(resolve, reject) { 93 | try { 94 | var result = writer.toXML(element); 95 | 96 | return resolve({ 97 | xml: result 98 | }); 99 | } catch (err) { 100 | return reject(err); 101 | } 102 | }); 103 | }; 104 | -------------------------------------------------------------------------------- /resources/dmn/json/di.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DI", 3 | "prefix": "di", 4 | "uri": "http://www.omg.org/spec/DMN/20180521/DI/", 5 | "types": [ 6 | { 7 | "name": "DiagramElement", 8 | "isAbstract": true, 9 | "properties": [ 10 | { 11 | "name": "extension", 12 | "type": "Extension" 13 | }, 14 | { 15 | "name": "id", 16 | "isAttr": true, 17 | "isId": true, 18 | "type": "String" 19 | }, 20 | { 21 | "name": "style", 22 | "isReference": true, 23 | "type": "Style", 24 | "xml": { 25 | "serialize": "property" 26 | } 27 | }, 28 | { 29 | "name": "sharedStyle", 30 | "isReference": true, 31 | "isVirtual": true, 32 | "type": "Style" 33 | } 34 | ] 35 | }, 36 | { 37 | "name": "Diagram", 38 | "superClass": [ 39 | "DiagramElement" 40 | ], 41 | "properties": [ 42 | { 43 | "name": "name", 44 | "isAttr": true, 45 | "type": "String" 46 | }, 47 | { 48 | "name": "documentation", 49 | "isAttr": true, 50 | "type": "String" 51 | }, 52 | { 53 | "name": "resolution", 54 | "isAttr": true, 55 | "type": "Real" 56 | } 57 | ] 58 | }, 59 | { 60 | "name": "Shape", 61 | "isAbstract": true, 62 | "properties": [ 63 | { 64 | "name": "bounds", 65 | "type": "dc:Bounds" 66 | } 67 | ], 68 | "superClass": [ 69 | "DiagramElement" 70 | ] 71 | }, 72 | { 73 | "name": "Edge", 74 | "isAbstract": true, 75 | "properties": [ 76 | { 77 | "name": "waypoint", 78 | "type": "dc:Point", 79 | "isMany": true, 80 | "xml": { 81 | "serialize": "property" 82 | } 83 | } 84 | ], 85 | "superClass": [ 86 | "DiagramElement" 87 | ] 88 | }, 89 | { 90 | "name": "Style", 91 | "isAbstract": true, 92 | "properties": [ 93 | { 94 | "name": "id", 95 | "isAttr": true, 96 | "isId": true, 97 | "type": "String" 98 | } 99 | ] 100 | }, 101 | { 102 | "name": "Extension", 103 | "properties": [ 104 | { 105 | "name": "values", 106 | "isMany": true, 107 | "type": "Element" 108 | } 109 | ] 110 | } 111 | ], 112 | "associations": [], 113 | "enumerations": [], 114 | "xml": { 115 | "tagAlias": "lowerCase" 116 | } 117 | } -------------------------------------------------------------------------------- /test/spec/xml/edit.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | 3 | import expect from '../../expect.js'; 4 | 5 | import DmnModdle from '../../../lib/index.js'; 6 | 7 | import { validateXML } from 'xsd-schema-validator'; 8 | 9 | const xsd = 'resources/dmn/xsd/DMN13.xsd'; 10 | 11 | describe('dmn-moddle - edit', function() { 12 | 13 | let moddle; 14 | 15 | beforeEach(function() { 16 | moddle = new DmnModdle(); 17 | }); 18 | 19 | function readFromFile(fileName, root = 'dmn:Definitions') { 20 | const file = fs.readFileSync(fileName, 'utf8'); 21 | 22 | return moddle.fromXML(file, root); 23 | } 24 | 25 | function write(element, options = { preamble: false }) { 26 | return moddle.toXML(element, options); 27 | } 28 | 29 | describe('dmn', function() { 30 | 31 | it('should edit dmn:Decision name', async function() { 32 | 33 | // given 34 | const expected = 35 | '' + 36 | '' + 37 | ''; 38 | 39 | const { rootElement: definitions } = await readFromFile('test/fixtures/dmn/dmn/decision.dmn'); 40 | 41 | // when 42 | definitions.name = 'Foo'; 43 | 44 | const { xml } = await write(definitions, { preamble: false }); 45 | 46 | // then 47 | expect(xml).to.equal(expected); 48 | }); 49 | 50 | 51 | it('should create dmn:ItemDefinition', async function() { 52 | 53 | const def_1 = moddle.create('dmn:ItemDefinition', { 54 | typeLanguage: 'FEEL', 55 | name: 'def_1', 56 | typeRef: 'xsd:string', 57 | allowedValues: moddle.create('dmn:UnaryTests', { 58 | text: 'a, b' 59 | }) 60 | }); 61 | 62 | const def_2_component = moddle.create('dmn:ItemDefinition', { 63 | typeLanguage: 'FEEL', 64 | name: 'def_2_component', 65 | typeRef: 'xsd:string', 66 | allowedValues: moddle.create('dmn:UnaryTests', { 67 | text: '[0..5]' 68 | }) 69 | }); 70 | 71 | const def_2 = moddle.create('dmn:ItemDefinition', { 72 | name: 'def_2', 73 | itemComponent: [ 74 | def_2_component 75 | ], 76 | isCollection: true 77 | }); 78 | 79 | const def_3 = moddle.create('dmn:ItemDefinition', { 80 | name: 'def_3', 81 | functionItem: moddle.create('dmn:FunctionItem', { 82 | parameters: [ 83 | moddle.create('dmn:InformationItem', { 84 | typeRef: 'string', 85 | name: 'ProductType' 86 | }), 87 | moddle.create('dmn:InformationItem', { 88 | typeRef: 'number', 89 | name: 'Rate' 90 | }) 91 | ], 92 | outputTypeRef: 'xsi:string' 93 | }) 94 | }); 95 | 96 | const definitions = moddle.create('dmn:Definitions', { 97 | name: 'foo', 98 | namespace: 'http://foo', 99 | itemDefinition: [ 100 | def_1, 101 | def_2, 102 | def_3 103 | ] 104 | }); 105 | 106 | // when 107 | const { xml } = await write(definitions); 108 | 109 | // then 110 | await validateXML(xml, xsd); 111 | }); 112 | 113 | }); 114 | 115 | }); 116 | -------------------------------------------------------------------------------- /tasks/transforms/transformDI.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | 3 | const { 4 | findProperty, 5 | findType, 6 | fixSequence, 7 | parseXML, 8 | removePrefixes, 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 | model.name = 'DI'; 18 | 19 | const prefix = model.prefix = 'di'; 20 | 21 | model.xml = { 22 | tagAlias: 'lowerCase' 23 | }; 24 | 25 | // remove properties without name 26 | model.types.forEach(type => { 27 | if (type.properties) { 28 | type.properties = type.properties.filter(property => !!property.name); 29 | } 30 | }); 31 | 32 | // remove associations 33 | model.associations = []; 34 | 35 | // move dc:Style from DC to DI (belongs to DI, not DC) 36 | const style = findType('dc:Style', model); 37 | 38 | style.name = 'di:Style'; 39 | 40 | // add di:Style#id (cf. https://github.com/omg-dmn-taskforce/omg-dmn-spec/issues/10) 41 | if (!style.properties) { 42 | style.properties = []; 43 | } 44 | 45 | style.properties.push({ 46 | name: 'id', 47 | isAttr: true, 48 | isId: true, 49 | type: 'String' 50 | }); 51 | 52 | // add di:DiagramElement#id and di:DiagramElement#style 53 | // (cf. https://github.com/omg-dmn-taskforce/omg-dmn-spec/issues/9) 54 | // sharedStyle is not added because it is already redefined in DMNDI 55 | const diagramElement = findType('di:DiagramElement', model); 56 | 57 | if (!diagramElement.properties) { 58 | diagramElement.properties = []; 59 | } 60 | 61 | diagramElement.properties.push({ 62 | name: 'extension', 63 | type: 'Extension' 64 | }, 65 | { 66 | name: 'id', 67 | isAttr: true, 68 | isId: true, 69 | type: 'String' 70 | }, { 71 | name: 'style', 72 | isReference: true, 73 | type: 'Style', 74 | xml: { 75 | serialize: 'property' 76 | } 77 | }, { 78 | name: 'sharedStyle', 79 | isReference: true, 80 | isVirtual: true, 81 | type: 'Style' 82 | }); 83 | 84 | // add superclass di:DiagramElement to di:Edge and di:Shape 85 | findType('di:Edge', model).superClass = [ 'di:DiagramElement' ]; 86 | findType('di:Shape', model).superClass = [ 'di:DiagramElement' ]; 87 | 88 | // rename di:Edge#wayPoints as specified in XSD 89 | // (cf. https://github.com/omg-dmn-taskforce/omg-dmn-spec/issues/11) 90 | const wayPoints = findProperty('di:Edge#wayPoints', model); 91 | 92 | wayPoints.name = 'waypoint'; 93 | 94 | wayPoints.xml = { 95 | serialize: 'property' 96 | }; 97 | 98 | // filter DI 99 | model.types = model.types.filter(({ name }) => { 100 | return name && name.includes('di:'); 101 | }); 102 | 103 | model.types.push({ 104 | name: 'Extension', 105 | properties: [ 106 | { 107 | name: 'values', 108 | isMany: true, 109 | type: 'Element' 110 | } 111 | ] 112 | }); 113 | 114 | model.enumerations = model.enumerations.filter(({ name }) => { 115 | return name && name.includes('di:'); 116 | }); 117 | 118 | model = removePrefixes(model, prefix); 119 | 120 | model = removeWhitespace(model); 121 | 122 | const file = fs.readFileSync('resources/dmn/xsd/DI.xsd', 'utf8'); 123 | 124 | const xsd = await parseXML(file); 125 | 126 | model = fixSequence(model, xsd); 127 | 128 | // set uri 129 | model.uri = xsd.elementsByTagName[ 'xsd:schema' ][ 0 ].targetNamespace; 130 | 131 | return model; 132 | }; 133 | -------------------------------------------------------------------------------- /test/integration/tck.js: -------------------------------------------------------------------------------- 1 | import { spawnSync as exec } from 'node:child_process'; 2 | 3 | import { globSync as glob } from 'glob'; 4 | 5 | import { 6 | readFileSync as readFile, 7 | existsSync as exists 8 | } from 'node:fs'; 9 | 10 | import path from 'node:path'; 11 | import process from 'node:process'; 12 | 13 | import DmnModdle from 'dmn-moddle'; 14 | 15 | import { validateXML } from 'xsd-schema-validator'; 16 | 17 | import { expect } from 'chai'; 18 | 19 | 20 | const DMN_XSD = 'resources/dmn/xsd/DMN13.xsd'; 21 | 22 | const __dirname = path.join(import.meta.dirname, '../..'); 23 | 24 | const tckDirectory = 'tmp/tck'; 25 | 26 | const tckBranch = 'master'; 27 | 28 | 29 | describe('dmn-moddle - TCK roundtrip', function() { 30 | 31 | const moddle = new DmnModdle(); 32 | 33 | this.timeout(30000); 34 | 35 | if (exists(path.join(tckDirectory, '.git'))) { 36 | console.log(`dmn-tck/tck repository already cloned, fetching and checking out ${tckBranch}...`); 37 | 38 | const cwd = path.join(__dirname, tckDirectory); 39 | 40 | exec('git', [ 'fetch' ], { cwd }); 41 | exec('git', [ 'checkout', tckBranch ], { cwd }); 42 | } else { 43 | console.log(`cloning repository dmn-tck/tck#${tckBranch} to ${ tckDirectory }...`); 44 | 45 | exec( 46 | 'git', 47 | [ 'clone', '--depth=1', `--branch=${tckBranch}`, 'https://github.com/dmn-tck/tck.git', tckDirectory ], 48 | { cwd: __dirname } 49 | ); 50 | } 51 | 52 | const fileNames = glob(tckDirectory + '/TestCases/**/*.dmn', { cwd: __dirname }); 53 | 54 | function shouldRun(fileName) { 55 | if ([ '0012-list-functions.dmn', '0086-import.dmn' ].some(f => fileName.endsWith(f))) { 56 | return false; 57 | } 58 | 59 | // DMN 1.5 (unsupported) 60 | if ([ 61 | '1154-boxed-every.dmn', 62 | '1161-boxed-list-expression.dmn', 63 | '1153-boxed-some.dmn', 64 | '1152-boxed-for.dmn', 65 | '1150-boxed-conditional.dmn', 66 | '1151-boxed-filter.dmn' 67 | ].some(f => fileName.endsWith(f))) { 68 | return false; 69 | } 70 | 71 | const match = process.env.GREP; 72 | 73 | return !match || fileName.toLowerCase().includes(match); 74 | } 75 | 76 | describe('should roundtrip', function() { 77 | 78 | for (const fileName of fileNames) { 79 | 80 | (shouldRun(fileName) ? it : it.skip)(fileName, async function() { 81 | 82 | this.timeout(5000); 83 | 84 | // given 85 | let fileContents = readFile(`${ __dirname }/${ fileName }`, 'utf8'); 86 | 87 | // replace DMN 1.2 namespaces with DMN 1.3 namespaces 88 | fileContents = replaceNamespaces(fileContents, { 89 | 90 | // DMN 1.2 91 | 'http://www.omg.org/spec/DMN/20180521/MODEL/': 'https://www.omg.org/spec/DMN/20191111/MODEL/', 92 | 'http://www.omg.org/spec/DMN/20180521/DMNDI/': 'https://www.omg.org/spec/DMN/20191111/DMNDI/', 93 | 94 | // DMN 1.4 95 | 'https://www.omg.org/spec/DMN/20211108/MODEL/': 'https://www.omg.org/spec/DMN/20191111/DMNDI/', 96 | 97 | // DMN 1.5 98 | 'https://www.omg.org/spec/DMN/20230324/MODEL/': 'https://www.omg.org/spec/DMN/20191111/MODEL/', 99 | 'https://www.omg.org/spec/DMN/20230324/DMNDI/': 'https://www.omg.org/spec/DMN/20191111/DMNDI/' 100 | }); 101 | 102 | // when 103 | const { 104 | rootElement: definitions, 105 | warnings 106 | } = await moddle.fromXML(fileContents, 'dmn:Definitions'); 107 | 108 | const warningsFiltered = filterIgnored(warnings, fileName); 109 | 110 | if (process.env.VERBOSE && warnings.length > 0) { 111 | console.log('import warnings', warningsFiltered); 112 | } 113 | 114 | expect(warningsFiltered, 'import warnings').to.be.empty; 115 | 116 | // then 117 | const { xml } = await moddle.toXML(definitions, { format: true }); 118 | 119 | await validateXML(xml, DMN_XSD); 120 | }); 121 | } 122 | }); 123 | }); 124 | 125 | /** 126 | * Replace namespaces in XML. 127 | * 128 | * @param {string} xml 129 | * @param {Object} namespaces 130 | * 131 | * @returns {string} 132 | */ 133 | function replaceNamespaces(xml, namespaces) { 134 | for (const namespace in namespaces) { 135 | const namespaceRegExp = new RegExp(namespace, 'g'); 136 | 137 | xml = xml.replace(namespaceRegExp, namespaces[ namespace ]); 138 | } 139 | 140 | return xml; 141 | } 142 | 143 | function filterIgnored(warnings, fileName) { 144 | 145 | if (fileName.endsWith('0086-import.dmn')) { 146 | return warnings.filter(err => err.message !== 'unresolved reference '); 147 | } 148 | 149 | if (fileName.endsWith('0012-list-functions.dmn')) { 150 | return warnings.filter(err => err.message !== 'unresolved reference <_883c9eb4-1f4f-4885-82c0-234f9ac9a1d7>'); 151 | } 152 | 153 | return warnings; 154 | } -------------------------------------------------------------------------------- /resources/dmn/xsd/DMNDI13.xsd: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | This element should never be instantiated directly, but rather concrete implementation should. It is placed there only to be referred in the sequence 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /resources/dmn/xsd/DI.xsd: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 11 | 12 | The Diagram Interchange (DI) package enables interchange of graphical information that language users have control over, such as position of nodes and line routing points. Language specifications specialize elements of DI to define diagram interchange elements for a language. 13 | 14 | 15 | 16 | 17 | This element should never be instantiated directly, but rather concrete implementation should. It is placed there only to be referred in the sequence 18 | 19 | 20 | 21 | 22 | 23 | DiagramElement is the abstract super type of all elements in diagrams, including diagrams themselves. When contained in a diagram, diagram elements are laid out relative to the diagram's origin. 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | an optional locally-owned style for this diagram element. 36 | 37 | 38 | 39 | 40 | 41 | a reference to an optional shared style element for this diagram element. 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | the name of the diagram. 54 | 55 | 56 | 57 | 58 | the documentation of the diagram. 59 | 60 | 61 | 62 | 63 | the resolution of the diagram expressed in user units per inch. 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | the optional bounds of the shape relative to the origin of its nesting plane. 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | an optional list of points relative to the origin of the nesting diagram that specifies the connected line segments of the edge 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Style contains formatting properties that affect the appearance or style of diagram elements, including diagram themselves. 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to [dmn-moddle](https://github.com/bpmn-io/dmn-moddle) 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 `exports` configuration ([#29](https://github.com/bpmn-io/dmn-moddle/pull/29)) 12 | * `FIX`: remove broken `main` export ([#29](https://github.com/bpmn-io/dmn-moddle/pull/29)) 13 | * `CHORE`: drop UMD distribution ([#29](https://github.com/bpmn-io/bpmn-moddle/pull/110)) 14 | * `CHORE`: turn into ES module ([#29](https://github.com/bpmn-io/dmn-moddle/pull/29)) 15 | * `CHORE`: require Node >= 18 16 | * `DEPS`: update to `moddle@7.0.0` ([#29](https://github.com/bpmn-io/dmn-moddle/pull/29)) 17 | * `DEPS`: update to `moddle-xml@11.0.0` ([#29](https://github.com/bpmn-io/dmn-moddle/pull/29)) 18 | 19 | ### Breaking Changes 20 | 21 | * Require Node >= 18 22 | * Drop UMD distribution. Use ES module export in modern JavaScript run-times 23 | 24 | ## 10.0.0 25 | 26 | * `FEAT`: promisify `#fromXML` and `#toXML` APIs ([#19](https://github.com/bpmn-io/dmn-moddle/issues/19)) 27 | * `FIX`: support `di:extension` elements ([#20](https://github.com/bpmn-io/dmn-moddle/issues/20)) 28 | * `CHORE`: bump to `moddle-xml@9.0.5` 29 | 30 | ### Breaking Changes 31 | 32 | * `#fromXML` and `#toXML` APIs now return a Promise. These APIs don't support callbacks anymore and their usage requires to move from using Callbacks to Promises. 33 | 34 | ## 9.1.0 35 | 36 | * `FEAT`: add biodi@2.0 package which allows to set columns width ([#15](https://github.com/bpmn-io/dmn-moddle/pull/15)) 37 | * `FIX`: use lowercase DMN prefix ([#8](https://github.com/bpmn-io/dmn-moddle/issues/8)) 38 | 39 | ## 9.0.0 40 | 41 | _An update of the meta-model to better represent the DMN schema ([#12](https://github.com/bpmn-io/dmn-moddle/pull/12))._ 42 | 43 | * `FEAT`: properly represent DMN model elements using DMN schema derived inheritance ([#12](https://github.com/bpmn-io/dmn-moddle/pull/12)) 44 | * `FIX`: correctly import `dmn:Context` elements 45 | * `FIX`: correct `FunctionItem#parameters` 46 | * `CHORE`: remove `Decision#literalExpression`, `Decision#decisionTable` accessors in favor of DMN schema derived `Decision#decisionLogic` 47 | * `CHORE`: remove `Definitions#performanceIndicator`, `Definitions#organizationUnit` accessors in favor of DMN schema derived `Definitions#businessContextElement` 48 | * `CHORE`: rename `List#element` to `List#elements` to match DMN schema 49 | * `CHORE`: remove unused virtual / inverse properties 50 | * `CHORE`: remove unneeded serialization hints 51 | 52 | ### Breaking Changes 53 | 54 | * As indicated above, access to certain elements, most importantly `Decision#...` is now done via DMN standards derived accessors. This improves the robustness of import, export, validation and error handling and prevents unintentional miss-use. 55 | 56 | ## 8.0.4 57 | 58 | * `FIX`: correct `dmn:TextAnnotation#textFormat` default 59 | 60 | ## 8.0.3 61 | 62 | * `FIX`: do not assign duplicate `dmndi:DMNStyle#id` 63 | * `FIX`: read and serialize correctly `dmndi:DMNDiagram#Size` 64 | 65 | ## 8.0.2 66 | 67 | * `CHORE`: drop unneeded files from bundle 68 | 69 | ## 8.0.1 70 | 71 | * `FIX`: add package names ([`2859ed`](https://github.com/bpmn-io/dmn-moddle/commit/2859edc1835217001fe39487eb57bedee4eba76a)) 72 | 73 | ## 8.0.0 74 | 75 | _A rewrite of the library that makes it compatible with DMN 1.3 files._ 76 | 77 | * `FEAT`: read and write DMN 1.3 diagrams 78 | * `FEAT`: recognize full DMN 1.3 79 | * `CHORE`: generate schema from XMI ([`f4bb15`](https://github.com/bpmn-io/dmn-moddle/pull/6/commits/f4bb15627fbc434b3342c4a3db7b5ab068d7e908)) 80 | * `CHORE`: run integration tests with DMN TCK ([`64a694`](https://github.com/bpmn-io/dmn-moddle/pull/6/commits/64a694d5e8616136af9a1ba6d147173334b3b9ac)) 81 | * `CHORE`: update to cmof-parser@0.5.0 82 | 83 | ### Breaking Changes 84 | 85 | * We support DMN 1.3 diagrams only. Stick to `dmn-moddle@7.0.0` or use the [`dmn-migration-utility`](https://github.com/bpmn-io/dmn-migration-utility) to convert your diagrams. 86 | * We removed the `biodi` package without replacement ([`2334ad`](https://github.com/bpmn-io/dmn-moddle/pull/6/commits/2334adaf3e10486e869781f30844c8def9b1b2df)). Migrating your diagrams to DMN 1.3 with the [`dmn-migration-utility`](https://github.com/bpmn-io/dmn-migration-utility) will convert `biodi` to proper DMN 1.3 DI. 87 | 88 | ## 7.0.0 89 | 90 | * `FEAT`: add pre-built distribution 91 | * `CHORE`: update to moddle@5.0.1, moddle-xml@8.0.1 92 | 93 | ### Breaking Changes 94 | 95 | * `FEAT`: dropped `lib/` from npm package; import from the root now 96 | 97 | ## 6.0.0 98 | 99 | ### Breaking Changes 100 | 101 | * `FEAT`: dropped `camunda` extension definition, it moved to a [seperate library](https://github.com/camunda/camunda-dmn-moddle) 102 | 103 | ## 5.0.0 104 | 105 | ### Breaking Changes 106 | 107 | * `FEAT`: migrate to ES modules. Use `esm` or a ES module aware transpiler to consume this library. 108 | 109 | ### Other Improvements 110 | 111 | * `CHORE`: bump dependency versions 112 | 113 | ## 4.0.0 114 | 115 | * `FEAT`: encode entities in body properties (rather than using CDATA escaping) 116 | 117 | ## 3.0.1 118 | 119 | * `FIX`: properly decode `text` entities 120 | 121 | ## 3.0.0 122 | 123 | * `CHORE`: improve error handling on invalid attributes 124 | * `CHORE`: drop lodash in favor of [min-dash](https://github.com/bpmn-io/min-dash) 125 | 126 | ## ... 127 | 128 | Check `git log` for earlier history. 129 | -------------------------------------------------------------------------------- /resources/dmn/json/dmndi13.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DMNDI", 3 | "prefix": "dmndi", 4 | "uri": "https://www.omg.org/spec/DMN/20191111/DMNDI/", 5 | "types": [ 6 | { 7 | "name": "DMNDI", 8 | "properties": [ 9 | { 10 | "name": "diagrams", 11 | "type": "DMNDiagram", 12 | "isMany": true 13 | }, 14 | { 15 | "name": "styles", 16 | "type": "DMNStyle", 17 | "isMany": true 18 | } 19 | ] 20 | }, 21 | { 22 | "name": "DMNStyle", 23 | "superClass": [ 24 | "di:Style" 25 | ], 26 | "properties": [ 27 | { 28 | "name": "fillColor", 29 | "type": "dc:Color", 30 | "isAttr": true 31 | }, 32 | { 33 | "name": "strokeColor", 34 | "type": "dc:Color", 35 | "isAttr": true 36 | }, 37 | { 38 | "name": "fontColor", 39 | "type": "dc:Color", 40 | "isAttr": true 41 | }, 42 | { 43 | "name": "fontSize", 44 | "isAttr": true, 45 | "type": "Real" 46 | }, 47 | { 48 | "name": "fontFamily", 49 | "isAttr": true, 50 | "type": "String" 51 | }, 52 | { 53 | "name": "fontItalic", 54 | "isAttr": true, 55 | "type": "Boolean" 56 | }, 57 | { 58 | "name": "fontBold", 59 | "isAttr": true, 60 | "type": "Boolean" 61 | }, 62 | { 63 | "name": "fontUnderline", 64 | "isAttr": true, 65 | "type": "Boolean" 66 | }, 67 | { 68 | "name": "fontStrikeThrough", 69 | "isAttr": true, 70 | "type": "Boolean" 71 | }, 72 | { 73 | "name": "labelHorizontalAlignment", 74 | "type": "dc:AlignmentKind", 75 | "isAttr": true 76 | }, 77 | { 78 | "name": "labelVerticalAlignment", 79 | "type": "dc:AlignmentKind", 80 | "isAttr": true 81 | } 82 | ] 83 | }, 84 | { 85 | "name": "DMNDiagram", 86 | "superClass": [ 87 | "di:Diagram" 88 | ], 89 | "properties": [ 90 | { 91 | "name": "dmnElementRef", 92 | "type": "dmn:DMNElement", 93 | "isAttr": true, 94 | "isReference": true 95 | }, 96 | { 97 | "name": "size", 98 | "type": "Size" 99 | }, 100 | { 101 | "name": "localStyle", 102 | "type": "DMNStyle", 103 | "isVirtual": true 104 | }, 105 | { 106 | "name": "sharedStyle", 107 | "type": "DMNStyle", 108 | "isVirtual": true, 109 | "isReference": true, 110 | "redefines": "di:DiagramElement#sharedStyle" 111 | }, 112 | { 113 | "name": "diagramElements", 114 | "type": "DMNDiagramElement", 115 | "isMany": true 116 | } 117 | ] 118 | }, 119 | { 120 | "name": "DMNDiagramElement", 121 | "isAbstract": true, 122 | "superClass": [ 123 | "di:DiagramElement" 124 | ], 125 | "properties": [ 126 | { 127 | "name": "dmnElementRef", 128 | "type": "dmn:DMNElement", 129 | "isAttr": true, 130 | "isReference": true 131 | }, 132 | { 133 | "name": "sharedStyle", 134 | "type": "DMNStyle", 135 | "isVirtual": true, 136 | "isReference": true, 137 | "redefines": "di:DiagramElement#sharedStyle" 138 | }, 139 | { 140 | "name": "localStyle", 141 | "type": "DMNStyle", 142 | "isVirtual": true 143 | }, 144 | { 145 | "name": "label", 146 | "type": "DMNLabel" 147 | } 148 | ] 149 | }, 150 | { 151 | "name": "DMNLabel", 152 | "superClass": [ 153 | "di:Shape" 154 | ], 155 | "properties": [ 156 | { 157 | "name": "text", 158 | "type": "Text" 159 | } 160 | ] 161 | }, 162 | { 163 | "name": "DMNShape", 164 | "superClass": [ 165 | "di:Shape", 166 | "DMNDiagramElement" 167 | ], 168 | "properties": [ 169 | { 170 | "name": "isListedInputData", 171 | "isAttr": true, 172 | "type": "Boolean" 173 | }, 174 | { 175 | "name": "decisionServiceDividerLine", 176 | "type": "DMNDecisionServiceDividerLine" 177 | }, 178 | { 179 | "name": "isCollapsed", 180 | "isAttr": true, 181 | "type": "Boolean" 182 | } 183 | ] 184 | }, 185 | { 186 | "name": "DMNEdge", 187 | "superClass": [ 188 | "di:Edge", 189 | "DMNDiagramElement" 190 | ], 191 | "properties": [ 192 | { 193 | "name": "sourceElement", 194 | "type": "DMNDiagramElement", 195 | "isAttr": true, 196 | "isReference": true 197 | }, 198 | { 199 | "name": "targetElement", 200 | "type": "DMNDiagramElement", 201 | "isAttr": true, 202 | "isReference": true 203 | } 204 | ] 205 | }, 206 | { 207 | "name": "DMNDecisionServiceDividerLine", 208 | "superClass": [ 209 | "di:Edge" 210 | ] 211 | }, 212 | { 213 | "name": "Text", 214 | "properties": [ 215 | { 216 | "name": "text", 217 | "isBody": true, 218 | "type": "String" 219 | } 220 | ] 221 | }, 222 | { 223 | "name": "Size", 224 | "superClass": [ 225 | "dc:Dimension" 226 | ] 227 | } 228 | ], 229 | "associations": [], 230 | "enumerations": [] 231 | } -------------------------------------------------------------------------------- /resources/dmn/xsd/DC.xsd: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Color is a data type that represents a color value in the RGB format. 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | A Point specifies an location in some x-y coordinate system. 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Dimension specifies two lengths (width and height) along the x and y axes in some x-y coordinate system. 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Bounds specifies a rectangular area in some x-y coordinate system that is defined by a location (x and y) and a size (width and height). 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | AlignmentKind enumerates the possible options for alignment for layout purposes. 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | KnownColor is an enumeration of 17 known colors. 69 | 70 | 71 | 72 | 73 | a color with a value of #800000 74 | 75 | 76 | 77 | 78 | a color with a value of #FF0000 79 | 80 | 81 | 82 | 83 | a color with a value of #FFA500 84 | 85 | 86 | 87 | 88 | a color with a value of #FFFF00 89 | 90 | 91 | 92 | 93 | a color with a value of #808000 94 | 95 | 96 | 97 | 98 | a color with a value of #800080 99 | 100 | 101 | 102 | 103 | a color with a value of #FF00FF 104 | 105 | 106 | 107 | 108 | a color with a value of #FFFFFF 109 | 110 | 111 | 112 | 113 | a color with a value of #00FF00 114 | 115 | 116 | 117 | 118 | a color with a value of #008000 119 | 120 | 121 | 122 | 123 | a color with a value of #000080 124 | 125 | 126 | 127 | 128 | a color with a value of #0000FF 129 | 130 | 131 | 132 | 133 | a color with a value of #00FFFF 134 | 135 | 136 | 137 | 138 | a color with a value of #008080 139 | 140 | 141 | 142 | 143 | a color with a value of #000000 144 | 145 | 146 | 147 | 148 | a color with a value of #C0C0C0 149 | 150 | 151 | 152 | 153 | a color with a value of #808080 154 | 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /tasks/transforms/helper.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | isArray, 5 | matchPattern, 6 | without 7 | } = require('min-dash'); 8 | 9 | const sax = require('sax'); 10 | 11 | const Stack = require('tiny-stack'); 12 | 13 | /** 14 | * Recursively find children in tree that match pattern. 15 | * 16 | * @param {Object} parent 17 | * @param {Function} pattern 18 | * 19 | * @returns {Array|null} 20 | */ 21 | function findChildren(parent, pattern) { 22 | const { children } = parent; 23 | 24 | if (!children) { 25 | return null; 26 | } 27 | 28 | const result = children.reduce((result, child) => { 29 | if (pattern(child)) { 30 | result = [ 31 | ...result, 32 | child 33 | ]; 34 | } 35 | 36 | if (child.children) { 37 | result = [ 38 | ...result, 39 | ...(findChildren(child, pattern) || []) 40 | ]; 41 | } 42 | 43 | return result; 44 | }, []); 45 | 46 | if (!result.length) { 47 | return null; 48 | } 49 | 50 | return result; 51 | } 52 | 53 | module.exports.findChildren = findChildren; 54 | 55 | /** 56 | * Find type with specified name in schema. 57 | * 58 | * @param {String} type 59 | * @param {Object} schema 60 | * 61 | * @returns {Object} 62 | */ 63 | function findType(type, schema) { 64 | return schema.types.find(({ name }) => name === type); 65 | } 66 | 67 | module.exports.findType = findType; 68 | 69 | /** 70 | * Find property with specified name in schema. 71 | * 72 | * @param {String} typeProperty 73 | * @param {Object} schema 74 | * 75 | * @returns {Object} 76 | */ 77 | function findProperty(typeProperty, schema) { 78 | const [ type, property ] = typeProperty.split('#'); 79 | 80 | return findType(type, schema).properties.find(({ name }) => name === property); 81 | } 82 | 83 | module.exports.findProperty = findProperty; 84 | 85 | /** 86 | * Fix order of properties according to indicators specified in XSD. 87 | * 88 | * Example: 89 | * 90 | * 91 | * 92 | * 93 | * 94 | * 95 | * 96 | * ... 97 | * 98 | * 99 | * 100 | * 101 | * 102 | * specifies that inputEntry elements must appear before outputEntry elements. 103 | * 104 | * @param {Object} model 105 | * @param {Object} parsedXSD 106 | * 107 | * @returns {Object} 108 | */ 109 | function fixSequence(model, parsedXSD) { 110 | const { elementsByTagName } = parsedXSD, 111 | xsdSchema = elementsByTagName[ 'xsd:schema' ][ 0 ]; 112 | 113 | const xsdComplexTypes = findChildren(xsdSchema, matchPattern({ tagName: 'xsd:complexType' })); 114 | 115 | if (!xsdComplexTypes) { 116 | return; 117 | } 118 | 119 | xsdComplexTypes.forEach(xsdComplexType => { 120 | 121 | // (1) find sequences 122 | const xsdSequences = findChildren(xsdComplexType, matchPattern({ tagName: 'xsd:sequence' })); 123 | 124 | if (!xsdSequences) { 125 | return; 126 | } 127 | 128 | const xsdSequence = xsdSequences[ 0 ]; 129 | 130 | // (2) find elements in sequence 131 | const xsdElements = findChildren(xsdSequence, matchPattern({ tagName: 'xsd:element' })); 132 | 133 | if (!xsdElements) { 134 | return; 135 | } 136 | 137 | // (3) find corresponding type 138 | const type = model.types.find(matchPattern({ name: xsdComplexType.name.slice(1) })); 139 | 140 | if (!type) { 141 | return; 142 | } 143 | 144 | // (4) fix sequence of type properties 145 | type.properties = type.properties 146 | .map(property => { 147 | 148 | // (4.1) find corresponding element 149 | const xsdElement = xsdElements.find(({ name, ref }) => name === property.name || ref === property.name); 150 | 151 | if (xsdElement) { 152 | 153 | // (4.1.1) return index of found corresponding element 154 | return { 155 | index: xsdElements.indexOf(xsdElement), 156 | name: property.name 157 | }; 158 | } else { 159 | 160 | // (4.1.2) find substitute 161 | const matcher = matchPattern({ 162 | tagName: 'xsd:element', 163 | type: `t${ property.type }` 164 | }); 165 | 166 | const substitutes = findChildren(xsdSchema, child => { 167 | return matcher(child) && child.substitutionGroup; 168 | }); 169 | 170 | if (substitutes) { 171 | const substitute = substitutes[ 0 ]; 172 | 173 | // (4.1.3) find substitutable 174 | const substitutable = xsdElements.find(matchPattern({ ref: substitute.substitutionGroup })); 175 | 176 | if (substitutable) { 177 | const index = xsdElements.indexOf(substitutable); 178 | 179 | // (4.1.4) return index of found substitutable 180 | return { 181 | substituteFor: substitutable.ref, 182 | index, 183 | name: property.name 184 | }; 185 | } 186 | } 187 | } 188 | 189 | // (4.2) return maximum index if index not specified in sequence 190 | return { 191 | index: type.properties.length - 1, 192 | name: property.name 193 | }; 194 | }) 195 | .sort((a, b) => { 196 | return a.index - b.index; 197 | }) 198 | .map(({ name }) => { 199 | return type.properties.find(matchPattern({ name })); 200 | }); 201 | }); 202 | 203 | return model; 204 | } 205 | 206 | module.exports.fixSequence = fixSequence; 207 | 208 | /** 209 | * Order properties of type. 210 | * 211 | * @param {string} typeName 212 | * @param {Array} propertiesOrder 213 | * @param {Object} schema 214 | * 215 | * @returns {Object} 216 | */ 217 | function orderProperties(typeName, propertiesOrder, schema) { 218 | const type = findType(typeName, schema); 219 | 220 | const { properties } = type; 221 | 222 | propertiesOrder.reverse().forEach(propertyName => { 223 | const index = properties.indexOf( 224 | properties.find(matchPattern({ name: propertyName })) 225 | ); 226 | 227 | properties.unshift(properties.splice(index, 1).pop()); 228 | }); 229 | 230 | return schema; 231 | } 232 | 233 | module.exports.orderProperties = orderProperties; 234 | 235 | /** 236 | * Parse XML tag. 237 | * 238 | * @param {Object} tag 239 | * @param {Object} parent 240 | * @param {Object} context 241 | * @param {Object} context.elementsByName 242 | * @param {Object} context.elementsByTagName 243 | * 244 | * @returns {Object} 245 | */ 246 | function parseTag(tag, parent, context) { 247 | const { 248 | attributes, 249 | name: tagName 250 | } = tag; 251 | 252 | const { name } = attributes; 253 | 254 | const { 255 | elementsByName, 256 | elementsByTagName 257 | } = context; 258 | 259 | const element = { 260 | tagName 261 | }; 262 | 263 | Object.entries(attributes).forEach(([ key, value ]) => { 264 | if (value === 'true') { 265 | value = true; 266 | } 267 | 268 | if (value === 'false') { 269 | value = false; 270 | } 271 | 272 | element[ key ] = value; 273 | }); 274 | 275 | if (name) { 276 | elementsByName[ name ] = element; 277 | } 278 | 279 | if (!elementsByTagName[ tagName ]) { 280 | elementsByTagName[ tagName ] = []; 281 | } 282 | 283 | elementsByTagName[ tagName ].push(element); 284 | 285 | if (parent) { 286 | if (!parent.children) { 287 | parent.children = []; 288 | } 289 | 290 | parent.children.push(element); 291 | } 292 | 293 | return element; 294 | } 295 | 296 | async function parseXML(file) { 297 | return new Promise((resolve, reject) => { 298 | const elementsByName = {}, 299 | elementsByTagName = {}; 300 | 301 | const context = { 302 | elementsByName, 303 | elementsByTagName 304 | }; 305 | 306 | const stack = new Stack(); 307 | 308 | const saxParser = sax.parser(true); 309 | 310 | saxParser.onerror = function(err) { 311 | console.error('error', err); 312 | 313 | this._parser.error = null; 314 | this._parser.resume(); 315 | 316 | reject(err); 317 | }; 318 | 319 | saxParser.onopentag = tag => { 320 | const parent = stack.peek(); 321 | 322 | stack.push(parseTag(tag, parent, context)); 323 | }; 324 | 325 | saxParser.onclosetag = name => { 326 | stack.pop(); 327 | }; 328 | 329 | saxParser.onend = () => { 330 | resolve(context); 331 | }; 332 | 333 | saxParser.write(file).close(); 334 | }); 335 | } 336 | 337 | module.exports.parseXML = parseXML; 338 | 339 | /** 340 | * Remove prefixes from all type names. 341 | * 342 | * @param {Object} model 343 | * @param {Array|string} prefixes 344 | * 345 | * @returns {Object} 346 | */ 347 | function removePrefixes(model, prefixes) { 348 | if (prefixes && !isArray(prefixes)) { 349 | prefixes = [ prefixes ]; 350 | } 351 | 352 | model.types.forEach(type => { 353 | type.name = withoutPrefixes(type.name, prefixes); 354 | 355 | if (type.superClass) { 356 | type.superClass = type.superClass.map(superClass => { 357 | return withoutPrefixes(superClass, prefixes); 358 | }); 359 | } 360 | 361 | if (type.properties) { 362 | type.properties.forEach(property => { 363 | property.type = withoutPrefixes(property.type, prefixes); 364 | }); 365 | } 366 | }); 367 | 368 | model.enumerations.forEach(enumeration => { 369 | enumeration.name = withoutPrefixes(enumeration.name, prefixes); 370 | }); 371 | 372 | return model; 373 | } 374 | 375 | module.exports.removePrefixes = removePrefixes; 376 | 377 | /** 378 | * Remove property from type. 379 | * 380 | * @param {string} typeProperty 381 | * @param {Object} model 382 | * 383 | * @returns {Object} 384 | */ 385 | function removeProperty(typeProperty, model) { 386 | let [ type, property ] = typeProperty.split('#'); 387 | 388 | type = findType(type, model); 389 | 390 | return type.properties = without(type.properties, matchPattern({ name: property })); 391 | } 392 | 393 | module.exports.removeProperty = removeProperty; 394 | 395 | /** 396 | * Remove properties from type. 397 | * 398 | * @param {string} type 399 | * @param {Array} properties 400 | * @param {Object} model 401 | */ 402 | function removeProperties(type, properties, model) { 403 | properties.forEach(property => { 404 | removeProperty(`${ type }#${ property }`, model); 405 | }); 406 | } 407 | 408 | module.exports.removeProperties = removeProperties; 409 | 410 | /** 411 | * Remove whitespace from all property names. 412 | * 413 | * @param {Object} model 414 | * 415 | * @returns {Object} 416 | */ 417 | function removeWhitespace(model) { 418 | model.types.forEach(({ properties }) => { 419 | if (!properties) { 420 | return; 421 | } 422 | 423 | properties.forEach(property => { 424 | property.name = property.name.replace(/\s/, ''); 425 | }); 426 | }); 427 | 428 | return model; 429 | } 430 | 431 | module.exports.removeWhitespace = removeWhitespace; 432 | 433 | /** 434 | * Remove specified prefixes from type. 435 | * 436 | * @param {string} prefixType 437 | * @param {Array} prefixes 438 | * 439 | * @returns {string} 440 | */ 441 | function withoutPrefixes(prefixType, prefixes) { 442 | let [ prefix, type ] = prefixType.split(':'); 443 | 444 | if (!type) { 445 | return prefix; 446 | } 447 | 448 | if (prefixes && prefixes.includes(prefix)) { 449 | return type; 450 | } 451 | 452 | return prefixType; 453 | } -------------------------------------------------------------------------------- /tasks/transforms/transformDMN.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | 3 | const { matchPattern } = require('min-dash'); 4 | 5 | const { 6 | findChildren, 7 | findProperty, 8 | findType, 9 | fixSequence, 10 | orderProperties, 11 | parseXML, 12 | removeProperties, 13 | removeWhitespace 14 | } = require('./helper.cjs'); 15 | 16 | module.exports = async function(results) { 17 | 18 | const xsdFile = fs.readFileSync('resources/dmn/xsd/DMN13.xsd', 'utf8'); 19 | 20 | const dmnXSD = await parseXML(xsdFile); 21 | 22 | const { elementsByType } = results; 23 | 24 | let model = elementsByType[ 'uml:Package' ][ 0 ]; 25 | 26 | model.xml = { 27 | tagAlias: 'lowerCase' 28 | }; 29 | 30 | // remove types without properties 31 | model.types = model.types.filter(({ properties }) => properties); 32 | 33 | // remove properties without name 34 | model.types.forEach(type => { 35 | if (type.properties) { 36 | type.properties = type.properties.filter(({ name }) => name); 37 | } 38 | }); 39 | 40 | // remove associations 41 | model.associations = []; 42 | 43 | // remove DC and DI 44 | model.types = model.types.filter(({ name }) => { 45 | return name && !name.includes(':'); 46 | }); 47 | 48 | model.enumerations = model.enumerations.filter(({ name }) => { 49 | return name && !name.includes(':'); 50 | }); 51 | 52 | // fix extension elements 53 | delete findProperty('DMNElement#extensionElements', model).isMany; 54 | 55 | findProperty('TextAnnotation#textFormat', model).default = 'text/plain'; 56 | 57 | findProperty('ExtensionElements#extensionElement', model).name = 'values'; 58 | 59 | // fix `Context#contextEntry` name 60 | findProperty('Context#contextEnrty', model).name = 'contextEntry'; 61 | 62 | // fix `TextAnnotation#textFormat` default 63 | findProperty('TextAnnotation#textFormat', model).default = 'text/plain'; 64 | 65 | findType('List', model).properties.push({ 66 | name: 'elements', 67 | isMany: true, 68 | type: 'Expression' 69 | }); 70 | 71 | // fix `ItemDefinition#containingDefinition` 72 | const containingDefinition = findProperty('ItemDefinition#containingDefinition', model); 73 | 74 | containingDefinition.name = 'functionItem'; 75 | containingDefinition.type = 'FunctionItem'; 76 | 77 | delete containingDefinition.isAttr; 78 | delete containingDefinition.isReference; 79 | 80 | containingDefinition.xml = { 81 | serialize: 'property' 82 | }; 83 | 84 | // fix `FunctionItem` 85 | findType('FunctionItem', model).properties.push({ 86 | name: 'parameters', 87 | isMany: true, 88 | type: 'InformationItem', 89 | xml: { 90 | serialize: 'property' 91 | } 92 | }); 93 | 94 | removeProperties('FunctionItem', [ 95 | 'outputType' 96 | ], model); 97 | 98 | model = removeWhitespace(model); 99 | 100 | model = fixElementReferences(model, dmnXSD); 101 | 102 | model = fixPropertySerialization(model, dmnXSD); 103 | 104 | model = fixSequence(model, dmnXSD); 105 | 106 | [ 107 | 'BusinessKnowledgeModel#authorityRequirement', 108 | 'BusinessKnowledgeModel#knowledgeRequirement', 109 | 'Decision#authorityRequirement', 110 | 'Decision#informationRequirement', 111 | 'Decision#knowledgeRequirement', 112 | 'Definitions#elementCollection', 113 | 'Definitions#itemDefinition', 114 | 'DMNElement#description', 115 | 'DMNElement#extensionElements', 116 | 'ImportedValues#importedElement', 117 | 'Invocation#binding', 118 | 'ItemDefinition#functionItem', 119 | 'ItemDefinition#typeRef', 120 | 'KnowledgeSource#authorityRequirement', 121 | 'KnowledgeSource#type', 122 | 'LiteralExpression#importedValues', 123 | 'LiteralExpression#text', 124 | 'RuleAnnotation#text', 125 | 'TextAnnotation#text', 126 | 'UnaryTests#text' 127 | ].forEach(name => { 128 | delete findProperty(name, model).xml; 129 | }); 130 | 131 | [ 132 | 'Artifact', 133 | 'BusinessContextElement', 134 | 'DRGElement', 135 | 'ElementCollection', 136 | 'Import', 137 | 'ItemDefinition' 138 | ].forEach(name => { 139 | removeProperties(name, [ 140 | 'definitions' 141 | ], model); 142 | }); 143 | 144 | // remove `AuthorityRequirement` properties 145 | removeProperties('AuthorityRequirement', [ 146 | 'bkm', 147 | 'decision', 148 | 'knowledgeSource' 149 | ], model); 150 | 151 | // remove `Binding` properties 152 | removeProperties('Binding', [ 153 | 'context', 154 | 'decisionTable', 155 | 'functionDefinition', 156 | 'invocation', 157 | 'list', 158 | 'literalExpression', 159 | 'relation' 160 | ], model); 161 | 162 | // remove `BusinessKnowledgeModel` properties 163 | removeProperties('BusinessKnowledgeModel', [ 164 | 'encapsulatedDecisions', 165 | 'parameter' 166 | ], model); 167 | 168 | // remove `ContextEntry` properties 169 | removeProperties('ContextEntry', [ 170 | 'context', 171 | 'decisionTable', 172 | 'functionDefinition', 173 | 'invocation', 174 | 'list', 175 | 'literalExpression', 176 | 'relation' 177 | ], model); 178 | 179 | // remove `Decision` properties 180 | removeProperties('Decision', [ 181 | 'context', 182 | 'decisionTable', 183 | 'functionDefinition', 184 | 'invocation', 185 | 'list', 186 | 'literalExpression', 187 | 'relation', 188 | 'requiresInformation' 189 | ], model); 190 | 191 | // remove `DecisionRule` properties 192 | removeProperties('DecisionRule', [ 193 | 'decisionTable' 194 | ], model); 195 | 196 | // remove Defintions#association, Definitions#group and Defintions#textAnnotation 197 | // artifacts will be accessible through Definitions#artifact 198 | removeProperties('Definitions', [ 199 | 'association', 200 | 'group', 201 | 'textAnnotation' 202 | ], model); 203 | 204 | // remove `Expression` properties 205 | removeProperties('Expression', [ 206 | 'binding', 207 | 'caller', 208 | 'contextEntry', 209 | 'decision', 210 | 'functionDefinition', 211 | 'list', 212 | 'type' 213 | ], model); 214 | 215 | // remove `ExtensionAttribute` properties 216 | removeProperties('ExtensionAttribute', [ 217 | 'element' 218 | ], model); 219 | 220 | // remove `ExtensionElements` properties 221 | removeProperties('ExtensionElements', [ 222 | 'element' 223 | ], model); 224 | 225 | // remove `FunctionDefinition` properties 226 | removeProperties('FunctionDefinition', [ 227 | 'bkm', 228 | 'context', 229 | 'decisionTable', 230 | 'invocation', 231 | 'literalExpression', 232 | 'relation' 233 | ], model); 234 | 235 | // remove `InformationItem` properties 236 | removeProperties('InformationItem', [ 237 | 'binding', 238 | 'bkm', 239 | 'context', 240 | 'contextEntry', 241 | 'decisionOutput', 242 | 'decisionTable', 243 | 'functionDefinition', 244 | 'inputData', 245 | 'invocation', 246 | 'list', 247 | 'literalExpression', 248 | 'relation', 249 | 'type', 250 | 'valueExpression' 251 | ], model); 252 | 253 | // remove `InformationRequirement` properties 254 | removeProperties('InformationRequirement', [ 255 | 'decision' 256 | ], model); 257 | 258 | // remove `InputClause` properties 259 | removeProperties('InputClause', [ 260 | 'decisionTable' 261 | ], model); 262 | 263 | // remove `InputData` properties 264 | removeProperties('InputData', [ 265 | 'requiresInformation' 266 | ], model); 267 | 268 | // remove `Invocation` properties 269 | removeProperties('Invocation', [ 270 | 'context', 271 | 'decisionTable', 272 | 'literalExpression', 273 | 'relation' 274 | ], model); 275 | 276 | // remove `ItemDefinition` properties 277 | removeProperties('ItemDefinition', [ 278 | 'definitions' 279 | ], model); 280 | 281 | // remove `KnowledgeRequirement` properties 282 | removeProperties('KnowledgeRequirement', [ 283 | 'bkm', 284 | 'decision' 285 | ], model); 286 | 287 | // remove `List` properties 288 | removeProperties('List', [ 289 | 'context', 290 | 'decisionTable', 291 | 'element', 292 | 'invocation', 293 | 'literalExpression', 294 | 'relation' 295 | ], model); 296 | 297 | // remove `LiteralExpression` properties 298 | removeProperties('LiteralExpression', [ 299 | 'expressionInput', 300 | 'output', 301 | 'ruleOutput' 302 | ], model); 303 | 304 | // remove `OutputClause` properties 305 | removeProperties('OutputClause', [ 306 | 'outputDefinition' 307 | ], model); 308 | 309 | // remove `RuleAnnotation` properties 310 | removeProperties('RuleAnnotation', [ 311 | 'ruleAnnotation' 312 | ], model); 313 | 314 | // remove `UnaryTests` properties 315 | removeProperties('UnaryTests', [ 316 | 'allowedInItemDefinition', 317 | 'input', 318 | 'output', 319 | 'ruleInput' 320 | ], model); 321 | 322 | orderProperties('Invocation', [ 323 | 'calledFunction', 324 | 'binding' 325 | ], model); 326 | 327 | // set uri 328 | model.uri = dmnXSD.elementsByTagName[ 'xsd:schema' ][ 0 ].targetNamespace; 329 | 330 | return model; 331 | }; 332 | 333 | /** 334 | * As specified in the XSD, in some instances, elements are not referenced via an attribute but via 335 | * a child element with an `href` property. 336 | * 337 | * Instead of 338 | * 339 | * 340 | * 341 | * the element is referenced like this: 342 | * 343 | * 344 | * 345 | * 346 | * 347 | * Therefore, a `DMNElementReference` type is introduced. 348 | */ 349 | function fixElementReferences(model, xsd) { 350 | const { elementsByTagName } = xsd, 351 | xsdSchema = elementsByTagName[ 'xsd:schema' ][ 0 ]; 352 | 353 | // (1) add `DMNElementReference` type 354 | model.types = [ 355 | ...model.types, 356 | { 357 | name: 'DMNElementReference', 358 | properties: [ 359 | { 360 | isAttr: true, 361 | name: 'href', 362 | type: 'String', 363 | }, 364 | ], 365 | } 366 | ]; 367 | 368 | // (2) fix element references 369 | model.types.forEach(type => { 370 | const { 371 | name, 372 | properties 373 | } = type; 374 | 375 | if (!properties) { 376 | return; 377 | } 378 | 379 | const xsdName = `t${ name }`; 380 | 381 | const xsdType = xsdSchema.children.find(matchPattern({ name: xsdName })); 382 | 383 | if (!xsdType) { 384 | return; 385 | } 386 | 387 | // (2.1) get element references 388 | const elementReferences = type.properties.filter(property => { 389 | const xsdElements = findChildren(xsdType, matchPattern({ tagName: 'xsd:element' })); 390 | 391 | if (!xsdElements) { 392 | return false; 393 | } 394 | 395 | const xsdElement = xsdElements.find(matchPattern({ name: property.name })); 396 | 397 | if (!xsdElement) { 398 | return false; 399 | } 400 | 401 | return xsdElement.type === 'tDMNElementReference'; 402 | }); 403 | 404 | // (2.2) fix element references 405 | properties.forEach(property => { 406 | if (elementReferences.find(matchPattern({ name: property.name }))) { 407 | property.type = 'DMNElementReference'; 408 | 409 | // serialize as child element instead of attribute 410 | property.xml = { 411 | serialize: 'property' 412 | }; 413 | 414 | delete property.isReference; 415 | } 416 | }); 417 | }); 418 | 419 | return model; 420 | } 421 | 422 | /** 423 | * Serialize properties if specified in XSD. 424 | * 425 | * Serializes 426 | * 427 | * { 428 | * $type: "DecisionTable", 429 | * input: [{ 430 | * $type: "InputClause" 431 | * }] 432 | * } 433 | * 434 | * as 435 | * 436 | * 437 | * 438 | * 439 | * 440 | * instead of 441 | * 442 | * 443 | * 444 | * 445 | * 446 | * @param {Object} model 447 | * 448 | * @returns {Object} 449 | */ 450 | function fixPropertySerialization(model, xsd) { 451 | const { elementsByTagName } = xsd, 452 | xsdSchema = elementsByTagName[ 'xsd:schema' ][ 0 ]; 453 | 454 | model.types.forEach(type => { 455 | const xsdComplexTypes = findChildren(xsdSchema, matchPattern({ 456 | name: `t${ type.name }`, 457 | tagName: 'xsd:complexType' 458 | })); 459 | 460 | if (!xsdComplexTypes) { 461 | return; 462 | } 463 | 464 | const xsdComplexType = xsdComplexTypes[ 0 ]; 465 | 466 | type.properties.forEach(property => { 467 | const xsdElements = findChildren(xsdComplexType, matchPattern({ 468 | name: property.name, 469 | tagName: 'xsd:element' 470 | })); 471 | 472 | if (!xsdElements) { 473 | return; 474 | } 475 | 476 | delete property.isAttr; 477 | 478 | property.xml = { 479 | serialize: 'property' 480 | }; 481 | }); 482 | }); 483 | 484 | return model; 485 | } 486 | -------------------------------------------------------------------------------- /test/spec/xml/write.js: -------------------------------------------------------------------------------- 1 | import expect from '../../expect.js'; 2 | 3 | import DmnModdle from '../../../lib/index.js'; 4 | 5 | 6 | describe('dmn-moddle - write', function() { 7 | 8 | let moddle; 9 | 10 | beforeEach(function() { 11 | moddle = new DmnModdle(); 12 | }); 13 | 14 | function write(element, options = { preamble: false }) { 15 | return moddle.toXML(element, options); 16 | } 17 | 18 | 19 | describe('dmn', function() { 20 | 21 | it('dmn:Definitions', async function() { 22 | 23 | // given 24 | const expected = ''; 25 | 26 | const definitions = moddle.create('dmn:Definitions'); 27 | 28 | // when 29 | const { xml } = await write(definitions); 30 | 31 | // then 32 | expect(xml).to.equal(expected); 33 | }); 34 | 35 | 36 | it('dmn:FunctionDefinition', async function() { 37 | 38 | // given 39 | const functionDefinition = moddle.create('dmn:FunctionDefinition', { 40 | kind: 'FEEL', 41 | body: moddle.create('dmn:Context') 42 | }); 43 | 44 | // when 45 | const { xml } = await write(functionDefinition); 46 | 47 | // then 48 | expect(xml).to.equal( 49 | '' + 50 | '' + 51 | '' 52 | ); 53 | }); 54 | 55 | 56 | it('dmn:LiteralExpression', async function() { 57 | 58 | // given 59 | const expected = 60 | '' + 61 | '' + 62 | '' + 63 | ''; 64 | 65 | const informationItem = moddle.create('dmn:InformationItem', { 66 | id: 'InformationItem_1', 67 | name: 'Bar', 68 | typeRef: 'integer' 69 | }); 70 | 71 | const literalExpression = moddle.create('dmn:LiteralExpression', { 72 | id: 'LiteralExpression_1' 73 | }); 74 | 75 | const decision = moddle.create('dmn:Decision', { 76 | id: 'Decision_1', 77 | decisionLogic: literalExpression, 78 | name: 'Foo', 79 | variable: informationItem 80 | }); 81 | 82 | // when 83 | const { xml } = await write(decision); 84 | 85 | // then 86 | expect(xml).to.equal(expected); 87 | }); 88 | 89 | 90 | it('dmn:UnaryTests', async function() { 91 | 92 | // given 93 | const unaryTests = moddle.create('dmn:UnaryTests', { 94 | expressionLanguage: 'LANG', 95 | text: 'FOO' 96 | }); 97 | 98 | // when 99 | const { xml } = await write(unaryTests); 100 | 101 | // then 102 | expect(xml).to.eql( 103 | '' + 104 | 'FOO' + 105 | '' 106 | ); 107 | }); 108 | 109 | 110 | it('dmn:Context', async function() { 111 | 112 | // given 113 | const variable = moddle.create('dmn:InformationItem', { 114 | name: 'foo' 115 | }); 116 | 117 | const literalExpression = moddle.create('dmn:LiteralExpression', { 118 | text: 'if foo = 10 then true else false' 119 | }); 120 | 121 | const contextEntry = moddle.create('dmn:ContextEntry', { 122 | variable, 123 | value: literalExpression 124 | }); 125 | 126 | const context = moddle.create('dmn:Context', { 127 | contextEntry: [ contextEntry ] 128 | }); 129 | 130 | // when 131 | const { xml } = await write(context); 132 | 133 | // then 134 | expect(xml).to.eql( 135 | '' + 136 | '' + 137 | '' + 138 | '' + 139 | 'if foo = 10 then true else false' + 140 | '' + 141 | '' + 142 | '' 143 | ); 144 | }); 145 | 146 | 147 | it('dmn:Invocation', async function() { 148 | 149 | // given 150 | const literalExpression1 = moddle.create('dmn:LiteralExpression', { 151 | id: 'LiteralExpression_1', 152 | text: 'FOO' 153 | }); 154 | 155 | const informationItem = moddle.create('dmn:InformationItem', { 156 | id: 'Parameter_1', 157 | name: 'BOOP' 158 | }); 159 | 160 | const literalExpression2 = moddle.create('dmn:LiteralExpression', { 161 | id: 'LiteralExpression_2', 162 | text: 'BAR' 163 | }); 164 | 165 | const binding = moddle.create('dmn:Binding', { 166 | parameter: informationItem, 167 | bindingFormula: literalExpression2 168 | }); 169 | 170 | const invocation = moddle.create('dmn:Invocation', { 171 | id: 'Invocation_1', 172 | calledFunction: literalExpression1, 173 | binding: [ binding ] 174 | }); 175 | 176 | // when 177 | const { xml } = await write(invocation); 178 | 179 | // then 180 | // dmn:LiteralExpression should come before dmn:Binding but doesn't 181 | expect(xml).to.equal( 182 | '' + 183 | '' + 184 | 'FOO' + 185 | '' + 186 | '' + 187 | '' + 188 | '' + 189 | 'BAR' + 190 | '' + 191 | '' + 192 | '' 193 | ); 194 | }); 195 | 196 | }); 197 | 198 | 199 | describe('di', function() { 200 | 201 | it('dmn:Decision', async function() { 202 | 203 | // given 204 | const expected = 205 | '' + 206 | '' + 207 | '' + 208 | '' + 209 | '' + 210 | '' + 211 | '' + 212 | '' + 213 | '' + 214 | ''; 215 | 216 | const definitions = moddle.create('dmn:Definitions'); 217 | 218 | const decision = moddle.create('dmn:Decision', { 219 | id: 'Decision_1', 220 | name: 'Decision_1' 221 | }); 222 | 223 | definitions.get('drgElement').push(decision); 224 | 225 | decision.$parent = definitions; 226 | 227 | const dmnDiagram = moddle.create('dmndi:DMNDiagram', { 228 | id: 'DMNDiagram_1', 229 | diagramElements: [] 230 | }); 231 | 232 | const dmnDI = moddle.create('dmndi:DMNDI', { 233 | diagrams: [ dmnDiagram ] 234 | }); 235 | 236 | dmnDiagram.$parent = dmnDI; 237 | 238 | definitions.set('dmnDI', dmnDI); 239 | 240 | dmnDI.$parent = definitions; 241 | 242 | const bounds = moddle.create('dc:Bounds', { 243 | height: 80, 244 | width: 180, 245 | x: 100, 246 | y: 100 247 | }); 248 | 249 | const shape = moddle.create('dmndi:DMNShape', { 250 | id: 'DMNShape_1', 251 | bounds, 252 | dmnElementRef: decision 253 | }); 254 | 255 | bounds.$parent = shape; 256 | 257 | dmnDiagram.get('diagramElements').push(shape); 258 | 259 | shape.$parent = dmnDiagram.get('diagramElements'); 260 | 261 | // when 262 | const { xml } = await write(definitions); 263 | 264 | // then 265 | expect(xml).to.equal(expected); 266 | }); 267 | 268 | 269 | it('dmn:InputData', async function() { 270 | 271 | // given 272 | const expected = 273 | '' + 274 | '' + 275 | '' + 276 | '' + 277 | '' + 278 | '' + 279 | '' + 280 | '' + 281 | '' + 282 | ''; 283 | 284 | const definitions = moddle.create('dmn:Definitions'); 285 | 286 | const inputData = moddle.create('dmn:InputData', { 287 | id: 'InputData_1', 288 | name: 'InputData_1' 289 | }); 290 | 291 | definitions.get('drgElement').push(inputData); 292 | 293 | inputData.$parent = definitions; 294 | 295 | const dmnDiagram = moddle.create('dmndi:DMNDiagram', { 296 | id: 'DMNDiagram_1', 297 | diagramElements: [] 298 | }); 299 | 300 | const dmnDI = moddle.create('dmndi:DMNDI', { 301 | diagrams: [ dmnDiagram ] 302 | }); 303 | 304 | dmnDiagram.$parent = dmnDI; 305 | 306 | definitions.set('dmnDI', dmnDI); 307 | 308 | dmnDI.$parent = definitions; 309 | 310 | const bounds = moddle.create('dc:Bounds', { 311 | height: 45, 312 | width: 125, 313 | x: 100, 314 | y: 100 315 | }); 316 | 317 | const shape = moddle.create('dmndi:DMNShape', { 318 | id: 'DMNShape_1', 319 | bounds, 320 | dmnElementRef: inputData 321 | }); 322 | 323 | bounds.$parent = shape; 324 | 325 | dmnDiagram.get('diagramElements').push(shape); 326 | 327 | shape.$parent = dmnDiagram.get('diagramElements'); 328 | 329 | // when 330 | const { xml } = await write(definitions); 331 | 332 | // then 333 | expect(xml).to.equal(expected); 334 | }); 335 | 336 | 337 | it('di:Extension', async function() { 338 | 339 | 340 | // given 341 | const expected = 342 | '' + 343 | '' + 344 | '' + 345 | '' + 346 | '' + 347 | '' + 348 | ''; 349 | 350 | const definitions = moddle.create('dmn:Definitions'); 351 | 352 | const dmnDiagram = moddle.create('dmndi:DMNDiagram', { 353 | id: 'DMNDiagram_1', 354 | diagramElements: [] 355 | }); 356 | 357 | const dmnDI = moddle.create('dmndi:DMNDI', { 358 | diagrams: [ dmnDiagram ] 359 | }); 360 | 361 | dmnDiagram.$parent = dmnDI; 362 | 363 | definitions.set('dmnDI', dmnDI); 364 | 365 | dmnDI.$parent = definitions; 366 | 367 | const extension = moddle.create('di:Extension'); 368 | 369 | dmnDiagram.set('extension', extension); 370 | 371 | extension.$parent = dmnDiagram; 372 | 373 | // when 374 | const { xml } = await write(definitions); 375 | 376 | // then 377 | expect(xml).to.equal(expected); 378 | }); 379 | 380 | }); 381 | 382 | 383 | describe('biodi', function() { 384 | 385 | it('should write DecisionTable#annotationsWidth', async function() { 386 | 387 | // given 388 | const expected = ''; 392 | 393 | const decisionTable = moddle.create('dmn:DecisionTable'); 394 | decisionTable.set('annotationsWidth', 200); 395 | 396 | // when 397 | const { xml } = await write(decisionTable); 398 | 399 | // then 400 | expect(xml).to.equal(expected); 401 | }); 402 | 403 | 404 | it('should write InputClause#width', async function() { 405 | 406 | // given 407 | const expected = ''; 411 | 412 | const inputClause = moddle.create('dmn:InputClause'); 413 | inputClause.set('width', 200); 414 | 415 | // when 416 | const { xml } = await write(inputClause); 417 | 418 | // then 419 | expect(xml).to.equal(expected); 420 | }); 421 | 422 | 423 | it('should write OutputClause#width', async function() { 424 | 425 | // given 426 | const expected = ''; 430 | 431 | const outputClause = moddle.create('dmn:OutputClause'); 432 | outputClause.set('width', 200); 433 | 434 | // when 435 | const { xml } = await write(outputClause); 436 | 437 | // then 438 | expect(xml).to.equal(expected); 439 | }); 440 | 441 | }); 442 | }); 443 | -------------------------------------------------------------------------------- /test/spec/xml/read.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | import expect from '../../expect.js'; 4 | 5 | import DmnModdle from '../../../lib/index.js'; 6 | 7 | 8 | describe('dmn-moddle - read', function() { 9 | 10 | let moddle; 11 | 12 | beforeEach(function() { 13 | moddle = new DmnModdle(); 14 | }); 15 | 16 | function readFromFile(fileName, root = 'dmn:Definitions') { 17 | const file = fs.readFileSync(fileName, 'utf8'); 18 | 19 | return moddle.fromXML(file, root); 20 | } 21 | 22 | 23 | describe('dmn', function() { 24 | 25 | it('dmn:Definitions#import', async function() { 26 | 27 | // when 28 | const { rootElement } = await readFromFile('test/fixtures/dmn/dmn/definitions-import.dmn'); 29 | 30 | // then 31 | expect(rootElement).to.jsonEqual({ 32 | $type: 'dmn:Definitions', 33 | name: 'Definitions', 34 | namespace: 'http://ns', 35 | import: [ 36 | { 37 | $type: 'dmn:Import', 38 | namespace: 'http://my.ns', 39 | name: 'myimport', 40 | importType: 'https://www.omg.org/spec/DMN/20191111/MODEL/' 41 | } 42 | ] 43 | }); 44 | }); 45 | 46 | 47 | it('dmn:Definitions#extensionElements', async function() { 48 | 49 | // when 50 | const { rootElement: definitions } = await readFromFile('test/fixtures/dmn/dmn/definitions-extensionElements.dmn'); 51 | 52 | // then 53 | expect(definitions).to.jsonEqual({ 54 | $type: 'dmn:Definitions', 55 | name: 'Definitions', 56 | namespace: 'http://ns', 57 | extensionElements: { 58 | $type: 'dmn:ExtensionElements', 59 | values: [ 60 | { 61 | $type: 'foo:prop', 62 | name: 'FOO', 63 | value: 'BAR' 64 | } 65 | ] 66 | } 67 | }); 68 | }); 69 | 70 | 71 | it('dmn:Decision#extensionElements', async function() { 72 | 73 | // when 74 | const { rootElement: decision } = await readFromFile( 75 | 'test/fixtures/dmn/dmn/decision-extensionElements.part.dmn', 76 | 'dmn:Decision' 77 | ); 78 | 79 | // then 80 | expect(decision).to.jsonEqual({ 81 | $type: 'dmn:Decision', 82 | extensionElements: { 83 | $type: 'dmn:ExtensionElements', 84 | values: [ 85 | { 86 | $type: 'foo:prop', 87 | name: 'FOO', 88 | value: 'BAR' 89 | } 90 | ] 91 | } 92 | }); 93 | }); 94 | 95 | 96 | it('dmn:Definitions extension attributes', async function() { 97 | 98 | // when 99 | const { rootElement: definitions } = await readFromFile('test/fixtures/dmn/dmn/definitions-extensionAttributes.dmn'); 100 | 101 | // then 102 | expect(definitions).to.jsonEqual({ 103 | $type: 'dmn:Definitions', 104 | name: 'Definitions', 105 | namespace: 'http://ns' 106 | }); 107 | 108 | expect(definitions.get('foo:bar')).to.eql('FOOBAR'); 109 | }); 110 | 111 | 112 | it('dmn:Decision extension attributes', async function() { 113 | 114 | // when 115 | const { rootElement: decision } = await readFromFile( 116 | 'test/fixtures/dmn/dmn/decision-extensionAttributes.part.dmn', 117 | 'dmn:Decision' 118 | ); 119 | 120 | // then 121 | expect(decision).to.jsonEqual({ 122 | $type: 'dmn:Decision' 123 | }); 124 | 125 | expect(decision.get('foo:bar')).to.eql('FOOBAR'); 126 | }); 127 | 128 | 129 | it('dmn:Decision', async function() { 130 | 131 | // given 132 | const expected = { 133 | $type: 'dmn:Decision', 134 | id: 'Decision_1', 135 | name: 'Decision' 136 | }; 137 | 138 | // when 139 | const { rootElement: decision } = await readFromFile('test/fixtures/dmn/dmn/decision.dmn'); 140 | 141 | // then 142 | expect(decision.get('drgElement')[ 0 ]).to.jsonEqual(expected); 143 | }); 144 | 145 | 146 | it('dmn:Decision#decisionLogic', async function() { 147 | 148 | // given 149 | const expected = { 150 | $type: 'dmn:Decision', 151 | id: 'Decision_1', 152 | name: 'Decision', 153 | decisionLogic: { 154 | $type: 'dmn:LiteralExpression', 155 | id: 'LiteralExpression_1' 156 | } 157 | }; 158 | 159 | // when 160 | const { rootElement: decision } = await readFromFile('test/fixtures/dmn/dmn/decision-decisionLogic.part.dmn', 'dmn:Decision'); 161 | 162 | // then 163 | expect(decision).to.jsonEqual(expected); 164 | }); 165 | 166 | 167 | it('dmn:InputData', async function() { 168 | 169 | // given 170 | const expected = { 171 | $type: 'dmn:InputData', 172 | id: 'InputData_1', 173 | name: 'InputData' 174 | }; 175 | 176 | // when 177 | const { rootElement: inputData } = await readFromFile('test/fixtures/dmn/dmn/input-data.dmn'); 178 | 179 | // then 180 | expect(inputData.get('drgElement')[ 0 ]).to.jsonEqual(expected); 181 | }); 182 | 183 | 184 | it('dmn:InformationRequirement', async function() { 185 | 186 | // given 187 | const expected = { 188 | $type: 'dmn:InformationRequirement', 189 | id: 'InformationRequirement_1', 190 | requiredInput: { 191 | $type: 'dmn:DMNElementReference', 192 | href: '#InputData_1' 193 | } 194 | }; 195 | 196 | // when 197 | const { rootElement: definitions } = await readFromFile('test/fixtures/dmn/dmn/information-requirement.dmn'); 198 | 199 | // then 200 | expect(definitions.get('drgElement')[ 0 ].get('informationRequirement')[ 0 ]).to.jsonEqual(expected); 201 | }); 202 | 203 | 204 | it('dmn:FunctionDefinition', async function() { 205 | 206 | // when 207 | const { rootElement: functionDefinition } = await readFromFile( 208 | 'test/fixtures/dmn/dmn/function-definition.part.dmn', 209 | 'dmn:FunctionDefinition' 210 | ); 211 | 212 | // then 213 | expect(functionDefinition).to.jsonEqual({ 214 | $type: 'dmn:FunctionDefinition', 215 | kind: 'FEEL', 216 | formalParameter: [ 217 | { 218 | $type: 'dmn:InformationItem', 219 | typeRef: 'string', 220 | name: 'ProductType' 221 | }, 222 | { 223 | $type: 'dmn:InformationItem', 224 | typeRef: 'number', 225 | name: 'Rate' 226 | } 227 | ], 228 | body: { 229 | $type: 'dmn:Context' 230 | } 231 | }); 232 | }); 233 | 234 | 235 | it('dmn:List', async function() { 236 | 237 | // when 238 | const { rootElement: list } = await readFromFile( 239 | 'test/fixtures/dmn/dmn/list.part.dmn', 240 | 'dmn:List' 241 | ); 242 | 243 | // then 244 | expect(list).to.jsonEqual({ 245 | $type: 'dmn:List', 246 | elements: [ 247 | { 248 | $type: 'dmn:LiteralExpression', 249 | text: 'FOO' 250 | }, 251 | { 252 | $type: 'dmn:LiteralExpression', 253 | text: 'BAR' 254 | } 255 | ] 256 | }); 257 | }); 258 | 259 | 260 | it('dmn:BusinessKnowledgeModel', async function() { 261 | 262 | // when 263 | const { rootElement: bkm } = await readFromFile( 264 | 'test/fixtures/dmn/dmn/business-knowledge-model.part.dmn', 265 | 'dmn:BusinessKnowledgeModel' 266 | ); 267 | 268 | // then 269 | expect(bkm).to.jsonEqual({ 270 | $type: 'dmn:BusinessKnowledgeModel', 271 | name: 'InstallmentCalculation', 272 | id: 'b_InstallmentCalculation', 273 | variable: { 274 | $type: 'dmn:InformationItem', 275 | name: 'InstallmentCalculation' 276 | }, 277 | encapsulatedLogic: { 278 | $type: 'dmn:FunctionDefinition', 279 | formalParameter: [ 280 | { 281 | $type: 'dmn:InformationItem', 282 | typeRef: 'string', 283 | name: 'ProductType' 284 | }, 285 | { 286 | $type: 'dmn:InformationItem', 287 | typeRef: 'number', 288 | name: 'Rate' 289 | }, 290 | { 291 | $type: 'dmn:InformationItem', 292 | typeRef: 'number', 293 | name: 'Term' 294 | }, 295 | { 296 | $type: 'dmn:InformationItem', 297 | typeRef: 'number', 298 | name: 'Amount' 299 | } 300 | ], 301 | body: { 302 | $type: 'dmn:Context' 303 | } 304 | } 305 | }); 306 | }); 307 | 308 | 309 | it('dmn:UnaryTests', async function() { 310 | 311 | // when 312 | const { rootElement: unaryTests } = await readFromFile( 313 | 'test/fixtures/dmn/dmn/unary-tests.part.dmn', 314 | 'dmn:UnaryTests' 315 | ); 316 | 317 | // then 318 | expect(unaryTests).to.jsonEqual({ 319 | $type: 'dmn:UnaryTests', 320 | expressionLanguage: 'LANG', 321 | text: 'FOO' 322 | }); 323 | 324 | }); 325 | 326 | 327 | it('dmn:Context', async function() { 328 | 329 | // when 330 | const { rootElement: context } = await readFromFile( 331 | 'test/fixtures/dmn/dmn/context.part.dmn', 332 | 'dmn:Context' 333 | ); 334 | 335 | // then 336 | expect(context).to.jsonEqual({ 337 | $type: 'dmn:Context', 338 | contextEntry: [ 339 | { 340 | $type: 'dmn:ContextEntry', 341 | variable: { 342 | $type: 'dmn:InformationItem', 343 | typeRef: 'number', 344 | name: 'MonthlyFee' 345 | }, 346 | value: { 347 | $type: 'dmn:LiteralExpression', 348 | text: 'if ProductType ="STANDARD LOAN" then 20.00 else if ProductType ="SPECIAL LOAN" then 25.00 else null' 349 | } 350 | }, 351 | { 352 | $type: 'dmn:ContextEntry', 353 | variable: { 354 | $type: 'dmn:InformationItem', 355 | typeRef: 'number', 356 | name: 'MonthlyRepayment' 357 | }, 358 | value: { 359 | $type: 'dmn:LiteralExpression', 360 | text: '(Amount *Rate/12) / (1 - (1 + Rate/12)**-Term)' 361 | } 362 | }, 363 | { 364 | $type: 'dmn:ContextEntry', 365 | value: { 366 | $type: 'dmn:LiteralExpression', 367 | typeRef: 'number', 368 | text: 'MonthlyRepayment+MonthlyFee' 369 | } 370 | } 371 | ] 372 | }); 373 | 374 | }); 375 | 376 | }); 377 | 378 | 379 | describe('di', function() { 380 | 381 | it('dmn:Decision + DI', async function() { 382 | 383 | // given 384 | const expected = { 385 | $type: 'dmndi:DMNDI', 386 | diagrams: [ 387 | { 388 | $type: 'dmndi:DMNDiagram', 389 | diagramElements: [ 390 | { 391 | $type: 'dmndi:DMNShape', 392 | bounds: { 393 | $type: 'dc:Bounds', 394 | height: 80, 395 | width: 180, 396 | x: 100, 397 | y: 100 398 | } 399 | } 400 | ] 401 | } 402 | ] 403 | }; 404 | 405 | // when 406 | const { rootElement: definitions } = await readFromFile('test/fixtures/dmn/dmndi/decision.dmn'); 407 | 408 | // then 409 | expect(definitions.get('dmnDI')).to.jsonEqual(expected); 410 | }); 411 | 412 | 413 | it('dmn:InputData + DI', async function() { 414 | 415 | // given 416 | const expected = { 417 | $type: 'dmndi:DMNDI', 418 | diagrams: [ 419 | { 420 | $type: 'dmndi:DMNDiagram', 421 | diagramElements: [ 422 | { 423 | $type: 'dmndi:DMNShape', 424 | bounds: { 425 | $type: 'dc:Bounds', 426 | height: 45, 427 | width: 125, 428 | x: 100, 429 | y: 100 430 | } 431 | } 432 | ] 433 | } 434 | ] 435 | }; 436 | 437 | // when 438 | const { rootElement: definitions } = await readFromFile('test/fixtures/dmn/dmndi/input-data.dmn'); 439 | 440 | // then 441 | expect(definitions.get('dmnDI')).to.jsonEqual(expected); 442 | }); 443 | 444 | 445 | it('dmn:TextAnnotation + DI', async function() { 446 | 447 | // when 448 | const { rootElement: definitions } = await readFromFile('test/fixtures/dmn/dmndi/text-annotation.dmn'); 449 | 450 | // then 451 | const artifact = definitions.get('artifact'); 452 | 453 | expect(artifact).to.have.lengthOf(2); 454 | 455 | const textAnnotation = artifact[ 0 ]; 456 | 457 | expect(textAnnotation).to.jsonEqual({ 458 | $type: 'dmn:TextAnnotation', 459 | id: 'TextAnnotation_1', 460 | text: 'TextAnnotation_1' 461 | }); 462 | 463 | const association = artifact[ 1 ]; 464 | 465 | expect(association).to.jsonEqual({ 466 | $type: 'dmn:Association', 467 | id: 'Association_1', 468 | sourceRef: { 469 | $type: 'dmn:DMNElementReference', 470 | href: '#Decision_1' 471 | }, 472 | targetRef: { 473 | $type: 'dmn:DMNElementReference', 474 | href: '#TextAnnotation_1' 475 | } 476 | }); 477 | }); 478 | 479 | 480 | it('dmndi:DMNLabel', async function() { 481 | 482 | // when 483 | const { rootElement: definitions } = await readFromFile('test/fixtures/dmn/dmndi/label.dmn'); 484 | 485 | // then 486 | expect(definitions.get('dmnDI')).to.jsonEqual({ 487 | $type: 'dmndi:DMNDI', 488 | diagrams: [ 489 | { 490 | $type: 'dmndi:DMNDiagram', 491 | diagramElements: [ 492 | { 493 | $type: 'dmndi:DMNShape', 494 | id: 'DMNShape_1', 495 | bounds: { 496 | $type: 'dc:Bounds', 497 | height: 80, 498 | width: 180, 499 | x: 100, 500 | y: 100 501 | }, 502 | label: { 503 | $type: 'dmndi:DMNLabel', 504 | text: { 505 | $type: 'dmndi:Text', 506 | text: 'Decision_1' 507 | } 508 | } 509 | }, 510 | { 511 | $type: 'dmndi:DMNShape', 512 | id: 'DMNShape_2', 513 | bounds: { 514 | $type: 'dc:Bounds', 515 | height: 45, 516 | width: 125, 517 | x: 127.5, 518 | y: 300 519 | }, 520 | label: { 521 | $type: 'dmndi:DMNLabel', 522 | text: { 523 | $type: 'dmndi:Text', 524 | text: 'InputData_1' 525 | } 526 | } 527 | }, 528 | { 529 | $type: 'dmndi:DMNEdge', 530 | id: 'DMNEdge_1', 531 | waypoint: [ 532 | { 533 | $type: 'dc:Point', 534 | x: 180, 535 | y: 190 536 | }, 537 | { 538 | $type: 'dc:Point', 539 | x: 180, 540 | y: 300 541 | } 542 | ], 543 | label: { 544 | $type: 'dmndi:DMNLabel', 545 | text: { 546 | $type: 'dmndi:Text', 547 | text: 'InformationRequirement_1' 548 | } 549 | } 550 | } 551 | ] 552 | } 553 | ], 554 | styles: [ 555 | { 556 | '$type': 'dmndi:DMNStyle', 557 | 'id': 'SharedStyle_1', 558 | 'fontFamily': 'arial,helvetica,sans-serif', 559 | 'fontSize': 14, 560 | 'fontBold': false, 561 | 'fontItalic': false, 562 | 'fontUnderline': false, 563 | 'fontStrikeThrough': false 564 | } 565 | ] 566 | }); 567 | }); 568 | 569 | 570 | it('dmndi:Diagram with dmndi:Size', async function() { 571 | 572 | // when 573 | const { rootElement: definitions } = await readFromFile('test/fixtures/dmn/dmndi/size.dmn'); 574 | 575 | // then 576 | const diagram = definitions.get('dmnDI').diagrams[0]; 577 | 578 | expect(diagram.size).to.jsonEqual({ 579 | $type: 'dmndi:Size', 580 | height: 650.0, 581 | width: 650.0 582 | }); 583 | }); 584 | 585 | 586 | it('dmndi:Diagram with di:Extension', async function() { 587 | 588 | // when 589 | const { rootElement: definitions } = await readFromFile('test/fixtures/dmn/dmndi/extension.dmn'); 590 | 591 | // then 592 | const diagram = definitions.get('dmnDI').diagrams[0]; 593 | 594 | expect(diagram.get('extension')).to.jsonEqual({ 595 | $type: 'di:Extension', 596 | values: [ 597 | { 598 | $type: 'custom:Element', 599 | id: 'CustomElement' 600 | } 601 | ] 602 | }); 603 | }); 604 | 605 | }); 606 | 607 | 608 | describe('biodi', function() { 609 | 610 | it('should read biodi', async function() { 611 | 612 | // when 613 | const { rootElement: definitions } = await readFromFile('test/fixtures/dmn/biodi/biodi.dmn'); 614 | 615 | // then 616 | const decisionTable = definitions.get('drgElement')[0].decisionLogic; 617 | 618 | expect(decisionTable).to.have.property('annotationsWidth', 200); 619 | expect(decisionTable.input[0]).to.have.property('width', 150); 620 | expect(decisionTable.output[0]).to.have.property('width', 150); 621 | }); 622 | }); 623 | }); 624 | -------------------------------------------------------------------------------- /resources/dmn/xsd/DMN13.xsd: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 11 | 12 | Include the DMN Diagram Interchange (DI) schema 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | -------------------------------------------------------------------------------- /resources/dmn/json/dmn13.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DMN", 3 | "prefix": "dmn", 4 | "uri": "https://www.omg.org/spec/DMN/20191111/MODEL/", 5 | "types": [ 6 | { 7 | "name": "AuthorityRequirement", 8 | "superClass": [ 9 | "DMNElement" 10 | ], 11 | "properties": [ 12 | { 13 | "name": "requiredAuthority", 14 | "type": "DMNElementReference", 15 | "xml": { 16 | "serialize": "property" 17 | } 18 | }, 19 | { 20 | "name": "requiredDecision", 21 | "type": "DMNElementReference", 22 | "xml": { 23 | "serialize": "property" 24 | } 25 | }, 26 | { 27 | "name": "requiredInput", 28 | "type": "DMNElementReference", 29 | "xml": { 30 | "serialize": "property" 31 | } 32 | } 33 | ] 34 | }, 35 | { 36 | "name": "ItemDefinition", 37 | "superClass": [ 38 | "NamedElement" 39 | ], 40 | "properties": [ 41 | { 42 | "name": "typeRef", 43 | "type": "String" 44 | }, 45 | { 46 | "name": "allowedValues", 47 | "type": "UnaryTests", 48 | "xml": { 49 | "serialize": "property" 50 | } 51 | }, 52 | { 53 | "name": "typeLanguage", 54 | "type": "String", 55 | "isAttr": true 56 | }, 57 | { 58 | "name": "itemComponent", 59 | "type": "ItemDefinition", 60 | "isMany": true, 61 | "xml": { 62 | "serialize": "property" 63 | } 64 | }, 65 | { 66 | "name": "functionItem", 67 | "type": "FunctionItem" 68 | }, 69 | { 70 | "name": "isCollection", 71 | "isAttr": true, 72 | "type": "Boolean" 73 | } 74 | ] 75 | }, 76 | { 77 | "name": "Definitions", 78 | "superClass": [ 79 | "NamedElement" 80 | ], 81 | "properties": [ 82 | { 83 | "name": "import", 84 | "type": "Import", 85 | "isMany": true 86 | }, 87 | { 88 | "name": "itemDefinition", 89 | "type": "ItemDefinition", 90 | "isMany": true 91 | }, 92 | { 93 | "name": "drgElement", 94 | "type": "DRGElement", 95 | "isMany": true 96 | }, 97 | { 98 | "name": "artifact", 99 | "type": "Artifact", 100 | "isMany": true 101 | }, 102 | { 103 | "name": "elementCollection", 104 | "type": "ElementCollection", 105 | "isMany": true 106 | }, 107 | { 108 | "name": "businessContextElement", 109 | "type": "BusinessContextElement", 110 | "isMany": true 111 | }, 112 | { 113 | "name": "namespace", 114 | "type": "String", 115 | "isAttr": true 116 | }, 117 | { 118 | "name": "expressionLanguage", 119 | "type": "String", 120 | "isAttr": true 121 | }, 122 | { 123 | "name": "typeLanguage", 124 | "type": "String", 125 | "isAttr": true 126 | }, 127 | { 128 | "name": "exporter", 129 | "isAttr": true, 130 | "type": "String" 131 | }, 132 | { 133 | "name": "exporterVersion", 134 | "isAttr": true, 135 | "type": "String" 136 | }, 137 | { 138 | "name": "dmnDI", 139 | "type": "dmndi:DMNDI" 140 | } 141 | ] 142 | }, 143 | { 144 | "name": "KnowledgeSource", 145 | "superClass": [ 146 | "DRGElement" 147 | ], 148 | "properties": [ 149 | { 150 | "name": "authorityRequirement", 151 | "type": "AuthorityRequirement", 152 | "isMany": true 153 | }, 154 | { 155 | "name": "type", 156 | "type": "String" 157 | }, 158 | { 159 | "name": "owner", 160 | "type": "DMNElementReference", 161 | "xml": { 162 | "serialize": "property" 163 | } 164 | }, 165 | { 166 | "name": "locationURI", 167 | "type": "String", 168 | "isAttr": true 169 | } 170 | ] 171 | }, 172 | { 173 | "name": "DecisionRule", 174 | "superClass": [ 175 | "DMNElement" 176 | ], 177 | "properties": [ 178 | { 179 | "name": "inputEntry", 180 | "type": "UnaryTests", 181 | "isMany": true, 182 | "xml": { 183 | "serialize": "property" 184 | } 185 | }, 186 | { 187 | "name": "outputEntry", 188 | "type": "LiteralExpression", 189 | "isMany": true, 190 | "xml": { 191 | "serialize": "property" 192 | } 193 | }, 194 | { 195 | "name": "annotationEntry", 196 | "type": "RuleAnnotation", 197 | "isMany": true, 198 | "xml": { 199 | "serialize": "property" 200 | } 201 | } 202 | ] 203 | }, 204 | { 205 | "name": "Expression", 206 | "isAbstract": true, 207 | "superClass": [ 208 | "DMNElement" 209 | ], 210 | "properties": [ 211 | { 212 | "name": "typeRef", 213 | "isAttr": true, 214 | "type": "String" 215 | } 216 | ] 217 | }, 218 | { 219 | "name": "InformationItem", 220 | "superClass": [ 221 | "NamedElement" 222 | ], 223 | "properties": [ 224 | { 225 | "name": "typeRef", 226 | "isAttr": true, 227 | "type": "String" 228 | } 229 | ] 230 | }, 231 | { 232 | "name": "Decision", 233 | "superClass": [ 234 | "DRGElement" 235 | ], 236 | "properties": [ 237 | { 238 | "name": "question", 239 | "type": "String", 240 | "xml": { 241 | "serialize": "property" 242 | } 243 | }, 244 | { 245 | "name": "allowedAnswers", 246 | "type": "String", 247 | "xml": { 248 | "serialize": "property" 249 | } 250 | }, 251 | { 252 | "name": "variable", 253 | "type": "InformationItem", 254 | "xml": { 255 | "serialize": "property" 256 | } 257 | }, 258 | { 259 | "name": "informationRequirement", 260 | "type": "InformationRequirement", 261 | "isMany": true 262 | }, 263 | { 264 | "name": "knowledgeRequirement", 265 | "type": "KnowledgeRequirement", 266 | "isMany": true 267 | }, 268 | { 269 | "name": "authorityRequirement", 270 | "type": "AuthorityRequirement", 271 | "isMany": true 272 | }, 273 | { 274 | "name": "supportedObjective", 275 | "isMany": true, 276 | "type": "DMNElementReference", 277 | "xml": { 278 | "serialize": "property" 279 | } 280 | }, 281 | { 282 | "name": "impactedPerformanceIndicator", 283 | "type": "DMNElementReference", 284 | "isMany": true, 285 | "xml": { 286 | "serialize": "property" 287 | } 288 | }, 289 | { 290 | "name": "decisionMaker", 291 | "type": "DMNElementReference", 292 | "isMany": true, 293 | "xml": { 294 | "serialize": "property" 295 | } 296 | }, 297 | { 298 | "name": "decisionOwner", 299 | "type": "DMNElementReference", 300 | "isMany": true, 301 | "xml": { 302 | "serialize": "property" 303 | } 304 | }, 305 | { 306 | "name": "usingProcess", 307 | "isMany": true, 308 | "type": "DMNElementReference", 309 | "xml": { 310 | "serialize": "property" 311 | } 312 | }, 313 | { 314 | "name": "usingTask", 315 | "isMany": true, 316 | "type": "DMNElementReference", 317 | "xml": { 318 | "serialize": "property" 319 | } 320 | }, 321 | { 322 | "name": "decisionLogic", 323 | "type": "Expression" 324 | } 325 | ] 326 | }, 327 | { 328 | "name": "Invocation", 329 | "superClass": [ 330 | "Expression" 331 | ], 332 | "properties": [ 333 | { 334 | "name": "calledFunction", 335 | "type": "Expression" 336 | }, 337 | { 338 | "name": "binding", 339 | "type": "Binding", 340 | "isMany": true 341 | } 342 | ] 343 | }, 344 | { 345 | "name": "OrganisationalUnit", 346 | "superClass": [ 347 | "BusinessContextElement" 348 | ], 349 | "properties": [ 350 | { 351 | "name": "decisionMade", 352 | "type": "Decision", 353 | "isReference": true, 354 | "isMany": true 355 | }, 356 | { 357 | "name": "decisionOwned", 358 | "type": "Decision", 359 | "isReference": true, 360 | "isMany": true 361 | } 362 | ] 363 | }, 364 | { 365 | "name": "Import", 366 | "superClass": [ 367 | "NamedElement" 368 | ], 369 | "properties": [ 370 | { 371 | "name": "importType", 372 | "type": "String", 373 | "isAttr": true 374 | }, 375 | { 376 | "name": "locationURI", 377 | "type": "String", 378 | "isAttr": true 379 | }, 380 | { 381 | "name": "namespace", 382 | "type": "String", 383 | "isAttr": true 384 | } 385 | ] 386 | }, 387 | { 388 | "name": "InformationRequirement", 389 | "superClass": [ 390 | "DMNElement" 391 | ], 392 | "properties": [ 393 | { 394 | "name": "requiredDecision", 395 | "type": "DMNElementReference", 396 | "xml": { 397 | "serialize": "property" 398 | } 399 | }, 400 | { 401 | "name": "requiredInput", 402 | "type": "DMNElementReference", 403 | "xml": { 404 | "serialize": "property" 405 | } 406 | } 407 | ] 408 | }, 409 | { 410 | "name": "ElementCollection", 411 | "superClass": [ 412 | "NamedElement" 413 | ], 414 | "properties": [ 415 | { 416 | "name": "drgElement", 417 | "type": "DMNElementReference", 418 | "isMany": true, 419 | "xml": { 420 | "serialize": "property" 421 | } 422 | } 423 | ] 424 | }, 425 | { 426 | "name": "DRGElement", 427 | "isAbstract": true, 428 | "superClass": [ 429 | "NamedElement" 430 | ], 431 | "properties": [] 432 | }, 433 | { 434 | "name": "InputData", 435 | "superClass": [ 436 | "DRGElement" 437 | ], 438 | "properties": [ 439 | { 440 | "name": "variable", 441 | "type": "InformationItem", 442 | "xml": { 443 | "serialize": "property" 444 | } 445 | } 446 | ] 447 | }, 448 | { 449 | "name": "DMNElement", 450 | "isAbstract": true, 451 | "properties": [ 452 | { 453 | "name": "description", 454 | "type": "String" 455 | }, 456 | { 457 | "name": "extensionElements", 458 | "type": "ExtensionElements" 459 | }, 460 | { 461 | "name": "id", 462 | "type": "String", 463 | "isAttr": true, 464 | "isId": true 465 | }, 466 | { 467 | "name": "extensionAttribute", 468 | "type": "ExtensionAttribute", 469 | "isMany": true 470 | }, 471 | { 472 | "name": "label", 473 | "isAttr": true, 474 | "type": "String" 475 | } 476 | ] 477 | }, 478 | { 479 | "name": "InputClause", 480 | "superClass": [ 481 | "DMNElement" 482 | ], 483 | "properties": [ 484 | { 485 | "name": "inputExpression", 486 | "type": "LiteralExpression", 487 | "xml": { 488 | "serialize": "property" 489 | } 490 | }, 491 | { 492 | "name": "inputValues", 493 | "type": "UnaryTests", 494 | "xml": { 495 | "serialize": "property" 496 | } 497 | } 498 | ] 499 | }, 500 | { 501 | "name": "DecisionTable", 502 | "superClass": [ 503 | "Expression" 504 | ], 505 | "properties": [ 506 | { 507 | "name": "input", 508 | "type": "InputClause", 509 | "isMany": true, 510 | "xml": { 511 | "serialize": "property" 512 | } 513 | }, 514 | { 515 | "name": "output", 516 | "type": "OutputClause", 517 | "isMany": true, 518 | "xml": { 519 | "serialize": "property" 520 | } 521 | }, 522 | { 523 | "name": "annotation", 524 | "type": "RuleAnnotationClause", 525 | "isMany": true, 526 | "xml": { 527 | "serialize": "property" 528 | } 529 | }, 530 | { 531 | "name": "rule", 532 | "type": "DecisionRule", 533 | "isMany": true, 534 | "xml": { 535 | "serialize": "property" 536 | } 537 | }, 538 | { 539 | "name": "hitPolicy", 540 | "type": "HitPolicy", 541 | "isAttr": true, 542 | "default": "UNIQUE" 543 | }, 544 | { 545 | "name": "aggregation", 546 | "type": "BuiltinAggregator", 547 | "isAttr": true 548 | }, 549 | { 550 | "name": "preferredOrientation", 551 | "type": "DecisionTableOrientation", 552 | "isAttr": true 553 | }, 554 | { 555 | "name": "outputLabel", 556 | "isAttr": true, 557 | "type": "String" 558 | } 559 | ] 560 | }, 561 | { 562 | "name": "LiteralExpression", 563 | "superClass": [ 564 | "Expression" 565 | ], 566 | "properties": [ 567 | { 568 | "name": "expressionLanguage", 569 | "type": "String", 570 | "isAttr": true 571 | }, 572 | { 573 | "name": "text", 574 | "type": "String" 575 | }, 576 | { 577 | "name": "importedValues", 578 | "type": "ImportedValues" 579 | } 580 | ] 581 | }, 582 | { 583 | "name": "Binding", 584 | "properties": [ 585 | { 586 | "name": "parameter", 587 | "type": "InformationItem", 588 | "xml": { 589 | "serialize": "property" 590 | } 591 | }, 592 | { 593 | "name": "bindingFormula", 594 | "type": "Expression" 595 | } 596 | ] 597 | }, 598 | { 599 | "name": "KnowledgeRequirement", 600 | "superClass": [ 601 | "DMNElement" 602 | ], 603 | "properties": [ 604 | { 605 | "name": "requiredKnowledge", 606 | "type": "DMNElementReference", 607 | "xml": { 608 | "serialize": "property" 609 | } 610 | } 611 | ] 612 | }, 613 | { 614 | "name": "BusinessKnowledgeModel", 615 | "superClass": [ 616 | "Invocable" 617 | ], 618 | "properties": [ 619 | { 620 | "name": "encapsulatedLogic", 621 | "type": "FunctionDefinition", 622 | "xml": { 623 | "serialize": "property" 624 | } 625 | }, 626 | { 627 | "name": "knowledgeRequirement", 628 | "type": "KnowledgeRequirement", 629 | "isMany": true 630 | }, 631 | { 632 | "name": "authorityRequirement", 633 | "type": "AuthorityRequirement", 634 | "isMany": true 635 | } 636 | ] 637 | }, 638 | { 639 | "name": "BusinessContextElement", 640 | "isAbstract": true, 641 | "superClass": [ 642 | "NamedElement" 643 | ], 644 | "properties": [ 645 | { 646 | "name": "URI", 647 | "type": "String", 648 | "isAttr": true 649 | } 650 | ] 651 | }, 652 | { 653 | "name": "PerformanceIndicator", 654 | "superClass": [ 655 | "BusinessContextElement" 656 | ], 657 | "properties": [ 658 | { 659 | "name": "impactingDecision", 660 | "type": "DMNElementReference", 661 | "isMany": true, 662 | "xml": { 663 | "serialize": "property" 664 | } 665 | } 666 | ] 667 | }, 668 | { 669 | "name": "FunctionDefinition", 670 | "superClass": [ 671 | "Expression" 672 | ], 673 | "properties": [ 674 | { 675 | "name": "formalParameter", 676 | "type": "InformationItem", 677 | "isMany": true, 678 | "xml": { 679 | "serialize": "property" 680 | } 681 | }, 682 | { 683 | "name": "body", 684 | "type": "Expression" 685 | }, 686 | { 687 | "name": "kind", 688 | "type": "FunctionKind", 689 | "isAttr": true 690 | } 691 | ] 692 | }, 693 | { 694 | "name": "Context", 695 | "superClass": [ 696 | "Expression" 697 | ], 698 | "properties": [ 699 | { 700 | "name": "contextEntry", 701 | "type": "ContextEntry", 702 | "isMany": true 703 | } 704 | ] 705 | }, 706 | { 707 | "name": "ContextEntry", 708 | "superClass": [ 709 | "DMNElement" 710 | ], 711 | "properties": [ 712 | { 713 | "name": "variable", 714 | "type": "InformationItem", 715 | "xml": { 716 | "serialize": "property" 717 | } 718 | }, 719 | { 720 | "name": "value", 721 | "type": "Expression" 722 | } 723 | ] 724 | }, 725 | { 726 | "name": "List", 727 | "superClass": [ 728 | "Expression" 729 | ], 730 | "properties": [ 731 | { 732 | "name": "elements", 733 | "isMany": true, 734 | "type": "Expression" 735 | } 736 | ] 737 | }, 738 | { 739 | "name": "Relation", 740 | "superClass": [ 741 | "Expression" 742 | ], 743 | "properties": [ 744 | { 745 | "name": "column", 746 | "type": "InformationItem", 747 | "isMany": true, 748 | "xml": { 749 | "serialize": "property" 750 | } 751 | }, 752 | { 753 | "name": "row", 754 | "type": "List", 755 | "isMany": true, 756 | "xml": { 757 | "serialize": "property" 758 | } 759 | } 760 | ] 761 | }, 762 | { 763 | "name": "OutputClause", 764 | "superClass": [ 765 | "DMNElement" 766 | ], 767 | "properties": [ 768 | { 769 | "name": "outputValues", 770 | "type": "UnaryTests", 771 | "xml": { 772 | "serialize": "property" 773 | } 774 | }, 775 | { 776 | "name": "defaultOutputEntry", 777 | "type": "LiteralExpression", 778 | "xml": { 779 | "serialize": "property" 780 | } 781 | }, 782 | { 783 | "name": "name", 784 | "isAttr": true, 785 | "type": "String" 786 | }, 787 | { 788 | "name": "typeRef", 789 | "isAttr": true, 790 | "type": "String" 791 | } 792 | ] 793 | }, 794 | { 795 | "name": "UnaryTests", 796 | "superClass": [ 797 | "Expression" 798 | ], 799 | "properties": [ 800 | { 801 | "name": "text", 802 | "type": "String" 803 | }, 804 | { 805 | "name": "expressionLanguage", 806 | "type": "String", 807 | "isAttr": true 808 | } 809 | ] 810 | }, 811 | { 812 | "name": "NamedElement", 813 | "isAbstract": true, 814 | "superClass": [ 815 | "DMNElement" 816 | ], 817 | "properties": [ 818 | { 819 | "name": "name", 820 | "isAttr": true, 821 | "type": "String" 822 | } 823 | ] 824 | }, 825 | { 826 | "name": "ImportedValues", 827 | "superClass": [ 828 | "Import" 829 | ], 830 | "properties": [ 831 | { 832 | "name": "importedElement", 833 | "type": "String" 834 | }, 835 | { 836 | "name": "expressionLanguage", 837 | "type": "String", 838 | "isAttr": true 839 | } 840 | ] 841 | }, 842 | { 843 | "name": "DecisionService", 844 | "superClass": [ 845 | "Invocable" 846 | ], 847 | "properties": [ 848 | { 849 | "name": "outputDecision", 850 | "type": "DMNElementReference", 851 | "isMany": true, 852 | "xml": { 853 | "serialize": "property" 854 | } 855 | }, 856 | { 857 | "name": "encapsulatedDecision", 858 | "type": "DMNElementReference", 859 | "isMany": true, 860 | "xml": { 861 | "serialize": "property" 862 | } 863 | }, 864 | { 865 | "name": "inputDecision", 866 | "type": "DMNElementReference", 867 | "isMany": true, 868 | "xml": { 869 | "serialize": "property" 870 | } 871 | }, 872 | { 873 | "name": "inputData", 874 | "type": "DMNElementReference", 875 | "isMany": true, 876 | "xml": { 877 | "serialize": "property" 878 | } 879 | } 880 | ] 881 | }, 882 | { 883 | "name": "ExtensionElements", 884 | "properties": [ 885 | { 886 | "name": "values", 887 | "type": "Element", 888 | "isMany": true 889 | } 890 | ] 891 | }, 892 | { 893 | "name": "ExtensionAttribute", 894 | "properties": [ 895 | { 896 | "name": "value", 897 | "type": "Element" 898 | }, 899 | { 900 | "name": "valueRef", 901 | "type": "Element", 902 | "isAttr": true, 903 | "isReference": true 904 | }, 905 | { 906 | "name": "name", 907 | "isAttr": true, 908 | "type": "String" 909 | } 910 | ] 911 | }, 912 | { 913 | "name": "Element", 914 | "isAbstract": true, 915 | "properties": [ 916 | { 917 | "name": "extensionAttribute", 918 | "type": "ExtensionAttribute", 919 | "isAttr": true, 920 | "isReference": true 921 | }, 922 | { 923 | "name": "elements", 924 | "type": "ExtensionElements", 925 | "isAttr": true, 926 | "isReference": true 927 | } 928 | ] 929 | }, 930 | { 931 | "name": "Artifact", 932 | "isAbstract": true, 933 | "superClass": [ 934 | "DMNElement" 935 | ], 936 | "properties": [] 937 | }, 938 | { 939 | "name": "Association", 940 | "superClass": [ 941 | "Artifact" 942 | ], 943 | "properties": [ 944 | { 945 | "name": "sourceRef", 946 | "type": "DMNElementReference", 947 | "xml": { 948 | "serialize": "property" 949 | } 950 | }, 951 | { 952 | "name": "targetRef", 953 | "type": "DMNElementReference", 954 | "xml": { 955 | "serialize": "property" 956 | } 957 | }, 958 | { 959 | "name": "associationDirection", 960 | "type": "AssociationDirection", 961 | "isAttr": true 962 | } 963 | ] 964 | }, 965 | { 966 | "name": "TextAnnotation", 967 | "superClass": [ 968 | "Artifact" 969 | ], 970 | "properties": [ 971 | { 972 | "name": "text", 973 | "type": "String" 974 | }, 975 | { 976 | "name": "textFormat", 977 | "isAttr": true, 978 | "type": "String", 979 | "default": "text/plain" 980 | } 981 | ] 982 | }, 983 | { 984 | "name": "RuleAnnotationClause", 985 | "properties": [ 986 | { 987 | "name": "name", 988 | "isAttr": true, 989 | "type": "String" 990 | } 991 | ] 992 | }, 993 | { 994 | "name": "RuleAnnotation", 995 | "properties": [ 996 | { 997 | "name": "text", 998 | "type": "String" 999 | } 1000 | ] 1001 | }, 1002 | { 1003 | "name": "Invocable", 1004 | "isAbstract": true, 1005 | "superClass": [ 1006 | "DRGElement" 1007 | ], 1008 | "properties": [ 1009 | { 1010 | "name": "variable", 1011 | "type": "InformationItem", 1012 | "xml": { 1013 | "serialize": "property" 1014 | } 1015 | } 1016 | ] 1017 | }, 1018 | { 1019 | "name": "Group", 1020 | "superClass": [ 1021 | "Artifact" 1022 | ], 1023 | "properties": [ 1024 | { 1025 | "name": "name", 1026 | "isAttr": true, 1027 | "type": "String" 1028 | } 1029 | ] 1030 | }, 1031 | { 1032 | "name": "FunctionItem", 1033 | "superClass": [ 1034 | "DMNElement" 1035 | ], 1036 | "properties": [ 1037 | { 1038 | "name": "parameters", 1039 | "isMany": true, 1040 | "type": "InformationItem", 1041 | "xml": { 1042 | "serialize": "property" 1043 | } 1044 | }, 1045 | { 1046 | "name": "outputTypeRef", 1047 | "isAttr": true, 1048 | "type": "String" 1049 | } 1050 | ] 1051 | }, 1052 | { 1053 | "name": "DMNElementReference", 1054 | "properties": [ 1055 | { 1056 | "isAttr": true, 1057 | "name": "href", 1058 | "type": "String" 1059 | } 1060 | ] 1061 | } 1062 | ], 1063 | "enumerations": [ 1064 | { 1065 | "name": "HitPolicy", 1066 | "literalValues": [ 1067 | { 1068 | "name": "UNIQUE" 1069 | }, 1070 | { 1071 | "name": "FIRST" 1072 | }, 1073 | { 1074 | "name": "PRIORITY" 1075 | }, 1076 | { 1077 | "name": "ANY" 1078 | }, 1079 | { 1080 | "name": "COLLECT" 1081 | }, 1082 | { 1083 | "name": "RULE ORDER" 1084 | }, 1085 | { 1086 | "name": "OUTPUT ORDER" 1087 | } 1088 | ] 1089 | }, 1090 | { 1091 | "name": "BuiltinAggregator", 1092 | "literalValues": [ 1093 | { 1094 | "name": "SUM" 1095 | }, 1096 | { 1097 | "name": "COUNT" 1098 | }, 1099 | { 1100 | "name": "MIN" 1101 | }, 1102 | { 1103 | "name": "MAX" 1104 | } 1105 | ] 1106 | }, 1107 | { 1108 | "name": "DecisionTableOrientation", 1109 | "literalValues": [ 1110 | { 1111 | "name": "Rule-as-Row" 1112 | }, 1113 | { 1114 | "name": "Rule-as-Column" 1115 | }, 1116 | { 1117 | "name": "CrossTable" 1118 | } 1119 | ] 1120 | }, 1121 | { 1122 | "name": "AssociationDirection", 1123 | "literalValues": [ 1124 | { 1125 | "name": "None" 1126 | }, 1127 | { 1128 | "name": "One" 1129 | }, 1130 | { 1131 | "name": "Both" 1132 | } 1133 | ] 1134 | }, 1135 | { 1136 | "name": "FunctionKind", 1137 | "literalValues": [ 1138 | { 1139 | "name": "FEEL" 1140 | }, 1141 | { 1142 | "name": "Java" 1143 | }, 1144 | { 1145 | "name": "PMML" 1146 | } 1147 | ] 1148 | } 1149 | ], 1150 | "associations": [], 1151 | "xml": { 1152 | "tagAlias": "lowerCase" 1153 | } 1154 | } --------------------------------------------------------------------------------