Browse Source

Doing some cleanup and appeasement of Intelephense, testing is broken

master
Dustin Wilson 6 months ago
parent
commit
1435ecbab3
  1. 150
      RoboFile.php
  2. 52
      composer.json
  3. 217
      composer.lock
  4. 11
      lib/ArgumentCountError.php
  5. 7
      lib/Attr.php
  6. 1
      lib/CharacterData.php
  7. 4
      lib/ChildNode.php
  8. 44
      lib/DOMException.php
  9. 15
      lib/DOMException/HierarchyRequestError.php
  10. 15
      lib/DOMException/InUseAttributeError.php
  11. 15
      lib/DOMException/InvalidCharacterError.php
  12. 15
      lib/DOMException/NamespaceError.php
  13. 15
      lib/DOMException/NoModificationAllowedError.php
  14. 15
      lib/DOMException/NotFoundError.php
  15. 15
      lib/DOMException/NotSupportedError.php
  16. 15
      lib/DOMException/SyntaxError.php
  17. 15
      lib/DOMException/WrongDocumentError.php
  18. 6
      lib/DOMImplementation.php
  19. 19
      lib/DOMTokenList.php
  20. 39
      lib/Document.php
  21. 4
      lib/DocumentOrElement.php
  22. 1
      lib/DocumentType.php
  23. 49
      lib/Element.php
  24. 73
      lib/Exception.php
  25. 15
      lib/FileNotFoundException.php
  26. 1
      lib/HTMLCollection.php
  27. 24
      lib/HTMLElement.php
  28. 11
      lib/IOException.php
  29. 4
      lib/Inner/Document.php
  30. 9
      lib/NamedNodeMap.php
  31. 90
      lib/Node.php
  32. 4
      lib/NonDocumentTypeChildNode.php
  33. 5
      lib/NonElementParentNode.php
  34. 11
      lib/OutOfBoundsException.php
  35. 9
      lib/ParentNode.php
  36. 1
      lib/ProcessingInstruction.php
  37. 20
      lib/Serializer.php
  38. 17
      lib/Text.php
  39. 15
      lib/UnknownException.php
  40. 2
      lib/XMLDocument.php
  41. 4
      lib/XPathEvaluate.php
  42. 20
      lib/XPathException.php
  43. 14
      robo
  44. 21
      robo.bat

150
RoboFile.php

@ -1,150 +0,0 @@
<?php
/** @license MIT
* Copyright 2017 , Dustin Wilson, J. King et al.
* See LICENSE and AUTHORS files for details */
use Robo\Result;
const BASE = __DIR__.\DIRECTORY_SEPARATOR;
const BASE_TEST = BASE."tests".\DIRECTORY_SEPARATOR;
define("IS_WIN", defined("PHP_WINDOWS_VERSION_MAJOR"));
define("IS_MAC", php_uname("s") === "Darwin");
error_reporting(0);
function norm(string $path): string {
$out = realpath($path);
if (!$out) {
$out = str_replace(["/", "\\"], \DIRECTORY_SEPARATOR, $path);
}
return $out;
}
class RoboFile extends \Robo\Tasks {
/** Runs the typical test suite
*
* Arguments passed to the task are passed on to PHPUnit. Thus one may, for
* example, run the following command and get the expected results:
*
* ./robo test --testsuite Tokenizer --exclude-group slow --testdox
*
* Please see the PHPUnit documentation for available options.
*/
public function test(array $args): Result {
return $this->runTests(escapeshellarg(\PHP_BINARY), "typical", $args);
}
/** Runs the full test suite
*
* This includes pedantic tests which may help to identify problems.
* See help for the "test" task for more details.
*/
public function testFull(array $args): Result {
return $this->runTests(escapeshellarg(\PHP_BINARY), "full", $args);
}
/**
* Runs a quick subset of the test suite
*
* See help for the "test" task for more details.
*/
public function testQuick(array $args): Result {
return $this->runTests(escapeshellarg(\PHP_BINARY), "quick", $args);
}
/** Manually updates the imported html5lib test suite */
public function testUpdate(): Result {
$dir = BASE_TEST."html5lib-tests";
if (is_dir($dir)) {
return $this->taskGitStack()->dir($dir)->pull()->run();
} else {
return $this->taskGitStack()->cloneRepo("https://github.com/html5lib/html5lib-tests", $dir)->run();
}
}
/** Produces a code coverage report
*
* By default this task produces an HTML-format coverage report in
* tests/coverage/. Additional reports may be produced by passing
* arguments to this task as one would to PHPUnit.
*/
public function coverage(array $args): Result {
// run tests with code coverage reporting enabled
$exec = $this->findCoverageEngine();
return $this->runTests($exec, "coverage", array_merge(["--coverage-html", BASE_TEST."coverage"], $args));
}
/** Produces a code coverage report, with redundant tests
*
* Depending on the environment, some tests that normally provide
* coverage may be skipped, while working alternatives are normally
* suppressed for reasons of time. This coverage report will try to
* run all tests which may cover code.
*
* See also help for the "coverage" task for more details.
*/
public function coverageFull(array $args): Result {
// run tests with code coverage reporting enabled
$exec = $this->findCoverageEngine();
return $this->runTests($exec, "typical", array_merge(["--coverage-html", BASE_TEST."coverage"], $args));
}
protected function findCoverageEngine(): string {
$dir = rtrim(ini_get("extension_dir"), "/").\DIRECTORY_SEPARATOR;
$ext = IS_WIN ? "dll" : "so";
$php = escapeshellarg(\PHP_BINARY);
$code = escapeshellarg(BASE."lib");
if (extension_loaded("pcov")) {
return "$php -d opcache.enable_cli=0 -d pcov.enabled=1 -d pcov.directory=$code";
} elseif (extension_loaded("xdebug")) {
return "$php -d opcache.enable_cli=0 -d xdebug.mode=coverage";
} elseif (file_exists($dir."pcov.$ext")) {
return "$php -d opcache.enable_cli=0 -d extension=pcov.$ext -d pcov.enabled=1 -d pcov.directory=$code";
} elseif (file_exists($dir."xdebug.$ext")) {
return "$php -d opcache.enable_cli=0 -d zend_extension=xdebug.$ext -d xdebug.mode=coverage";
} else {
if (IS_WIN) {
$dbg = dirname(\PHP_BINARY)."\\phpdbg.exe";
$dbg = file_exists($dbg) ? $dbg : "";
} else {
$dbg = trim(`which phpdbg 2>/dev/null`);
}
if ($dbg) {
return escapeshellarg($dbg)." -qrr";
} else {
return $php;
}
}
}
protected function blackhole(bool $all = false): string {
$hole = IS_WIN ? "nul" : "/dev/null";
return $all ? ">$hole 2>&1" : "2>$hole";
}
protected function runTests(string $executor, string $set, array $args) : Result {
switch ($set) {
case "typical":
$set = ["--exclude-group", "optional"];
break;
case "quick":
$set = ["--exclude-group", "optional,slow"];
break;
case "coverage":
$set = ["--exclude-group", "optional,coverageOptional"];
break;
case "full":
$set = [];
break;
default:
throw new \Exception;
}
$execpath = norm(BASE."vendor-bin/phpunit/vendor/phpunit/phpunit/phpunit");
$confpath = realpath(BASE_TEST."phpunit.dist.xml") ?: norm(BASE_TEST."phpunit.xml");
// clone the html5lib test suite if it's not already present
if (!is_dir(BASE_TEST."html5lib-tests")) {
$this->testUpdate();
}
return $this->taskExec($executor)->option("-d", "zend.assertions=1")->arg($execpath)->option("-c", $confpath)->args(array_merge($set, $args))->run();
}
}

52
composer.json

@ -1,22 +1,14 @@
{ {
"name": "mensbeam/html-dom", "name": "mensbeam/html-dom",
"description": "Modern DOM library written in PHP for HTML documents", "autoload": {
"type": "library", "psr-4": {
"require": { "MensBeam\\HTML\\DOM\\": [
"php": ">=8.0.2", "lib/",
"ext-dom": "*", "lib/DOMException",
"mensbeam/html-parser": ">=1.2.1", "lib/Exception",
"symfony/css-selector": ">=5.3", "lib/HTMLElement"
"mensbeam/getters-and-setters": ">=1.1" ]
}, }
"require-dev": {
"bamarni/composer-bin-plugin": "^1.3",
"mikey179/vfsstream": "^1.6",
"nikic/php-parser": "^4.13"
},
"scripts": {
"post-install-cmd": ["@composer bin all install"],
"post-update-cmd": ["@composer bin all update"]
}, },
"license": "MIT", "license": "MIT",
"authors": [ "authors": [
@ -31,27 +23,9 @@
"homepage": "https://jkingweb.ca/" "homepage": "https://jkingweb.ca/"
} }
], ],
"autoload": { "require": {
"psr-4": { "mensbeam/html-parser": "^1.3",
"MensBeam\\HTML\\DOM\\": [ "symfony/css-selector": "^6.3",
"lib/", "mensbeam/getters-and-setters": "^1.1"
"lib/HTMLElement"
]
}
},
"autoload-dev": {
"psr-4": {
"MensBeam\\HTML\\DOM\\Test\\": "tests/lib/",
"MensBeam\\HTML\\DOM\\TestCase\\": [
"tests/cases/",
"tests/cases/Document",
"tests/cases/Serializer"
]
}
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
} }
} }

217
composer.lock

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "dc49ff7a1b170e6b1f368a4f5145543e", "content-hash": "b4a731d7d5cb3bb8b0520670bb74f4c5",
"packages": [ "packages": [
{ {
"name": "mensbeam/getters-and-setters", "name": "mensbeam/getters-and-setters",
@ -51,16 +51,16 @@
}, },
{ {
"name": "mensbeam/html-parser", "name": "mensbeam/html-parser",
"version": "1.2.6", "version": "1.3.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/mensbeam/HTML-Parser.git", "url": "https://github.com/mensbeam/HTML-Parser.git",
"reference": "fa0591bd9ce241631894bda290cb99f30b7c288d" "reference": "2b8a31ce472190013faab710f6f7ad41a486a740"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/mensbeam/HTML-Parser/zipball/fa0591bd9ce241631894bda290cb99f30b7c288d", "url": "https://api.github.com/repos/mensbeam/HTML-Parser/zipball/2b8a31ce472190013faab710f6f7ad41a486a740",
"reference": "fa0591bd9ce241631894bda290cb99f30b7c288d", "reference": "2b8a31ce472190013faab710f6f7ad41a486a740",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -115,10 +115,9 @@
"parsing" "parsing"
], ],
"support": { "support": {
"issues": "https://github.com/mensbeam/HTML-Parser/issues", "source": "https://github.com/mensbeam/HTML-Parser/tree/1.3.1"
"source": "https://github.com/mensbeam/HTML-Parser/tree/1.2.6"
}, },
"time": "2023-01-25T22:49:58+00:00" "time": "2023-04-20T19:55:47+00:00"
}, },
{ {
"name": "mensbeam/intl", "name": "mensbeam/intl",
@ -228,25 +227,25 @@
}, },
{ {
"name": "psr/http-message", "name": "psr/http-message",
"version": "1.0.1", "version": "1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/http-message.git", "url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.0" "php": "^7.2 || ^8.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.0.x-dev" "dev-master": "1.1.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -275,22 +274,22 @@
"response" "response"
], ],
"support": { "support": {
"source": "https://github.com/php-fig/http-message/tree/master" "source": "https://github.com/php-fig/http-message/tree/1.1"
}, },
"time": "2016-08-06T14:39:51+00:00" "time": "2023-04-04T09:50:52+00:00"
}, },
{ {
"name": "symfony/css-selector", "name": "symfony/css-selector",
"version": "v6.2.5", "version": "v6.3.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/css-selector.git", "url": "https://github.com/symfony/css-selector.git",
"reference": "bf1b9d4ad8b1cf0dbde8b08e0135a2f6259b9ba1" "reference": "883d961421ab1709877c10ac99451632a3d6fa57"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/bf1b9d4ad8b1cf0dbde8b08e0135a2f6259b9ba1", "url": "https://api.github.com/repos/symfony/css-selector/zipball/883d961421ab1709877c10ac99451632a3d6fa57",
"reference": "bf1b9d4ad8b1cf0dbde8b08e0135a2f6259b9ba1", "reference": "883d961421ab1709877c10ac99451632a3d6fa57",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -326,7 +325,7 @@
"description": "Converts CSS selectors to XPath expressions", "description": "Converts CSS selectors to XPath expressions",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/css-selector/tree/v6.2.5" "source": "https://github.com/symfony/css-selector/tree/v6.3.2"
}, },
"funding": [ "funding": [
{ {
@ -342,184 +341,16 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-01-01T08:38:09+00:00" "time": "2023-07-12T16:00:22+00:00"
}
],
"packages-dev": [
{
"name": "bamarni/composer-bin-plugin",
"version": "1.8.2",
"source": {
"type": "git",
"url": "https://github.com/bamarni/composer-bin-plugin.git",
"reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880",
"reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880",
"shasum": ""
},
"require": {
"composer-plugin-api": "^2.0",
"php": "^7.2.5 || ^8.0"
},
"require-dev": {
"composer/composer": "^2.0",
"ext-json": "*",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-phpunit": "^1.1",
"phpunit/phpunit": "^8.5 || ^9.5",
"symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0",
"symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0",
"symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0"
},
"type": "composer-plugin",
"extra": {
"class": "Bamarni\\Composer\\Bin\\BamarniBinPlugin"
},
"autoload": {
"psr-4": {
"Bamarni\\Composer\\Bin\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "No conflicts for your bin dependencies",
"keywords": [
"composer",
"conflict",
"dependency",
"executable",
"isolation",
"tool"
],
"support": {
"issues": "https://github.com/bamarni/composer-bin-plugin/issues",
"source": "https://github.com/bamarni/composer-bin-plugin/tree/1.8.2"
},
"time": "2022-10-31T08:38:03+00:00"
},
{
"name": "mikey179/vfsstream",
"version": "v1.6.11",
"source": {
"type": "git",
"url": "https://github.com/bovigo/vfsStream.git",
"reference": "17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bovigo/vfsStream/zipball/17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f",
"reference": "17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.5|^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.6.x-dev"
}
},
"autoload": {
"psr-0": {
"org\\bovigo\\vfs\\": "src/main/php"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Frank Kleine",
"homepage": "http://frankkleine.de/",
"role": "Developer"
}
],
"description": "Virtual file system to mock the real file system in unit tests.",
"homepage": "http://vfs.bovigo.org/",
"support": {
"issues": "https://github.com/bovigo/vfsStream/issues",
"source": "https://github.com/bovigo/vfsStream/tree/master",
"wiki": "https://github.com/bovigo/vfsStream/wiki"
},
"time": "2022-02-23T02:02:42+00:00"
},
{
"name": "nikic/php-parser",
"version": "v4.15.3",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039",
"reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": ">=7.0"
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
},
"bin": [
"bin/php-parse"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.9-dev"
}
},
"autoload": {
"psr-4": {
"PhpParser\\": "lib/PhpParser"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Nikita Popov"
}
],
"description": "A PHP parser written in PHP",
"keywords": [
"parser",
"php"
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3"
},
"time": "2023-01-16T22:05:37+00:00"
} }
], ],
"packages-dev": [],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": [], "stability-flags": [],
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": [],
"php": ">=8.0.2",
"ext-dom": "*"
},
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.3.0" "plugin-api-version": "2.6.0"
} }

11
lib/ArgumentCountError.php

@ -0,0 +1,11 @@
<?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;
class ArgumentCountError extends \ArgumentCountError {}

7
lib/Attr.php

@ -9,7 +9,10 @@ declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
/** @property \DOMAttr $_innerNode */
class Attr extends Node { class Attr extends Node {
protected \DOMAttr $_innerNode;
protected function __get_localName(): string { protected function __get_localName(): string {
// PHP's DOM does this correctly already. // PHP's DOM does this correctly already.
// Need to uncoerce string if necessary. // Need to uncoerce string if necessary.
@ -37,7 +40,9 @@ class Attr extends Node {
return null; return null;
} }
return $this->_innerNode->ownerDocument->getWrapperNode($this->_innerNode->ownerElement); /** @var MensBeam\HTML\DOM\Inner\Document $innerOwnerDocument */
$innerOwnerDocument = $this->_innerNode->ownerDocument;
return $innerOwnerDocument->getWrapperNode($this->_innerNode->ownerElement);
} }
protected function __get_prefix(): string { protected function __get_prefix(): string {

1
lib/CharacterData.php

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
/** @property \DOMCharacterData $_innerNode */
abstract class CharacterData extends Node { abstract class CharacterData extends Node {
use ChildNode, NonDocumentTypeChildNode; use ChildNode, NonDocumentTypeChildNode;

4
lib/ChildNode.php

@ -7,10 +7,6 @@
declare(strict_types=1); declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\Inner\{
Document as InnerDocument,
Reflection
};
trait ChildNode { trait ChildNode {

44
lib/DOMException.php

@ -8,46 +8,4 @@
declare(strict_types=1); declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
abstract class DOMException extends \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;
public const INVALID_CHARACTER = 5;
public const NO_MODIFICATION_ALLOWED = 7;
public const NOT_FOUND = 8;
public const NOT_SUPPORTED = 9;
public const IN_USE_ATTRIBUTE = 10;
public const SYNTAX_ERROR = 12;
public const INVALID_MODIFICATION = 13;
public const NAMESPACE_ERROR = 14;
public const INVALID_ACCESS = 15;
public const FILE_NOT_FOUND = 301;
public function __construct(int $code, ...$args) {
self::$messages = array_replace(parent::$messages, [
1 => 'Invalid index size',
3 => 'Hierarchy request error',
4 => 'Supplied node does not belong to this document',
5 => 'Invalid character',
7 => 'Modification not allowed here',
8 => 'Not found error',
9 => 'Feature is not supported',
10 => 'The attribute is in use',
12 => 'Syntax error',
13 => 'Invalid modification error',
14 => 'Namespace error',
15 => 'Invalid access error',
301 => 'File not found'
]);
parent::__construct($code, ...$args);
}
}

15
lib/DOMException/HierarchyRequestError.php

@ -0,0 +1,15 @@
<?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;
class HierarchyRequestError extends DOMException {
public function __construct(?\Throwable $previous = null) {
parent::__construct('The requested operation would yield an incorrect node tree', 3, $previous);
}
}

15
lib/DOMException/InUseAttributeError.php

@ -0,0 +1,15 @@
<?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;
class InUseAttributeError extends DOMException {
public function __construct(?\Throwable $previous = null) {
parent::__construct('The attribute is in use by another element', 10, $previous);
}
}

15
lib/DOMException/InvalidCharacterError.php

@ -0,0 +1,15 @@
<?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;
class InvalidCharacterError extends DOMException {
public function __construct(?\Throwable $previous = null) {
parent::__construct('The string contains invalid characters', 5, $previous);
}
}

15
lib/DOMException/NamespaceError.php

@ -0,0 +1,15 @@
<?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;
class NamespaceError extends DOMException {
public function __construct(?\Throwable $previous = null) {
parent::__construct('The operation is not allowed by namespaces', 14, $previous);
}
}

15
lib/DOMException/NoModificationAllowedError.php

@ -0,0 +1,15 @@
<?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;
class NoModificationAllowedError extends DOMException {
public function __construct(?\Throwable $previous = null) {
parent::__construct('The object can not be modified', 7, $previous);
}
}

15
lib/DOMException/NotFoundError.php

@ -0,0 +1,15 @@
<?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;
class NotFoundError extends DOMException {
public function __construct(?\Throwable $previous = null) {
parent::__construct('The object can not be found here', 8, $previous);
}
}

15
lib/DOMException/NotSupportedError.php

@ -0,0 +1,15 @@
<?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;
class NotSupportedError extends DOMException {
public function __construct(?\Throwable $previous = null) {
parent::__construct('The operation is not supported', 9, $previous);
}
}

15
lib/DOMException/SyntaxError.php

@ -0,0 +1,15 @@
<?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;
class SyntaxError extends DOMException {
public function __construct(?\Throwable $previous = null) {
parent::__construct('The string did not match the expected pattern', 12, $previous);
}
}

15
lib/DOMException/WrongDocumentError.php

@ -0,0 +1,15 @@
<?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;
class WrongDocumentError extends DOMException {
public function __construct(?\Throwable $previous = null) {
parent::__construct('The object is in the wrong document', 4, $previous);
}
}

6
lib/DOMImplementation.php

@ -82,7 +82,7 @@ class DOMImplementation {
$contentType = 'application/xml'; $contentType = 'application/xml';
} }
Reflection::setProtectedProperties($document, ['_contentType' => $contentType ]); Reflection::setProtectedProperties($document, [ '_contentType' => $contentType ]);
# 8. Return document. # 8. Return document.
return $document; return $document;
@ -95,7 +95,7 @@ class DOMImplementation {
# To validate a qualifiedName, throw an "InvalidCharacterError" DOMException if # To validate a qualifiedName, throw an "InvalidCharacterError" DOMException if
# qualifiedName does not match the QName production. # qualifiedName does not match the QName production.
if (!preg_match(InnerDocument::QNAME_PRODUCTION_REGEX, $qualifiedName)) { if (!preg_match(InnerDocument::QNAME_PRODUCTION_REGEX, $qualifiedName)) {
throw new DOMException(DOMException::INVALID_CHARACTER); throw new InvalidCharacterError();
} }
# 2. Return a new doctype, with qualifiedName as its name, publicId as its # 2. Return a new doctype, with qualifiedName as its name, publicId as its
@ -145,7 +145,7 @@ class DOMImplementation {
$documentElement->appendChild($doc->createElement('body')); $documentElement->appendChild($doc->createElement('body'));
# 8. doc’s origin is this’s associated document’s origin. # 8. doc’s origin is this’s associated document’s origin.
// Not necessary. No scripting in this implementation. // DEVIATION: There is no scripting in this implementation.
# 9. Return doc. # 9. Return doc.
return $doc; return $doc;

19
lib/DOMTokenList.php

@ -8,8 +8,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
use MensBeam\HTML\Parser\Data, use MensBeam\HTML\Parser\Data,
MensBeam\GettersAndSetters, MensBeam\GettersAndSetters;
MensBeam\HTML\DOM\Inner\Reflection;
class DOMTokenList implements \ArrayAccess, \Countable, \Iterator { class DOMTokenList implements \ArrayAccess, \Countable, \Iterator {
@ -95,13 +94,13 @@ class DOMTokenList implements \ArrayAccess, \Countable, \Iterator {
foreach ($tokens as $token) { foreach ($tokens as $token) {
# 1. If token is the empty string, then throw a "SyntaxError" DOMException. # 1. If token is the empty string, then throw a "SyntaxError" DOMException.
if ($token === '') { if ($token === '') {
throw new DOMException(DOMException::SYNTAX_ERROR); throw new SyntaxError();
} }
# 2. If token contains any ASCII whitespace, then throw an # 2. If token contains any ASCII whitespace, then throw an
# "InvalidCharacterError" DOMException. # "InvalidCharacterError" DOMException.
if (preg_match(Data::WHITESPACE_REGEX, $token)) { if (preg_match(Data::WHITESPACE_REGEX, $token)) {
throw new DOMException(DOMException::INVALID_CHARACTER); throw new InvalidCharacterError();
} }
} }
@ -176,13 +175,13 @@ class DOMTokenList implements \ArrayAccess, \Countable, \Iterator {
foreach ($tokens as $token) { foreach ($tokens as $token) {
# 1. If token is the empty string, then throw a "SyntaxError" DOMException. # 1. If token is the empty string, then throw a "SyntaxError" DOMException.
if ($token === '') { if ($token === '') {
throw new DOMException(DOMException::SYNTAX_ERROR); throw new SyntaxError();
} }
# 2. If token contains any ASCII whitespace, then throw an # 2. If token contains any ASCII whitespace, then throw an
# "InvalidCharacterError" DOMException. # "InvalidCharacterError" DOMException.
if (preg_match(Data::WHITESPACE_REGEX, $token)) { if (preg_match(Data::WHITESPACE_REGEX, $token)) {
throw new DOMException(DOMException::INVALID_CHARACTER); throw new InvalidCharacterError();
} }
} }
@ -210,13 +209,13 @@ class DOMTokenList implements \ArrayAccess, \Countable, \Iterator {
# 1. If either token or newToken is the empty string, then throw a "SyntaxError" # 1. If either token or newToken is the empty string, then throw a "SyntaxError"
# DOMException. # DOMException.
if ($token === '' || $newToken === '') { if ($token === '' || $newToken === '') {
throw new DOMException(DOMException::SYNTAX_ERROR); throw new SyntaxError();
} }
# 2. If either token or newToken contains any ASCII whitespace, then throw an # 2. If either token or newToken contains any ASCII whitespace, then throw an
# "InvalidCharacterError" DOMException. # "InvalidCharacterError" DOMException.
if (preg_match(Data::WHITESPACE_REGEX, $token) || preg_match(Data::WHITESPACE_REGEX, $newToken)) { if (preg_match(Data::WHITESPACE_REGEX, $token) || preg_match(Data::WHITESPACE_REGEX, $newToken)) {
throw new DOMException(DOMException::INVALID_CHARACTER); throw new InvalidCharacterError();
} }
# 3. If this’s token set does not contain token, then return false. # 3. If this’s token set does not contain token, then return false.
@ -268,13 +267,13 @@ class DOMTokenList implements \ArrayAccess, \Countable, \Iterator {
public function toggle(string $token, ?bool $force = null): bool { public function toggle(string $token, ?bool $force = null): bool {
# 1. If token is the empty string, then throw a "SyntaxError" DOMException. # 1. If token is the empty string, then throw a "SyntaxError" DOMException.
if ($token === '') { if ($token === '') {
throw new DOMException(DOMException::SYNTAX_ERROR); throw new SyntaxError();
} }
# 2. If token contains any ASCII whitespace, then throw an # 2. If token contains any ASCII whitespace, then throw an
# "InvalidCharacterError" DOMException. # "InvalidCharacterError" DOMException.
if (preg_match(Data::WHITESPACE_REGEX, $token)) { if (preg_match(Data::WHITESPACE_REGEX, $token)) {
throw new DOMException(DOMException::INVALID_CHARACTER); throw new InvalidCharacterError();
} }
# 3. If this’s token set[token] exists, then: # 3. If this’s token set[token] exists, then:

39
lib/Document.php

@ -20,6 +20,7 @@ use MensBeam\HTML\Parser\{
}; };
/** @property InnerDocument $_innerNode */
class Document extends Node implements \ArrayAccess { class Document extends Node implements \ArrayAccess {
use DocumentOrElement, NonElementParentNode, ParentNode, XPathEvaluatorBase; use DocumentOrElement, NonElementParentNode, ParentNode, XPathEvaluatorBase;
@ -45,7 +46,7 @@ class Document extends Node implements \ArrayAccess {
if ($n !== null) { if ($n !== null) {
do { do {
if ($n instanceof \DOMElement && $n->namespaceURI === null && ($n->nodeName === 'body' || $n->nodeName === 'frameset')) { if ($n instanceof \DOMElement && $n->namespaceURI === null && ($n->nodeName === 'body' || $n->nodeName === 'frameset')) {
return $n->ownerDocument->getWrapperNode($n); return $this->_innerNode->getWrapperNode($n);
} }
} while ($n = $n->nextSibling); } while ($n = $n->nextSibling);
} }
@ -328,19 +329,19 @@ class Document extends Node implements \ArrayAccess {
$element = $title; $element = $title;
} }
# 3. Otherwise: # 3. Otherwise:
else { elseif ($head !== null) {
# 1. Let element be the result of creating an element given the document # 1. Let element be the result of creating an element given the document
# element's node document, title, and the HTML namespace. # element's node document, title, and the HTML namespace.
$element = $this->_innerNode->createElementNS(Node::SVG_NAMESPACE, 'title'); $element = $this->_innerNode->createElementNS(Node::SVG_NAMESPACE, 'title');
# 2. Append element to the head element. # 2. Append element to the head element.
$head->appendChild($element); $head->appendChild($element);
}
# 4. String replace all with the given value within element. # 4. String replace all with the given value within element.
// This is basically what textContent will do for us... // This is basically what textContent will do for us...
if ($element !== null) { if ($element !== null) {
$element->textContent = $value; $element->textContent = $value;
}
} }
} }
@ -380,7 +381,7 @@ class Document extends Node implements \ArrayAccess {
# #
# 1. If node is a document, then throw a "NotSupportedError" DOMException. # 1. If node is a document, then throw a "NotSupportedError" DOMException.
if ($node instanceof Document) { if ($node instanceof Document) {
throw new DOMException(DOMException::NOT_SUPPORTED); throw new NotSupportedError();
} }
# 2. If node is a shadow root, then throw a "HierarchyRequestError" DOMException. # 2. If node is a shadow root, then throw a "HierarchyRequestError" DOMException.
@ -390,7 +391,7 @@ class Document extends Node implements \ArrayAccess {
// DEVIATION: One can't just return here? // DEVIATION: One can't just return here?
if ($node instanceof DocumentFragment) { if ($node instanceof DocumentFragment) {
$host = Reflection::getProtectedProperty($node, 'host'); $host = Reflection::getProtectedProperty($node, 'host');
if ($host !== null || $host->get() !== null) { if ($host instanceof \WeakReference && $host->get() !== null || $host !== null) {
return $node; return $node;
} }
} }
@ -419,7 +420,7 @@ class Document extends Node implements \ArrayAccess {
# 1. If localName does not match the Name production in XML, then throw an # 1. If localName does not match the Name production in XML, then throw an
# "InvalidCharacterError" DOMException. # "InvalidCharacterError" DOMException.
if (preg_match(InnerDocument::NAME_PRODUCTION_REGEX, $localName) !== 1) { if (preg_match(InnerDocument::NAME_PRODUCTION_REGEX, $localName) !== 1) {
throw new DOMException(DOMException::INVALID_CHARACTER); throw new InvalidCharacterError();
} }
# 2. If this is an HTML document, then set localName to localName in ASCII # 2. If this is an HTML document, then set localName to localName in ASCII
@ -509,13 +510,13 @@ class Document extends Node implements \ArrayAccess {
# #
# 1. If this is an HTML document, then throw a "NotSupportedError" DOMException. # 1. If this is an HTML document, then throw a "NotSupportedError" DOMException.
if (!$this instanceof XMLDocument) { if (!$this instanceof XMLDocument) {
throw new DOMException(DOMException::NOT_SUPPORTED); throw new NotSupportedError();
} }
# 2. If data contains the string "]]>", then throw an "InvalidCharacterError" # 2. If data contains the string "]]>", then throw an "InvalidCharacterError"
# DOMException. # DOMException.
if (str_contains(needle: ']]>', haystack: $data)) { if (str_contains(needle: ']]>', haystack: $data)) {
throw new DOMException(DOMException::INVALID_CHARACTER); throw new InvalidCharacterError();
} }
# 3. Return a new CDATASection node with its data set to data and node document # 3. Return a new CDATASection node with its data set to data and node document
@ -539,12 +540,12 @@ class Document extends Node implements \ArrayAccess {
# 1. If localName does not match the Name production, then throw an # 1. If localName does not match the Name production, then throw an
# "InvalidCharacterError" DOMException. # "InvalidCharacterError" DOMException.
if (!preg_match(InnerDocument::NAME_PRODUCTION_REGEX, $localName)) { if (!preg_match(InnerDocument::NAME_PRODUCTION_REGEX, $localName)) {
throw new DOMException(DOMException::INVALID_CHARACTER); throw new InvalidCharacterError();
} }
# 2. If this is an HTML document, then set localName to localName in ASCII # 2. If this is an HTML document, then set localName to localName in ASCII
# lowercase. # lowercase.
if (!$this instanceof XMLElement) { if (!$this instanceof XMLDocument) {
$localName = strtolower($localName); $localName = strtolower($localName);
} }
@ -642,7 +643,7 @@ class Document extends Node implements \ArrayAccess {
# DOMException. # DOMException.
// Because this can import from PHP's DOM we must check for more stuff. // Because this can import from PHP's DOM we must check for more stuff.
if ($node instanceof Document || $node instanceof \DOMDocument || ($node instanceof \DOMNode && $node->ownerDocument::class !== 'DOMDocument') || $node instanceof \DOMEntityReference) { if ($node instanceof Document || $node instanceof \DOMDocument || ($node instanceof \DOMNode && $node->ownerDocument::class !== 'DOMDocument') || $node instanceof \DOMEntityReference) {
throw new DOMException(DOMException::NOT_SUPPORTED); throw new NotSupportedError();
} }
# 2. Return a clone of node, with this and the clone children flag set if deep # 2. Return a clone of node, with this and the clone children flag set if deep
@ -652,7 +653,7 @@ class Document extends Node implements \ArrayAccess {
public function load(string $source = null, ?string $charset = null): void { public function load(string $source = null, ?string $charset = null): void {
if ($this->hasChildNodes()) { if ($this->hasChildNodes()) {
throw new DOMException(DOMException::NO_MODIFICATION_ALLOWED); throw new NoModificationAllowedError();
} }
$config = new ParserConfig(); $config = new ParserConfig();
@ -675,7 +676,7 @@ class Document extends Node implements \ArrayAccess {
public function loadFile(string $filename, ?string $charset = null): void { public function loadFile(string $filename, ?string $charset = null): void {
$f = @fopen($filename, 'r'); $f = @fopen($filename, 'r');
if (!$f) { if (!$f) {
throw new DOMException(DOMException::FILE_NOT_FOUND); throw new FileNotFoundException();
} }
$data = stream_get_contents($f); $data = stream_get_contents($f);
@ -810,7 +811,7 @@ class Document extends Node implements \ArrayAccess {
public function serialize(?Node $node = null, array $config = []): string { public function serialize(?Node $node = null, array $config = []): string {
$node = $node ?? $this; $node = $node ?? $this;
if ($node !== $this && $node->ownerDocument !== $this) { if ($node !== $this && $node->ownerDocument !== $this) {
throw new DOMException(DOMException::WRONG_DOCUMENT); throw new WrongDocumentError();
} }
return Serializer::serialize($node->innerNode, $config); return Serializer::serialize($node->innerNode, $config);
@ -819,7 +820,7 @@ class Document extends Node implements \ArrayAccess {
public function serializeInner(?Node $node = null, array $config = []): string { public function serializeInner(?Node $node = null, array $config = []): string {
$node = $node ?? $this; $node = $node ?? $this;
if ($node !== $this && $node->ownerDocument !== $this) { if ($node !== $this && $node->ownerDocument !== $this) {
throw new DOMException(DOMException::WRONG_DOCUMENT); throw new WrongDocumentError();
} }
return Serializer::serializeInner($node->innerNode, $config); return Serializer::serializeInner($node->innerNode, $config);

4
lib/DocumentOrElement.php

@ -160,7 +160,7 @@ trait DocumentOrElement {
# To validate a qualifiedName, throw an "InvalidCharacterError" DOMException if # To validate a qualifiedName, throw an "InvalidCharacterError" DOMException if
# qualifiedName does not match the QName production. # qualifiedName does not match the QName production.
if (preg_match(InnerDocument::QNAME_PRODUCTION_REGEX, $qualifiedName) !== 1) { if (preg_match(InnerDocument::QNAME_PRODUCTION_REGEX, $qualifiedName) !== 1) {
throw new DOMException(DOMException::INVALID_CHARACTER); throw new InvalidCharacterError();
} }
# 3. Let prefix be null. # 3. Let prefix be null.
@ -190,7 +190,7 @@ trait DocumentOrElement {
(($qualifiedName === 'xmlns' || $prefix === 'xmlns') && $namespace !== self::XMLNS_NAMESPACE) || (($qualifiedName === 'xmlns' || $prefix === 'xmlns') && $namespace !== self::XMLNS_NAMESPACE) ||
($namespace === self::XMLNS_NAMESPACE && $qualifiedName !== 'xmlns' && $prefix !== 'xmlns') ($namespace === self::XMLNS_NAMESPACE && $qualifiedName !== 'xmlns' && $prefix !== 'xmlns')
) { ) {
throw new DOMException(DOMException::NAMESPACE_ERROR); throw new NamespaceError();
} }
# 10. Return namespace, prefix, and localName. # 10. Return namespace, prefix, and localName.

1
lib/DocumentType.php

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
/** @property \DOMDocumentType $_innerNode */
class DocumentType extends Node { class DocumentType extends Node {
use ChildNode; use ChildNode;

49
lib/Element.php

@ -16,6 +16,7 @@ use MensBeam\HTML\Parser,
Symfony\Component\CssSelector\Exception\SyntaxErrorException as SymfonySyntaxErrorException; Symfony\Component\CssSelector\Exception\SyntaxErrorException as SymfonySyntaxErrorException;
/** @property \DOMElement $_innerNode */
class Element extends Node { class Element extends Node {
use ChildNode, DocumentOrElement, NonDocumentTypeChildNode, ParentNode; use ChildNode, DocumentOrElement, NonDocumentTypeChildNode, ParentNode;
@ -76,11 +77,13 @@ class Element extends Node {
// object. // object.
$context = $this; $context = $this;
$innerContext = $this->_innerNode; $innerContext = $this->_innerNode;
/** @var InnerDocument */
$innerOwnerDocument = $innerContext->ownerDocument;
# 2. Let fragment be the result of invoking the fragment parsing algorithm with # 2. Let fragment be the result of invoking the fragment parsing algorithm with
# the new value as markup, and with context element. # the new value as markup, and with context element.
$innerFragment = Parser::parseFragment($innerContext, Parser::NO_QUIRKS_MODE, $value, 'UTF-8'); $innerFragment = Parser::parseFragment($innerContext, Parser::NO_QUIRKS_MODE, $value, 'UTF-8');
$fragment = $innerContext->ownerDocument->getWrapperNode($innerFragment); $fragment = $innerOwnerDocument->getWrapperNode($innerFragment);
$this->postParsingTemplatesFix($innerFragment); $this->postParsingTemplatesFix($innerFragment);
# 3. If the context object is a template element, then let context object be the # 3. If the context object is a template element, then let context object be the
@ -136,6 +139,8 @@ class Element extends Node {
# 1. Let parent be the context object's parent. # 1. Let parent be the context object's parent.
$parent = $this->parentNode; $parent = $this->parentNode;
$innerParent = $this->_innerNode->parentNode; $innerParent = $this->_innerNode->parentNode;
/** @var InnerDocument */
$innerOwnerDocument = $this->_innerNode->ownerDocument;
# 2. If parent is null, terminate these steps. There would be no way to obtain a # 2. If parent is null, terminate these steps. There would be no way to obtain a
# reference to the nodes created even if the remaining steps were run. # reference to the nodes created even if the remaining steps were run.
@ -145,7 +150,7 @@ class Element extends Node {
# 3. If parent is a Document, throw a "NoModificationAllowedError" DOMException. # 3. If parent is a Document, throw a "NoModificationAllowedError" DOMException.
if ($innerParent instanceof \DOMDocument) { if ($innerParent instanceof \DOMDocument) {
throw new DOMException(DOMException::NO_MODIFICATION_ALLOWED); throw new NoModificationAllowedError();
} }
# 4. If parent is a DocumentFragment, let parent be a new Element with: # 4. If parent is a DocumentFragment, let parent be a new Element with:
@ -153,13 +158,13 @@ class Element extends Node {
# • The HTML namespace as its namespace, and # • The HTML namespace as its namespace, and
# • The context object's node document as its node document. # • The context object's node document as its node document.
if ($innerParent instanceof \DOMDocumentFragment) { if ($innerParent instanceof \DOMDocumentFragment) {
$innerParent = $this->_innerNode->ownerDocument->createElement('body'); $innerParent = $innerOwnerDocument->createElement('body');
} }
# 5. Let fragment be the result of invoking the fragment parsing algorithm with # 5. Let fragment be the result of invoking the fragment parsing algorithm with
# the new value as markup, and parent as the context element. # the new value as markup, and parent as the context element.
$innerFragment = Parser::parseFragment($innerParent, Parser::NO_QUIRKS_MODE, $value, 'UTF-8'); $innerFragment = Parser::parseFragment($innerParent, Parser::NO_QUIRKS_MODE, $value, 'UTF-8');
$fragment = $this->_innerNode->ownerDocument->getWrapperNode($innerFragment); $fragment = $innerOwnerDocument->getWrapperNode($innerFragment);
$this->postParsingTemplatesFix($innerFragment); $this->postParsingTemplatesFix($innerFragment);
# 6. Replace the context object with fragment within the context object's # 6. Replace the context object with fragment within the context object's
@ -192,7 +197,7 @@ class Element extends Node {
} catch (\Exception $e) { } catch (\Exception $e) {
# 2. If s is failure, throw a "SyntaxError" DOMException. # 2. If s is failure, throw a "SyntaxError" DOMException.
if ($e instanceof SymfonySyntaxErrorException) { if ($e instanceof SymfonySyntaxErrorException) {
throw new DOMException(DOMException::SYNTAX_ERROR); throw new SyntaxError();
} }
} }
@ -276,6 +281,8 @@ class Element extends Node {
} }
$qualifiedName = $this->coerceName($qualifiedName); $qualifiedName = $this->coerceName($qualifiedName);
/** @var InnerDocument */
$innerOwnerDocument = $this->_innerNode->ownerDocument;
# 2. Return the first attribute in element’s attribute list whose qualified name is # 2. Return the first attribute in element’s attribute list whose qualified name is
# qualifiedName; otherwise null. # qualifiedName; otherwise null.
@ -285,7 +292,7 @@ class Element extends Node {
$attributes = $this->_innerNode->attributes; $attributes = $this->_innerNode->attributes;
foreach ($attributes as $attr) { foreach ($attributes as $attr) {
if ($attr->nodeName === $qualifiedName) { if ($attr->nodeName === $qualifiedName) {
return $this->_innerNode->ownerDocument->getWrapperNode($attr); return $innerOwnerDocument->getWrapperNode($attr);
} }
} }
@ -307,6 +314,8 @@ class Element extends Node {
} }
$localName = $this->coerceName($localName); $localName = $this->coerceName($localName);
/** @var InnerDocument */
$innerOwnerDocument = $this->_innerNode->ownerDocument;
# 2. Return the attribute in element’s attribute list whose namespace is namespace # 2. Return the attribute in element’s attribute list whose namespace is namespace
# and local name is localName, if any; otherwise null. # and local name is localName, if any; otherwise null.
@ -316,7 +325,7 @@ class Element extends Node {
$attributes = $this->_innerNode->attributes; $attributes = $this->_innerNode->attributes;
foreach ($attributes as $attr) { foreach ($attributes as $attr) {
if ($attr->namespaceURI === $namespace && $attr->localName === $localName) { if ($attr->namespaceURI === $namespace && $attr->localName === $localName) {
return $this->_innerNode->ownerDocument->getWrapperNode($attr); return $innerOwnerDocument->getWrapperNode($attr);
} }
} }
@ -419,7 +428,7 @@ class Element extends Node {
# If context is null or a Document, throw a "NoModificationAllowedError" DOMException. # If context is null or a Document, throw a "NoModificationAllowedError" DOMException.
if ($context === null || $context instanceof Document) { if ($context === null || $context instanceof Document) {
throw new DOMException(DOMException::NO_MODIFICATION_ALLOWED); throw new NoModificationAllowedError();
} }
break; break;
case 'afterbegin': case 'afterbegin':
@ -428,7 +437,7 @@ class Element extends Node {
$context = $this; $context = $this;
$innerContext = $this->_innerNode; $innerContext = $this->_innerNode;
break; break;
default: throw new DOMException(DOMException::SYNTAX_ERROR); default: throw new SyntaxError();
} }
# 2. If context is not an Element or the following are all true: # 2. If context is not an Element or the following are all true:
@ -495,7 +504,7 @@ class Element extends Node {
} catch (\Exception $e) { } catch (\Exception $e) {
# 2. If s is failure, throw a "SyntaxError" DOMException. # 2. If s is failure, throw a "SyntaxError" DOMException.
if ($e instanceof SymfonySyntaxErrorException) { if ($e instanceof SymfonySyntaxErrorException) {
throw new DOMException(DOMException::SYNTAX_ERROR); throw new SyntaxError();
} }
} }
@ -503,9 +512,11 @@ class Element extends Node {
# :scope element this, returns success, then return true; otherwise, return # :scope element this, returns success, then return true; otherwise, return
# false. [SELECTORS4] # false. [SELECTORS4]
$innerNode = $this->_innerNode; $innerNode = $this->_innerNode;
/** @var InnerDocument */
$innerOwnerDocument = $innerNode->ownerDocument;
// Query the parent as the context node, yes. This is due to how the XPath // Query the parent as the context node, yes. This is due to how the XPath
// queries are generated from Symfony. // queries are generated from Symfony.
return ($innerNode->ownerDocument->xpath->query($s, $innerNode->parentNode)->item(0) === $innerNode); return ($innerOwnerDocument->xpath->query($s, $innerNode->parentNode)->item(0) === $innerNode);
} }
public function removeAttribute(string $qualifiedName): void { public function removeAttribute(string $qualifiedName): void {
@ -527,14 +538,20 @@ class Element extends Node {
# The removeAttributeNode(attr) method steps are: # The removeAttributeNode(attr) method steps are:
# 1. If this’s attribute list does not contain attr, then throw a # 1. If this’s attribute list does not contain attr, then throw a
# "NotFoundError" DOMException. # "NotFoundError" DOMException.
// PHP's DOM does this already. Will catch its exception and rethrow as HTML-DOM // PHP's DOM does this already. Will catch its exceptions and rethrow as HTML-DOM
// DOMException. // DOMException.
# 2. Remove attr. # 2. Remove attr.
try { try {
$this->_innerNode->removeAttributeNode($attr->innerNode); $this->_innerNode->removeAttributeNode($attr->innerNode);
} catch (\DOMException $e) { } catch (\DOMException $e) {
throw new DOMException($e->code); switch ($e->getCode()) {
case 8: throw new NotFoundError();
break;
case 9: throw new NotSupportedError();
break;
default: throw new UnknownException();
}
} }
# 3. Return attr. # 3. Return attr.
@ -560,7 +577,7 @@ class Element extends Node {
# 1. If qualifiedName does not match the Name production in XML, then throw an # 1. If qualifiedName does not match the Name production in XML, then throw an
# "InvalidCharacterError" DOMException. # "InvalidCharacterError" DOMException.
if (!preg_match(InnerDocument::NAME_PRODUCTION_REGEX, $qualifiedName)) { if (!preg_match(InnerDocument::NAME_PRODUCTION_REGEX, $qualifiedName)) {
throw new DOMException(DOMException::INVALID_CHARACTER); throw new InvalidCharacterError();
} }
# 2. If this is in the HTML namespace and its node document is an HTML document, # 2. If this is in the HTML namespace and its node document is an HTML document,
@ -603,7 +620,7 @@ class Element extends Node {
# "InUseAttributeError" DOMException. # "InUseAttributeError" DOMException.
$ownerElement = $attr->ownerElement; $ownerElement = $attr->ownerElement;
if ($ownerElement !== null && $ownerElement !== $this) { if ($ownerElement !== null && $ownerElement !== $this) {
throw new DOMException(DOMException::IN_USE_ATTRIBUTE); throw new InUseAttributeError();
} }
// PHP's DOM doesn't do this method correctly. It returns the old node if it is // PHP's DOM doesn't do this method correctly. It returns the old node if it is
@ -756,7 +773,7 @@ class Element extends Node {
return $element->parentNode->insertBefore($node, $element->nextSibling); return $element->parentNode->insertBefore($node, $element->nextSibling);
break; break;
default: throw new DOMException(DOMException::SYNTAX_ERROR); default: throw new SyntaxError();
} }
} }
} }

73
lib/Exception.php

@ -1,73 +0,0 @@
<?php
/**
* @license MIT
* Copyright 2021, Dustin Wilson, J. King et al.
* See LICENSE and AUTHORS files for details
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
class Exception extends \Exception {
const INVALID_CODE = 100;
const UNKNOWN_ERROR = 101;
const INCORRECT_PARAMETERS_FOR_MESSAGE = 102;
const UNREACHABLE_CODE = 103;
const NONEXISTENT_PROPERTY = 201;
const READONLY_PROPERTY = 202;
const ARGUMENT_TYPE_ERROR = 203;
const UNDEFINED_METHOD = 204;
const RETURN_TYPE_ERROR = 205;
protected static $messages = [
100 => 'Invalid error code',
101 => 'Unknown error; escaping',
102 => 'Incorrect number of parameters for Exception message; %s expected',
103 => 'Unreachable code',
201 => 'Property %s does not exist',
202 => 'Cannot write readonly property %s',
203 => 'Argument #%s ($%s) must be of type %s, %s given',
204 => 'Call to undefined method %s::%s()',
205 => 'Function should return type %s, %s given'
];
public function __construct(int $code, ...$args) {
if (!isset(self::$messages[$code])) {
throw new Exception(Exception::INVALID_CODE);
}
$message = self::$messages[$code];
$previous = null;
// @codeCoverageIgnoreStart
if ($args) {
// Grab a previous exception if there is one.
if ($args[0] instanceof \Throwable) {
$previous = array_shift($args);
} elseif (end($args) instanceof \Throwable) {
$previous = array_pop($args);
}
}
// @codeCoverageIgnoreEnd
// Count the number of replacements needed in the message.
preg_match_all('/(\%(?:\d+\$)?s)/', $message, $matches);
$count = count($matches[1]);
// If the number of replacements don't match the arguments then oops.
if (count($args) !== $count) {
throw new Exception(Exception::INCORRECT_PARAMETERS_FOR_MESSAGE, $count);
}
if ($count > 0) {
// Go through each of the arguments and run sprintf on the strings.
$message = call_user_func_array('sprintf', array_merge([$message], $args));
}
parent::__construct($message, $code, $previous);
}
}

15
lib/FileNotFoundException.php

@ -0,0 +1,15 @@
<?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;
class FileNotFoundException extends IOException {
public function __construct(int $code = 0, \Throwable $previous = null, string $path = null) {
parent::__construct(($path === null) ? 'File could not be found' : sprintf('File "%s" could not be found', $path), $code, $previous);
}
}

1
lib/HTMLCollection.php

@ -7,7 +7,6 @@
declare(strict_types=1); declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
use MensBeam\HTML\Parser;
# An HTMLCollection object is a collection of elements. # An HTMLCollection object is a collection of elements.

24
lib/HTMLElement.php

@ -104,7 +104,7 @@ class HTMLElement extends Element {
case 'false': case 'false':
$this->setAttribute('contenteditable', $value); $this->setAttribute('contenteditable', $value);
break; break;
default: throw new DOMException(DOMException::SYNTAX_ERROR); default: throw new SyntaxError();
} }
} }
@ -225,9 +225,10 @@ class HTMLElement extends Element {
} }
$n = $this->_innerNode; $n = $this->_innerNode;
/** @var InnerDocument */
$doc = $n->ownerDocument; $doc = $n->ownerDocument;
while ($n = $n->parentNode) { while ($n = $n->parentNode) {
if ($doc->getWrapperNode($n) instanceof HTMLElement && $n->hasAttribute('hidden')) { if ($doc->getWrapperNode($n) instanceof HTMLElement && $n instanceof \DOMElement && $n->hasAttribute('hidden')) {
return true; return true;
} }
} }
@ -334,7 +335,9 @@ class HTMLElement extends Element {
$n = $this->_innerNode; $n = $this->_innerNode;
while ($n = $n->parentNode) { while ($n = $n->parentNode) {
if ($n instanceof \DOMElement) { if ($n instanceof \DOMElement) {
if ($n->getAttribute('contenteditable') === 'true' && $n->ownerDocument->getWrapperNode($n) instanceof HTMLElement) { /** @var InnerDocument */
$ownerDocument = $n->ownerDocument;
if ($n->getAttribute('contenteditable') === 'true' && $ownerDocument->getWrapperNode($n) instanceof HTMLElement) {
return true; return true;
} }
} }
@ -366,7 +369,7 @@ class HTMLElement extends Element {
# DOMException. # DOMException.
$innerNode = $this->_innerNode; $innerNode = $this->_innerNode;
if ($this->parentNode === null) { if ($this->parentNode === null) {
throw new DOMException(DOMException::NO_MODIFICATION_ALLOWED); throw new NoModificationAllowedError();
} }
# 2. Let next be this's next sibling. # 2. Let next be this's next sibling.
@ -391,9 +394,14 @@ class HTMLElement extends Element {
# with the next text node given next's previous sibling. # with the next text node given next's previous sibling.
if ($next !== null && $next->previousSibling instanceof \DOMText) { if ($next !== null && $next->previousSibling instanceof \DOMText) {
# To merge with the next text node given a Text node node: # To merge with the next text node given a Text node node:
// This gets confusing because this is a process that's in another section of
// the spec, but in this situation _node_ is next's previous sibling, so _node's
// next sibling_ will be next.
# 1. Let next be node's next sibling. # 1. Let next be node's next sibling.
# 2. If next is not a Text node, then return. # 2. If next is not a Text node, then return.
// Already checked for if (!$next instanceof \DOMText) {
return;
}
# 3. Replace data with node, node's data's length, 0, and next's data. # 3. Replace data with node, node's data's length, 0, and next's data.
$next->previousSibling->data .= $next->data; $next->previousSibling->data .= $next->data;
@ -476,11 +484,13 @@ class HTMLElement extends Element {
} }
$n = $this->_innerNode; $n = $this->_innerNode;
/** @var InnerDocument */
$doc = $n->ownerDocument; $doc = $n->ownerDocument;
while ($n = $n->parentNode) { while ($n = $n->parentNode) {
// This looks weird but it's faster to check for the method here first because // This looks weird but it's faster to check for the method here first because
// getting a wrapper node causes a wrapper element to be created if it doesn't // getting a wrapper node causes a wrapper element to be created if it doesn't
// already exist. Don't want to create unnecessary wrappers. // already exist. Don't want to create unnecessary wrappers.
/** @var \DOMElement $n */
if (method_exists($n, 'getAttribute') && $n->getAttribute('translate') === 'yes' && $doc->getWrapperNode($n) instanceof HTMLElement) { if (method_exists($n, 'getAttribute') && $n->getAttribute('translate') === 'yes' && $doc->getWrapperNode($n) instanceof HTMLElement) {
return true; return true;
} }
@ -519,7 +529,9 @@ class HTMLElement extends Element {
$n = $element; $n = $element;
while ($n = $n->parentNode) { while ($n = $n->parentNode) {
if ($n->tagName === 'form' && $n->ownerDocument->getWrapperNode($n) instanceof HTMLElement) { /** @var InnerDocumente */
$ownerDocument = $n->ownerDocument;
if ($ownerDocument->getWrapperNode($n) instanceof HTMLElement && $n instanceof \DOMElement && $n->tagName === 'form') {
return $this->autoCapitalizationHint($n); return $this->autoCapitalizationHint($n);
} }
} }

11
lib/IOException.php

@ -0,0 +1,11 @@
<?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;
class IOException extends \RuntimeException {}

4
lib/Inner/Document.php

@ -68,7 +68,7 @@ class Document extends \DOMDocument {
} }
if ($node instanceof \DOMDocument) { if ($node instanceof \DOMDocument) {
throw new DOMException(DOMException::NOT_SUPPORTED); throw new NotSupportedError();
} }
// There's no way to see whether unappended doctypes are owned by a document in // There's no way to see whether unappended doctypes are owned by a document in
@ -76,7 +76,7 @@ class Document extends \DOMDocument {
// document. This is worked around in the wrapper DOM but a problem here when // document. This is worked around in the wrapper DOM but a problem here when
// creating the wrapper node. // creating the wrapper node.
if (!$node instanceof \DOMDocumentType && $node->ownerDocument !== $this) { if (!$node instanceof \DOMDocumentType && $node->ownerDocument !== $this) {
throw new DOMException(DOMException::WRONG_DOCUMENT); throw new WrongDocumentError();
} }
// If the wrapper node already exists then return that. // If the wrapper node already exists then return that.

9
lib/NamedNodeMap.php

@ -7,11 +7,8 @@
declare(strict_types=1); declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\Inner\{ use MensBeam\HTML\DOM\Inner\Document as InnerDocument,
Document as InnerDocument, MensBeam\HTML\Parser\NameCoercion;
Reflection
};
use MensBeam\HTML\Parser\NameCoercion;
class NamedNodeMap extends Collection { class NamedNodeMap extends Collection {
@ -124,7 +121,7 @@ class NamedNodeMap extends Collection {
# 2. If attr is null, then throw a "NotFoundError" DOMException. # 2. If attr is null, then throw a "NotFoundError" DOMException.
if ($attr === null) { if ($attr === null) {
throw new DOMException(DOMException::NOT_FOUND); throw new NotFoundError();
} }
# 3. Return attr. # 3. Return attr.

90
lib/Node.php

@ -8,7 +8,6 @@
declare(strict_types=1); declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
use MensBeam\GettersAndSetters, use MensBeam\GettersAndSetters,
MensBeam\HTML\Parser,
MensBeam\HTML\Parser\NameCoercion; MensBeam\HTML\Parser\NameCoercion;
use MensBeam\HTML\DOM\Inner\{ use MensBeam\HTML\DOM\Inner\{
Document as InnerDocument, Document as InnerDocument,
@ -227,11 +226,12 @@ abstract class Node implements \Stringable {
# otherwise this’s node document. # otherwise this’s node document.
// PHP's DOM does this correctly on everything but document types. That's taken // PHP's DOM does this correctly on everything but document types. That's taken
// care of in DocumentType. // care of in DocumentType.
if ($this instanceof Document || !$ownerDocument = $this->_innerNode->ownerDocument) { /** @var InnerDocument $ownerDocument */
if ($this instanceof Document || !($ownerDocument = $this->_innerNode->ownerDocument)) {
return null; return null;
} }
return $this->_innerNode->ownerDocument->getWrapperNode($ownerDocument); return $ownerDocument->getWrapperNode($ownerDocument);
} }
protected function __get_parentElement(): ?Element { protected function __get_parentElement(): ?Element {
@ -376,6 +376,7 @@ abstract class Node implements \Stringable {
# 5. If node2 is an attribute, then: # 5. If node2 is an attribute, then:
if ($node2 instanceof Attr) { if ($node2 instanceof Attr) {
# 1. Set attr2 to node2 and node2 to attr2’s element. # 1. Set attr2 to node2 and node2 to attr2’s element.
/** @var \DOMAttr $innerNode2 */
$attr2 = $innerNode2; $attr2 = $innerNode2;
$node2 = $attr2->ownerElement; $node2 = $attr2->ownerElement;
$innerNode2 = $innerNode2->ownerElement; $innerNode2 = $innerNode2->ownerElement;
@ -480,7 +481,13 @@ abstract class Node implements \Stringable {
while ($n = $n->parentNode) { while ($n = $n->parentNode) {
$root = $n; $root = $n;
} }
return (!$root instanceof InnerDocument) ? $root->ownerDocument->getWrapperNode($root) : $root->wrapperNode;
if (!$root instanceof InnerDocument) {
/** @var Node $root */
return $root->ownerDocument->getWrapperNode($root);
}
return $root->wrapperNode;
} }
public function hasChildNodes(): bool { public function hasChildNodes(): bool {
@ -612,7 +619,7 @@ abstract class Node implements \Stringable {
# 1. If parent is not a Document, DocumentFragment, or Element node, then throw # 1. If parent is not a Document, DocumentFragment, or Element node, then throw
# a "HierarchyRequestError" DOMException. # a "HierarchyRequestError" DOMException.
if (!$inner instanceof InnerDocument && !$inner instanceof \DOMDocumentFragment && !$inner instanceof \DOMElement) { if (!$inner instanceof InnerDocument && !$inner instanceof \DOMDocumentFragment && !$inner instanceof \DOMElement) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
# 2. If node is a host-including inclusive ancestor of parent, then throw a # 2. If node is a host-including inclusive ancestor of parent, then throw a
@ -621,25 +628,25 @@ abstract class Node implements \Stringable {
// host-including inclusive ancestor of child, but it should. All browsers check // host-including inclusive ancestor of child, but it should. All browsers check
// for this. // for this.
if ($this->containsInner($node, $inner) || $this->containsInner($node, $child)) { if ($this->containsInner($node, $inner) || $this->containsInner($node, $child)) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
# 3. If child’s parent is not parent, then throw a "NotFoundError" DOMException. # 3. If child’s parent is not parent, then throw a "NotFoundError" DOMException.
if ($child->parentNode !== $inner) { if ($child->parentNode !== $inner) {
throw new DOMException(DOMException::NOT_FOUND); throw new NotFoundError();
} }
# 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData # 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData
# node, then throw a "HierarchyRequestError" DOMException. # node, then throw a "HierarchyRequestError" DOMException.
if (!$node instanceof \DOMDocumentFragment && !$node instanceof \DOMDocumentType && !$node instanceof \DOMElement && !$node instanceof \DOMCharacterData) { if (!$node instanceof \DOMDocumentFragment && !$node instanceof \DOMDocumentType && !$node instanceof \DOMElement && !$node instanceof \DOMCharacterData) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
# 5. If either node is a Text node and parent is a document, or node is a # 5. If either node is a Text node and parent is a document, or node is a
# doctype and parent is not a document, then throw a "HierarchyRequestError" # doctype and parent is not a document, then throw a "HierarchyRequestError"
# DOMException. # DOMException.
if (($node instanceof \DOMText && $inner instanceof InnerDocument) || ($node instanceof \DOMDocumentType && !$inner instanceof InnerDocument)) { if (($node instanceof \DOMText && $inner instanceof InnerDocument) || ($node instanceof \DOMDocumentType && !$inner instanceof InnerDocument)) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
# 6. If parent is a document, and any of the statements below, switched on the # 6. If parent is a document, and any of the statements below, switched on the
@ -653,14 +660,14 @@ abstract class Node implements \Stringable {
if ($node instanceof \DOMDocumentFragment) { if ($node instanceof \DOMDocumentFragment) {
$nodeChildElementCount = $node->childElementCount; $nodeChildElementCount = $node->childElementCount;
if ($nodeChildElementCount > 1) { if ($nodeChildElementCount > 1) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
$n = $node->firstChild; $n = $node->firstChild;
if ($n !== null) { if ($n !== null) {
do { do {
if ($n instanceof \DOMText) { if ($n instanceof \DOMText) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
} while ($n = $n->nextSibling); } while ($n = $n->nextSibling);
} }
@ -671,7 +678,7 @@ abstract class Node implements \Stringable {
$beforeChild = ($n !== $child); $beforeChild = ($n !== $child);
do { do {
if (($n instanceof \DOMElement && $n !== $child) || (!$beforeChild && $n instanceof \DOMDocumentType)) { if (($n instanceof \DOMElement && $n !== $child) || (!$beforeChild && $n instanceof \DOMDocumentType)) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} elseif ($n === $child) { } elseif ($n === $child) {
$beforeChild = false; $beforeChild = false;
} }
@ -689,7 +696,7 @@ abstract class Node implements \Stringable {
$beforeChild = ($n !== $child); $beforeChild = ($n !== $child);
do { do {
if (($n instanceof \DOMElement && $n !== $child) || (!$beforeChild && $n instanceof \DOMDocumentType)) { if (($n instanceof \DOMElement && $n !== $child) || (!$beforeChild && $n instanceof \DOMDocumentType)) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} elseif ($n === $child) { } elseif ($n === $child) {
$beforeChild = false; $beforeChild = false;
} }
@ -706,7 +713,7 @@ abstract class Node implements \Stringable {
$beforeChild = ($n !== $child); $beforeChild = ($n !== $child);
do { do {
if (($n instanceof \DOMDocumentType && $n !== $child) || $beforeChild && $n instanceof \DOMElement) { if (($n instanceof \DOMDocumentType && $n !== $child) || $beforeChild && $n instanceof \DOMElement) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} elseif ($n === $child) { } elseif ($n === $child) {
$beforeChild = false; $beforeChild = false;
} }
@ -824,14 +831,18 @@ abstract class Node implements \Stringable {
# with the clone children flag set. # with the clone children flag set.
# 3. Append copied contents to copy's template contents. # 3. Append copied contents to copy's template contents.
// Template contents are stored in the wrapper nodes. // Template contents are stored in the wrapper nodes.
$copyWrapperContent = $copy->ownerDocument->getWrapperNode($copy)->content; /** @var InnerDocument */
$copyOwnerDocument = $copy->ownerDocument;
$copyWrapperContent = $copyOwnerDocument->getWrapperNode($copy)->content;
// If the cloning is called for as a result of parsing serialized markup the // If the cloning is called for as a result of parsing serialized markup the
// contents of the node should be appended to the wrapper element's content // contents of the node should be appended to the wrapper element's content
// document fragment. Otherwise, clone the content document fragment instead. // document fragment. Otherwise, clone the content document fragment instead.
if (!$parsing) { if (!$parsing) {
$copyWrapperContentInner = $copyWrapperContent->innerNode; $copyWrapperContentInner = $copyWrapperContent->innerNode;
$nodeWrapperContent = $node->ownerDocument->getWrapperNode($node)->content->innerNode; /** @var InnerDocument */
$nodeWrapperOwnerDocument = $node->ownerDocument;
$nodeWrapperContent = $nodeWrapperOwnerDocument->getWrapperNode($node)->content->innerNode;
$childNodes = $nodeWrapperContent->childNodes; $childNodes = $nodeWrapperContent->childNodes;
if ($childNodes->length > 0) { if ($childNodes->length > 0) {
// This garbage is necessary because the appendChildInner method // This garbage is necessary because the appendChildInner method
@ -1063,6 +1074,7 @@ abstract class Node implements \Stringable {
# ↪ DocumentType # ↪ DocumentType
# Its name, public ID, and system ID. # Its name, public ID, and system ID.
if ($thisNode instanceof \DOMDocumentType) { if ($thisNode instanceof \DOMDocumentType) {
/** @var \DOMDocumentType $otherNode */
if ($thisNode->name !== $otherNode->name || $thisNode->publicId !== $otherNode->publicId || $thisNode->systemId !== $thisNode->publicId) { if ($thisNode->name !== $otherNode->name || $thisNode->publicId !== $otherNode->publicId || $thisNode->systemId !== $thisNode->publicId) {
return false; return false;
} }
@ -1070,6 +1082,7 @@ abstract class Node implements \Stringable {
# ↪ Element # ↪ Element
# Its namespace, namespace prefix, local name, and its attribute list’s size. # Its namespace, namespace prefix, local name, and its attribute list’s size.
elseif ($thisNode instanceof \DOMElement) { elseif ($thisNode instanceof \DOMElement) {
/** @var \DOMElement $otherNode */
$otherAttributes = $otherNode->attributes; $otherAttributes = $otherNode->attributes;
if ($thisNode->namespaceURI !== $otherNode->namespaceURI || $thisNode->prefix !== $otherNode->prefix || $thisNode->localName !== $otherNode->localName || $thisNode->attributes->length !== $otherAttributes->length) { if ($thisNode->namespaceURI !== $otherNode->namespaceURI || $thisNode->prefix !== $otherNode->prefix || $thisNode->localName !== $otherNode->localName || $thisNode->attributes->length !== $otherAttributes->length) {
return false; return false;
@ -1091,6 +1104,7 @@ abstract class Node implements \Stringable {
# ↪ Attr # ↪ Attr
# Its namespace, local name, and value. # Its namespace, local name, and value.
elseif ($thisNode instanceof \DOMAttr) { elseif ($thisNode instanceof \DOMAttr) {
/** @var \DOMAttr $otherNode */
if ($thisNode->namespaceURI !== $otherNode->namespaceURI || $thisNode->localName !== $otherNode->localName || $thisNode->value !== $otherNode->value) { if ($thisNode->namespaceURI !== $otherNode->namespaceURI || $thisNode->localName !== $otherNode->localName || $thisNode->value !== $otherNode->value) {
return false; return false;
} }
@ -1099,12 +1113,14 @@ abstract class Node implements \Stringable {
# ↪ Comment # ↪ Comment
# Its data. # Its data.
elseif ($thisNode instanceof \DOMText || $thisNode instanceof \DOMComment) { elseif ($thisNode instanceof \DOMText || $thisNode instanceof \DOMComment) {
/** @var \DOMText|\DOMComment $otherNode */
if ($thisNode->data !== $otherNode->data) { if ($thisNode->data !== $otherNode->data) {
return false; return false;
} }
} }
if ($thisNode instanceof \DOMDocument || $thisNode instanceof \DOMDocumentFragment || $thisNode instanceof \DOMElement) { if ($thisNode instanceof \DOMDocument || $thisNode instanceof \DOMDocumentFragment || $thisNode instanceof \DOMElement) {
/** @var \DOMDocument|\DOMDocumentFragment|\DOMElement $otherNode */
# • A and B have the same number of children. # • A and B have the same number of children.
if ($thisNode->childNodes->length !== $otherNode->childNodes->length) { if ($thisNode->childNodes->length !== $otherNode->childNodes->length) {
return false; return false;
@ -1129,7 +1145,9 @@ abstract class Node implements \Stringable {
# ↪ Element # ↪ Element
if ($node instanceof \DOMElement) { if ($node instanceof \DOMElement) {
// Work around PHP DOM HTML namespace bug // Work around PHP DOM HTML namespace bug
if ($node->namespaceURI === null && !$node->ownerDocument->getWrapperNode($node->ownerDocument) instanceof XMLDocument) { /** @var \InnerDocument */
$ownerDocument = $node->ownerDocument;
if ($node->namespaceURI === null && !$ownerDocument->getWrapperNode($ownerDocument) instanceof XMLDocument) {
$namespace = self::HTML_NAMESPACE; $namespace = self::HTML_NAMESPACE;
} else { } else {
$namespace = $node->namespaceURI; $namespace = $node->namespaceURI;
@ -1289,7 +1307,9 @@ abstract class Node implements \Stringable {
// below walks through this node and temporarily replaces foreign descendants // below walks through this node and temporarily replaces foreign descendants
// with bullshit elements which are then replaced once the node is inserted. // with bullshit elements which are then replaced once the node is inserted.
if ($element->namespaceURI === null && ($this instanceof DocumentFragment || $this->getRootNode() !== null) && $element->hasChildNodes()) { if ($element->namespaceURI === null && ($this instanceof DocumentFragment || $this->getRootNode() !== null) && $element->hasChildNodes()) {
$foreign = $element->ownerDocument->xpath->query('.//*[parent::*[namespace-uri()=""] and not(namespace-uri()="") and name()=local-name()]', $element); /** @var InnerDocument */
$ownerDocument = $element->ownerDocument;
$foreign = $ownerDocument->xpath->query('.//*[parent::*[namespace-uri()=""] and not(namespace-uri()="") and name()=local-name()]', $element);
$this->bullshitReplacements = []; $this->bullshitReplacements = [];
if ($foreign->length > 0) { if ($foreign->length > 0) {
$count = 0; $count = 0;
@ -1317,7 +1337,7 @@ abstract class Node implements \Stringable {
# 1. If parent is not a Document, DocumentFragment, or Element node, then throw # 1. If parent is not a Document, DocumentFragment, or Element node, then throw
# a "HierarchyRequestError" Exception. # a "HierarchyRequestError" Exception.
if (!$parent instanceof InnerDocument && !$parent instanceof \DOMDocumentFragment && !$parent instanceof \DOMElement) { if (!$parent instanceof InnerDocument && !$parent instanceof \DOMDocumentFragment && !$parent instanceof \DOMElement) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
# 2. If node is a host-including inclusive ancestor of parent, then throw a # 2. If node is a host-including inclusive ancestor of parent, then throw a
@ -1328,7 +1348,7 @@ abstract class Node implements \Stringable {
# host-including inclusive ancestor of B’s root’s host. # host-including inclusive ancestor of B’s root’s host.
if ($node->parentNode !== null) { if ($node->parentNode !== null) {
if ($parent->parentNode !== null && ($parent === $node || $this->containsInner($node, $parent))) { if ($parent->parentNode !== null && ($parent === $node || $this->containsInner($node, $parent))) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} else { } else {
$n = $parent; $n = $parent;
do { do {
@ -1336,10 +1356,12 @@ abstract class Node implements \Stringable {
} while ($n = $n->parentNode); } while ($n = $n->parentNode);
if ($parentRoot instanceof \DOMDocumentFragment) { if ($parentRoot instanceof \DOMDocumentFragment) {
$wrappedParentRoot = $parentRoot->ownerDocument->getWrapperNode($parentRoot); /** @var InnerDocument */
$ownerDocument = $parentRoot->ownerDocument;
$wrappedParentRoot = $ownerDocument->getWrapperNode($parentRoot);
$parentRootHost = Reflection::getProtectedProperty($wrappedParentRoot, 'host'); $parentRootHost = Reflection::getProtectedProperty($wrappedParentRoot, 'host');
if ($parentRootHost !== null && ($parentRootHost === $node || $this->containsInner($node, $parentRootHost->get()->innerNode))) { if ($parentRootHost !== null && ($parentRootHost === $node || $this->containsInner($node, $parentRootHost->get()->innerNode))) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
} }
} }
@ -1348,21 +1370,21 @@ abstract class Node implements \Stringable {
# 3. If child is non-null and its parent is not parent, then throw a # 3. If child is non-null and its parent is not parent, then throw a
# "NotFoundError" Exception. # "NotFoundError" Exception.
if ($child !== null && ($child->parentNode === null || $child->parentNode !== $parent)) { if ($child !== null && ($child->parentNode === null || $child->parentNode !== $parent)) {
throw new DOMException(DOMException::NOT_FOUND); throw new NotFoundError();
} }
# 4. If node is not a DocumentFragment, DocumentType, Element, Text, # 4. If node is not a DocumentFragment, DocumentType, Element, Text,
# ProcessingInstruction, or Comment node, then throw a "HierarchyRequestError" # ProcessingInstruction, or Comment node, then throw a "HierarchyRequestError"
# Exception. # Exception.
if (!$node instanceof \DOMDocumentFragment && !$node instanceof \DOMDocumentType && !$node instanceof \DOMElement && !$node instanceof \DOMText && !$node instanceof \DOMProcessingInstruction && !$node instanceof \DOMComment) { if (!$node instanceof \DOMDocumentFragment && !$node instanceof \DOMDocumentType && !$node instanceof \DOMElement && !$node instanceof \DOMText && !$node instanceof \DOMProcessingInstruction && !$node instanceof \DOMComment) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
# 5. If either node is a Text node and parent is a document, or node is a # 5. If either node is a Text node and parent is a document, or node is a
# doctype and parent is not a document, then throw a "HierarchyRequestError" # doctype and parent is not a document, then throw a "HierarchyRequestError"
# Exception. # Exception.
if (($node instanceof \DOMText && $parent instanceof \DOMDocument) || ($node instanceof \DOMDocumentType && !$parent instanceof InnerDocument)) { if (($node instanceof \DOMText && $parent instanceof \DOMDocument) || ($node instanceof \DOMDocumentType && !$parent instanceof InnerDocument)) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
# 6. If parent is a document, and any of the statements below, switched on the # 6. If parent is a document, and any of the statements below, switched on the
@ -1377,13 +1399,13 @@ abstract class Node implements \Stringable {
if ($node instanceof \DOMDocumentFragment) { if ($node instanceof \DOMDocumentFragment) {
$nodeChildElementCount = $node->childElementCount; $nodeChildElementCount = $node->childElementCount;
if ($nodeChildElementCount > 1) { if ($nodeChildElementCount > 1) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} else { } else {
$n = $node->firstChild; $n = $node->firstChild;
if ($n !== null) { if ($n !== null) {
do { do {
if ($n instanceof \DOMText) { if ($n instanceof \DOMText) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
} while ($n = $n->nextSibling); } while ($n = $n->nextSibling);
} }
@ -1391,14 +1413,14 @@ abstract class Node implements \Stringable {
if ($nodeChildElementCount === 1) { if ($nodeChildElementCount === 1) {
if ($parent->childElementCount > 0 || $child instanceof \DOMDocumentType) { if ($parent->childElementCount > 0 || $child instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
if ($child !== null) { if ($child !== null) {
$n = $child; $n = $child;
while ($n = $n->nextSibling) { while ($n = $n->nextSibling) {
if ($n instanceof \DOMDocumentType) { if ($n instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
} }
} }
@ -1410,14 +1432,14 @@ abstract class Node implements \Stringable {
# doctype is following child. # doctype is following child.
elseif ($node instanceof \DOMElement) { elseif ($node instanceof \DOMElement) {
if ($child instanceof \DOMDocumentType) { if ($child instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
if ($child !== null) { if ($child !== null) {
$n = $child; $n = $child;
while ($n = $n->nextSibling) { while ($n = $n->nextSibling) {
if ($n instanceof \DOMDocumentType) { if ($n instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
} }
} }
@ -1426,7 +1448,7 @@ abstract class Node implements \Stringable {
$n = $parent->firstChild; $n = $parent->firstChild;
do { do {
if ($n instanceof \DOMElement) { if ($n instanceof \DOMElement) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
} while ($n = $n->nextSibling); } while ($n = $n->nextSibling);
} }
@ -1441,7 +1463,7 @@ abstract class Node implements \Stringable {
$n = $firstChild; $n = $firstChild;
do { do {
if ($n instanceof \DOMDocumentType || ($child === null && $n instanceof \DOMElement)) { if ($n instanceof \DOMDocumentType || ($child === null && $n instanceof \DOMElement)) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
} while ($n = $n->nextSibling); } while ($n = $n->nextSibling);
} }
@ -1450,7 +1472,7 @@ abstract class Node implements \Stringable {
$n = $child; $n = $child;
while ($n = $n->previousSibling) { while ($n = $n->previousSibling) {
if ($n instanceof \DOMElement) { if ($n instanceof \DOMElement) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); throw new HierarchyRequestError();
} }
} }
} }

4
lib/NonDocumentTypeChildNode.php

@ -7,10 +7,6 @@
declare(strict_types=1); declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\Inner\{
Document as InnerDocument,
Reflection
};
trait NonDocumentTypeChildNode { trait NonDocumentTypeChildNode {

5
lib/NonElementParentNode.php

@ -7,11 +7,6 @@
declare(strict_types=1); declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\Inner\{
Document as InnerDocument,
Reflection
};
use MensBeam\HTML\Parser;
trait NonElementParentNode { trait NonElementParentNode {

11
lib/OutOfBoundsException.php

@ -0,0 +1,11 @@
<?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;
class OutOfBoundsException extends \OutOfBoundsException {}

9
lib/ParentNode.php

@ -7,10 +7,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\Inner\{ use MensBeam\HTML\DOM\Inner\Reflection;
Document as InnerDocument,
Reflection
};
use Symfony\Component\CssSelector\CssSelectorConverter, use Symfony\Component\CssSelector\CssSelectorConverter,
Symfony\Component\CssSelector\Exception\SyntaxErrorException as SymfonySyntaxErrorException; Symfony\Component\CssSelector\Exception\SyntaxErrorException as SymfonySyntaxErrorException;
@ -121,7 +118,7 @@ trait ParentNode {
continue 2; continue 2;
case Node::WALK_REJECT: case Node::WALK_REJECT:
break; break;
default: throw new DOMException(DOMException::SYNTAX_ERROR); default: throw new SyntaxError();
} }
if ($node->parentNode !== null && $node->hasChildNodes()) { if ($node->parentNode !== null && $node->hasChildNodes()) {
@ -146,7 +143,7 @@ trait ParentNode {
// so only throw exception when an actual syntax error, otherwise return an // so only throw exception when an actual syntax error, otherwise return an
// empty nodelist. // empty nodelist.
if ($e instanceof SymfonySyntaxErrorException) { if ($e instanceof SymfonySyntaxErrorException) {
throw new DOMException(DOMException::SYNTAX_ERROR); throw new SyntaxError();
} }
return new \DOMNodeList; return new \DOMNodeList;

1
lib/ProcessingInstruction.php

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
/** @property \DOMProcessingInstruction $_innerNode */
class ProcessingInstruction extends CharacterData { class ProcessingInstruction extends CharacterData {
protected function __get_target(): string { protected function __get_target(): string {
// Need to uncoerce string if necessary. // Need to uncoerce string if necessary.

20
lib/Serializer.php

@ -8,20 +8,21 @@
declare(strict_types=1); declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
use MensBeam\HTML\DOM\Inner\Reflection, use MensBeam\HTML\DOM\Inner\Reflection,
MensBeam\HTML\Parser; MensBeam\HTML\Parser,
use MensBeam\HTML\Parser\{ MensBeam\HTML\Parser\Serializer as ParserSerializer;
Config,
Serializer as ParserSerializer
};
class Serializer extends ParserSerializer { class Serializer extends ParserSerializer {
protected static function fragmentHasHost(\DOMDocumentFragment $fragment): bool { protected static function fragmentHasHost(\DOMDocumentFragment $fragment): bool {
return (Reflection::getProtectedProperty($fragment->ownerDocument->getWrapperNode($fragment), 'host') !== null); /** @var InnerDocument */
$ownerDocument = $fragment->ownerDocument;
return (Reflection::getProtectedProperty($ownerDocument->getWrapperNode($fragment), 'host') !== null);
} }
protected static function getTemplateContent(\DOMElement $node): \DOMNode { protected static function getTemplateContent(\DOMElement $node): \DOMNode {
return $node->ownerDocument->getWrapperNode($node)->content->innerNode; /** @var InnerDocument */
$ownerDocument = $node->ownerDocument;
return $ownerDocument->getWrapperNode($node)->content->innerNode;
} }
protected static function isPreformattedContent(\DOMNode $node): bool { protected static function isPreformattedContent(\DOMNode $node): bool {
@ -32,7 +33,9 @@ class Serializer extends ParserSerializer {
return true; return true;
} }
} elseif ($n instanceof \DOMDocumentFragment) { } elseif ($n instanceof \DOMDocumentFragment) {
$host = Reflection::getProtectedProperty($node->ownerDocument->getWrapperNode($n), 'host'); /** @var InnerDocument */
$ownerDocument = $node->ownerDocument;
$host = Reflection::getProtectedProperty($ownerDocument->getWrapperNode($n), 'host');
if ($host !== null) { if ($host !== null) {
$n = $host->get()->innerNode; $n = $host->get()->innerNode;
} }
@ -43,6 +46,7 @@ class Serializer extends ParserSerializer {
} }
protected static function treatAsBlockWithTemplates(\DOMNode $node): bool { protected static function treatAsBlockWithTemplates(\DOMNode $node): bool {
/** @var InnerDocument */
$document = $node->ownerDocument; $document = $node->ownerDocument;
$xpath = $document->xpath; $xpath = $document->xpath;
$templates = $xpath->query('.//template[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"][not(ancestor::iframe[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::listing[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::noembed[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::noframes[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::noscript[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::plaintext[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::pre[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::style[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::script[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::textarea[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::title[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::xmp[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"])]', $node); $templates = $xpath->query('.//template[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"][not(ancestor::iframe[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::listing[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::noembed[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::noframes[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::noscript[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::plaintext[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::pre[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::style[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::script[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::textarea[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::title[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"] or ancestor::xmp[namespace-uri()="" or namespace-uri()="http://www.w3.org/1999/xhtml"])]', $node);

17
lib/Text.php

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
/** @property \DOMText $_innerNode */
class Text extends CharacterData { class Text extends CharacterData {
protected function __get_wholeText(): string { protected function __get_wholeText(): string {
// PHP's DOM does this correctly already. // PHP's DOM does this correctly already.
@ -19,11 +20,21 @@ class Text extends CharacterData {
public function splitText(int $offset): Text { public function splitText(int $offset): Text {
// PHP DOM mostly handles this correctly with the exception of not throwing an // PHP DOM mostly handles this correctly with the exception of not throwing an
// exception when the offset is greater than the length, so let's fix that. // exception when the offset is greater than the length, so let's fix that.
if ($offset > $this->length) { // DEVIATION?: All browsers error when supplying negative numbers here, so let's check for that, too.
throw new DOMException(DOMException::INDEX_SIZE_ERROR); if ($offset < 0 || $offset > $this->length) {
# 2. If offset is greater than length, then throw an "IndexSizeError" DOMException.
// DEVIATION?: WebIDL standard
// (https://webidl.spec.whatwg.org/#idl-DOMException-error-names) says
// IndexSizeError is deprecated and to use RangeError instead which isn't a
// DOMException but specified to be an analog of ECMAScript's built-in errors.
// Because of this we're going to use OutOfBoundsException which is PHP's
// equivalent.
throw new OutOfBoundsException(sprintf('Offset %s is outside of Text node\'s range of %s to %s', $offset, 0, $this->length));
} }
return $this->_innerNode->ownerDocument->getWrapperNode($this->_innerNode->splitText($offset)); /** @var \MensBeam\HTML\DOM\Inner\Document */
$ownerDocument = $this->_innerNode->ownerDocument;
return $ownerDocument->getWrapperNode($this->_innerNode->splitText($offset));
} }

15
lib/UnknownException.php

@ -0,0 +1,15 @@
<?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;
class UnknownException extends \LogicException {
public function __construct(int $code = 0, ?\Throwable $previous = null) {
parent::__construct('The program reached an invalid state; this error should be reported', $code, $previous);
}
}

2
lib/XMLDocument.php

@ -16,7 +16,7 @@ class XMLDocument extends Document {
public function load(string $source = null, ?string $charset = null): void { public function load(string $source = null, ?string $charset = null): void {
if ($this->hasChildNodes()) { if ($this->hasChildNodes()) {
throw new DOMException(DOMException::NO_MODIFICATION_ALLOWED); throw new NoModificationAllowedError();
} }
$this->_innerNode->encoding = Charset::fromCharset((string)$charset) ?? 'UTF-8'; $this->_innerNode->encoding = Charset::fromCharset((string)$charset) ?? 'UTF-8';

4
lib/XPathEvaluate.php

@ -82,7 +82,7 @@ trait XPathEvaluate {
$resultType = XPathResult::ORDERED_NODE_ITERATOR_TYPE; $resultType = XPathResult::ORDERED_NODE_ITERATOR_TYPE;
break; break;
default: default:
throw new DOMException(DOMException::NOT_SUPPORTED); throw new NotSupportedError();
} }
} else { } else {
switch ($type) { switch ($type) {
@ -148,7 +148,7 @@ trait XPathEvaluate {
$result = $result->item(0); $result = $result->item(0);
break; break;
default: throw new DOMException(DOMException::NOT_SUPPORTED); default: throw new NotSupportedError();
} }
$resultType = $type; $resultType = $type;

20
lib/XPathException.php

@ -9,18 +9,22 @@ declare(strict_types=1);
namespace MensBeam\HTML\DOM; namespace MensBeam\HTML\DOM;
class XPathException extends Exception { class XPathException extends \Exception {
public const INVALID_EXPRESSION = 51; public const INVALID_EXPRESSION = 51;
public const TYPE_ERROR = 52; public const TYPE_ERROR = 52;
public const UNRESOLVABLE_NAMESPACE_PREFIX = 53; public const UNRESOLVABLE_NAMESPACE_PREFIX = 53;
public function __construct(int $code, ...$args) { public function __construct(int $code = 0, ?\Throwable $previous = null) {
self::$messages = array_replace(parent::$messages, [ switch ($code) {
51 => 'Invalid expression error', case self::INVALID_EXPRESSION: $message = 'Invalid expression error';
52 => 'Expression cannot be converted to the specified type', break;
53 => 'Unresolvable namespace prefix' case self::TYPE_ERROR: $message = 'Expression cannot be converted to the specified type';
]); break;
case self::UNRESOLVABLE_NAMESPACE_PREFIX: $message = 'Unresolvable namespace prefix';
break;
default: throw new UnknownException();
}
parent::__construct($code, ...$args); parent::__construct($message, $code, $previous);
} }
} }

14
robo

@ -1,14 +0,0 @@
#! /bin/sh
base=`dirname "$0"`
roboCommand="$1"
if [ $# -eq 0 ]; then
"$base/vendor/bin/robo"
else
shift
ulimit -n 2048
if [ "$1" = "clean" ]; then
"$base/vendor/bin/robo" "$roboCommand" "$@"
else
"$base/vendor/bin/robo" "$roboCommand" -- "$@"
fi
fi

21
robo.bat

@ -1,21 +0,0 @@
@echo off
setlocal
set base=%~dp0
set roboCommand=%1
rem get all arguments except the first
shift
set "args="
:parse
if "%~1" neq "" (
set args=%args% %1
shift
goto :parse
)
if defined args set args=%args:~1%
if "%1"=="clean" (
call "%base%vendor\bin\robo" "%roboCommand%" %args%
) else (
call "%base%vendor\bin\robo" "%roboCommand%" -- %args%
)
Loading…
Cancel
Save