Browse Source

Continuing with tests

wrapper-classes
Dustin Wilson 3 years ago
parent
commit
82c9dac054
  1. 2
      composer.json
  2. 9
      composer.lock
  3. 36
      lib/Document.php
  4. 68
      lib/Element.php
  5. 4
      lib/Inner/Document.php
  6. 9
      lib/Serializer.php
  7. 63
      tests/cases/TestElement.php
  8. 2
      tests/cases/TestNode.php
  9. 1
      tests/phpunit.dist.xml

2
composer.json

@ -5,7 +5,7 @@
"require": {
"php": ">=8.0",
"ext-dom": "*",
"mensbeam/html-parser": "dev-pretty-print",
"mensbeam/html-parser": "dev-master",
"mensbeam/framework": "dev-main",
"symfony/css-selector": "^5.3"
},

9
composer.lock

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "80f59b5505ffc9b8d7fd2a408538d861",
"content-hash": "6ed06e9556bdcc4bbbb5f3f23958e33d",
"packages": [
{
"name": "mensbeam/framework",
@ -59,11 +59,11 @@
},
{
"name": "mensbeam/html-parser",
"version": "dev-pretty-print",
"version": "dev-master",
"source": {
"type": "git",
"url": "mensbeam-gitea:MensBeam/HTML-Parser.git",
"reference": "8361ea0d88c4406213e39e2cb983bfcb1e9b9b37"
"reference": "5495e7c81e36409bebb998962f8624b5744a1b30"
},
"require": {
"ext-dom": "*",
@ -77,6 +77,7 @@
"suggest": {
"ext-ctype": "Improved performance"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
@ -129,7 +130,7 @@
"parsing",
"whatwg"
],
"time": "2021-11-17T05:59:36+00:00"
"time": "2021-11-18T17:39:20+00:00"
},
{
"name": "mensbeam/intl",

36
lib/Document.php

@ -438,41 +438,7 @@ class Document extends Node {
$node = $node->innerNode;
}
$parserConfig = new ParserConfig();
foreach ($config as $key => $value) {
switch ($key) {
case 'indentStep':
if (!is_int($value)) {
$type = gettype($value);
if ($type === 'object') {
$type = get_class($value);
}
trigger_error("Value for serializer configuration option \"$key\" must be an integer; $type given", \E_USER_WARNING);
continue 2;
}
break;
case 'indentWithSpaces':
case 'reformatWhitespace':
case 'serializeBooleanAttributeValues':
case 'serializeForeignVoidEndTags':
if (!is_bool($value)) {
$type = gettype($value);
if ($type === 'object') {
$type = get_class($value);
}
trigger_error("Value for serializer configuration option \"$key\" must be an integer; $type given", \E_USER_WARNING);
continue 2;
}
break;
default:
trigger_error("\"$key\" is an invalid serializer configuration option", \E_USER_WARNING);
continue 2;
}
$parserConfig->$key = $value;
}
return Serializer::serialize($node, $parserConfig);
return Serializer::serialize($node, $config);
}

68
lib/Element.php

@ -64,6 +64,21 @@ class Element extends Node {
}
public function getAttribute(string $qualifiedName): ?string {
# The getAttribute(qualifiedName) method steps are:
#
# 1. Let attr be the result of getting an attribute given qualifiedName and this.
$attr = $this->getAttributeNode($qualifiedName);
# 2. If attr is null, return null.
if ($attr === null) {
return null;
}
# 3. Return attr’s value.
// Uncoerce the value if necessary
$value = $attr->value;
return (!strpos($value, 'U')) ? $value : $this->uncoerceName($value);
}
public function getAttributeNames(): array {
# The getAttributeNames() method steps are to return the qualified names of the
# attributes in this’s attribute list, in order; otherwise a new list.
@ -78,8 +93,40 @@ class Element extends Node {
return $list;
}
public function getAttributeNS(string $namespace, string $localName): ?string {
return $this->innerNode->getAttributeNS($namespace, $localName);
public function getAttributeNodeNS(?string $namespace, string $localName): ?Attr {
# The getAttributeNodeNS(namespace, localName) method steps are to return the
# result of getting an attribute given namespace, localName, and this.
#
# To get an attribute by namespace and local name given a namespace, localName,
# and element element, run these steps:
#
# 1. If namespace is the empty string, then set it to null.
if ($namespace === '') {
$namespace = null;
}
# 2. Return the attribute in element’s attribute list whose namespace is namespace
# and local name is localName, if any; otherwise null.
// Going to try to handle this by getting the PHP DOM to do the heavy lifting
// when we can because it's faster.
$value = $this->innerNode->getAttributeNodeNS($namespace, $localName);
if (!$value) {
// Replace any offending characters with "UHHHHHH" where H are the uppercase
// hexadecimal digits of the character's code point
$localName = $this->coerceName($localName);
// The PHP DOM does not acknowledge the presence of XMLNS-namespace attributes
// sometimes, too... so this will get those as well in those circumstances.
$attributes = $this->innerNode->attributes;
foreach ($attributes as $a) {
if ($a->namespaceURI === $namespace && $a->localName === $localName) {
return $this->innerNode->ownerDocument->getWrapperNode($a);
}
}
return null;
}
return ($value !== false) ? $this->innerNode->ownerDocument->getWrapperNode($value) : null;
}
public function getAttributeNode(string $qualifiedName): ?Attr {
@ -118,6 +165,23 @@ class Element extends Node {
return ($attr !== false) ? $this->innerNode->ownerDocument->getWrapperNode($attr) : null;
}
public function getAttributeNS(?string $namespace, string $localName): ?string {
# The getAttributeNS(namespace, localName) method steps are:
#
# 1. Let attr be the result of getting an attribute given namespace, localName,
# and this.
$attr = $this->getAttributeNodeNS($namespace, $localName);
# 2. If attr is null, return null.
if ($attr === null) {
return null;
}
# 3. Return attr’s value.
// Uncoerce the value if necessary
return $attr->value;
}
public function hasAttributes(): bool {
# The hasAttributes() method steps are to return false if this’s attribute list
# is empty; otherwise true.

4
lib/Inner/Document.php

@ -48,10 +48,6 @@ class Document extends \DOMDocument {
}
public function getInnerNode(WrapperNode $node): ?\DOMNode {
return Reflection::getProtectedProperty($node, 'innerNode');
}
public function getWrapperNode(\DOMNode $node): ?WrapperNode {
// If the node is a Document then the wrapperNode is this's wrapperNode
// property.

9
lib/Serializer.php

@ -16,18 +16,11 @@ use MensBeam\HTML\Parser\{
class Serializer extends ParserSerializer {
protected static function getTemplateContent(\DOMElement $node, ?Config $config = null): \DOMNode {
// NOTE: PHP's DOM does not support the content property on template elements
// natively. This method exists purely so implementors of userland PHP DOM
// solutions may extend this method to get template contents how they need them.
protected static function getTemplateContent(\DOMElement $node): \DOMNode {
return Reflection::getProtectedProperty($node->ownerDocument->getWrapperNode($node)->content, 'innerNode');
}
protected static function isPreformattedContent(\DOMNode $node): bool {
// NOTE: This method is used only when pretty printing. Implementors of userland
// PHP DOM solutions with template contents will need to extend this method to
// be able to moonwalk through document fragment hosts.
$n = $node;
do {
if ($n instanceof \DOMElement) {

63
tests/cases/TestElement.php

@ -0,0 +1,63 @@
<?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\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException,
Element,
Node,
Text,
XMLDocument
};
/** @covers \MensBeam\HTML\DOM\Element */
class TestElement extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\Element::getAttributeNames
*
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Collection::item
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentOrElement::getElementsByTagName
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\HTMLCollection::item
* @covers \MensBeam\HTML\DOM\HTMLCollection::offsetGet
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testMethod_getAttributeNames() {
$d = new Document('<!DOCTYPE html><html><body><div id="ook" class="eek" ack="ack" foo="foo" bar="bar"></div></body></html>');
$div = $d->getElementsByTagName('div')[0];
$this->assertSame([ 'id', 'class', 'ack', 'foo', 'bar' ], $div->getAttributeNames());
}
public function testMethod_getAttributeNS() {
$d = new Document('<!DOCTYPE html><html><head></head><body><svg xmlns="' . Node::SVG_NAMESPACE . '" xmlns:xlink="' . Node::XLINK_NAMESPACE . '"></svg></body></html>');
$svg = $d->getElementsByTagNameNS(Node::SVG_NAMESPACE, 'svg')[0];
// Parser doesn't parse xmlns prefixed attributes except xlink, so let's add one manually instead to test coercion.
$svg->setAttributeNS(Node::XMLNS_NAMESPACE, 'xmlns:poop💩', 'https://poop💩.poop');
$this->assertSame(Node::SVG_NAMESPACE, $svg->getAttributeNS(Node::XMLNS_NAMESPACE, 'xmlns'));
$this->assertSame(Node::XLINK_NAMESPACE, $svg->getAttributeNS(Node::XMLNS_NAMESPACE, 'xlink'));
$this->assertSame('https://poop💩.poop', $svg->getAttributeNS(Node::XMLNS_NAMESPACE, 'poop💩'));
}
}

2
tests/cases/TestNode.php

@ -289,7 +289,6 @@ class TestNode extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getInnerNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
@ -864,7 +863,6 @@ class TestNode extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getInnerNode
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has

1
tests/phpunit.dist.xml

@ -18,6 +18,7 @@
<testsuite name="DOM">
<file>cases/TestDocument.php</file>
<file>cases/TestDocumentOrElement.php</file>
<file>cases/TestElement.php</file>
<file>cases/TestNode.php</file>
</testsuite>
</testsuites>

Loading…
Cancel
Save