diff --git a/composer.json b/composer.json index 3fc1ae9..b90fc27 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "php": ">=8.0", "ext-dom": "*", "mensbeam/html-parser": ">=1.0", - "mensbeam/framework": "^1.0", + "mensbeam/framework": "dev-main", "symfony/css-selector": "^5.3" }, "scripts": { @@ -48,5 +48,11 @@ "bamarni/composer-bin-plugin": "^1.3", "daux/daux.io": "^0.16.0", "mikey179/vfsstream": "^1.6" - } + }, + "repositories": [ + { + "type": "git", + "url": "mensbeam-gitea:MensBeam/Framework.git" + } + ] } diff --git a/composer.lock b/composer.lock index c6e2b3f..f3577d4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,15 +4,15 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "66041b42116f0d3ea068de467bb0a990", + "content-hash": "3897d645c52d2c74808975ebcdddff81", "packages": [ { "name": "mensbeam/framework", - "version": "1.0.2", + "version": "dev-main", "source": { "type": "git", - "url": "https://code.mensbeam.com/MensBeam/Framework", - "reference": "d2999f53be0a0484a81639a0999ffd1f1832b31c" + "url": "mensbeam-gitea:MensBeam/Framework.git", + "reference": "07af6f6a0c8a911d9e08870bdd54946757c0e958" }, "require": { "php": ">=7.4" @@ -20,6 +20,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.3" }, + "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -28,7 +29,21 @@ ] } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "MensBeam\\Framework\\TestCase\\": [ + "tests/cases/" + ] + } + }, + "scripts": { + "post-install-cmd": [ + "@composer bin all install" + ], + "post-update-cmd": [ + "@composer bin all update" + ] + }, "license": [ "MIT" ], @@ -40,7 +55,7 @@ } ], "description": "Common classes and traits used in many Mensbeam projects", - "time": "2021-10-15T14:15:35+00:00" + "time": "2021-10-21T04:01:22+00:00" }, { "name": "mensbeam/html-parser", @@ -539,24 +554,25 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.3.0", + "version": "7.4.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "7008573787b430c1c1f650e3722d9bba59967628" + "reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628", - "reference": "7008573787b430c1c1f650e3722d9bba59967628", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/868b3571a039f0ebc11ac8f344f4080babe2cb94", + "reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.4", - "guzzlehttp/psr7": "^1.7 || ^2.0", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.8.3 || ^2.1", "php": "^7.2.5 || ^8.0", - "psr/http-client": "^1.0" + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2" }, "provide": { "psr/http-client-implementation": "1.0" @@ -566,7 +582,7 @@ "ext-curl": "*", "php-http/client-integration-tests": "^3.0", "phpunit/phpunit": "^8.5.5 || ^9.3.5", - "psr/log": "^1.1" + "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { "ext-curl": "Required for CURL handler support", @@ -576,7 +592,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.3-dev" + "dev-master": "7.4-dev" } }, "autoload": { @@ -592,19 +608,43 @@ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, { "name": "Márk Sági-Kazár", "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], "description": "Guzzle is a PHP HTTP client library", - "homepage": "http://guzzlephp.org/", "keywords": [ "client", "curl", @@ -618,7 +658,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.3.0" + "source": "https://github.com/guzzle/guzzle/tree/7.4.0" }, "funding": [ { @@ -630,15 +670,11 @@ "type": "github" }, { - "url": "https://github.com/alexeyshockov", - "type": "github" - }, - { - "url": "https://github.com/gmponos", - "type": "github" + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" } ], - "time": "2021-03-23T11:33:13+00:00" + "time": "2021-10-18T09:52:00+00:00" }, { "name": "guzzlehttp/promises", @@ -2679,7 +2715,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "mensbeam/framework": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/lib/ChildNode.php b/lib/ChildNode.php index 22e7f4e..1cff8d6 100644 --- a/lib/ChildNode.php +++ b/lib/ChildNode.php @@ -7,7 +7,8 @@ declare(strict_types=1); namespace MensBeam\HTML\DOM; -use MensBeam\Framework\MagicProperties; +use MensBeam\Framework\MagicProperties, + MensBeam\HTML\DOM\InnerNode\Factory; trait ChildNode { @@ -40,9 +41,9 @@ trait ChildNode { } if ($node instanceof DocumentFragment) { - $host = $node->host; + $host = Factory::getProtectedProperty($node, 'host'); if ($host !== null) { - $next = $host; + $next = $host->get(); } } } while ($node = $next); diff --git a/lib/Collection.php b/lib/Collection.php deleted file mode 100644 index ee462c4..0000000 --- a/lib/Collection.php +++ /dev/null @@ -1,118 +0,0 @@ -count(); - } - - - protected function __construct(array|\Closure $arrayOrClosure = []) { - // In this implementation the root part of the creation is handled either before - // the NodeList is created (array) or within the filter (\Closure). - if ($arrayOrClosure === null) { - $arrayOrClosure = []; - } - - if (is_callable($arrayOrClosure)) { - $this->filter = $arrayOrClosure; - } else { - // Check types while also unpacking the iterable. - $array = []; - foreach ($arrayOrClosure as $i) { - if (!$i instanceof Node) { - $type = gettype($i); - if ($type === 'object') { - $type = get_class($i); - } - throw new Exception(Exception::ARGUMENT_TYPE_ERROR, 1, 'arrayOrClosure', 'array|\\Closure>', $type); - } - - $array[] = $i; - } - - $this->nodeArray = $array; - $this->_length = count($array); - } - } - - public function count(): int { - if ($this->nodeArray !== null) { - return count($this->nodeArray); - } - - $nodeArray = ($this->filter)(); - return count($nodeArray); - } - - public function current() { - return $this->item($this->position); - } - - public function key(): int { - return $this->position; - } - - public function next(): void { - $this->position++; - } - - public function rewind(): void { - $this->position = 0; - } - - public function offsetExists($offset): bool { - $nodeArray = ($this->nodeArray !== null) ? $this->nodeArray : ($this->filter)(); - return array_key_exists($offset, $nodeArray); - } - - public function offsetGet($offset) { - return (is_int($offset)) ? $this->item($offset) : $this->namedItem($offset); - return $this->item($offset); - } - - public function offsetSet($offset, $value): void { - // NodeLists are immutable; the spec is ambiguous as to what to do here. - // Browsers silently fail here, so that's what we're going to do. - } - - public function offsetUnset($offset): void { - // NodeLists are immutable; the spec is ambiguous as to what to do here. - // Browsers silently fail here, so that's what we're going to do. - } - - public function valid() { - $this->offsetExists($this->position); - } -} \ No newline at end of file diff --git a/lib/Document.php b/lib/Document.php index 884d877..2e3d605 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -18,8 +18,11 @@ class Document extends Node { } + public function createDocumentFragment(): DocumentFragment { + return $this->innerNode->getWrapperNode($this->innerNode->createDocumentFragment()); + } + public function createElement(string $localName): Element { - $element = $this->innerNode->createElement($localName); - return $this->innerNode->getWrapperNode($element); + return $this->innerNode->getWrapperNode($this->innerNode->createElement($localName)); } } \ No newline at end of file diff --git a/lib/DocumentFragment.php b/lib/DocumentFragment.php new file mode 100644 index 0000000..feb36f5 --- /dev/null +++ b/lib/DocumentFragment.php @@ -0,0 +1,22 @@ +_content; + } + + protected function __construct(InnerElement $element) { + parent::__construct($element); + + $this->_content = $this->ownerWrapperDocument->get()->createDocumentFragment(); + Factory::setProtectedProperty($this->_content, 'host', \WeakReference::create($this)); + } +} \ No newline at end of file diff --git a/lib/InnerNode/Factory.php b/lib/InnerNode/Factory.php index 5e5059a..a3edc8e 100644 --- a/lib/InnerNode/Factory.php +++ b/lib/InnerNode/Factory.php @@ -25,4 +25,11 @@ class Factory { $property->setAccessible(true); return $property->getValue($instance); } + + public static function setProtectedProperty(mixed $instance, string $propertyName, mixed $value): mixed { + $reflector = new \ReflectionClass($instance::class); + $property = new \ReflectionProperty($instance, $propertyName); + $property->setAccessible(true); + return $property->setValue($instance, $value); + } } \ No newline at end of file diff --git a/lib/Node.php b/lib/Node.php index 556d8d9..d8376ba 100644 --- a/lib/Node.php +++ b/lib/Node.php @@ -36,6 +36,7 @@ abstract class Node { protected ?NodeList $_childNodes = null; protected \DOMNode $innerNode; + protected ?\WeakReference $ownerWrapperDocument = null; protected function __get_childNodes(): NodeList { // NodeLists cannot be created from their constructors normally. @@ -43,7 +44,7 @@ abstract class Node { // the node is even capable of having children, otherwise will just be an empty // NodeList. There is no sense in generating a live list that will never update. if ($this instanceof Document || $this instanceof DocumentFragment || $this instanceof Element) { - $doc = ($this instanceof Document) ? $this->innerNode : $this->innerNode->nodeDocument; + $doc = ($this instanceof Document) ? $this->innerNode : $this->innerNode->ownerDocument; return Factory::createFromProtectedConstructor(__NAMESPACE__ . '\\NodeList', function() use($doc) { $result = []; $innerChildNodes = $this->innerNode->childNodes; @@ -158,7 +159,6 @@ abstract class Node { # An object that participates in a tree has a parent, which is either null or an # object, and has children, which is an ordered set of objects. An object A # whose parent is object B is a child of B. - if ($this instanceof Document) { return null; } @@ -172,28 +172,14 @@ abstract class Node { } protected function __get_textContent(): string { - # The textContent getter steps are to return the following, switching on the interface this implements: - // PHP's DOM has some weird bugs concerning textContent, and because there isn't - // a special element class for template elements will return the contents of any - // template elements. So, let's do this manually, shall we? - - # ↪ DocumentFragment - # ↪ Element - # The descendant text content of this. - if ($node instanceof DocumentFragment || $node instanceof Element) {} - # ↪ Attr - # this’s value. - - # ↪ CharacterData - # this’s data. - - # ↪ Otherwise - # Null. + // PHP's DOM does this correctly already. + return $this->innerNode->textContent; } protected function __construct(\DOMNode $innerNode) { $this->innerNode = $innerNode; + $this->ownerWrapperDocument = (!$this instanceof Document) ? \WeakReference::create($innerNode->ownerDocument->wrapperNode) : null; } @@ -225,6 +211,11 @@ abstract class Node { return ($otherNode === $this); } + public function normalize(): void { + // PHP's DOM does this correctly already. + $this->innerNode->normalize(); + } + protected function preInsertionValidity(Node $node, ?Node $child = null) { // "parent" in the spec comments below is $this @@ -247,7 +238,7 @@ abstract class Node { } else { $parentRoot = $this->getRootNode(); if ($parentRoot instanceof DocumentFragment) { - $parentRootHost = $parentRoot->host; + $parentRootHost = Factory::getProtectedProperty($parentRoot, 'host')->get(); if ($parentRootHost !== null && ($parentRootHost === $node || $node->contains($parentRootHost))) { throw new Exception(Exception::HIERARCHY_REQUEST_ERROR); } @@ -322,7 +313,8 @@ abstract class Node { } } - foreach ($this->childNodes as $c) { + $childNodes = $this->childNodes; + foreach ($childNodes as $c) { if ($c instanceof Element) { throw new Exception(Exception::HIERARCHY_REQUEST_ERROR); } @@ -333,7 +325,8 @@ abstract class Node { # parent has a doctype child, child is non-null and an element is preceding # child, or child is null and parent has an element child. elseif ($node instanceof DocumentType) { - foreach ($this->childNodes as $c) { + $childNodes = $this->childNodes; + foreach ($childNodes as $c) { if ($c instanceof DocumentType) { throw new Exception(Exception::HIERARCHY_REQUEST_ERROR); } @@ -347,7 +340,8 @@ abstract class Node { } } } else { - foreach ($this->childNodes as $c) { + $childNodes = $this->childNodes; + foreach ($childNodes as $c) { if ($c instanceof Element) { throw new Exception(Exception::HIERARCHY_REQUEST_ERROR); } diff --git a/vendor-bin/phpunit/composer.lock b/vendor-bin/phpunit/composer.lock index d1691ea..d221c04 100644 --- a/vendor-bin/phpunit/composer.lock +++ b/vendor-bin/phpunit/composer.lock @@ -355,16 +355,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.2.2", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", "shasum": "" }, "require": { @@ -375,7 +375,8 @@ "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2" + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -405,9 +406,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" }, - "time": "2020-09-03T19:13:55+00:00" + "time": "2021-10-19T17:43:47+00:00" }, { "name": "phpdocumentor/type-resolver",