Skip to content

Commit eab116c

Browse files
committed
WiP
1 parent 656a688 commit eab116c

File tree

8 files changed

+285
-16
lines changed

8 files changed

+285
-16
lines changed

src/document/Document.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public function merge(self|DocumentCollection ...$toMerge): self {
100100
if ($item instanceof self) {
101101
$id = $item->id();
102102
if ($id === null) {
103-
throw new RuntimeException('Document must have an ID to be merged.');
103+
throw new DocumentException('Document must have an ID to be merged.');
104104
}
105105

106106
$mergeList->add(
@@ -114,7 +114,7 @@ public function merge(self|DocumentCollection ...$toMerge): self {
114114
foreach ($item as $single) {
115115
$id = $single->id();
116116
if ($id === null) {
117-
throw new RuntimeException('All documents in collection must have an ID to be merged.');
117+
throw new DocumentException('All documents in collection must have an ID to be merged.');
118118
}
119119
$mergeList->add(
120120
$id,

src/merger/MergeList.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ class MergeList {
99

1010
private array $documents = [];
1111

12+
public function isEmpty(): bool {
13+
return count($this->documents) === 0;
14+
}
15+
1216
public function add(Id $id, DOMDocument $dom) {
1317
$idString = $id->asString();
1418

@@ -19,17 +23,17 @@ public function add(Id $id, DOMDocument $dom) {
1923
$this->documents[$idString][] = $dom;
2024
}
2125

22-
public function has(string $id): bool {
23-
return isset($this->documents[$id]);
26+
public function has(Id $id): bool {
27+
return isset($this->documents[$id->asString()]);
2428
}
2529

26-
public function get(string $id): ArrayIterator {
30+
public function get(Id $id): ArrayIterator {
2731
if (!$this->has($id)) {
2832
throw new MergeListException(
29-
sprintf('Empty List for id %s', $id)
33+
sprintf('Empty List for id %s', $id->asString())
3034
);
3135
}
3236

33-
return new ArrayIterator($this->documents[$id]);
37+
return new ArrayIterator($this->documents[$id->asString()]);
3438
}
3539
}

src/merger/Merger.php

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,17 @@ final class Merger {
2323
private array $seen;
2424

2525
public function merge(DOMDocument $target, MergeList $toMerge): void {
26+
if ($target->documentElement === null) {
27+
throw new MergerException('Cannot merge into a document without a root element', MergerException::EmptyDocument);
28+
}
29+
30+
if ($toMerge->isEmpty()) {
31+
throw new MergerException('MergeList must not be empty', MergerException::EmptyList);
32+
}
33+
2634
$this->documents = $toMerge;
2735
$this->seen = [];
2836

29-
if ($target->documentElement === null) {
30-
throw new MergerException('Cannot merge into a document without a root element');
31-
}
3237

3338
$this->processContext($target->documentElement);
3439
}
@@ -47,17 +52,18 @@ private function processContext(DOMElement $context) {
4752
continue;
4853
}
4954

50-
$id = $contextChild->getAttribute('id');
55+
$id = new Id($contextChild->getAttribute('id'));
5156
if (!$this->documents->has($id)) {
5257
continue;
5358
}
5459

55-
if (isset($this->seen[$id])) {
56-
throw new RuntimeException(
57-
sprintf('Duplicate id "%s" in document detected - bailing out.', $id)
60+
if (isset($this->seen[$id->asString()])) {
61+
throw new MergerException(
62+
sprintf('Duplicate id "%s" in document detected - bailing out.', $id->asString()),
63+
MergerException::DuplicateId
5864
);
5965
}
60-
$this->seen[$id] = true;
66+
$this->seen[$id->asString()] = true;
6167

6268
foreach($this->documents->get($id) as $childDocument) {
6369
assert($childDocument instanceof DOMDocument);

src/merger/MergerException.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@
33

44
class MergerException extends Exception {
55

6+
const EmptyDocument = 1;
7+
const EmptyList = 2;
8+
const DuplicateId = 3;
69
}

tests/document/DocumentTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
#[UsesClass(FormDataRenderer::class)]
2727
#[UsesClass(SnapshotAttributeList::class)]
2828
#[UsesClass(SnapshotDOMNodelist::class)]
29+
#[UsesClass(MergeList::class)]
30+
#[UsesClass(Merger::class)]
31+
#[UsesClass(DocumentCollection::class)]
2932
class DocumentTest extends TestCase {
3033
use DomDocumentsEqualTrait;
3134

@@ -182,4 +185,47 @@ public function testCSRFProtectionCanBeApplied(): void {
182185
);
183186
}
184187

188+
public function testDocumentsCanBeMerged(): void {
189+
$dom = new DOMDocument();
190+
$dom->loadXML('<html xmlns="http://www.w3.org/1999/xhtml"><body><span id="a" /><span id="b" /><span id="c" /></body></html>');
191+
192+
$target = Document::fromDomDocument($dom);
193+
194+
$snipA = Document::fromString('<templado:document xmlns="http://www.w3.org/1999/xhtml" xmlns:templado="https://templado.io/document/1.0"><div id="d" /><div id="e" /></templado:document>', new Id('a'));
195+
196+
$list = new DocumentCollection(
197+
Document::fromString('<templado:document xmlns="http://www.w3.org/1999/xhtml" xmlns:templado="https://templado.io/document/1.0" id="c"><div id="f" /><div id="g" /></templado:document>', new Id('c')),
198+
Document::fromString('<templado:document xmlns="http://www.w3.org/1999/xhtml" xmlns:templado="https://templado.io/document/1.0"><div id="h" /><div id="i" /></templado:document>', new Id('d'))
199+
);
200+
201+
$target->merge(
202+
$snipA,
203+
$list
204+
);
205+
206+
$expected = new DOMDocument();
207+
$expected->loadXML('<?xml version="1.0"?><html xmlns="http://www.w3.org/1999/xhtml"><body><span id="a"><div id="d"><div id="h"/><div id="i"/></div><div id="e"/></span><span id="b"/><div id="f"/><div id="g"/></body></html>');
208+
209+
$this->assertResultMatches(
210+
$expected->documentElement,
211+
$dom->documentElement
212+
);
213+
}
214+
215+
public function testTryingToMergeDocumentWithoutIdThrowsException(): void {
216+
$target = Document::fromString('<?xml version="1.0" ?><root />');
217+
218+
$this->expectException(DocumentException::class);
219+
$target->merge($target);
220+
}
221+
222+
public function testTryingToMergeDocumentCollectionWithDocumentWithoutIdThrowsException(): void {
223+
$target = Document::fromString('<?xml version="1.0" ?><root />');
224+
225+
$list = new DocumentCollection($target);
226+
227+
$this->expectException(DocumentException::class);
228+
$target->merge($list);
229+
}
230+
185231
}

tests/merger/MergeListTest.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php declare(strict_types = 1);
2+
namespace merger;
3+
4+
use DOMDocument;
5+
use PHPUnit\Framework\Attributes\CoversClass;
6+
use PHPUnit\Framework\Attributes\UsesClass;
7+
use PHPUnit\Framework\TestCase;
8+
use Templado\Engine\Id;
9+
use Templado\Engine\MergeList;
10+
use Templado\Engine\MergeListException;
11+
12+
#[CoversClass(MergeList::class)]
13+
#[UsesClass(Id::class)]
14+
class MergeListTest extends TestCase {
15+
16+
public function testIsInitiallyEmpty(): void {
17+
$this->assertTrue(
18+
(new MergeList())->isEmpty()
19+
);
20+
}
21+
22+
public function testCanAddDocument(): void {
23+
$list = new MergeList();
24+
$list->add(
25+
new Id('foo'),
26+
new DOMDocument()
27+
);
28+
29+
$this->assertFalse($list->isEmpty());
30+
}
31+
32+
public function testAddedDocumentCanBeFoundById(): void{
33+
$id = new Id('foo');
34+
$list = new MergeList();
35+
$list->add(
36+
$id,
37+
new DOMDocument()
38+
);
39+
40+
$this->assertTrue($list->has($id));
41+
}
42+
43+
public function testAddedDocumentsCanBeRetrievedById(): void {
44+
$id = new Id('foo');
45+
$docs = array_fill(0, 3, new DOMDocument());
46+
47+
$list = new MergeList();
48+
49+
foreach($docs as $doc) {
50+
$list->add($id, $doc);
51+
}
52+
53+
foreach($list->get($id) as $pos => $found) {
54+
$this->assertSame($docs[$pos], $found);
55+
}
56+
}
57+
58+
public function testRequestingNonExistingIdThrowsException(): void {
59+
$this->expectException(MergeListException::class);
60+
(new MergeList())->get(new Id('not-existing'));
61+
}
62+
}

tests/merger/MergerTest.php

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,25 @@ class MergerTest extends TestCase {
1414

1515
use DomDocumentsEqualTrait;
1616

17+
public function testUsingEmptyDocumentThrowsException(): void {
18+
$merger = new Merger();
19+
$dom = new DOMDocument();
20+
21+
$this->expectException(MergerException::class);
22+
$this->expectExceptionCode(MergerException::EmptyDocument);
23+
$merger->merge($dom, new MergeList());
24+
}
25+
26+
public function testUsingEmptyMergeListThrowsException(): void {
27+
$merger = new Merger();
28+
$dom = new DOMDocument();
29+
$dom->loadXML('<?xml version="1.0" ?><root />');
30+
31+
$this->expectException(MergerException::class);
32+
$this->expectExceptionCode(MergerException::EmptyList);
33+
$merger->merge($dom, new MergeList());
34+
}
35+
1736
public function testCanMergeSingleDocument(): void {
1837
$merger = new Merger();
1938

@@ -29,13 +48,129 @@ public function testCanMergeSingleDocument(): void {
2948
$toMerge
3049
);
3150

32-
3351
$merger->merge($dom, $list);
3452

3553
$expected = new DOMDocument();
3654
$expected->loadXML('<?xml version="1.0" ?><root><node id="test"><merged /></node></root>');
3755

3856
$this->assertResultMatches($expected->documentElement, $dom->documentElement);
57+
}
58+
59+
public function testCanReplaceSingleDocument(): void {
60+
$merger = new Merger();
61+
62+
$dom = new DOMDocument();
63+
$dom->loadXML('<?xml version="1.0" ?><root><node id="test" /></root>');
64+
65+
$toMerge = new DOMDocument();
66+
$toMerge->loadXML('<?xml version="1.0" ?><merged id="test" />');
67+
68+
$list = new MergeList();
69+
$list->add(
70+
new Id('test'),
71+
$toMerge
72+
);
73+
74+
$merger->merge($dom, $list);
75+
76+
$expected = new DOMDocument();
77+
$expected->loadXML('<?xml version="1.0" ?><root><merged id="test" /></root>');
78+
79+
$this->assertResultMatches($expected->documentElement, $dom->documentElement);
80+
}
81+
82+
public function testMissingIdGetsSilentlyIgnored(): void {
83+
$merger = new Merger();
84+
85+
$dom = new DOMDocument();
86+
$dom->loadXML('<?xml version="1.0" ?><root><node id="test" /></root>');
87+
88+
$list = new MergeList();
89+
$list->add(
90+
new Id('not-used'),
91+
new DOMDocument()
92+
);
93+
94+
$merger->merge($dom, $list);
95+
96+
$expected = new DOMDocument();
97+
$expected->loadXML('<?xml version="1.0" ?><root><node id="test" /></root>');
98+
99+
$this->assertResultMatches($expected->documentElement, $dom->documentElement);
100+
}
101+
102+
public function testChildOfReplacedNodeGetsSkipped(): void {
103+
$merger = new Merger();
104+
105+
$dom = new DOMDocument();
106+
$dom->loadXML('<?xml version="1.0" ?><root><node id="test"><child id="child" /></node></root>');
107+
108+
$replace = new DOMDocument();
109+
$replace->loadXML('<?xml version="1.0" ?><replace id="test" />');
110+
111+
$list = new MergeList();
112+
$list->add(
113+
new Id('test'),
114+
$replace
115+
);
116+
117+
$merger->merge($dom, $list);
118+
119+
$expected = new DOMDocument();
120+
$expected->loadXML('<?xml version="1.0" ?><root><replace id="test" /></root>');
121+
122+
$this->assertResultMatches($expected->documentElement, $dom->documentElement);
123+
}
124+
125+
public function testDuplicateUseOfIdThrowsException(): void {
126+
$merger = new Merger();
127+
128+
$dom = new DOMDocument();
129+
$dom->loadXML('<?xml version="1.0" ?><root><first id="test" /></root>');
39130

131+
$test = new DOMDocument();
132+
$test->loadXML('<?xml version="1.0" ?><root><second id="test" /></root>');
133+
134+
$list = new MergeList();
135+
$list->add(
136+
new Id('test'),
137+
$test
138+
);
139+
140+
$this->expectException(MergerException::class);
141+
$this->expectExceptionCode(MergerException::DuplicateId);
142+
$merger->merge($dom, $list);
40143
}
144+
145+
public function testMultipleDocumentsGetMergedRecursively(): void {
146+
$merger = new Merger();
147+
148+
$dom = new DOMDocument();
149+
$dom->loadXML('<html xmlns="http://www.w3.org/1999/xhtml"><body><span id="a" /><span id="b" /><span id="c" /></body></html>');
150+
151+
$snippets = [
152+
'a' => '<templado:document xmlns="http://www.w3.org/1999/xhtml" xmlns:templado="https://templado.io/document/1.0"><div id="d" /><div id="e" /></templado:document>',
153+
'c' => '<templado:document xmlns="http://www.w3.org/1999/xhtml" xmlns:templado="https://templado.io/document/1.0" id="c"><div id="f" /><div id="g" /></templado:document>',
154+
'd' => '<templado:document xmlns="http://www.w3.org/1999/xhtml" xmlns:templado="https://templado.io/document/1.0"><div id="h" /><div id="i" /></templado:document>'
155+
];
156+
157+
$list = new MergeList();
158+
foreach($snippets as $id => $snippetXml) {
159+
$snippetDom = new DOMDocument();
160+
$snippetDom->loadXML($snippetXml);
161+
162+
$list->add(
163+
new Id($id),
164+
$snippetDom
165+
);
166+
}
167+
168+
$merger->merge($dom, $list);
169+
170+
$expected = new DOMDocument();
171+
$expected->loadXML('<?xml version="1.0"?><html xmlns="http://www.w3.org/1999/xhtml"><body><span id="a"><div id="d"><div id="h"/><div id="i"/></div><div id="e"/></span><span id="b"/><div id="f"/><div id="g"/></body></html>');
172+
173+
$this->assertResultMatches($expected->documentElement, $dom->documentElement);
174+
}
175+
41176
}

0 commit comments

Comments
 (0)