Modern DOM library written in PHP for HTML documents
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

151 lines
5.2 KiB

<?php
declare(strict_types=1);
namespace dW\HTML5;
class DOM {
public $document = null;
public $implementation = null;
// Instance used to pass around the implementation and the document. PHP's DOM
// cannot append a DOCTYPE to a DOMDocument, so the document must be created
// when the DOCTYPE is. This creates a problem where the Parser sometimes needs
// an implementation before the TreeBuilder is initiated.
public function __construct($document = null) {
if (is_null($document)) {
$this->implementation = new \DOMImplementation();
return;
}
if (!$document instanceof \DOMDocument) {
throw new Exception(Exception::DOM_DOMDOCUMENT_EXPECTED, gettype($document));
}
$this->document = $document;
}
public static function getAncestor(mixed $needle, \DOMElement $context): \DOMElement {
return static::ancestor($needle, $context, true);
}
public static function hasAncestor(mixed $needle, \DOMElement $context): bool {
return static::ancestor($needle, $context, false);
}
public static function getDescendant(mixed $needle, \DOMElement $context): \DOMNode {
return static::descendant($needle, $context, true);
}
public static function hasDescendant(mixed $needle, \DOMElement $context): bool {
return static::descendant($needle, $context, false);
}
public static function isMathMLTextIntegrationPoint(\DOMElement $node): bool {
return (
$node->namespaceURI === Parser::MATHML_NAMESPACE && (
$node->nodeName === 'mi' || $node->nodeName === 'mo' || $node->nodeName === 'mn' || $node->nodeName === 'ms' || $node->nodeName === 'mtext'
)
);
}
public static function isHTMLIntegrationPoint(\DOMElement $node): bool {
$encoding = strtolower($node->getAttribute('encoding'));
return ((
$node->namespaceURI === Parser::MATHML_NAMESPACE &&
$node->nodeName === 'annotation-xml' && (
$encoding === 'text/html' || $encoding === 'application/xhtml+xml'
)
) || (
$node->namespaceURI === Parser::SVG_NAMESPACE && (
$node->nodeName === 'foreignObject' || $node->nodeName === 'desc' || $node->nodeName === 'title'
)
)
);
}
public static function fixIdAttributes(\DOMDocument $dom) {
// TODO: Accept DOMDocumentFragment, append it to a document, fix shit, and
// then poop out a fragment so selecting id attributes works on fragments.
// Fix id attributes so they may be selected by the DOM. Fix the PHP id attribute
// bug. Allows DOMDocument->getElementById() to work on id attributes.
$dom->relaxNGValidateSource('<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<start>
<element>
<anyName/>
<ref name="anythingID"/>
</element>
</start>
<define name="anythingID">
<zeroOrMore>
<choice>
<element>
<anyName/>
<ref name="anythingID"/>
</element>
<attribute name="id"><data type="ID"/></attribute>
<zeroOrMore><attribute><anyName/></attribute></zeroOrMore>
<text/>
</choice>
</zeroOrMore>
</define>
</grammar>');
$dom->normalize();
return $dom;
}
protected static function ancestor(mixed $needle, \DOMElement $context, bool $returnNode = true) {
while ($context = $context->parentNode) {
$result = static::compare($needle, $context);
if (!is_null($result)) {
return ($returnNode === true) ? $result : true;
}
}
return ($returnNode === true) ? null : false;
}
protected static function compare(mixed $needle, \DOMNode $context): \DOMNode {
if (is_string($needle)) {
if ($context instanceof \DOMElement && $context->nodeName == $needle) {
return $context;
}
} elseif ($needle instanceof \DOMNode) {
if ($context->isSameNode($needle)) {
return $context;
}
} elseif ($needle instanceof \Closure) {
if ($needle($context) === true) {
return $context;
}
} else {
throw new Exception(Exception::DOM_DOMELEMENT_STRING_OR_CLOSURE_EXPECTED, gettype($needle));
}
return null;
}
protected static function descendant(mixed $needle, \DOMElement $context, bool $returnNode = true): \DOMNode {
if ($context->hasChildNodes() === false) {
return ($returnNode === true) ? null : false;
}
$context = $context->firstChild;
do {
$result = static::compare($needle, $context);
if (!is_null($result)) {
return ($returnNode === true) ? $result : true;
}
$result = static::descendant($needle, $context);
if (!is_null($result)) {
return ($returnNode === true) ? $result : true;
}
} while ($context = $context->nextSibling);
return ($returnNode === true) ? null : false;
}
}