Browse Source

Add Walk and Moonwalk generators

split-manual
Dustin Wilson 3 years ago
parent
commit
fbefea3073
  1. 2
      lib/DOM/Comment.php
  2. 12
      lib/DOM/DOMException.php
  3. 95
      lib/DOM/Document.php
  4. 2
      lib/DOM/DocumentFragment.php
  5. 4
      lib/DOM/Element.php
  6. 2
      lib/DOM/ProcessingInstruction.php
  7. 2
      lib/DOM/Text.php
  8. 27
      lib/DOM/traits/Ancestor.php
  9. 25
      lib/DOM/traits/Compare.php
  10. 37
      lib/DOM/traits/Descendant.php
  11. 17
      lib/DOM/traits/Moonwalk.php
  12. 2
      lib/DOM/traits/Serialize.php
  13. 22
      lib/DOM/traits/Walk.php
  14. 120
      lib/Exception.php

2
lib/DOM/Comment.php

@ -3,7 +3,7 @@ declare(strict_types=1);
namespace dW\HTML5;
class Comment extends \DOMComment {
use Ancestor;
use Moonwalk;
public function __toString(): string {
# Append the literal string "<!--" (U+003C LESS-THAN SIGN, U+0021 EXCLAMATION

12
lib/DOM/DOMException.php

@ -4,16 +4,18 @@ namespace dW\HTML5;
class DOMException extends \Exception {
// From PHP's DOMException; keeping error codes consistent
const WRONG_DOCUMENT = 4;
const NO_MODIFICATION_ALLOWED = 7;
const DOCUMENT_DOCUMENTFRAG_EXPECTED = 100;
const STRING_OR_CLOSURE_EXPECTED = 101;
const DOCUMENT_ELEMENT_DOCUMENTFRAG_EXPECTED = 100;
const STRING_EXPECTED = 101;
const OUTER_HTML_FAILED_NOPARENT = 102;
protected static $messages = [
4 => 'Supplied node does not belong to this document',
7 => 'Modification not allowed here',
100 => 'Element, Document, or DOMDocumentFragment expected; found %s',
101 => 'The first argument must either be an instance of \DOMNode, a string, or a closure; found %s',
100 => 'Document, Element, or DocumentFragment expected; found %s',
101 => 'The "%s" argument should be a string; found %s',
102 => 'Failed to set the "outerHTML" property; the element does not have a parent node'
];
@ -36,7 +38,7 @@ class DOMException extends \Exception {
// Count the number of replacements needed in the message.
preg_match_all('/(\%(?:\d+\$)?s)/', $message, $matches);
$count = count(array_unique($matches[1]));
$count = count($matches[1]);
// If the number of replacements don't match the arguments then oops.
if (count($args) !== $count) {

95
lib/DOM/Document.php

@ -3,7 +3,7 @@ declare(strict_types=1);
namespace dW\HTML5;
class Document extends \DOMDocument {
use Descendant, Serialize, EscapeString;
use EscapeString, Moonwalk, Serialize, Walk;
// Quirks mode constants
public const NO_QUIRKS_MODE = 0;
@ -31,23 +31,32 @@ class Document extends \DOMDocument {
$this->registerNodeClass('DOMText', '\dW\HTML5\Text');
}
public function load($source, $options = null, ?string $encodingOrContentType = null): bool {
$data = Parser::fetchFile($source, $encodingOrContentType);
if (!$data) {
return false;
public function createAttribute($name) {
try {
return parent::createAttribute($name);
} catch (\DOMException $e) {
// The element name is invalid for XML
// Replace any offending characters with "UHHHHHH" where H are the
// uppercase hexadecimal digits of the character's code point
$this->mangledAttributes = true;
$name = $this->coerceName($name);
return parent::createAttribute($name);
}
[$data, $encodingOrContentType] = $data;
Parser::parse($data, $this, $encodingOrContentType, null, (string) $source);
return true;
}
public function loadHTML($source, $options = null, ?string $encodingOrContentType = null): bool {
Parser::parse((string)$source, $this, $encodingOrContentType);
return true;
public function createAttributeNS($namespaceURI, $qualifiedName) {
try {
return parent::createAttributeNS($namespaceURI, $qualifiedName);
} catch (\DOMException $e) {
// The element name is invalid for XML
// Replace any offending characters with "UHHHHHH" where H are the
// uppercase hexadecimal digits of the character's code point
$this->mangledAttributes = true;
$qualifiedName = $this->coerceName($qualifiedName);
return parent::createAttributeNS($namespaceURI, $qualifiedName);
}
}
public function saveHTMLFile($filename) {}
public function createElement($name, $value = "") {
try {
$e = parent::createElement($name, $value);
@ -84,30 +93,50 @@ class Document extends \DOMDocument {
}
}
public function createAttribute($name) {
try {
return parent::createAttribute($name);
} catch (\DOMException $e) {
// The element name is invalid for XML
// Replace any offending characters with "UHHHHHH" where H are the
// uppercase hexadecimal digits of the character's code point
$this->mangledAttributes = true;
$name = $this->coerceName($name);
return parent::createAttribute($name);
public function load($source, $options = null, ?string $encodingOrContentType = null): bool {
$data = Parser::fetchFile($source, $encodingOrContentType);
if (!$data) {
return false;
}
[$data, $encodingOrContentType] = $data;
Parser::parse($data, $this, $encodingOrContentType, null, (string) $source);
return true;
}
public function createAttributeNS($namespaceURI, $qualifiedName) {
try {
return parent::createAttributeNS($namespaceURI, $qualifiedName);
} catch (\DOMException $e) {
// The element name is invalid for XML
// Replace any offending characters with "UHHHHHH" where H are the
// uppercase hexadecimal digits of the character's code point
$this->mangledAttributes = true;
$qualifiedName = $this->coerceName($qualifiedName);
return parent::createAttributeNS($namespaceURI, $qualifiedName);
public function loadHTML($source, $options = null, ?string $encodingOrContentType = null): bool {
assert(is_string($source), new DOMException(DOMException::STRING_EXPECTED, 'source', gettype($source)));
Parser::parse($source, $this, $encodingOrContentType);
return true;
}
public function save($filename, $options = 0): string {
return file_put_contents($filename, $this->serialize());
}
public function saveHTML(\DOMNode $node = null): string {
if ($node === null) {
$node = $this;
} elseif ($node->ownerDocument !== $this) {
throw new DOMException(DOMException::WRONG_DOCUMENT);
}
return $node->serialize();
}
public function saveHTMLFile($filename): int {
return $this->save($filename);
}
public function saveXML(?\DOMNode $node = null, $options = null) {
return false;
}
public function validate(): bool {
return true;
}
public function xinclude($options = null): bool {
return false;
}
public function __toString() {

2
lib/DOM/DocumentFragment.php

@ -3,7 +3,7 @@ declare(strict_types=1);
namespace dW\HTML5;
class DocumentFragment extends \DOMDocumentFragment {
use Descendant, Serialize;
use Moonwalk, Serialize;
public function __toString() {
return $this->serialize();

4
lib/DOM/Element.php

@ -3,9 +3,7 @@ declare(strict_types=1);
namespace dW\HTML5;
class Element extends \DOMElement {
use Ancestor, Descendant, EscapeString, Serialize {
Ancestor::compare insteadof Descendant;
}
use EscapeString, Moonwalk, Serialize, Walk;
// Used for template elements
public $content = null;

2
lib/DOM/ProcessingInstruction.php

@ -3,7 +3,7 @@ declare(strict_types=1);
namespace dW\HTML5;
class ProcessingInstruction extends \DOMProcessingInstruction {
use Ancestor;
use Moonwalk;
public function __toString(): string {
# Append the literal string "<?" (U+003C LESS-THAN SIGN, U+003F QUESTION MARK),

2
lib/DOM/Text.php

@ -3,7 +3,7 @@ declare(strict_types=1);
namespace dW\HTML5;
class Text extends \DOMText {
use Ancestor, EscapeString;
use EscapeString, Moonwalk;
function __toString(): string {
# If the parent of current node is a style, script, xmp, iframe, noembed,

27
lib/DOM/traits/Ancestor.php

@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
namespace dW\HTML5;
trait Ancestor {
use Compare;
public function getAncestor($needle): Element {
return $this->ancestor($needle, true);
}
public static function hasAncestor($needle): bool {
return $this->ancestor($needle, false);
}
protected function ancestor($needle, bool $returnNode = true) {
$context = $this->parentNode;
do {
$result = self::compare($needle, $context);
if (!is_null($result)) {
return ($returnNode === true) ? $result : true;
}
} while ($context = $context->parentNode);
return ($returnNode === true) ? null : false;
}
}

25
lib/DOM/traits/Compare.php

@ -1,25 +0,0 @@
<?php
declare(strict_types=1);
namespace dW\HTML5;
trait Compare {
protected function compare($needle, Element $context): \DOMNode {
if (is_string($needle)) {
if ($context->nodeName == $needle) {
return $this;
}
} elseif ($needle instanceof \DOMNode) {
if ($context->isSameNode($needle)) {
return $context;
}
} elseif ($needle instanceof \Closure) {
if ($needle($context) === true) {
return $context;
}
} else {
throw new DOMException(DOMException::STRING_OR_CLOSURE_EXPECTED, gettype($needle));
}
return null;
}
}

37
lib/DOM/traits/Descendant.php

@ -1,37 +0,0 @@
<?php
declare(strict_types=1);
namespace dW\HTML5;
trait Descendant {
use Compare;
public function getDescendant($needle): \DOMNode {
return self::descendant($needle, true);
}
public function hasDescendant($needle): bool {
return self::descendant($needle, false);
}
protected function descendant($needle, bool $returnNode = true): \DOMNode {
if ($this->hasChildNodes() === false) {
return ($returnNode === true) ? null : false;
}
$context = $this->firstChild;
do {
$result = $this->compare($needle, $context);
if (!is_null($result)) {
return ($returnNode === true) ? $result : true;
}
$result = $this->descendant($needle, $context);
if (!is_null($result)) {
return ($returnNode === true) ? $result : true;
}
} while ($context = $context->nextSibling);
return ($returnNode === true) ? null : false;
}
}

17
lib/DOM/traits/Moonwalk.php

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace dW\HTML5;
trait Moonwalk {
public function moonwalk(\Closure $filter): \Generator {
return $this->moonwalkGenerator($this, $filter);
}
private function moonwalkGenerator(\DOMNode $node, \Closure $filter) {
do {
if ($filter($node)) {
yield $node;
}
} while ($node = $node->parentNode);
}
}

2
lib/DOM/traits/Serialize.php

@ -18,7 +18,7 @@ trait Serialize {
}
if (!$node instanceof Element && !$node instanceof Document && !$node instanceof DocumentFragment) {
throw new DOMException(DOMException::DOCUMENT_DOCUMENTFRAG_EXPECTED, gettype($node));
throw new DOMException(DOMException::DOCUMENT_ELEMENT_DOCUMENTFRAG_EXPECTED, gettype($node));
}
# 13.3. Serializing HTML fragments

22
lib/DOM/traits/Walk.php

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace dW\HTML5;
trait Walk {
public function walk(\Closure $filter): \Generator {
return $this->walkGenerator($this, $filter);
}
private function walkGenerator(\DOMNode $node, \Closure $filter) {
if ($filter($node)) {
yield $node;
}
if ($node->hasChildNodes()) {
$children = $node->childNodes;
foreach ($children as $c) {
yield from $this->walkGenerator($c, $filter);
}
}
}
}

120
lib/Exception.php

@ -3,68 +3,68 @@ declare(strict_types=1);
namespace dW\HTML5;
class Exception extends \Exception {
const INVALID_CODE = 10000;
const UNKNOWN_ERROR = 10001;
const INCORRECT_PARAMETERS_FOR_MESSAGE = 10002;
const UNREACHABLE_CODE = 10003;
const PARSER_NONEMPTY_DOCUMENT = 10101;
const STACK_INVALID_INDEX = 10201;
const STACK_ELEMENT_DOCUMENT_DOCUMENTFRAG_EXPECTED = 10202;
const STACK_ELEMENT_STRING_ARRAY_EXPECTED = 10203;
const STACK_STRING_ARRAY_EXPECTED = 10204;
const STACK_INCORRECTLY_EMPTY = 10205;
const STACK_INVALID_STATE = 10206;
const STACK_NO_CONTEXT_EXISTS = 10207;
const STACK_INVALID_VALUE = 10208;
const STACK_INVALID_OFFSET = 10209;
const STACK_ROOT_ELEMENT_DELETE = 10210;
const DATA_NODATA = 10301;
const DATA_INVALID_DATA_CONSUMPTION_LENGTH = 10302;
const TOKENIZER_INVALID_STATE = 10401;
const TOKENIZER_INVALID_CHARACTER_REFERENCE_STATE = 10402;
const TREEBUILDER_FORMELEMENT_EXPECTED = 10501;
const TREEBUILDER_DOCUMENTFRAG_ELEMENT_DOCUMENT_DOCUMENTFRAG_EXPECTED = 10502;
const TREEBUILDER_UNEXPECTED_END_OF_FILE = 10503;
const TREEBUILDER_NON_EMPTY_TARGET_DOCUMENT = 10504;
const TREEBUILDER_INVALID_TOKEN_CLASS = 10505;
const TREEBUILDER_INVALID_INSERTION_LOCATION = 10506;
const INVALID_CODE = 100;
const UNKNOWN_ERROR = 101;
const INCORRECT_PARAMETERS_FOR_MESSAGE = 102;
const UNREACHABLE_CODE = 103;
const PARSER_NONEMPTY_DOCUMENT = 201;
const STACK_INVALID_INDEX = 301;
const STACK_ELEMENT_DOCUMENT_DOCUMENTFRAG_EXPECTED = 302;
const STACK_ELEMENT_STRING_ARRAY_EXPECTED = 303;
const STACK_STRING_ARRAY_EXPECTED = 304;
const STACK_INCORRECTLY_EMPTY = 305;
const STACK_INVALID_STATE = 306;
const STACK_NO_CONTEXT_EXISTS = 307;
const STACK_INVALID_VALUE = 308;
const STACK_INVALID_OFFSET = 309;
const STACK_ROOT_ELEMENT_DELETE = 310;
const DATA_NODATA = 401;
const DATA_INVALID_DATA_CONSUMPTION_LENGTH = 402;
const TOKENIZER_INVALID_STATE = 501;
const TOKENIZER_INVALID_CHARACTER_REFERENCE_STATE = 502;
const TREEBUILDER_FORMELEMENT_EXPECTED = 601;
const TREEBUILDER_DOCUMENTFRAG_ELEMENT_DOCUMENT_DOCUMENTFRAG_EXPECTED = 602;
const TREEBUILDER_UNEXPECTED_END_OF_FILE = 603;
const TREEBUILDER_NON_EMPTY_TARGET_DOCUMENT = 604;
const TREEBUILDER_INVALID_TOKEN_CLASS = 605;
const TREEBUILDER_INVALID_INSERTION_LOCATION = 606;
protected static $messages = [
10000 => 'Invalid error code',
10001 => 'Unknown error; escaping',
10002 => 'Incorrect number of parameters for Exception message; %s expected',
10003 => 'Unreachable code',
10101 => 'Non-empty Document supplied as argument for Parser',
10201 => 'Invalid Stack index at %s',
10202 => 'Element, Document, or DOMDocumentFragment expected for fragment context',
10203 => 'Element, string, or array expected',
10204 => 'String or array expected',
10205 => 'Stack is incorrectly empty',
10206 => 'Stack is in an invalid state; dump: %s',
10207 => 'No %s context exists in stack',
10208 => 'Stack value is invalid',
10209 => 'Invalid stack offset; offset must be %s',
10210 => 'Root element cannot be deleted from the stack',
10301 => 'Data string expected; found %s',
10302 => '%s is an invalid data consumption length; a value of 1 or above is expected',
10401 => 'The Tokenizer has entered an invalid state: %s',
10402 => 'Invalid character reference consumption state: %s',
10501 => 'Form element expected, found %s',
10502 => 'Element, Document, or DOMDocumentFragment expected; found %s',
10503 => 'Unexpected end of file',
10504 => 'Target document is not empty',
10505 => 'Invalid token class: %s',
10506 => 'Invalid insertion location'
100 => 'Invalid error code',
101 => 'Unknown error; escaping',
102 => 'Incorrect number of parameters for Exception message; %s expected',
103 => 'Unreachable code',
201 => 'Non-empty Document supplied as argument for Parser',
301 => 'Invalid Stack index at %s',
302 => 'Element, Document, or DOMDocumentFragment expected for fragment context',
303 => 'Element, string, or array expected',
304 => 'String or array expected',
305 => 'Stack is incorrectly empty',
306 => 'Stack is in an invalid state; dump: %s',
307 => 'No %s context exists in stack',
308 => 'Stack value is invalid',
309 => 'Invalid stack offset; offset must be %s',
310 => 'Root element cannot be deleted from the stack',
401 => 'Data string expected; found %s',
402 => '%s is an invalid data consumption length; a value of 1 or above is expected',
501 => 'The Tokenizer has entered an invalid state: %s',
502 => 'Invalid character reference consumption state: %s',
601 => 'Form element expected, found %s',
602 => 'Element, Document, or DOMDocumentFragment expected; found %s',
603 => 'Unexpected end of file',
604 => 'Target document is not empty',
605 => 'Invalid token class: %s',
606 => 'Invalid insertion location'
];
public function __construct(int $code, ...$args) {

Loading…
Cancel
Save