Browse Source

Mostly implemented HTMLElement, not tested yet

master
Dustin Wilson 2 years ago
parent
commit
82770ba859
  1. 4
      lib/DOMException.php
  2. 4
      lib/DOMTokenList.php
  3. 34
      lib/Document.php
  4. 98
      lib/Element.php
  5. 503
      lib/HTMLElement.php
  6. 42
      lib/HTMLOrSVGElement.php
  7. 4
      lib/SVGElement.php
  8. 95
      tests/cases/TestElement.php
  9. 112
      tests/cases/TestHTMLElement.php
  10. 1
      tests/phpunit.dist.xml

4
lib/DOMException.php

@ -11,6 +11,10 @@ use MensBeam\Framework\Exception;
class DOMException extends Exception {
// DEVIATION (kinda): The specification states clearly that using the old
// integer values for exceptions has been deprecated, but all the browsers
// continue to honor them even if they're not explicitly used. Besides, it's
// bonkers to create numerous exception classes for each error type.
public const INDEX_SIZE_ERROR = 1;
public const HIERARCHY_REQUEST_ERROR = 3;
public const WRONG_DOCUMENT = 4;

4
lib/DOMTokenList.php

@ -56,9 +56,7 @@ class DOMTokenList implements \ArrayAccess, \Countable, \Iterator {
# When a DOMTokenList object is created, then:
#
# 1. Let element be associated element.
// Using a weak reference here to prevent a circular reference. We should be
// creating a weak reference to the inner element, but there are memory
// management issues concerning PHP's garbage reference counting.
// Using a weak reference here to prevent a circular reference.
$this->element = \WeakReference::create($element);
# 2. Let localName be associated attribute’s local name.
$this->localName = $attributeLocalName;

34
lib/Document.php

@ -68,6 +68,40 @@ class Document extends Node implements \ArrayAccess {
return $this->_contentType;
}
protected function __get_dir(): string {
# The dir IDL attribute on Document objects must reflect the dir content attribute
# of the html element, if any, limited to only known values. If there is no such
# element, then the attribute must return the empty string and do nothing on
# setting.
# 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.
$documentElement = $this->documentElement;
return ($documentElement !== null && $documentElement->namespaceURI === Node::HTML_NAMESPACE && $documentElement->tagName === 'html') ? $documentElement->dir : '';
}
protected function __set_dir(string $value): void {
# The dir IDL attribute on Document objects must reflect the dir content attribute
# of the html element, if any, limited to only known values. If there is no such
# element, then the attribute must return the empty string and do nothing on
# setting.
# On setting, the content attribute must be set to the specified new value.
$documentElement = $this->documentElement;
if ($documentElement !== null && $documentElement->namespaceURI === Node::HTML_NAMESPACE && $documentElement->tagName === 'html') {
$documentElement->dir = $value;
}
}
protected function __get_doctype(): ?DocumentType {
// PHP's DOM does this correctly already.
$doctype = $this->innerNode->doctype;

98
lib/Element.php

@ -106,33 +106,6 @@ class Element extends Node {
}
}
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_localName(): ?string {
// PHP's DOM does this correctly already.
return $this->innerNode->localName;
@ -194,77 +167,6 @@ class Element extends Node {
$parent->replaceChild($fragment, $this);
}
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_prefix(): ?string {
$prefix = $this->innerNode->prefix;
return ($prefix !== '') ? $prefix : null;

503
lib/HTMLElement.php

@ -9,4 +9,505 @@ declare(strict_types=1);
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');
}
}

42
lib/HTMLOrSVGElement.php

@ -0,0 +1,42 @@
<?php
/**
* @license MIT
* Copyright 2017 Dustin Wilson, J. King, et al.
* See LICENSE and AUTHORS files for details
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
trait HTMLOrSVGElement {
protected function __get_autofocus(): bool {
# 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.)
return ($this->getAttribute('autofocus') !== null);
}
protected function __set_autofocus(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('autofocus', '');
} else {
$this->removeAttribute('autofocus');
}
}
}

4
lib/SVGElement.php

@ -9,4 +9,6 @@ declare(strict_types=1);
namespace MensBeam\HTML\DOM;
class SVGElement extends Element {}
class SVGElement extends Element {
use HTMLOrSVGElement;
}

95
tests/cases/TestElement.php

@ -1372,101 +1372,6 @@ class TestElement extends \PHPUnit\Framework\TestCase {
}
/**
* @covers \MensBeam\HTML\DOM\Element::__get_innerText
* @covers \MensBeam\HTML\DOM\Element::__set_innerText
* @covers \MensBeam\HTML\DOM\Element::__get_outerText
* @covers \MensBeam\HTML\DOM\Element::__set_outerText
*
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Collection::__get_length
* @covers \MensBeam\HTML\DOM\Collection::count
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_innerHTML
* @covers \MensBeam\HTML\DOM\Element::getRenderedTextFragment
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_childNodes
* @covers \MensBeam\HTML\DOM\Node::__get_parentNode
* @covers \MensBeam\HTML\DOM\Node::__get_textContent
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerNode
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testProperty_innerText_outerText() {
$d = new Document();
$d->appendChild($d->createElement('html'));
$d->documentElement->appendChild($d->createElement('body'));
$body = $d->body;
$body->appendChild($d->createTextNode('ook '));
$s = $body->appendChild($d->createElement('span'));
$s->appendChild($d->createTextNode('ook'));
$body->appendChild($d->createTextNode(' eek'));
$this->assertSame('ook <span>ook</span> eek', $body->innerHTML);
$s->innerText = <<<TEXT
ook\r\n
eek ook
TEXT;
$this->assertSame('ook ookook eek ook eek', $body->innerText);
$this->assertSame('ook<br><br>ook eek ook', $s->innerHTML);
$s->outerText = 'ack';
$this->assertSame('ook ack eek', $body->outerText);
$this->assertEquals(1, $body->childNodes->length);
$s = $body->appendChild($d->createElement('span'));
$s->outerText = '';
$this->assertSame('ook ack eek', $body->outerText);
}
/**
* @covers \MensBeam\HTML\DOM\Element::__set_outerText
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMException::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_parentNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_outerText__errors() {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::NO_MODIFICATION_ALLOWED);
$d = new Document();
$h = $d->createElement('html');
$h->outerText = 'fail';
}
/**
* @covers \MensBeam\HTML\DOM\Element::__get_outerHTML
* @covers \MensBeam\HTML\DOM\Element::__set_outerHTML

112
tests/cases/TestHTMLElement.php

@ -0,0 +1,112 @@
<?php
/**
* @license MIT
* Copyright 2017 Dustin Wilson, J. King, et al.
* See LICENSE and AUTHORS files for details
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException
};
/** @covers \MensBeam\HTML\DOM\HTMLElement */
class TestHTMLElement extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_innerText
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_innerText
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_outerText
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_outerText
*
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Collection::__get_length
* @covers \MensBeam\HTML\DOM\Collection::count
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_innerHTML
* @covers \MensBeam\HTML\DOM\Element::getRenderedTextFragment
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_childNodes
* @covers \MensBeam\HTML\DOM\Node::__get_parentNode
* @covers \MensBeam\HTML\DOM\Node::__get_textContent
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerNode
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testProperty_innerText_outerText() {
$d = new Document();
$d->appendChild($d->createElement('html'));
$d->documentElement->appendChild($d->createElement('body'));
$body = $d->body;
$body->appendChild($d->createTextNode('ook '));
$s = $body->appendChild($d->createElement('span'));
$s->appendChild($d->createTextNode('ook'));
$body->appendChild($d->createTextNode(' eek'));
$this->assertSame('ook <span>ook</span> eek', $body->innerHTML);
$s->innerText = <<<TEXT
ook\r\n
eek ook
TEXT;
$this->assertSame('ook ookook eek ook eek', $body->innerText);
$this->assertSame('ook<br><br>ook eek ook', $s->innerHTML);
$s->outerText = 'ack';
$this->assertSame('ook ack eek', $body->outerText);
$this->assertEquals(1, $body->childNodes->length);
$s = $body->appendChild($d->createElement('span'));
$s->outerText = '';
$this->assertSame('ook ack eek', $body->outerText);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_outerText
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMException::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_parentNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_outerText__errors() {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::NO_MODIFICATION_ALLOWED);
$d = new Document();
$h = $d->createElement('html');
$h->outerText = 'fail';
}
}

1
tests/phpunit.dist.xml

@ -26,6 +26,7 @@
<file>cases/TestDOMTokenList.php</file>
<file>cases/TestElement.php</file>
<file>cases/TestHTMLCollection.php</file>
<file>cases/TestHTMLElement.php</file>
<file>cases/TestInnerDocument.php</file>
<file>cases/TestNamedNodeMap.php</file>
<file>cases/TestNode.php</file>

Loading…
Cancel
Save