Browse Source

Fix pushing to the list of active formatting elements

split-manual
J. King 3 years ago
parent
commit
400940af36
  1. 162
      lib/ActiveFormattingElementsList.php

162
lib/ActiveFormattingElementsList.php

@ -25,72 +25,86 @@ class ActiveFormattingElementsList extends Stack {
}
public function offsetSet($offset, $value) {
assert($offset >= 0 && $offset <= count($this->_storage), new Exception(Exception::STACK_INVALID_INDEX, $offset));
if (is_null($offset)) {
$count = count($this->_storage);
assert($offset >= 0 && $offset <= $count, new Exception(Exception::STACK_INVALID_INDEX, $offset));
assert($value instanceof ActiveFormattingElementsMarker || (
is_array($value)
&& sizeof($value) === 2
&& isset($value['token'])
&& isset($value['element'])
&& $value['token'] instanceof StartTagToken
&& $value['element'] instanceof Element
), new \Exception("Active formatting element value is invalid"));
if ($value instanceof ActiveFormattingElementsMarker) {
$this->_storage[$offset] = $value;
} elseif (($offset ?? $count) === $count) {
# When the steps below require the UA to push onto the list of active formatting
# elements an element element, the UA must perform the following steps:
if ($value instanceof \DOMElement) {
# 1. If there are already three elements in the list of active formatting
# elements after the last marker, if any, or anywhere in the list if there are
# no markers, that have the same tag name, namespace, and attributes as element,
# then remove the earliest such element from the list of active formatting
# elements. For these purposes, the attributes must be compared as they were
# when the elements were created by the parser; two elements have the same
# attributes if all their parsed attributes can be paired such that the two
# attributes in each pair have identical names, namespaces, and values (the
# order of the attributes does not matter).
$lastMarkerIndex = $this->lastMarker;
$start = ($lastMarkerIndex !== false) ? $lastMarkerIndex + 1 : 0;
$length = count($this->_storage);
if ($start < $length - 3) {
$count = 0;
for ($i = $length - 1; $i > $start; $i--) {
$cur = $this->_storage[$i];
if ($cur->nodeName === $value->nodeName && $cur->namespaceURI === $value->namespaceURI && $cur->attributes->length === $value->attributes->length) {
$a = [];
for ($j = 0; $j < $cur->attributes->length; $cur++) {
$cur2 = $cur->attributes[$j];
$a[] = $cur2->name . $cur2->namespaceURI . $cur2->value;
}
$b = [];
for ($j = 0; $j < $value->attributes->length; $cur++) {
$cur2 = $value->attributes[$j];
$b[] = $cur2->name . $cur2->namespaceURI . $cur2->value;
}
sort($a);
sort($b);
if ($a === $b) {
$count++;
if ($count === 3) {
$this->offsetUnset($i);
break;
}
}
}
}
// First find the position of the last marker, if any
$lastMarker = -1;
foreach ($this as $pos => $item) {
if ($item instanceof ActiveFormattingElementsMarker) {
$lastMarker = $pos;
break;
}
}
# 2. Add element to the list of active formatting elements.
# If there are already three elements in the list of active formatting
# elements after the last marker, if any, or anywhere in the list if there are
# no markers, that have the same tag name, namespace, and attributes as element,
# then remove the earliest such element from the list of active formatting
# elements.
$pos = $count - 1;
$matches = 0;
do {
$matches += (int) $this->matchElement($value['element'], $this->_storage[$pos]['element']);
// Stop once there are three matches or the marker is reached
} while ($matches < 3 && (--$pos) > $lastMarker);
if ($matches === 3) {
array_splice($this->_storage, $pos, 1, []);
}
# Add element to the list of active formatting elements.
$this->_storage[] = $value;
} else {
parent::offsetSet($offset, $value);
$this->_storage[$offset] = $value;
}
}
public function insert(StartTagToken $token, \DOMElement $element) {
$this->_storage[] = [
protected function matchElement(Element $a, Element $b): bool {
// Compare elements as part of pushing an element onto the stack
# 1. If there are already three elements in the list of active formatting
# elements after the last marker, if any, or anywhere in the list if there are
# no markers, that have the same tag name, namespace, and attributes as element,
# then remove the earliest such element from the list of active formatting
# elements.
# For these purposes, the attributes must be compared as they were
# when the elements were created by the parser; two elements have the same
# attributes if all their parsed attributes can be paired such that the two
# attributes in each pair have identical names, namespaces, and values (the
# order of the attributes does not matter).
if (
$a->nodeName !== $b->nodeName
|| $a->namespaceURI !== $b->namespaceURI
|| $a->attributes->length !== $b->attributes->length
) {
return false;
}
foreach ($a->attributes as $attr) {
if (!$b->hasAttributeNS($attr->namespaceURI, $attr->nodeName) || $b->getAttributeNS($attr->namespaceURI, $attr->nodeName) !== $attr->value) {
return false;
}
}
return true;
}
public function insert(StartTagToken $token, Element $element): void {
$this[] = [
'token' => $token,
'element' => $element
];
}
public function insertMarker() {
$this->offsetSet(null, new ActiveFormattingElementMarker());
public function insertMarker(): void {
$this[] = new ActiveFormattingElementsMarker;
}
public function reconstruct() {
@ -112,7 +126,7 @@ class ActiveFormattingElementsList extends Stack {
# elements is a marker, or if it is an element that is in the stack of open
# elements, then there is nothing to reconstruct; stop this algorithm.
$entry = end($this->_storage);
if ($entry instanceof ActiveFormattingElementMarker || in_array($entry['element'], $this->stack)) {
if ($entry instanceof ActiveFormattingElementsMarker || in_array($entry['element'], $this->stack)) {
return;
}
@ -133,7 +147,7 @@ class ActiveFormattingElementsList extends Stack {
# 6. If entry is neither a marker nor an element that is also in the stack of
# open elements, go to the step labeled Rewind.
if (!$entry instanceof ActiveFormattingElementMarker || $this->stack->search($entry['element']) === -1) {
if (!$entry instanceof ActiveFormattingElementsMarker || $this->stack->search($entry['element']) === -1) {
goto rewind;
}
@ -157,7 +171,7 @@ class ActiveFormattingElementsList extends Stack {
}
}
public function clearToTheLastMarker() {
public function clearToTheLastMarker(): void {
# When the steps below require the UA to clear the list of active formatting
# elements up to the last marker, the UA must perform the following steps:
# 1. Let entry be the last (most recently added) entry in the list of active
@ -166,41 +180,13 @@ class ActiveFormattingElementsList extends Stack {
# 3. If entry was a marker, then stop the algorithm at this point. The list has
# been cleared up to the last marker.
# 4. Go to step 1.
// Just going to go backwards through the array until a marker is reached. Does
// the same thing.
for ($end = count($this->_storage) - 1, $i = $end; $i >= 0; $i--) {
if ($this->_storage[$i] instanceof ActiveFormattingElementMarker) {
return;
while ($this->_storage) {
$popped = array_pop($this->_storage);
if ($popped instanceof ActiveFormattingElementsMarker) {
break;
}
unset($this->_storage[$i]);
}
// Reindex the array.
$this->_storage = array_values($this->_storage);
}
public function __get($property) {
$value = parent::__get($property);
if (!is_null($value)) {
return $value;
}
switch ($property) {
case 'lastMarker':
for ($end = count($this->_storage) - 1, $i = $end; $i >= 0; $i--) {
if ($this->_storage[$i] instanceof ActiveFormattingElementMarker) {
return $i;
}
}
return false;
break;
default: return null;
}
}
}
class ActiveFormattingElementMarker {}
class ActiveFormattingElementsMarker {}

Loading…
Cancel
Save