Browse Source

Added DocumentFragment & HTMLTemplateElement

wrapper-classes
Dustin Wilson 3 years ago
parent
commit
0eb04c81de
  1. 10
      composer.json
  2. 90
      composer.lock
  3. 7
      lib/ChildNode.php
  4. 118
      lib/Collection.php
  5. 7
      lib/Document.php
  6. 22
      lib/DocumentFragment.php
  7. 27
      lib/HTMLTemplateElement.php
  8. 7
      lib/InnerNode/Factory.php
  9. 40
      lib/Node.php
  10. 15
      vendor-bin/phpunit/composer.lock

10
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"
}
]
}

90
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": {

7
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);

118
lib/Collection.php

@ -1,118 +0,0 @@
<?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;
use MensBeam\Framework\MagicProperties;
# A collection is an object that represents a list of nodes. A collection can be
# either live or static. Unless otherwise stated, a collection must be live.
#
# If a collection is live, then the attributes and methods on that object must
# operate on the actual underlying data, not a snapshot of the data.
#
# When a collection is created, a filter and a root are associated with it.
#
# The collection then represents a view of the subtree rooted at the
# collection’s root, containing only nodes that match the given filter. The view
# is linear. In the absence of specific requirements to the contrary, the nodes
# within the collection must be sorted in tree order.
trait Collection {
use MagicProperties;
protected ?\Closure $filter = null;
protected int $_length = 0;
protected ?array $nodeArray = null;
protected int $position = 0;
protected function __get_length(): int {
# The length attribute must return the number of nodes represented by the
# collection.
return $this->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<Node>|\\Closure<array<Node>>', $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);
}
}

7
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));
}
}

22
lib/DocumentFragment.php

@ -0,0 +1,22 @@
<?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;
use MensBeam\HTML\DOM\InnerNode\DocumentFragment as InnerDocumentFragment;
class DocumentFragment extends Node {
use ParentNode;
protected ?\WeakReference $host = null;
protected function __construct(InnerDocumentFragment $fragment) {
parent::__construct($fragment);
}
}

27
lib/HTMLTemplateElement.php

@ -0,0 +1,27 @@
<?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;
use MensBeam\HTML\DOM\InnerNode\Element as InnerElement,
MensBeam\HTML\DOM\InnerNode\Factory;
class HTMLTemplateElement extends Element {
protected DocumentFragment $_content;
protected function __get_content(): DocumentFragment {
return $this->_content;
}
protected function __construct(InnerElement $element) {
parent::__construct($element);
$this->_content = $this->ownerWrapperDocument->get()->createDocumentFragment();
Factory::setProtectedProperty($this->_content, 'host', \WeakReference::create($this));
}
}

7
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);
}
}

40
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);
}

15
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",

Loading…
Cancel
Save