Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
9.52% |
4 / 42 |
CRAP | |
20.62% |
33 / 160 |
Document | |
0.00% |
0 / 1 |
|
9.52% |
4 / 42 |
3612.66 | |
20.62% |
33 / 160 |
__rereference_doctype_and_documentElement | |
0.00% |
0 / 1 |
4.03 | |
87.50% |
7 / 8 |
|||
__construct | |
0.00% |
0 / 1 |
4.02 | |
90.00% |
9 / 10 |
|||
_templateDoc | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
getNodeType | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getNodeName | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getCharacterSet | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getCharset | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getInputEncoding | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getImplementation | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getDocumentURI | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getURL | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getCompatMode | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 1 |
|||
getContentType | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getDoctype | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getDocumentElement | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
createTextNode | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
createComment | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
createDocumentFragment | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
createProcessingInstruction | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 3 |
|||
createAttribute | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 5 |
|||
createAttributeNS | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 6 |
|||
createElement | |
0.00% |
0 / 1 |
2.06 | |
75.00% |
3 / 4 |
|||
createElementNS | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 8 |
|||
_createElementNS | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 4 |
|||
adoptNode | |
0.00% |
0 / 1 |
7.19 | |
55.56% |
5 / 9 |
|||
importNode | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
insertBefore | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
replaceChild | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
removeChild | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
cloneNode | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 8 |
|||
getElementById | |
0.00% |
0 / 1 |
4.12 | |
50.00% |
3 / 6 |
|||
_isHTMLDocument | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 5 |
|||
_subclass_cloneNodeShallow | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 4 |
|||
_subclass_isEqualNode | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
__add_to_id_table | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 7 |
|||
__remove_from_id_table | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 8 |
|||
__mutate_value | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 6 |
|||
__mutate_attr | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 6 |
|||
__mutate_remove_attr | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 6 |
|||
__mutate_remove | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 6 |
|||
__mutate_insert | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 6 |
|||
__mutate_move | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 5 |
<?php | |
declare( strict_types = 1 ); | |
// @phan-file-suppress PhanParamSignatureMismatch | |
// @phan-file-suppress PhanTypeMismatchReturnNullable | |
// @phan-file-suppress PhanUndeclaredConstant | |
// @phan-file-suppress PhanUndeclaredFunction | |
// @phan-file-suppress PhanUndeclaredMethod | |
// @phan-file-suppress PhanUndeclaredProperty | |
// phpcs:disable Generic.NamingConventions.CamelCapsFunctionName.MethodDoubleUnderscore | |
// phpcs:disable Generic.NamingConventions.CamelCapsFunctionName.ScopeNotCamelCaps | |
// phpcs:disable Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase | |
// phpcs:disable MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic | |
// phpcs:disable MediaWiki.Commenting.FunctionComment.WrongStyle | |
// phpcs:disable MediaWiki.Commenting.PropertyDocumentation.MissingDocumentationPublic | |
// phpcs:disable MediaWiki.Commenting.PropertyDocumentation.WrongStyle | |
namespace Wikimedia\Dodo; | |
/****************************************************************************** | |
* Document.php | |
* ------------ | |
* Implements a Document. It is a somewhat complicated class, | |
* having a number of internal bookkeeping needs. | |
*/ | |
/** | |
* The Document class. Note that there is another class called | |
* HTMLDocument with an extended interface, only for Documents | |
* that contain HTML. Rather than use a separate class, we load | |
* it onto the Document class and switch behavior using a flag | |
* in the constructor. Not sure about that, but it probably | |
* things easier. | |
*/ | |
/* | |
* Each document has an associated | |
* encoding (an encoding), | |
* content type (a string), | |
* URL (a URL), | |
* origin (an origin), | |
* type ("xml" or "html"), and | |
* mode ("no-quirks", "quirks", or "limited-quirks"). | |
*/ | |
/* | |
* Each document has an associated encoding (an encoding), content type | |
* (a string), URL (a URL), origin (an origin), type ("xml" or "html"), | |
* and mode ("no-quirks", "quirks", or "limited-quirks"). | |
* | |
* Unless stated otherwise, a document’s encoding is the utf-8 encoding, | |
* content type is "application/xml", URL is "about:blank", origin is an | |
* opaque origin, type is "xml", and its mode is "no-quirks". | |
* | |
* A document is said to be an XML document if its type is "xml", and an | |
* HTML document otherwise. Whether a document is an HTML document or an | |
* XML document affects the behavior of certain APIs. | |
* | |
* A document is said to be in no-quirks mode if its mode is "no-quirks", | |
* quirks mode if its mode is "quirks", and limited-quirks mode if its mode | |
* is "limited-quirks". | |
*/ | |
class Document extends Node implements \Wikimedia\IDLeDOM\Document { | |
// DOM mixins | |
use DocumentOrShadowRoot; | |
use NonElementParentNode; | |
use ParentNode; | |
use XPathEvaluatorBase; | |
// Stub out methods not yet implemented. | |
use \Wikimedia\IDLeDOM\Stub\Document; | |
use UnimplementedTrait; | |
// Helper functions from IDLeDOM | |
use \Wikimedia\IDLeDOM\Helper\Document; | |
/********************************************************************** | |
* Properties that are for internal use by this library | |
*/ | |
/* | |
* DEVELOPERS NOTE | |
* Certain APIs are only defined when the Document contains HTML | |
* (rather than XML). Rather than implement a separate HTMLDocument | |
* class, we simply store the type of Document in a variable. | |
* | |
* Outside callers will use the isHTMLDocument() method, which | |
* makes use of this value. | |
*/ | |
protected $__type; | |
/* | |
* DEVELOPERS NOTE: | |
* Used to assign the document index to Nodes on ADOPTION. | |
*/ | |
protected $__document_index_next = 2; | |
/* | |
* DEVELOPERS NOTE: | |
* Document's aren't going to adopt themselves, so we set this to a default of 1. | |
*/ | |
// XXX PORT FIXME this overrides a property of Node! | |
//protected $__document_index = 1; | |
/** | |
* Element nodes having an 'id' attribute are stored in this | |
* table, indexed by their 'id' value. | |
* | |
* This is how getElementById performs its fast lookup. | |
* | |
* The table must be mutated on: | |
* - Element insertion | |
* - Element removal | |
* - mutation of 'id' attribute | |
* on an inserted Element. | |
* | |
* @var array | |
*/ | |
private $__id_to_element = []; | |
/********************************************************************** | |
* Properties that appear in DOM-LS | |
*/ | |
/* | |
* Part of Node parent class | |
*/ | |
public $_ownerDocument = null; | |
/* | |
* Part of Document class | |
*/ | |
public const _characterSet = 'UTF-8'; | |
public $_encoding = 'UTF-8'; | |
public $_type = 'xml'; | |
public $_contentType = 'application/xml'; | |
public $_URL = 'about:blank'; | |
public $_origin = null; | |
public $_compatMode = 'no-quirks'; | |
/* | |
* ANNOYING LIVE REFERENCES | |
* | |
* The below are slightly annoying because we must keep them updated | |
* whenever there is mutation to the children of the Document. | |
*/ | |
/* | |
* Reference to the first DocumentType child, in document order. | |
* Null if no such child exists. | |
*/ | |
public $_doctype = null; | |
/* | |
* Reference to the first Element child, in document order. | |
* Null if no such child exists. | |
*/ | |
public $_documentElement = null; | |
/** | |
* Called when a child is inserted or removed from the document. | |
* Keeps the above references live. | |
*/ | |
private function __rereference_doctype_and_documentElement(): void { | |
$this->_doctype = null; | |
$this->_documentElement = null; | |
for ( $n = $this->getFirstChild(); $n !== null; $n = $n->getNextSibling() ) { | |
if ( $n->getNodeType() === Node::DOCUMENT_TYPE_NODE ) { | |
$this->_doctype = $n; | |
} elseif ( $n->getNodeType() === Node::ELEMENT_NODE ) { | |
$this->_documentElement = $n; | |
} | |
} | |
} | |
/* TODO: These three amigos. */ | |
public $_implementation; | |
public $_readyState; | |
public $__mutation_handler = null; | |
// USED EXCLUSIVELY IN htmlelts.js to make <TEMPLATE> | |
private $_templateDocCache; | |
/** | |
* @param string $type | |
* @param ?string $url | |
*/ | |
public function __construct( string $type = "xml", ?string $url = null ) { | |
parent::__construct(); | |
/** DOM-LS */ | |
/* Having an HTML Document affects some APIs */ | |
if ( $type === 'html' ) { | |
$this->_contentType = 'text/html'; | |
$this->__type = 'html'; | |
} | |
/* DOM-LS: used by the documentURI and URL method */ | |
if ( $url !== null ) { | |
$this->_URL = $url; | |
} | |
/* DOM-LS: DOMImplementation associated with document */ | |
$this->_implementation = new DOMImplementation( $this ); | |
/** JUNK */ | |
$this->_readyState = "loading"; | |
/* USED EXCLUSIVELY IN htmlelts.js to make <TEMPLATE> */ | |
$this->_templateDocCache = null; | |
} | |
/* USED EXCLUSIVELY IN htmlelts.js to make <TEMPLATE> */ | |
public function _templateDoc() { | |
if ( !$this->_templateDocCache ) { | |
/* "associated inert template document" */ | |
$newDoc = new Document( $this->isHTML, $this->_address ); | |
$this->_templateDocCache = $newDoc->_templateDocCache = $newDoc; | |
} | |
return $this->_templateDocCache; | |
} | |
/* | |
* Accessors for read-only properties defined in Document | |
*/ | |
/** | |
* @copydoc Node::getNodeType() | |
* @inheritDoc | |
*/ | |
public function getNodeType() : int { | |
return Node::DOCUMENT_NODE; | |
} | |
/** | |
* @copydoc Node::getNodeName() | |
* @inheritDoc | |
*/ | |
final public function getNodeName() : string { | |
return "#document"; | |
} | |
/** | |
* @copydoc Wikimedia\IDLeDOM\Document::getCharacterSet() | |
* @inheritDoc | |
*/ | |
public function getCharacterSet(): string { | |
return $this->_characterSet; | |
} | |
/** @return string */ | |
public function getCharset(): string { | |
return $this->_characterSet; /* historical alias */ | |
} | |
/** @return string */ | |
public function getInputEncoding(): string { | |
return $this->_characterSet; /* historical alias */ | |
} | |
/** @return DOMImplementation */ | |
public function getImplementation(): DOMImplementation { | |
return $this->_implementation; | |
} | |
/** @inheritDoc */ | |
public function getDocumentURI() : string { | |
return $this->_URL; | |
} | |
/** @return string */ | |
public function getURL() : string { | |
return $this->_URL; /** Alias for HTMLDocuments */ | |
} | |
/** @inheritDoc */ | |
public function getCompatMode() : string { | |
return $this->_compatMode === "quirks" ? "BackCompat" : "CSS1Compat"; | |
} | |
/** @inheritDoc */ | |
public function getContentType(): string { | |
return $this->_contentType; | |
} | |
/** @inheritDoc */ | |
public function getDoctype() { | |
return $this->_doctype; | |
} | |
/** @inheritDoc */ | |
public function getDocumentElement() { | |
return $this->_documentElement; | |
} | |
/* | |
* NODE CREATION | |
*/ | |
/** @inheritDoc */ | |
public function createTextNode( string $data ) { | |
return new Text( $this, $data ); | |
} | |
/** @inheritDoc */ | |
public function createComment( string $data ) { | |
return new Comment( $this, $data ); | |
} | |
/** @inheritDoc */ | |
public function createDocumentFragment() { | |
return new DocumentFragment( $this ); | |
} | |
/** @inheritDoc */ | |
public function createProcessingInstruction( string $target, string $data ) { | |
if ( !WhatWG::is_valid_xml_name( $target ) || strpos( $data, '?' . '>' ) !== false ) { | |
Util::error( 'InvalidCharacterError' ); | |
} | |
return new ProcessingInstruction( $this, $target, $data ); | |
} | |
/** @inheritDoc */ | |
public function createAttribute( string $localName ) { | |
if ( !WhatWG::is_valid_xml_name( $localName ) ) { | |
Util::error( 'InvalidCharacterError' ); | |
} | |
if ( $this->isHTMLDocument() ) { | |
$localName = Util::ascii_to_lowercase( $localName ); | |
} | |
return new Attr( null, $localName, null, null, '' ); | |
} | |
/** @inheritDoc */ | |
public function createAttributeNS( ?string $ns, string $qname ) { | |
if ( $ns === '' ) { | |
$ns = null; /* spec */ | |
} | |
$lname = null; | |
$prefix = null; | |
WhatWG::validate_and_extract( $ns, $qname, $prefix, $lname ); | |
return new Attr( null, $lname, $prefix, $ns, '' ); | |
} | |
/** @inheritDoc */ | |
public function createElement( string $lname, $options = null ) { | |
$lname = strval( $lname ); | |
if ( !WhatWG::is_valid_xml_name( $lname ) ) { | |
error( "InvalidCharacterError" ); | |
} | |
/* | |
* Per spec, namespace should be HTML namespace if | |
* "context object is an HTML document or context | |
* object's content type is "application/xhtml+xml", | |
* and null otherwise. | |
*/ | |
return new Element( $this, $lname, null, null ); | |
// if ($this->_contentType === 'text/html') { | |
//if (!ctype_lower($lname)) { | |
//$lname = Util::ascii_to_lowercase($lname); | |
//} | |
//[> TODO STUB <] | |
////return Dodo\html\createElement($this, $lname, NULL); | |
//} else if ($this->_contentType === 'application/xhtml+xml') { | |
//[> TODO STUB <] | |
////return Dodo\html\createElement($this, $lname, NULL); | |
//} else { | |
//return new Element($this, $lname, NULL, NULL); | |
//} | |
} | |
/** @inheritDoc */ | |
public function createElementNS( ?string $ns, string $qname, $options = null ) { | |
/* Convert parameter types according to WebIDL */ | |
if ( $ns === null || $ns === "" ) { | |
$ns = null; | |
} else { | |
$ns = strval( $ns ); | |
} | |
$qname = strval( $qname ); | |
$lname = null; | |
$prefix = null; | |
WhatWG::validate_and_extract( $ns, $qname, $prefix, $lname ); | |
return $this->_createElementNS( $lname, $ns, $prefix ); | |
} | |
/* | |
* This is used directly by HTML parser, which allows it to create | |
* elements with localNames containing ':' and non-default namespaces | |
*/ | |
public function _createElementNS( $lname, $ns, $prefix ) { | |
if ( $ns === Util::NAMESPACE_HTML ) { | |
/* TODO STUB */ | |
//return Dodo\html\createElement($this, $lname, $prefix); | |
} elseif ( $ns === Util::NAMESPACE_SVG ) { | |
/* TODO STUB */ | |
//return svg\createElement($this, $lname, $prefix); | |
} else { | |
return new Element( $this, $lname, $ns, $prefix ); | |
} | |
} | |
/********************************************************************* | |
* MUTATION | |
*/ | |
/** | |
* Adopt the subtree rooted at Node into this Document. | |
* | |
* This means setting ownerDocument of each node in the subtree to point to $this. | |
* | |
* No insertion is performed, but if Node is inserted into another Document, | |
* it will be removed. | |
* | |
* @inheritDoc | |
*/ | |
public function adoptNode( $node ) { | |
if ( $node->getNodeType() === Node::DOCUMENT_NODE ) { | |
// A Document cannot adopt another Document. Throw a "NotSupported" exception. | |
Util::error( "NotSupported" ); | |
} | |
if ( $node->getNodeType() === Node::ATTRIBUTE_NODE ) { | |
// Attributes do not have an ownerDocument, so do nothing. | |
return $node; | |
} | |
if ( $node->getParentNode() ) { | |
/* | |
* If the Node is currently inserted in some Document, remove it. | |
* | |
* TODO: | |
* Why is this not using $node->__is_rooted()? | |
* Is this diagnostic for rooted-ness? Why | |
* doesn't __is_rooted() just do this? | |
*/ | |
$node->getParentNode()->removeChild( $node ); | |
} | |
if ( $node->_ownerDocument !== $this ) { | |
/* | |
* If the Node is not currently connected to this Document, | |
* then recursively set the ownerDocument. | |
* | |
* (The recursion skips the above checks because they don't make sense.) | |
*/ | |
$node->__set_owner( $this ); | |
} | |
/* DOM-LS requires this return $node */ | |
return $node; | |
} | |
/** | |
* Clone and then adopt either $node or, if $deep === true, the entire subtree | |
* rooted at $node, into the Document. | |
* | |
* By default, only $node will be cloned. | |
* | |
* @inheritDoc | |
*/ | |
public function importNode( $node, bool $deep = false ) { | |
return $this->adoptNode( $node->cloneNode( $deep ) ); | |
} | |
/* | |
* The following three methods are a simple extension of the Node methods, with an | |
* added call to update the doctype and documentElement references that are specific | |
* to the Document interface. | |
* | |
* Note: appendChild is not extended, because it calls insertBefore. | |
*/ | |
/** | |
* @inheritDoc | |
*/ | |
public function insertBefore( $node, $refChild ) { | |
$ret = parent::insertBefore( $node, $refChild ); | |
$this->__rereference_doctype_and_documentElement(); | |
return $ret; | |
} | |
/** | |
* @inheritDoc | |
*/ | |
public function replaceChild( $node, $child ) { | |
$ret = parent::replaceChild( $node, $child ); | |
$this->__rereference_doctype_and_documentElement(); | |
return $ret; | |
} | |
/** | |
* @inheritDoc | |
*/ | |
public function removeChild( $child ) { | |
$ret = parent::removeChild( $child ); | |
$this->__rereference_doctype_and_documentElement(); | |
return $ret; | |
} | |
/** | |
* Clone this Document, import nodes, and call __update_document_state | |
* | |
* extends Node::cloneNode() | |
* spec DOM-LS | |
* | |
* NOTE: | |
* 1. What a tangled web we weave | |
* 2. With Document nodes, we need to take the additional step of | |
* calling importNode() to bring copies of child nodes into this | |
* document. | |
* 3. We also need to call _updateDocTypeElement() | |
* | |
* @inheritDoc | |
*/ | |
public function cloneNode( bool $deep = false ) { | |
/* Make a shallow clone */ | |
$clone = parent::cloneNode( false ); | |
if ( $deep === false ) { | |
/* Return shallow clone */ | |
$clone->__rereference_doctype_and_documentElement(); | |
return $clone; | |
} | |
/* Clone children too */ | |
for ( $n = $this->getFirstChild(); $n !== null; $n = $n->getNextSibling() ) { | |
$clone->appendChild( $clone->importNode( $n, true ) ); | |
} | |
$clone->__rereference_doctype_and_documentElement(); | |
return $clone; | |
} | |
/* | |
* Query methods | |
*/ | |
/** | |
* Fetch an Element in this Document with a given ID value | |
* | |
* spec DOM-LS | |
* | |
* NOTE | |
* In the spec, this is actually the sole method of the | |
* NonElementParentNode mixin. | |
* | |
* @inheritDoc | |
*/ | |
public function getElementById( string $id ) { | |
$n = $this->__id_to_element[$id] ?? null; | |
if ( $n === null ) { | |
return null; | |
} | |
if ( $n instanceof MultiId ) { | |
/* there was more than one element with this id */ | |
return $n->get_first(); | |
} | |
return $n; | |
} | |
/* | |
* Utility methods extending normal DOM behavior | |
*/ | |
/** | |
* TODO Where does this fit in? | |
* | |
* @return bool | |
*/ | |
public function _isHTMLDocument(): bool { | |
if ( $this->__type === 'html' ) { | |
$elt = $this->getDocumentElement(); | |
if ( $elt !== null && $elt->_isHTMLElement() ) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Delegated method called by Node::cloneNode() | |
* Performs the shallow clone branch. | |
* | |
* spec Dodo | |
* | |
* @return Document with same invocation as $this | |
*/ | |
protected function _subclass_cloneNodeShallow(): Node { | |
$shallow = new Document( $this->isHTMLDocument(), $this->_address ); | |
$shallow->_mode = $this->_mode; | |
$shallow->_contentType = $this->_contentType; | |
return $shallow; | |
} | |
/** | |
* Delegated method called by Node::isEqualNode() | |
* | |
* spec DOM-LS | |
* | |
* NOTE: | |
* Any two Documents are shallowly equal, since equality | |
* is determined by their children; this will be tested by | |
* Node::isEqualNode(), so just return true. | |
* | |
* @param Node|null $other to compare | |
* @return bool True (two Documents are always equal) | |
*/ | |
protected function _subclass_isEqualNode( Node $other = null ): bool { | |
return true; | |
} | |
/* | |
* Internal book-keeping tables: | |
* | |
* Documents manage 2: the node table, and the id table. | |
* <full explanation goes here> | |
* | |
* Called by Node::__root() and Node::__uproot() | |
* | |
* See, we are adding, and removing, but never using...? | |
*/ | |
/** | |
* @param string $id | |
* @param Element $elt | |
*/ | |
public function __add_to_id_table( string $id, Element $elt ): void { | |
if ( !isset( $this->__id_to_element[$id] ) ) { | |
$this->__id_to_element[$id] = $elt; | |
} else { | |
if ( !( $this->__id_to_element[$id] instanceof MultiId ) ) { | |
$this->__id_to_element[$id] = new MultiId( | |
$this->__id_to_element[$id] | |
); | |
} | |
$this->__id_to_element[$id]->add( $elt ); | |
} | |
} | |
/** | |
* @param string $id | |
* @param Element $elt | |
*/ | |
public function __remove_from_id_table( string $id, Element $elt ): void { | |
if ( isset( $this->__id_to_element[$id] ) ) { | |
if ( $this->__id_to_element[$id] instanceof MultiId ) { | |
$item = $this->__id_to_element[$id]; | |
$item->del( $elt ); | |
// convert back to a single node | |
if ( $item->length === 1 ) { | |
$this->__id_to_element[$id] = $item->downgrade(); | |
} | |
} else { | |
unset( $this->__id_to_element[$id] ); | |
} | |
} | |
} | |
/* | |
* MUTATION STUFF | |
* TODO: The mutationHandler checking | |
* | |
* NOTES: | |
* Whenever a document is updated, these mutation functions | |
* are called, e.g. Node::_insertOrReplace. | |
* | |
* To attach a handler to watch how a document is mutated, | |
* you set the handler in DOMImplementation. It will be | |
* provided with a single argument, an array. | |
* | |
* See usage below. | |
* | |
* These mutations have nothing to do with MutationEvents or | |
* MutationObserver, which is confusing. | |
*/ | |
/* | |
* Implementation-specific function. Called when a text, comment, | |
* or pi value changes. | |
*/ | |
public function __mutate_value( $node ) { | |
if ( $this->__mutation_handler ) { | |
$this->__mutation_handler( [ | |
"type" => MUTATE_VALUE, | |
"target" => $node, | |
"data" => $node->getData() | |
] ); | |
} | |
} | |
/* | |
* Invoked when an attribute's value changes. Attr holds the new | |
* value. oldval is the old value. Attribute mutations can also | |
* involve changes to the prefix (and therefore the qualified name) | |
*/ | |
public function __mutate_attr( $attr, $oldval ) { | |
if ( $this->__mutation_handler ) { | |
$this->__mutation_handler( [ | |
"type" => MUTATE_ATTR, | |
"target" => $attr->getOwnerElement(), | |
"attr" => $attr | |
] ); | |
} | |
} | |
/* Used by removeAttribute and removeAttributeNS for attributes. */ | |
public function __mutate_remove_attr( $attr ) { | |
if ( $this->__mutation_handler ) { | |
$this->__mutation_handler( [ | |
"type" => MUTATE_REMOVE_ATTR, | |
"target" => $attr->getOwnerElement(), | |
"attr" => $attr | |
] ); | |
} | |
} | |
/* | |
* Called by Node.removeChild, etc. to remove a rooted element from | |
* the tree. Only needs to generate a single mutation event when a | |
* node is removed, but must recursively mark all descendants as not | |
* rooted. | |
*/ | |
public function __mutate_remove( $node ) { | |
/* Send a single mutation event */ | |
if ( $this->__mutation_handler ) { | |
$this->__mutation_handler( [ | |
"type" => MUTATE_REMOVE, | |
"target" => $node->getParentNode(), | |
"node" => $node | |
] ); | |
} | |
} | |
/* | |
* Called when a new element becomes rooted. It must recursively | |
* generate mutation events for each of the children, and mark | |
* them all as rooted. | |
* | |
* Called in Node::_insertOrReplace. | |
*/ | |
public function __mutate_insert( $node ) { | |
/* Send a single mutation event */ | |
if ( $this->__mutation_handler ) { | |
$this->__mutation_handler( [ | |
"type" => MUTATE_INSERT, | |
"target" => $node->getParentNode(), | |
"node" => $node | |
] ); | |
} | |
} | |
/* | |
* Called when a rooted element is moved within the document | |
*/ | |
public function __mutate_move( $node ) { | |
if ( $this->__mutation_handler ) { | |
$this->__mutation_handler( [ | |
"type" => MUTATE_MOVE, | |
"target" => $node | |
] ); | |
} | |
} | |
} |