Getting Error on adding Sub Process as piece of code in the current rendered XML

Rendered the BPMN flow diagram on react s UI using bpmn modeler
The flow is rendered from xml on the UI.
XML is rendered already and on the rendered xml we want to add sub process,
I am having piece of subProcessCode already just only want to append the xml as string and then insert the code and re render again.

I tried the following code
Explanation of the code is below :-

  1. DiagramService.getCurrentDiagramXml() is called to retrieve the XML representation of the current BPMN diagram. It returns a promise that resolves to the XML string.
  2. The retrieved XML string is then parsed using DOMParser() to create an XML document object (xmlDoc).
  3. The parent node where the custom subprocess will be appended is identified using querySelector() based on its ID (Process_1).
  4. The XML representing the custom subprocess (childSubProcess) is defined as a string. This subprocess contains various BPMN elements such as tasks, events, and sequence flows.
  5. Another DOMParser() is used to create a document fragment (fragment) from the XML string representing the custom subprocess.
  6. Append each child node of the document fragment to the parent node. After appending the custom subprocess, the modified XML document (xmlDoc) is serialized back to a string using XMLSerializer().
  7. The resulting XML string (newXml).
  8. Finally, DiagramService.renderDiagramCanvas(newXml) is called to render the modified BPMN diagram.

Note : We have attributes of the task node as in the format
voice:voiceTaskType , voice:voiceFileInfo

Code :-
const insertCustomSubProcess = async () => {

try {

const diagramXML = await DiagramService.getCurrentDiagramXml();

// Parsing diagramXML

let parser = new DOMParser();

let xmlDoc = parser.parseFromString(diagramXML, "text/xml" );

// Find the parent node

let parentNode = xmlDoc.querySelector(process[id= “Process_1” ]);

// subProcess that we nees to append

const childSubProcess = <subProcess id= “Activity_0009n6k” name= “Greetings” >`

<incoming>Flow_1s78vky</incoming>

<outgoing>Flow_16nzjzj</outgoing>

<task id= "Activity_08gzhgi" name= "Greetings" voice:taskType= "playVoiceFile" voice:voiceFileInfo= "{" en ":

{" filePath ":" welcome "," ttsText ":" "," gender ":" "," playSpeed ":" "," voiceFileType ":" libraryFile "," fileUrl ":" "," fileSize ":" "}," es ":{" filePath ":" es/welcome "," ttsText ":" "," gender ":" "," playSpeed ":" ",

" voiceFileType ":" libraryFile "," fileUrl ":" "," fileSize ":" "," id ":" "," option ":" "}}" session:stepName= "PlayGreeting" >

<incoming>Flow_1we1xkv</incoming>

<outgoing>Flow_0ordxhx</outgoing>

</task>

<startEvent id= "Event_0fwsfrr" name= "Greetings_Start_1" >

<outgoing>Flow_1we1xkv</outgoing>

</startEvent>

<sequenceFlow id= "Flow_1we1xkv" sourceRef= "Event_0fwsfrr" targetRef= "Activity_08gzhgi" />

<intermediateThrowEvent id= "Event_12tlhn0" name= "Greetings_Intermediate_1" >

<incoming>Flow_0ordxhx</incoming>

</intermediateThrowEvent>

<sequenceFlow id= "Flow_0ordxhx" sourceRef= "Activity_08gzhgi" targetRef= "Event_12tlhn0" />

</subProcess>;`

// Create a document fragment for the new child XML

let fragment = parser.parseFromString(childSubProcess, "text/xml" );

// Append the new child XML to the parent node

while (fragment.firstChild) {

parentNode.appendChild(fragment.firstChild);

}

// Serialize the modified XML back to a string

let serializer = new XMLSerializer();

let newXml = serializer.serializeToString(xmlDoc);

console.log( "newXml" , newXml);

DiagramService.renderDiagramCanvas(newXml);

} catch (error) {

// log the error here

}

}

I got the following error :

This page contains the following errors:

error on line 4 at column 683: Namespace prefix voice for taskType on task is not defined

Below is a rendering of the page up to the first error.

Flow_1s78vky Flow_16nzjzj

voice:voiceTaskType and voice:voiceFileInfo are the attributes of the node name task
image

Facing this error ?

@barmac any help on this?

@philippfromme any help on this ?

Tried another approach using string manipulation.
Where a small xml code is being inserted dynamically into the current rendered xml and then the updated xml is rendered again.
Code :

const insertCustomSubProcess = async () => {
    try {
      const diagramXML = await DiagramService.getCurrentDiagramXml();
      // Find the position where we want to insert the sub-process
      const insertIndex = diagramXML.indexOf('</process>');
 
      // Define the sub-process XML
      const childSubProcess = `<subProcess id="Activity_0009n6K" name="Hello">
            <incoming>Flow_1s78vky</incoming>
            <outgoing>Flow_16nzjzj</outgoing>
            <task id="Activity_08gzhgi" name="Hello" voice:taskType="playVoiceFile" voice:voiceFileInfo="{"en":{"filePath":"welcome","ttsText":"","gender":"","playSpeed":"","voiceFileType":"libraryFile","fileUrl":"","fileSize":""},"es":{"filePath":"es/welcome","ttsText":"","gender":"","playSpeed":"","voiceFileType":"libraryFile","fileUrl":"","fileSize":"","id":"","option":""}}" session:stepName="PlayGreeting">
                <incoming>Flow_1we1xkv</incoming>
                <outgoing>Flow_0ordxhx</outgoing>
            </task>
            <startEvent id="Event_0fwsfrr" name="Hello_Start_1">
                <outgoing>Flow_1we1xkv</outgoing>
            </startEvent>
            <sequenceFlow id="Flow_1we1xkv" sourceRef="Event_0fwsfrr" targetRef="Activity_08gzhgi" />
            <intermediateThrowEvent id="Event_12tlhn0" name="Hello_Intermediate_1">
                <incoming>Flow_0ordxhx</incoming>
            </intermediateThrowEvent>
            <sequenceFlow id="Flow_0ordxhx" sourceRef="Activity_08gzhgi" targetRef="Event_12tlhn0" />
        </subProcess>`;
 
      // Insert the sub-process XML into the diagramXML at the defined position
      const newDiagramXML = diagramXML.slice(0, insertIndex) + childSubProcess + diagramXML.slice(insertIndex);
      console.log("newXml", newDiagramXML);
      // Render the modified XML
      await DiagramService.renderDiagramCanvas(newDiagramXML);
 
    } catch (error) {
      console.error("Error occurred:", error);
      // Handle the error gracefully, e.g., show a message to the user
    }
  }

Result :
XML gets updated but due to its internal creation of co-ordinates of the flow , it is not rendering.Below is the xml code for co ordinates that bpmn generates own its own.

<bpmndi:BPMNShape id="Activity_0009n6k_di" bpmnElement="Activity_0009n6k" isExpanded="false">
        <omgdc:Bounds x="-1482" y="-2765" width="270" height="120" />
      </bpmndi:BPMNShape>

The way to change diagrams in bpmn-js is not via modifying XML but via the APIs. Check out this example: bpmn-js-examples/modeling-api at main · bpmn-io/bpmn-js-examples · GitHub

Hi @barmac
Thanks for the help,
I tried the approach
Below is my code
Explanation:
I created a subProcess and then added the child elements inside it

const insertCustomSubProcess = async () => {
   try {
     // json object of the greetings sub process node
     const jsonSubProcess = [{
       name: "Greetings", coOrdinates: { x: -1500, y: -2500 },
       children: [{ name: "Greetings_Start", task: "bpmn:StartEvent", coOrdinates: { x: -2000, y: -2500 } },
       { name: "Greetings", task: "bpmn:Task", taskType: "playVoiceFile", coOrdinates: { x: -1800, y: -2500 } },
       { name: "Greetings_Intermediate", task: "bpmn:IntermediateThrowEvent", coOrdinates: { x: -1500, y: -2500 } }]
     }];

     const modeler = await ModelerService.getModeler();
     const modeling = modeler.get('modeling'),
       bpmnFactory = modeler.get('bpmnFactory'),
       elementFactory = modeler.get('elementFactory'),
       elementRegistry = modeler.get('elementRegistry');
     const process = elementRegistry.get('Process_1');


     // creating child elements
     jsonSubProcess.forEach((subProcess) => {
       const subProcessBusinessObject = bpmnFactory.create('bpmn:SubProcess');

       // Create the SubProcess shape
       const eventSubProcess = elementFactory.createShape({
         type: 'bpmn:SubProcess',
         businessObject: subProcessBusinessObject
       });

       // Add the event sub process to the diagram
       modeling.createShape(eventSubProcess, { x: subProcess.coOrdinates.x, y: subProcess.coOrdinates.y }, process);

       // set the name of the sub process
       modeling.updateProperties(eventSubProcess, { name: subProcess.name });

       subProcess?.children?.forEach((innerElements) => {
         // create business object of inner element i.e child element
         const businessObject = bpmnFactory.create(innerElements.task);
         businessObject.taskType = innerElements?.taskType;
         if (innerElements.taskType === "playVoiceFile") {
           businessObject.voiceFilePath = "";
         }
         // create shape for that inner child element
         const subProcessChild = elementFactory.createShape({
           type: innerElements.task,
           businessObject: businessObject,
         });

         // create it as the child of sub process
         modeling.createShape(subProcessChild, { x: innerElements.coOrdinates.x, y: innerElements.coOrdinates.y },
           eventSubProcess);

         // set the name of the inner child elements
         modeling.updateProperties(subProcessChild, { name: AppUtil.getTaskName(innerElements.name) });
       });
       // toggle twice to hide the inner child elements of the sub process
       modeling.toggleCollapse(eventSubProcess);
       modeling.toggleCollapse(eventSubProcess);
     });

   } catch (error) {
     console.error("Error occurred:", error);
   }
 }

I do not want co-ordinates x and y to be set in the code and to be fixed
It should dynamically generates the co ordinates and show on the viewPort within the screen width and and height

Any Help on this ?

Again, businessObject is supposed to be a ModdleElement, and not a plain javascript object. Please check out the examples or the bpmn-js source code to see how elements are created.