Browse Source

Add Walk and Moonwalk generators

split-manual
Dustin Wilson 4 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; namespace dW\HTML5;
class Comment extends \DOMComment { class Comment extends \DOMComment {
use Ancestor; use Moonwalk;
public function __toString(): string { public function __toString(): string {
# Append the literal string "<!--" (U+003C LESS-THAN SIGN, U+0021 EXCLAMATION # 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 { class DOMException extends \Exception {
// From PHP's DOMException; keeping error codes consistent // From PHP's DOMException; keeping error codes consistent
const WRONG_DOCUMENT = 4;
const NO_MODIFICATION_ALLOWED = 7; const NO_MODIFICATION_ALLOWED = 7;
const DOCUMENT_DOCUMENTFRAG_EXPECTED = 100; const DOCUMENT_ELEMENT_DOCUMENTFRAG_EXPECTED = 100;
const STRING_OR_CLOSURE_EXPECTED = 101; const STRING_EXPECTED = 101;
const OUTER_HTML_FAILED_NOPARENT = 102; const OUTER_HTML_FAILED_NOPARENT = 102;
protected static $messages = [ protected static $messages = [
4 => 'Supplied node does not belong to this document',
7 => 'Modification not allowed here', 7 => 'Modification not allowed here',
100 => 'Element, Document, or DOMDocumentFragment expected; found %s', 100 => 'Document, Element, or DocumentFragment expected; found %s',
101 => 'The first argument must either be an instance of \DOMNode, a string, or a closure; 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' 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. // Count the number of replacements needed in the message.
preg_match_all('/(\%(?:\d+\$)?s)/', $message, $matches); 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 the number of replacements don't match the arguments then oops.
if (count($args) !== $count) { if (count($args) !== $count) {

95
lib/DOM/Document.php

@ -3,7 +3,7 @@ declare(strict_types=1);
namespace dW\HTML5; namespace dW\HTML5;
class Document extends \DOMDocument { class Document extends \DOMDocument {
use Descendant, Serialize, EscapeString; use EscapeString, Moonwalk, Serialize, Walk;
// Quirks mode constants // Quirks mode constants
public const NO_QUIRKS_MODE = 0; public const NO_QUIRKS_MODE = 0;
@ -31,23 +31,32 @@ class Document extends \DOMDocument {
$this->registerNodeClass('DOMText', '\dW\HTML5\Text'); $this->registerNodeClass('DOMText', '\dW\HTML5\Text');
} }
public function load($source, $options = null, ?string $encodingOrContentType = null): bool { public function createAttribute($name) {
$data = Parser::fetchFile($source, $encodingOrContentType); try {
if (!$data) { return parent::createAttribute($name);
return false; } 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 { public function createAttributeNS($namespaceURI, $qualifiedName) {
Parser::parse((string)$source, $this, $encodingOrContentType); try {
return true; 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 = "") { public function createElement($name, $value = "") {
try { try {
$e = parent::createElement($name, $value); $e = parent::createElement($name, $value);
@ -84,30 +93,50 @@ class Document extends \DOMDocument {
} }
} }
public function createAttribute($name) { public function load($source, $options = null, ?string $encodingOrContentType = null): bool {
try { $data = Parser::fetchFile($source, $encodingOrContentType);
return parent::createAttribute($name); if (!$data) {
} catch (\DOMException $e) { return false;
// 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 createAttributeNS($namespaceURI, $qualifiedName) { public function loadHTML($source, $options = null, ?string $encodingOrContentType = null): bool {
try { assert(is_string($source), new DOMException(DOMException::STRING_EXPECTED, 'source', gettype($source)));
return parent::createAttributeNS($namespaceURI, $qualifiedName); Parser::parse($source, $this, $encodingOrContentType);
} catch (\DOMException $e) { return true;
// 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 public function save($filename, $options = 0): string {
$this->mangledAttributes = true; return file_put_contents($filename, $this->serialize());
$qualifiedName = $this->coerceName($qualifiedName); }
return parent::createAttributeNS($namespaceURI, $qualifiedName);
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() { public function __toString() {

2
lib/DOM/DocumentFragment.php

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

4
lib/DOM/Element.php

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

2
lib/DOM/ProcessingInstruction.php

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

Loading…
Cancel
Save