const BPMNModdleModule = require('bpmn-moddle');
const BPMNModdle = BPMNModdleModule.default || BPMNModdleModule;
const bpmnSchema = require('../customSchema.json');
const moddle = new BPMNModdle({ proceed: bpmnSchema });
/**
* @module @proceed/bpmn-helper
*/
/**
* Sets the xmlns:proceed attribute in the definitions element of the bpmn to the one expected in our custom schema
*
* This is to make sure that importing the xml with bpmn-moddle will not lead to every proceed element being prefixed with ns0
*
* @param {String} xml
*/
function ensureCorrectProceedNamespace(xml) {
return xml.replace(/(xmlns:proceed=\")([^\"]*)(\")/g, `$1${bpmnSchema.uri}$3`);
}
/**
* Function that converts the given XML to a traversable object representation
*
* @param {string} xml - the BPMN XML that should be converted
* @param {string} [typename] - name of the root element, optional
* @returns {Promise<Object>} a traversable object representation of the given XML
* @throws {Error} if the given string is not an XML
* @throws {Error} if the given XML can not be converted to a bpmn-moddle object (multiple possible reasons)
*/
function toBpmnObject(xml, typename) {
return new Promise((resolve, reject) => {
moddle.fromXML(xml, typename, (err, obj) => {
if (err) {
reject(err);
} else {
resolve(obj);
}
});
});
}
/**
* Function that converts the given bpmn object to xml
*
* @param {Object} bpmn traversable object representation
* @returns {Promise<string>} a xml representation of the given object
*/
function toBpmnXml(obj) {
return new Promise((resolve, reject) => {
moddle.toXML(obj, { format: true }, (saveErr, xml) => {
if (saveErr) {
reject(saveErr);
}
resolve(xml);
});
});
}
/**
* Finds all kinds of childnodes in a given node
*
* @param {object} travObj object of which we want to know the childnodes
* @returns {array} all childnodes of the given node
*/
function getChildren(travObj) {
const childNodeTypes = [
'rootElements',
'flowElements',
'values',
'diagrams',
'imports',
'extensionElements',
];
const allChildren = childNodeTypes
.filter((childNodeType) => travObj[childNodeType])
.flatMap((childNodeType) => travObj[childNodeType]);
return allChildren;
}
/**
* A function that given a traversable object returns all occurences
*
* @param {object} travObj the object we want to search in
* @returns {array} - all nodes within the object
*/
function getAllElements(travObj) {
// retrieve children for current object
const allElements = getChildren(travObj)
// retrieve all grandchilds
.flatMap((child) => getAllElements(child));
allElements.push(travObj);
return allElements;
}
/**
* A function that given a traversable object returns all occurences of a given tagName
*
* @param {object} travObj the object we want to search in
* @param {string} tagname the name we are searching for
* @returns {array} - all nodes with the given tagName
*/
function getElementsByTagName(travObj, tagname) {
const matches = getChildren(travObj)
// recursively search in all children
.flatMap((child) => getElementsByTagName(child, tagname));
if (travObj.$type === tagname) {
matches.push(travObj);
}
return matches;
}
/**
* Gets the diagram element for the given model element
*
* @param {Object} element the model element
* @param {Object} [definitions] the definitions object to search in
*/
function getElementDI(element, definitions) {
if (!definitions) {
// search the root element which is the definitions element
definitions = element;
while (definitions.$parent) {
definitions = definitions.$parent;
}
}
for (const diagram of definitions.diagrams) {
for (const planeElement of diagram.plane.planeElement) {
if (planeElement.bpmnElement === element) {
return planeElement;
}
}
}
return null;
}
/**
* A function that given a traversable object returns the nested object with the given id
*
* @param {object} travObj the object we want to search in
* @param {string} id the id of the object we want to find
* @returns {object|undefined} - returns the found object or undefined when no matching object was found
*/
function getElementById(travObj, id) {
if (travObj.id === id) {
return travObj;
}
const matchedElement = getChildren(travObj)
.map((child) => getElementById(child, id))
.find((matchInChild) => matchInChild);
return matchedElement;
}
/**
* @callback manipulationFunction
* @param {object} bpmn-moddle-element - the element return by searching the bpmn-moddle process
*/
/**
* Function that changes an element in the given xml using the given manipulation function
*
* @param {(string|object)} bpmn - the process definition as XML string or BPMN-Moddle Object
* @param {string} id - the id of the element that should be changed
* @param {manipulationFunction} manipFunc - the function that will be used to change the element
* @returns {(object|Promise<string>)} the BPMN process as bpmn-moddle object or XML string based on input
*/
async function manipulateElementById(bpmn, id, manipFunc) {
const bpmnObj = typeof bpmn === 'string' ? await toBpmnObject(bpmn) : bpmn;
const element = getElementById(bpmnObj, id);
manipFunc(element);
return typeof bpmn === 'string' ? await toBpmnXml(bpmnObj) : bpmnObj;
}
/**
* Function that changes all elements in the given xml with the given tagname
* using the given function
*
* @param {(string|object)} bpmn - the process definition as XML string or BPMN-Moddle Object
* @param {string} tagName - the tagname of the elements we want to change, starts with 'bpmn:', e.g. 'bpmn:Definitions'
* @param {manipulationFunction} manipFunc - the function that gets called on each element with a forEach-Loop
* @returns {(object|Promise<string>)} the BPMN process as bpmn-moddle object or XML string based on input
*/
async function manipulateElementsByTagName(bpmn, tagName, manipFunc) {
const bpmnObj = typeof bpmn === 'string' ? await toBpmnObject(bpmn) : bpmn;
const elements = getElementsByTagName(bpmnObj, tagName);
elements.forEach(manipFunc);
return typeof bpmn === 'string' ? await toBpmnXml(bpmnObj) : bpmnObj;
}
module.exports = {
moddle,
ensureCorrectProceedNamespace,
toBpmnObject,
toBpmnXml,
getChildren,
getElementsByTagName,
getAllElements,
getElementById,
getElementDI,
manipulateElementById,
manipulateElementsByTagName,
};