-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathListElement.php
114 lines (100 loc) · 3.24 KB
/
ListElement.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<?php
namespace Gt\DomTemplate;
use Gt\Dom\Element;
use Gt\Dom\Node;
use Gt\Dom\Text;
use Throwable;
class ListElement {
const ATTRIBUTE_LIST_PARENT = "data-list-parent";
private string $listItemParentPath;
private null|Element $listItemNextSibling;
private int $insertCount;
public function __construct(
private readonly Node|Element $originalElement
) {
$parentElement = $this->originalElement->parentElement;
if(!$parentElement->getAttribute(self::ATTRIBUTE_LIST_PARENT)) {
$parentElement->setAttribute(self::ATTRIBUTE_LIST_PARENT, uniqid("list-parent-"));
}
$this->listItemParentPath = new NodePathCalculator($parentElement);
$siblingContext = $this->originalElement;
while($siblingContext = $siblingContext->nextElementSibling) {
if(!$siblingContext->hasAttribute("data-list")
&& !$siblingContext->hasAttribute("data-template")) {
break;
}
}
$this->listItemNextSibling =
is_null($siblingContext)
? null
: $siblingContext;
$this->insertCount = 0;
}
public function removeOriginalElement():void {
$this->originalElement->remove();
try {
$parent = $this->getListItemParent();
if(count($parent->children) === 0) {
if($firstNode = $parent->childNodes[0] ?? null) {
if(trim($firstNode->wholeText) === "") {
$parent->innerHTML = "";
}
}
}
}
// In nested lists, there may not be an actual element attached to the document
// yet, but the parent still has a path - this outcome is expected and
// completely fine in this case.
catch(Throwable) {}
}
public function getClone():Node|Element {
// TODO: #368 Bug here - the template-parent-xxx ID is being generated the same for multiple instances.
/** @var Element $element */
$element = $this->originalElement->cloneNode(true);
// foreach($this->originalElement->ownerDocument->evaluate("./*[starts-with(@id,'template-parent-')]", $element) as $existingTemplateElement) {
// $existingTemplateElement->id = uniqid("template-parent-");
// }
// $this->templateParentPath = new NodePathCalculator($element->parentElement);
return $element;
}
/**
* Inserts a deep clone of the original element in place where it was
* originally extracted from the document, returning the newly-inserted
* clone.
*/
public function insertListItem():Element {
$clone = $this->getClone();
$listItemParent = $this->getListItemParent();
$listItemParent->insertBefore(
$clone,
$this->getListItemNextSibling()
);
$this->insertCount++;
return $clone;
}
public function getListItemParent():Node|Element {
$matches = $this->originalElement->ownerDocument->evaluate(
$this->listItemParentPath
);
do {
/** @var Element $parent */
$parent = $matches->current();
$matches->next();
}
while($matches->valid());
return $parent;
}
public function getListItemNextSibling():null|Node|Element {
return $this->listItemNextSibling ?? null;
}
public function getListItemName():?string {
$listName = $this->originalElement->getAttribute("data-list") ?? $this->originalElement->getAttribute("data-template");
if(strlen($listName) === 0) {
return null;
}
elseif($listName[0] === "/") {
throw new InvalidListElementNameException("A list's name must not start with a forward slash (\"$listName\")");
}
return $listName;
}
}