|
@ -9,4 +9,505 @@ declare(strict_types=1); |
|
|
namespace MensBeam\HTML\DOM; |
|
|
namespace MensBeam\HTML\DOM; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class HTMLElement extends Element {} |
|
|
class HTMLElement extends Element { |
|
|
|
|
|
use HTMLOrSVGElement; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected function __get_accessKey(): string { |
|
|
|
|
|
# The accessKey IDL attribute must reflect the accesskey content attribute. |
|
|
|
|
|
return $this->getAttribute('accesskey') ?? ''; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __set_accessKey(string $value): void { |
|
|
|
|
|
$this->setAttribute('accesskey', $value); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_autocapitalize(): string { |
|
|
|
|
|
# The autocapitalize getter steps are to: |
|
|
|
|
|
# |
|
|
|
|
|
# 1. Let state be the own autocapitalization hint of this. |
|
|
|
|
|
## To compute the own autocapitalization hint of an element element, run the |
|
|
|
|
|
## following steps: |
|
|
|
|
|
## 1. If the autocapitalize content attribute is present on element, and its |
|
|
|
|
|
## value is not the empty string, return the state of the attribute. |
|
|
|
|
|
$value = $this->getAttribute('autocapitalize'); |
|
|
|
|
|
if ($value !== null && $value !== '') { |
|
|
|
|
|
$state = $value; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
## 2. If element is an autocapitalize-inheriting element and has a non-null form |
|
|
|
|
|
## owner, return the own autocapitalization hint of element's form owner. |
|
|
|
|
|
// button fieldset input output select textarea |
|
|
|
|
|
elseif (in_array($this->tagName, [ 'button', 'fieldset', 'input', 'output', 'select', 'textarea' ])) |
|
|
|
|
|
|
|
|
|
|
|
## 3. Return default. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 2. If state is default, then return the empty string. |
|
|
|
|
|
|
|
|
|
|
|
# 3. If state is none, then return "none". |
|
|
|
|
|
|
|
|
|
|
|
# 4. If state is sentences, then return "sentences". |
|
|
|
|
|
|
|
|
|
|
|
# 5. Return the keyword value corresponding to state. |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function autoCapitalizationHint(HTMLElement $element): string { |
|
|
|
|
|
# To compute the own autocapitalization hint of an element element, run the |
|
|
|
|
|
# following steps: |
|
|
|
|
|
# 1. If the autocapitalize content attribute is present on element, and its |
|
|
|
|
|
# value is not the empty string, return the state of the attribute. |
|
|
|
|
|
$value = $this->getAttribute('autocapitalize'); |
|
|
|
|
|
if ($value !== null && $value !== '') { |
|
|
|
|
|
return $value; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# 2. If element is an autocapitalize-inheriting element and has a non-null form |
|
|
|
|
|
# owner, return the own autocapitalization hint of element's form owner. |
|
|
|
|
|
elseif (in_array($this->tagName, [ 'button', 'fieldset', 'input', 'output', 'select', 'textarea' ])) { |
|
|
|
|
|
# A form-associated element can have a relationship with a form element, which |
|
|
|
|
|
# is called the element's form owner. If a form-associated element is not |
|
|
|
|
|
# associated with a form element, its form owner is said to be null. |
|
|
|
|
|
|
|
|
|
|
|
# A form-associated element is, by default, associated with its nearest ancestor |
|
|
|
|
|
# form element (as described below), but, if it is listed, may have a form |
|
|
|
|
|
# attribute specified to override this. |
|
|
|
|
|
|
|
|
|
|
|
$n = $this; |
|
|
|
|
|
while ($n = $this->parentNode) { |
|
|
|
|
|
if ($n instanceof HTMLElement && $n->tagName === 'form') { |
|
|
|
|
|
return $this->autoCapitalizationHint($n); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
## 3. Return default. |
|
|
|
|
|
// The default of this user agent is 'off' |
|
|
|
|
|
return 'off'; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_contentEditable(): string { |
|
|
|
|
|
# The contenteditable content attribute is an enumerated attribute whose |
|
|
|
|
|
# keywords are the empty string, true, and false. The empty string and the true |
|
|
|
|
|
# keyword map to the true state. The false keyword maps to the false state. In |
|
|
|
|
|
# addition, there is a third state, the inherit state, which is the missing |
|
|
|
|
|
# value default and the invalid value default. |
|
|
|
|
|
|
|
|
|
|
|
# The true state indicates that the element is editable. The inherit state |
|
|
|
|
|
# indicates that the element is editable if its parent is. The false state |
|
|
|
|
|
# indicates that the element is not editable. |
|
|
|
|
|
|
|
|
|
|
|
# The contentEditable IDL attribute, on getting, must return the string "true" |
|
|
|
|
|
# if the content attribute is set to the true state, "false" if the content |
|
|
|
|
|
# attribute is set to the false state, and "inherit" otherwise. |
|
|
|
|
|
|
|
|
|
|
|
# On setting, if the new value is an ASCII case-insensitive match for the string |
|
|
|
|
|
# "inherit" then the content attribute must be removed, if the new value is an |
|
|
|
|
|
# ASCII case-insensitive match for the string "true" then the content attribute |
|
|
|
|
|
# must be set to the string "true", if the new value is an ASCII |
|
|
|
|
|
# case-insensitive match for the string "false" then the content attribute must |
|
|
|
|
|
# be set to the string "false", and otherwise the attribute setter must throw a |
|
|
|
|
|
# "SyntaxError" DOMException. |
|
|
|
|
|
|
|
|
|
|
|
$value = strtolower($this->getAttribute('contenteditable')); |
|
|
|
|
|
return ($value === 'true' || $value === 'false') ? $value : 'inherit'; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __set_contentEditable(string $value): void { |
|
|
|
|
|
# On setting, if the new value is an ASCII case-insensitive match for the string |
|
|
|
|
|
# "inherit" then the content attribute must be removed, if the new value is an |
|
|
|
|
|
# ASCII case-insensitive match for the string "true" then the content attribute |
|
|
|
|
|
# must be set to the string "true", if the new value is an ASCII |
|
|
|
|
|
# case-insensitive match for the string "false" then the content attribute must |
|
|
|
|
|
# be set to the string "false", and otherwise the attribute setter must throw a |
|
|
|
|
|
# "SyntaxError" DOMException. |
|
|
|
|
|
$value = strtolower($value); |
|
|
|
|
|
switch ($value) { |
|
|
|
|
|
case 'inherit': |
|
|
|
|
|
$this->removeAttribute('contenteditable'); |
|
|
|
|
|
break; |
|
|
|
|
|
case 'true': |
|
|
|
|
|
case 'false': |
|
|
|
|
|
$this->setAttribute('contenteditable', $value); |
|
|
|
|
|
break; |
|
|
|
|
|
default: throw new DOMException(DOMException::SYNTAX_ERROR); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_dir(): string { |
|
|
|
|
|
# The dir IDL attribute on an element must reflect the dir content attribute of |
|
|
|
|
|
# that element, limited to only known values. |
|
|
|
|
|
|
|
|
|
|
|
# If a reflecting IDL attribute is a DOMString attribute whose content attribute |
|
|
|
|
|
# is an enumerated attribute, and the IDL attribute is limited to only known |
|
|
|
|
|
# values, then, on getting, the IDL attribute must return the keyword value |
|
|
|
|
|
# associated with the state the attribute is in, if any, or the empty string if |
|
|
|
|
|
# the attribute is in a state that has no associated keyword value or if the |
|
|
|
|
|
# attribute is not in a defined state (e.g. the attribute is missing and there |
|
|
|
|
|
# is no missing value default). If there are multiple keyword values for the |
|
|
|
|
|
# state, then return the conforming one. If there are multiple conforming |
|
|
|
|
|
# keyword values, then one will be designated the canonical keyword; choose that |
|
|
|
|
|
# one. |
|
|
|
|
|
|
|
|
|
|
|
$value = $this->getAttribute('dir'); |
|
|
|
|
|
return (in_array($value, [ 'auto', 'ltr', 'rtl' ])) ? $value : ''; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __set_dir(string $value): void { |
|
|
|
|
|
# On setting, the content attribute must be set to the specified new value. |
|
|
|
|
|
$this->setAttribute('dir', $value); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_draggable(): bool { |
|
|
|
|
|
# The draggable IDL attribute, whose value depends on the content attribute's in |
|
|
|
|
|
# the way described below, controls whether or not the element is draggable. |
|
|
|
|
|
# Generally, only text selections are draggable, but elements whose draggable |
|
|
|
|
|
# IDL attribute is true become draggable as well. |
|
|
|
|
|
|
|
|
|
|
|
# If an element's draggable content attribute has the state true, the draggable |
|
|
|
|
|
# IDL attribute must return true. |
|
|
|
|
|
|
|
|
|
|
|
# Otherwise, if the element's draggable content attribute has the state false, |
|
|
|
|
|
# the draggable IDL attribute must return false. |
|
|
|
|
|
|
|
|
|
|
|
# Otherwise, the element's draggable content attribute has the state auto. If |
|
|
|
|
|
# the element is an img element, an object element that represents an image, or |
|
|
|
|
|
# an a element with an href content attribute, the draggable IDL attribute must |
|
|
|
|
|
# return true; otherwise, the draggable IDL attribute must return false. |
|
|
|
|
|
|
|
|
|
|
|
$value = $this->getAttribute('draggable'); |
|
|
|
|
|
$value = ($value === 'true' || $value === 'false') ? $value : 'auto'; |
|
|
|
|
|
if ($value === 'true') { |
|
|
|
|
|
return true; |
|
|
|
|
|
} elseif ($value === 'false') { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$tagName = $this->tagName; |
|
|
|
|
|
if ($tagName === 'img' || $this->hasAttribute('href')) { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
// Without actually being able to read the image in question it's impossible to |
|
|
|
|
|
// completely tell if an object element is representing an image. What we're |
|
|
|
|
|
// going to do here is check for a type attribute with a mimetype associated |
|
|
|
|
|
// with a known web image type, and, failing that, check the file extension of |
|
|
|
|
|
// the data attribute. |
|
|
|
|
|
elseif ($tagName === 'object') { |
|
|
|
|
|
$type = $this->getAttribute('type'); |
|
|
|
|
|
if ($type !== null && in_array($type, [ 'image/apng', 'image/avif', 'image/gif', 'image/jpeg', 'image/png', 'image/svg+xml', 'image/webp' ])) { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$data = $this->getAttribute('data'); |
|
|
|
|
|
if ($data !== null && in_array(strtolower(substr(strrchr($fileName, '.'), 1)), [ 'apng', 'avif', 'gif', 'jpeg', 'jpg', 'png', 'svg', 'webp' ])) { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __set_draggable(bool $value): void { |
|
|
|
|
|
# If the draggable IDL attribute is set to the value false, the draggable |
|
|
|
|
|
# content attribute must be set to the literal value "false". If the draggable |
|
|
|
|
|
# IDL attribute is set to the value true, the draggable content attribute must |
|
|
|
|
|
# be set to the literal value "true". |
|
|
|
|
|
$this->setAttribute('draggable', ($value) ? 'true' : 'false'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_enterKeyHint(): string { |
|
|
|
|
|
# The enterKeyHint IDL attribute must reflect the enterkeyhint content |
|
|
|
|
|
# attribute, limited to only known values. |
|
|
|
|
|
|
|
|
|
|
|
# If a reflecting IDL attribute is a DOMString attribute whose content attribute |
|
|
|
|
|
# is an enumerated attribute, and the IDL attribute is limited to only known |
|
|
|
|
|
# values, then, on getting, the IDL attribute must return the keyword value |
|
|
|
|
|
# associated with the state the attribute is in, if any, or the empty string if |
|
|
|
|
|
# the attribute is in a state that has no associated keyword value or if the |
|
|
|
|
|
# attribute is not in a defined state (e.g. the attribute is missing and there |
|
|
|
|
|
# is no missing value default). If there are multiple keyword values for the |
|
|
|
|
|
# state, then return the conforming one. If there are multiple conforming |
|
|
|
|
|
# keyword values, then one will be designated the canonical keyword; choose that |
|
|
|
|
|
# one. |
|
|
|
|
|
|
|
|
|
|
|
$value = $this->getAttribute('enterkeyhint'); |
|
|
|
|
|
return (in_array($value, [ 'done', 'enter', 'go', 'next', 'previous', 'search', 'send' ])) ? $value : ''; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __set_enterKeyHint(string $value): void { |
|
|
|
|
|
# On setting, the content attribute must be set to the specified new value. |
|
|
|
|
|
$this->setAttribute('enterkeyhint', $value); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_hidden(): bool { |
|
|
|
|
|
# The hidden getter steps are to return true if this's visibility state is |
|
|
|
|
|
# "hidden", otherwise false. |
|
|
|
|
|
|
|
|
|
|
|
$value = ($this->getAttribute('hidden') !== null); |
|
|
|
|
|
if ($value) { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$n = $this; |
|
|
|
|
|
while ($n = $n->parentNode) { |
|
|
|
|
|
if (property_exists($n, 'hidden') && ($n->getAttribute('hidden') !== null)) { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __set_hidden(bool $value): void { |
|
|
|
|
|
# The autofocus IDL attribute must reflect the content attribute of the same |
|
|
|
|
|
# name. |
|
|
|
|
|
|
|
|
|
|
|
# If a reflecting IDL attribute is a boolean attribute, then on getting the IDL |
|
|
|
|
|
# attribute must return true if the content attribute is set, and false if it is |
|
|
|
|
|
# absent. On setting, the content attribute must be removed if the IDL attribute |
|
|
|
|
|
# is set to false, and must be set to the empty string if the IDL attribute is |
|
|
|
|
|
# set to true. (This corresponds to the rules for boolean content attributes.) |
|
|
|
|
|
|
|
|
|
|
|
if ($value) { |
|
|
|
|
|
$this->setAttribute('hidden', ''); |
|
|
|
|
|
} else { |
|
|
|
|
|
$this->removeAttribute('hidden'); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_innerText(): ?string { |
|
|
|
|
|
# The innerText and outerText getter steps are: |
|
|
|
|
|
# 1. If this is not being rendered or if the user agent is a non-CSS user agent, |
|
|
|
|
|
# then return this's descendant text content. |
|
|
|
|
|
// This is a non-CSS user agent. Nothing else to do here. |
|
|
|
|
|
return $this->__get_textContent(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __set_innerText(string $value): void { |
|
|
|
|
|
# The innerText setter steps are: |
|
|
|
|
|
# 1. Let fragment be the rendered text fragment for the given value given this's node |
|
|
|
|
|
# document. |
|
|
|
|
|
$fragment = $this->getRenderedTextFragment($value); |
|
|
|
|
|
|
|
|
|
|
|
# 2. Replace all with fragment within this. |
|
|
|
|
|
$innerNode = $this->innerNode; |
|
|
|
|
|
$children = $innerNode->childNodes; |
|
|
|
|
|
while ($innerNode->hasChildNodes()) { |
|
|
|
|
|
$innerNode->removeChild($innerNode->firstChild); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Check for child nodes before appending to prevent a stupid warning. |
|
|
|
|
|
if ($fragment->hasChildNodes()) { |
|
|
|
|
|
$innerNode->appendChild($fragment); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_inputMode(): string { |
|
|
|
|
|
# The inputMode IDL attribute must reflect the inputmode content attribute, |
|
|
|
|
|
# limited to only known values. |
|
|
|
|
|
|
|
|
|
|
|
# If a reflecting IDL attribute is a DOMString attribute whose content attribute |
|
|
|
|
|
# is an enumerated attribute, and the IDL attribute is limited to only known |
|
|
|
|
|
# values, then, on getting, the IDL attribute must return the keyword value |
|
|
|
|
|
# associated with the state the attribute is in, if any, or the empty string if |
|
|
|
|
|
# the attribute is in a state that has no associated keyword value or if the |
|
|
|
|
|
# attribute is not in a defined state (e.g. the attribute is missing and there |
|
|
|
|
|
# is no missing value default). If there are multiple keyword values for the |
|
|
|
|
|
# state, then return the conforming one. If there are multiple conforming |
|
|
|
|
|
# keyword values, then one will be designated the canonical keyword; choose that |
|
|
|
|
|
# one. |
|
|
|
|
|
|
|
|
|
|
|
$value = $this->getAttribute('inputmode'); |
|
|
|
|
|
return (in_array($value, [ 'decimal', 'email', 'none', 'numeric', 'search', 'tel', 'text', 'url' ])) ? $value : ''; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __set_inputMode(string $value): void { |
|
|
|
|
|
# On setting, the content attribute must be set to the specified new value. |
|
|
|
|
|
$this->setAttribute('inputmode', $value); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_isContentEditable(): string { |
|
|
|
|
|
# The isContentEditable IDL attribute, on getting, must return true if the |
|
|
|
|
|
# element is either an editing host or editable, and false otherwise. |
|
|
|
|
|
|
|
|
|
|
|
# An editing host is either an HTML element with its contenteditable attribute |
|
|
|
|
|
# in the true state, or a child HTML element of a Document whose design mode |
|
|
|
|
|
# enabled is true. |
|
|
|
|
|
|
|
|
|
|
|
# Something is editable if it is a node; it is not an editing host; it does not |
|
|
|
|
|
# have a contenteditable attribute set to the false state; its parent is an |
|
|
|
|
|
# editing host or editable; and either it is an HTML element, or it is an svg or |
|
|
|
|
|
# math element, or it is not an Element and its parent is an HTML element. |
|
|
|
|
|
|
|
|
|
|
|
$doc = ($this instanceof Document) ? $this : $this->ownerDocument; |
|
|
|
|
|
if ($doc->designMode) { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$value = strtolower($this->getAttribute('contenteditable')); |
|
|
|
|
|
if ($value === 'true') { |
|
|
|
|
|
return true; |
|
|
|
|
|
} elseif ($value === 'false') { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ($doc !== this) { |
|
|
|
|
|
$n = $this; |
|
|
|
|
|
while ($n = $n->parentNode) { |
|
|
|
|
|
if (property_exists($n, 'contentEditable') && $n->contentEditable === 'true') { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_lang(): string { |
|
|
|
|
|
# The accessKey IDL attribute must reflect the title content attribute in no namespace. |
|
|
|
|
|
return $this->getAttribute('lang') ?? ''; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __set_lang(string $value): void { |
|
|
|
|
|
$this->setAttribute('lang', $value); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_outerText(): ?string { |
|
|
|
|
|
# The innerText and outerText getter steps are: |
|
|
|
|
|
# 1. If this is not being rendered or if the user agent is a non-CSS user agent, |
|
|
|
|
|
# then return this's descendant text content. |
|
|
|
|
|
// This is a non-CSS user agent. Nothing else to do here. |
|
|
|
|
|
return $this->__get_textContent(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __set_outerText(string $value): void { |
|
|
|
|
|
# The outerText setter steps are: |
|
|
|
|
|
# 1. If this's parent is null, then throw a "NoModificationAllowedError" |
|
|
|
|
|
# DOMException. |
|
|
|
|
|
$innerNode = $this->innerNode; |
|
|
|
|
|
if ($this->parentNode === null) { |
|
|
|
|
|
throw new DOMException(DOMException::NO_MODIFICATION_ALLOWED); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# 2. Let next be this's next sibling. |
|
|
|
|
|
$next = $innerNode->nextSibling; |
|
|
|
|
|
|
|
|
|
|
|
# 3. Let previous be this's previous sibling. |
|
|
|
|
|
$previous = $innerNode->previousSibling; |
|
|
|
|
|
|
|
|
|
|
|
# 4. Let fragment be the rendered text fragment for the given value given this's node |
|
|
|
|
|
# document. |
|
|
|
|
|
$fragment = $this->getRenderedTextFragment($value); |
|
|
|
|
|
|
|
|
|
|
|
# 5. Replace this with fragment within this's parent. |
|
|
|
|
|
// Check for child nodes before appending to prevent a stupid warning. |
|
|
|
|
|
if ($fragment->hasChildNodes()) { |
|
|
|
|
|
$innerNode->parentNode->replaceChild($fragment, $innerNode); |
|
|
|
|
|
} else { |
|
|
|
|
|
$innerNode->parentNode->removeChild($innerNode); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# 6. If next is non-null and next's previous sibling is a Text node, then merge |
|
|
|
|
|
# with the next text node given next's previous sibling. |
|
|
|
|
|
if ($next !== null && $next->previousSibling instanceof \DOMText) { |
|
|
|
|
|
# To merge with the next text node given a Text node node: |
|
|
|
|
|
# 1. Let next be node's next sibling. |
|
|
|
|
|
# 2. If next is not a Text node, then return. |
|
|
|
|
|
// Already checked for |
|
|
|
|
|
|
|
|
|
|
|
# 3. Replace data with node, node's data's length, 0, and next's data. |
|
|
|
|
|
$next->previousSibling->data .= $next->data; |
|
|
|
|
|
|
|
|
|
|
|
# 4. If next's parent is non-null, then remove next. |
|
|
|
|
|
// DEVIATION: There are no mutation events in this implementation, so there's no |
|
|
|
|
|
// reason to check for a parent here. |
|
|
|
|
|
$next->parentNode->removeChild($next); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# 7. If previous is a Text node, then merge with the next text node given previous. |
|
|
|
|
|
if ($previous instanceof \DOMText) { |
|
|
|
|
|
# To merge with the next text node given a Text node node: |
|
|
|
|
|
# 1. Let next be node's next sibling. |
|
|
|
|
|
$next = $previous->nextSibling; |
|
|
|
|
|
|
|
|
|
|
|
# 2. If next is not a Text node, then return. |
|
|
|
|
|
if ($next instanceof \DOMText) { |
|
|
|
|
|
# 3. Replace data with node, node's data's length, 0, and next's data. |
|
|
|
|
|
$previous->data .= $next->data; |
|
|
|
|
|
|
|
|
|
|
|
# 4. If next's parent is non-null, then remove next. |
|
|
|
|
|
// DEVIATION: There are no mutation events in this implementation, so there's no |
|
|
|
|
|
// reason to check for a parent here. |
|
|
|
|
|
$next->parentNode->removeChild($next); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_spellcheck(): bool { |
|
|
|
|
|
# The spellcheck IDL attribute, on getting, must return true if the element's |
|
|
|
|
|
# spellcheck content attribute is in the true state, or if the element's |
|
|
|
|
|
# spellcheck content attribute is in the default state and the element's default |
|
|
|
|
|
# behavior is true-by-default, or if the element's spellcheck content attribute |
|
|
|
|
|
# is in the default state and the element's default behavior is |
|
|
|
|
|
# inherit-by-default and the element's parent element's spellcheck IDL attribute |
|
|
|
|
|
# would return true; otherwise, if none of those conditions applies, then the |
|
|
|
|
|
# attribute must instead return false. |
|
|
|
|
|
|
|
|
|
|
|
// This user agent will be false-by-default. |
|
|
|
|
|
return ($this->getAttribute('spellcheck') === 'true'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __set_spellcheck(bool $value): bool { |
|
|
|
|
|
# On setting, if the new value is true, then the element's spellcheck content |
|
|
|
|
|
# attribute must be set to the literal string "true", otherwise it must be set |
|
|
|
|
|
# to the literal string "false". |
|
|
|
|
|
$this->setAttribute('spellcheck', ($value) ? 'true' : 'false'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_title(): string { |
|
|
|
|
|
# The accessKey IDL attribute must reflect the title content attribute. |
|
|
|
|
|
return $this->getAttribute('title') ?? ''; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __set_title(string $value): void { |
|
|
|
|
|
$this->setAttribute('title', $value); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __get_translate(): bool { |
|
|
|
|
|
# The translate IDL attribute must, on getting, return true if the element's |
|
|
|
|
|
# translation mode is translate-enabled, and false otherwise. |
|
|
|
|
|
|
|
|
|
|
|
# Each element (even non-HTML elements) has a translation mode, which is in |
|
|
|
|
|
# either the translate-enabled state or the no-translate state. If an HTML |
|
|
|
|
|
# element's translate attribute is in the yes state, then the element's |
|
|
|
|
|
# translation mode is in the translate-enabled state; otherwise, if the |
|
|
|
|
|
# element's translate attribute is in the no state, then the element's |
|
|
|
|
|
# translation mode is in the no-translate state. Otherwise, either the element's |
|
|
|
|
|
# translate attribute is in the inherit state, or the element is not an HTML |
|
|
|
|
|
# element and thus does not have a translate attribute; in either case, the |
|
|
|
|
|
# element's translation mode is in the same state as its parent element's, if |
|
|
|
|
|
# any, or in the translate-enabled state, if the element is a document element. |
|
|
|
|
|
|
|
|
|
|
|
$value = strtolower($this->getAttribute('translate')); |
|
|
|
|
|
if ($value === 'yes') { |
|
|
|
|
|
return true; |
|
|
|
|
|
} elseif ($value === 'no') { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$n = $this; |
|
|
|
|
|
while ($n = $n->parentNode) { |
|
|
|
|
|
if (property_exists($n, 'translate') && $n->getAttribute('translate') === 'yes') { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function __set_translate(bool $value): void { |
|
|
|
|
|
# On setting, it must set the content attribute's value to "yes" if the new |
|
|
|
|
|
# value is true, and set the content attribute's value to "no" otherwise. |
|
|
|
|
|
$this->setAttribute('translate', ($value) ? 'yes' : 'no'); |
|
|
|
|
|
} |
|
|
|
|
|
} |