Skip to content

Commit 311855d

Browse files
authored
Merge pull request #230 from ProcessMaker/bugfix/FOUR-23865
FOUR-23865 Add support to html entities in bpmn documents
2 parents 07bc74a + 157625c commit 311855d

File tree

5 files changed

+213
-1
lines changed

5 files changed

+213
-1
lines changed

.github/workflows/sonarqube.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323

2424
- name: Cache Composer dependencies
2525
id: composer-cache
26-
uses: actions/cache@v2
26+
uses: actions/cache@v4
2727
with:
2828
path: vendor
2929
key: composer-v1-${{ hashFiles('composer.json') }}

src/ProcessMaker/Nayra/Storage/BpmnDocument.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,52 @@ public function __construct($version = null, $encoding = null)
508508
$this->registerNodeClass(DOMElement::class, BpmnElement::class);
509509
}
510510

511+
/**
512+
* Load the XML source and replace the html entities with the correct characters.
513+
*
514+
* This is a workaround to avoid the DOMDocument::loadXML() function to throw an error when
515+
* the XML contains html entities.
516+
* HTML entities can be introduced by the ProcessMaker Modeler when the user inserts html
517+
* tags in the documentation field of a task.
518+
*
519+
* @param string $source
520+
* @param int $options
521+
* @return bool
522+
*/
523+
public function loadXML(string $source, int $options = 0): bool
524+
{
525+
$source = self::replaceHtmlEntities($source);
526+
return parent::loadXML($source, $options);
527+
}
528+
529+
/**
530+
* Replace the html entities with the correct characters.
531+
*
532+
* @param string $source
533+
*
534+
* @return string
535+
*/
536+
public static function replaceHtmlEntities($source)
537+
{
538+
$source = str_replace([
539+
' ',
540+
'<',
541+
'>',
542+
'&',
543+
''',
544+
'"',
545+
], [
546+
' ',
547+
'<',
548+
'>',
549+
'&',
550+
''',
551+
'"',
552+
], $source);
553+
554+
return $source;
555+
}
556+
511557
/**
512558
* Set the factory used to create BPMN elements.
513559
*
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
3+
namespace Tests\Feature\Engine;
4+
5+
use ProcessMaker\Nayra\Storage\BpmnDocument;
6+
use DOMDocument;
7+
8+
/**
9+
* Test for handling HTML entities in BPMN files.
10+
*/
11+
class BpmnDocumentHtmlEntitiesTest extends EngineTestCase
12+
{
13+
/**
14+
* Test the replaceHtmlEntities method directly.
15+
*/
16+
public function testReplaceHtmlEntities()
17+
{
18+
// String with HTML entities
19+
$source = 'This is a text with &lt;html&gt; entities &amp; special chars like &quot;quotes&quot; and &apos;apostrophes&apos; and &nbsp;spaces.';
20+
21+
// Expected result after replacement
22+
$expected = 'This is a text with &#60;html&#62; entities &#38; special chars like &#34;quotes&#34; and &#39;apostrophes&#39; and &#160;spaces.';
23+
24+
// Test the static method
25+
$result = BpmnDocument::replaceHtmlEntities($source);
26+
27+
// Assert the result matches the expected output
28+
$this->assertEquals($expected, $result);
29+
}
30+
31+
/**
32+
* Test loadXML with HTML entities.
33+
*/
34+
public function testLoadXmlWithHtmlEntities()
35+
{
36+
// Create a BPMN XML string with HTML entities in the documentation tag
37+
$bpmnXml = file_get_contents(__DIR__ . '/files/BpmnWithHtmlEntities.bpmn');
38+
39+
// 1. First try to load with regular DOMDocument - should fail or produce incorrect results
40+
$regularDom = new DOMDocument();
41+
$regularLoaded = @$regularDom->loadXML($bpmnXml); // @ to suppress warnings
42+
43+
// If it loads without errors, check that the content is different from what we expect
44+
if ($regularLoaded) {
45+
$startEventDoc = $regularDom->getElementsByTagName('documentation')->item(0);
46+
$originalContent = $startEventDoc ? $startEventDoc->textContent : '';
47+
48+
// The content should be mangled or different from what we expect with proper entity handling
49+
$expectedContent = 'This contains <b>HTML</b> entities & special chars like "quotes" and \'apostrophes\' and spaces.';
50+
$this->assertNotEquals($expectedContent, $originalContent, 'Standard DOMDocument should not correctly handle HTML entities');
51+
}
52+
53+
// 2. Now load with BpmnDocument which should handle HTML entities correctly
54+
$bpmnDocument = new BpmnDocument();
55+
$bpmnDocument->setEngine($this->engine);
56+
$bpmnDocument->setFactory($this->repository);
57+
58+
// Load the XML with HTML entities
59+
$result = $bpmnDocument->loadXML($bpmnXml);
60+
$this->assertTrue($result, 'BpmnDocument should successfully load the XML with HTML entities');
61+
62+
// Verify that documentation tags contain correctly converted entities
63+
$startEventDoc = $bpmnDocument->getElementsByTagName('documentation')->item(0);
64+
$this->assertNotNull($startEventDoc, 'Documentation element should exist');
65+
66+
// The text content should have the HTML entities properly converted
67+
$nbsp = "\xC2\xA0";
68+
$expectedContent = 'This contains <b>HTML</b> entities & special chars like "quotes" and \'apostrophes\' and ' . $nbsp . 'spaces.';
69+
$this->assertEquals($expectedContent, $startEventDoc->textContent, 'HTML entities should be correctly converted');
70+
71+
// Check the second documentation tag too
72+
$taskDoc = $bpmnDocument->getElementsByTagName('documentation')->item(1);
73+
$this->assertNotNull($taskDoc, 'Second documentation element should exist');
74+
$expectedTaskContent = 'Another <strong>documentation</strong> with & entities.';
75+
$this->assertEquals($expectedTaskContent, $taskDoc->textContent, 'HTML entities in second documentation should be correctly converted');
76+
}
77+
78+
/**
79+
* Test loading a complex BPMN with HTML entities in various places.
80+
*/
81+
public function testLoadComplexBpmnWithHtmlEntities()
82+
{
83+
// Create a more complex BPMN with HTML entities in various attributes and text content
84+
$complexBpmnXml = file_get_contents(__DIR__ . '/files/BpmnWithComplexHtml.bpmn');
85+
86+
// Load with BpmnDocument
87+
$bpmnDocument = new BpmnDocument();
88+
$bpmnDocument->setEngine($this->engine);
89+
$bpmnDocument->setFactory($this->repository);
90+
91+
$result = $bpmnDocument->loadXML($complexBpmnXml);
92+
$this->assertTrue($result, 'BpmnDocument should successfully load complex XML with HTML entities');
93+
94+
// Check process name attribute
95+
$process = $bpmnDocument->getElementsByTagName('process')->item(0);
96+
$this->assertEquals('Process & HTML entities', $process->getAttribute('name'), 'Process name attribute should have entities converted');
97+
98+
// Check start event name attribute
99+
$startEvent = $bpmnDocument->getElementsByTagName('startEvent')->item(0);
100+
$this->assertEquals('Start <event>', $startEvent->getAttribute('name'), 'Start event name attribute should have entities converted');
101+
102+
// Check documentation content
103+
$documentation = $startEvent->getElementsByTagName('documentation')->item(0);
104+
$this->assertEquals(
105+
'Documentation with <ul><li>HTML list</li></ul> and & "quotes"',
106+
$documentation->textContent,
107+
'Documentation should have entities converted'
108+
);
109+
110+
// Check sequence flow name attribute
111+
$sequenceFlow = $bpmnDocument->getElementsByTagName('sequenceFlow')->item(0);
112+
$this->assertEquals(
113+
'Flow with & special "chars"',
114+
$sequenceFlow->getAttribute('name'),
115+
'Sequence flow name attribute should have entities converted'
116+
);
117+
}
118+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
3+
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
4+
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
5+
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
6+
id="Definitions_1"
7+
name="Process with &lt;special&gt; &amp; chars"
8+
targetNamespace="http://bpmn.io/schema/bpmn">
9+
<bpmn:process id="Process_1" name="Process &amp; HTML entities" isExecutable="true">
10+
<bpmn:startEvent id="StartEvent_1" name="Start &lt;event&gt;">
11+
<bpmn:documentation>Documentation with &lt;ul&gt;&lt;li&gt;HTML list&lt;/li&gt;&lt;/ul&gt; and &amp; &quot;quotes&quot;</bpmn:documentation>
12+
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
13+
</bpmn:startEvent>
14+
<bpmn:sequenceFlow id="SequenceFlow_1" name="Flow with &amp; special &quot;chars&quot;" sourceRef="StartEvent_1" targetRef="Task_1" />
15+
<bpmn:task id="Task_1" name="Task &amp; more">
16+
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
17+
<bpmn:outgoing>SequenceFlow_2</bpmn:outgoing>
18+
</bpmn:task>
19+
<bpmn:sequenceFlow id="SequenceFlow_2" sourceRef="Task_1" targetRef="EndEvent_1" />
20+
<bpmn:endEvent id="EndEvent_1" name="End &lt;event&gt;">
21+
<bpmn:incoming>SequenceFlow_2</bpmn:incoming>
22+
</bpmn:endEvent>
23+
</bpmn:process>
24+
</bpmn:definitions>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
3+
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
4+
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
5+
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
6+
id="Definitions_1"
7+
targetNamespace="http://bpmn.io/schema/bpmn">
8+
<bpmn:process id="Process_1" isExecutable="true">
9+
<bpmn:startEvent id="StartEvent_1">
10+
<bpmn:documentation>This contains &lt;b&gt;HTML&lt;/b&gt; entities &amp; special chars like &quot;quotes&quot; and &apos;apostrophes&apos; and &nbsp;spaces.</bpmn:documentation>
11+
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
12+
</bpmn:startEvent>
13+
<bpmn:task id="Task_1">
14+
<bpmn:documentation>Another &lt;strong&gt;documentation&lt;/strong&gt; with &amp; entities.</bpmn:documentation>
15+
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
16+
<bpmn:outgoing>SequenceFlow_2</bpmn:outgoing>
17+
</bpmn:task>
18+
<bpmn:endEvent id="EndEvent_1">
19+
<bpmn:incoming>SequenceFlow_2</bpmn:incoming>
20+
</bpmn:endEvent>
21+
<bpmn:sequenceFlow id="SequenceFlow_1" sourceRef="StartEvent_1" targetRef="Task_1" />
22+
<bpmn:sequenceFlow id="SequenceFlow_2" sourceRef="Task_1" targetRef="EndEvent_1" />
23+
</bpmn:process>
24+
</bpmn:definitions>

0 commit comments

Comments
 (0)