1import * as BPMN from "./bpmn.js"; 2 3/** 4 * Parser class to parse a BPMN diagram to a BPMN object 5 * 6 * @class Parser 7 * @property {Document} xml - The XML document to parse 8 * @property {BPMN} BPMN - The BPMN object to store the parsed BPMN diagram 9 * @property {Object} layouts - The layouts of the shapes and edges in the BPMN diagram 10 * @property {Object} nodeTypeMap - The mapping of the BPMN node types to the BPMN classes 11 */ 12class Parser { 13 xml = null; 14 BPMN = new BPMN.BPMN(); 15 layouts = {}; 16 nodeTypeMap = { 17 startEvent: BPMN.StartEvent, 18 endEvent: BPMN.EndEvent, 19 task: BPMN.Activity, 20 lane: BPMN.Participant, 21 exclusiveGateway: BPMN.ExclusiveGateway, 22 parallelGateway: BPMN.ParallelGateway, 23 inclusiveGateway: BPMN.InclusiveGateway, 24 complexGateway: BPMN.ComplexGateway, 25 eventBasedGateway: BPMN.EventBasedGateway, 26 userTask: BPMN.UserTask, 27 businessRuleTask: BPMN.BusinessRuleTask, 28 serviceTask: BPMN.ServiceTask, 29 sendTask: BPMN.SendTask, 30 receiveTask: BPMN.ReceiveTask, 31 scriptTask: BPMN.ScriptTask, 32 manualTask: BPMN.ManualTask, 33 intermediateThrowEvent: BPMN.IntermediateThrowEvent, 34 intermediateCatchEvent: BPMN.IntermediateCatchEvent, 35 boundaryEvent: BPMN.BoundaryEvent, 36 dataObjectReference: BPMN.DataObject, 37 dataStoreReference: BPMN.DataStoreReference, 38 textAnnotation: BPMN.TextAnnotation, 39 }; 40 41 constructor(xml) { 42 this.xml = xml; 43 try { 44 this.shapesLayouts = this.parseShapesLayouts(); 45 this.edgesLayouts = this.parseEdgesLayouts(); 46 } catch (e) { 47 console.error(e); 48 throw new Error("Error parsing BPMN diagram layouts: " + e.message); 49 } 50 try { 51 this.parseProcesses(); 52 } catch (e) { 53 console.error(e); 54 throw new Error("Error parsing BPMN processes: " + e.message); 55 } 56 try { 57 this.parseMessageFlows(); 58 } catch (e) { 59 console.error(e); 60 throw new Error("Error parsing BPMN message flows: " + e.message); 61 } 62 } 63 64 /** 65 * Parse the shapes layouts from the XML 66 * 67 * @returns {Object} The layouts of the shapes 68 */ 69 parseShapesLayouts() { 70 const layouts = {}; 71 const nodes = this.xml.getElementsByTagName("BPMNShape"); 72 for (let i = 0; i < nodes.length; i++) { 73 const node = nodes[i]; 74 const id = node.getAttribute("bpmnElement"); 75 const bounds = node.getElementsByTagName("Bounds")[0]; 76 const x = parseInt(bounds.getAttribute("x")); 77 const y = parseInt(bounds.getAttribute("y")); 78 const width = parseInt(bounds.getAttribute("width")); 79 const height = parseInt(bounds.getAttribute("height")); 80 layouts[id] = { x, y, width, height }; 81 } 82 return layouts; 83 } 84 85 /** 86 * Parse the edges layouts from the XML 87 * 88 * @returns {Object} The layouts of the edges 89 */ 90 parseEdgesLayouts() { 91 const layouts = {}; 92 const edges = this.xml.getElementsByTagName("BPMNEdge"); 93 for (let i = 0; i < edges.length; i++) { 94 const edge = edges[i]; 95 const id = edge.getAttribute("bpmnElement"); 96 const waypoints = edge.getElementsByTagName("waypoint"); 97 const points = []; 98 for (let j = 0; j < waypoints.length; j++) { 99 const waypoint = waypoints[j]; 100 const x = parseInt(waypoint.getAttribute("x")); 101 const y = parseInt(waypoint.getAttribute("y")); 102 points.push({ x, y }); 103 } 104 layouts[id] = points; 105 } 106 return layouts; 107 } 108 109 /** 110 * Find the layout of a shape or edge by its ID 111 * 112 * @param {string} id - The ID of the shape or edge 113 * @returns {Object} The layout of the shape or edge 114 * @returns {null} If the layout is not found 115 */ 116 findLayout(id) { 117 return this.shapesLayouts[id] || this.edgesLayouts[id]; 118 } 119 120 /** 121 * Parse the processes from the XML 122 */ 123 parseProcesses() { 124 const processes = this.xml.getElementsByTagName("process"); 125 for (let i = 0; i < processes.length; i++) { 126 const process = processes[i]; 127 const id = process.getAttribute("id"); 128 const name = process.getAttribute("name"); 129 const processObj = new BPMN.Process(id, name); 130 131 for (const tag in this.nodeTypeMap) { 132 this.parseNodes(process, processObj, tag); 133 } 134 135 this.parseFlows(process, processObj); 136 this.parseAssociations(process, processObj); 137 this.setNodeArcs(processObj); 138 this.BPMN.addProcess(processObj); 139 } 140 } 141 142 /** 143 * Parse the nodes from the XML 144 * 145 * @param {Element} process - The process element to parse the nodes from 146 * @param {Process} processObj - The process object to add the nodes to 147 * @param {string} tagName - The tag name of the nodes to parse 148 */ 149 parseNodes(process, processObj, tagName) { 150 const elements = process.getElementsByTagName(tagName); 151 const NodeClass = this.nodeTypeMap[tagName]; 152 153 for (let i = 0; i < elements.length; i++) { 154 const element = elements[i]; 155 156 // 1) Salta i nodi senza id (tipicamente extensionElements come <zeebe:userTask />) 157 const id = element.getAttribute("id"); 158 if (!id) continue; 159 160 const name = element.getAttribute("name") || ""; 161 162 // 2) Recupera il layout; se manca, usa un fallback neutro 163 const layout = this.findLayout(id) || { 164 x: 0, 165 y: 0, 166 width: 50, 167 height: 50, 168 }; 169 170 const nodeObj = new NodeClass( 171 id, 172 name, 173 layout.x, 174 layout.y, 175 layout.width, 176 layout.height, 177 processObj 178 ); 179 180 processObj.addNode(nodeObj); 181 182 // Handle boundary event attachedToRef 183 const attachedToRef = element.getAttribute("attachedToRef"); 184 if (attachedToRef && nodeObj instanceof BPMN.BoundaryEvent) { 185 nodeObj.attachedToRef = attachedToRef; 186 const cancelAttr = element.getAttribute("cancelActivity");
187 nodeObj.cancelActivity = cancelAttr !== "false"; // default true 188 } 189 190 // Parse data associations (inputs/outputs) for this node 191 this.parseDataAssociations(element, nodeObj, processObj); 192 } 193 } 194 195 /** 196 * Parse data input and output associations for a node 197 * 198 * @param {Element} element - The XML element of the node 199 * @param {BPMN.Node} nodeObj - The node object 200 * @param {BPMN.Process} processObj - The process object 201 */ 202 parseDataAssociations(element, nodeObj, processObj) { 203 // Parse Data Output Associations (Task -> Object) 204 const outputs = element.getElementsByTagName("dataOutputAssociation"); 205 for (let i = 0; i < outputs.length; i++) { 206 const output = outputs[i]; 207 const id = output.getAttribute("id"); 208 const targetRefs = output.getElementsByTagName("targetRef"); 209 210 if (targetRefs.length > 0) { 211 const targetRefId = targetRefs[0].textContent; 212 const layout = this.findLayout(id) || []; 213 214 const assoc = new BPMN.Association( 215 nodeObj, // Source is the task/node 216 new BPMN.BPMNNode(targetRefId), // Target is the data object 217 id, 218 "", 219 layout, 220 processObj 221 ); 222 processObj.addFlow(assoc); 223 } 224 } 225 226 // Parse Data Input Associations (Object -> Task) 227 const inputs = element.getElementsByTagName("dataInputAssociation"); 228 for (let i = 0; i < inputs.length; i++) { 229 const input = inputs[i]; 230 const id = input.getAttribute("id"); 231 const sourceRefs = input.getElementsByTagName("sourceRef"); 232 233 if (sourceRefs.length > 0) { 234 const sourceRefId = sourceRefs[0].textContent; 235 const layout = this.findLayout(id) || []; 236 237 const assoc = new BPMN.Association( 238 new BPMN.BPMNNode(sourceRefId), // Source is the data object 239 nodeObj, // Target is the task/node 240 id, 241 "", 242 layout, 243 processObj 244 ); 245 processObj.addFlow(assoc); 246 } 247 } 248 } 249 250 /** 251 * Parse the flows from the XML 252 * 253 * @param {Element} process - The process element to parse the flows from 254 * @param {Process} processObj - The process object to add the flows to 255 */ 256 parseFlows(process, processObj) { 257 const flows = process.getElementsByTagName("sequenceFlow"); 258 for (let i = 0; i < flows.length; i++) { 259 const flow = flows[i]; 260 const id = flow.getAttribute("id"); 261 const name = flow.getAttribute("name"); 262 const sourceRef = new BPMN.BPMNNode(flow.getAttribute("sourceRef")); 263 const targetRef = new BPMN.BPMNNode(flow.getAttribute("targetRef")); 264 const layout = this.findLayout(id) || []; // se non c'è edge layout, usa [] 265 266 const flowObj = new BPMN.SequenceFlow( 267 sourceRef, 268 targetRef, 269 id, 270 name, 271 layout, 272 processObj 273 ); 274 processObj.addFlow(flowObj); 275 } 276 } 277 278 /** 279 * Parse the associations from the XML 280 * 281 * @param {Element} process - The process element to parse the associations from 282 * @param {Process} processObj - The process object to add the associations to 283 */ 284 parseAssociations(process, processObj) { 285 const associations = process.getElementsByTagName("association"); 286 for (let i = 0; i < associations.length; i++) { 287 const assoc = associations[i]; 288 const id = assoc.getAttribute("id"); 289 const name = assoc.getAttribute("name") || ""; 290 const sourceRef = new BPMN.BPMNNode(assoc.getAttribute("sourceRef")); 291 const targetRef = new BPMN.BPMNNode(assoc.getAttribute("targetRef")); 292 const layout = this.findLayout(id) || []; 293 294 const assocObj = new BPMN.Association( 295 sourceRef, 296 targetRef, 297 id, 298 name, 299 layout, 300 processObj 301 ); 302 processObj.addFlow(assocObj); 303 } 304 } 305 306 /** 307 * Set the arcs of the nodes in the process 308 * 309 * @param {Process} process - The process object to set the arcs for 310 */ 311 setNodeArcs(process) { 312 const flows = process.flows; 313 for (let i = 0; i < flows.length; i++) { 314 let flow = flows[i]; 315 const sourceRef = flow.source; 316 const targetRef = flow.target; 317 318 const sourceNode = process.getNodeById(sourceRef.id); 319 const targetNode = process.getNodeById(targetRef.id); 320 321 flow.source = sourceNode;
322 flow.target = targetNode; 323 324 if (sourceNode != null && targetNode != null) { 325 sourceNode.addOutArc(flow); 326 targetNode.addInArc(flow); 327 } 328 } 329 } 330 331 /** 332 * Parse the message flows from the XML 333 */ 334 parseMessageFlows() { 335 const messageFlows = this.xml.getElementsByTagName("messageFlow"); 336 for (let i = 0; i < messageFlows.length; i++) { 337 const messageFlow = messageFlows[i]; 338 const id = messageFlow.getAttribute("id"); 339 const name = messageFlow.getAttribute("name"); 340 const sourceRef = messageFlow.getAttribute("sourceRef"); 341 const targetRef = messageFlow.getAttribute("targetRef"); 342 const source = this.BPMN.getNodeById(sourceRef); 343 const target = this.BPMN.getNodeById(targetRef); 344 const layout = this.findLayout(id) || []; // fallback 345 346 const messageFlowObj = new BPMN.MessageFlow( 347 source, 348 target, 349 id, 350 name, 351 layout 352 ); 353 this.BPMN.addMessageFlow(messageFlowObj); 354 } 355 } 356} 357 358export default Parser;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.