namespaceURI ?? Parser::HTML_NAMESPACE, [Parser::HTML_NAMESPACE, Parser::SVG_NAMESPACE, Parser::MATHML_NAMESPACE])) { $tagName = self::uncoerceName($n->localName); } else { $tagName = self::uncoerceName($n->tagName); } # Append a U+003C LESS-THAN SIGN character (<), followed by tagname. $s .= "<$tagName"; # If current node's is value is not null, and the element does # not have an is attribute in its attribute list, then # append the string " is="", followed by current node's is # value escaped as described below in attribute mode, # followed by a U+0022 QUOTATION MARK character ("). // DEVIATION: We don't support custom elements # For each attribute that the element has, append a # U+0020 SPACE character, the attribute's serialized name as # described below, a U+003D EQUALS SIGN character (=), a # U+0022 QUOTATION MARK character ("), the attribute's # value, escaped as described below in attribute mode, and # a second U+0022 QUOTATION MARK character ("). foreach ($n->attributes as $a) { # An attribute's serialized name for the purposes of the previous # paragraph must be determined as follows: # If the attribute has no namespace if ($a->namespaceURI === null) { # The attribute's serialized name is the attribute's local name. $name = self::uncoerceName($a->localName); } # If the attribute is in the XML namespace elseif ($a->namespaceURI === Parser::XML_NAMESPACE) { # The attribute's serialized name is the string "xml:" followed # by the attribute's local name. $name = "xml:".self::uncoerceName($a->localName); } # If the attribute is in the XMLNS namespace... elseif ($a->namespaceURI === Parser::XMLNS_NAMESPACE) { # ... and the attribute's local name is xmlns if ($a->localName === "xmlns") { # The attribute's serialized name is the string "xmlns". $name = "xmlns"; } # ... and the attribute's local name is not xmlns else { # The attribute's serialized name is the string "xmlns:" # followed by the attribute's local name. $name = "xmlns:".self::uncoerceName($a->localName); } } # If the attribute is in the XLink namespace elseif ($a->namespaceURI === Parser::XLINK_NAMESPACE) { # The attribute's serialized name is the string "xlink:" # followed by the attribute's local name. $name = "xlink:".self::uncoerceName($a->localName); } # If the attribute is in some other namespace else { # The attribute's serialized name is the attribute's qualified name. $name = ($a->prefix !== "") ? $a->prefix.":".$a->name : $a->name; } $value = self::escapeString((string) $a->value, true); $s .= " $name=\"$value\""; } # Append a U+003E GREATER-THAN SIGN character (>). $s .= ">"; # If current node serializes as void, then continue on to the # next child node at this point. # Append the value of running the HTML fragment serialization # algorithm on the current node element (thus recursing into # this algorithm for that element), followed by a # U+003C LESS-THAN SIGN character (<), a U+002F SOLIDUS # character (/), tagname again, and finally a # U+003E GREATER-THAN SIGN character (>). if (($n->namespaceURI ?? Parser::HTML_NAMESPACE) !== Parser::HTML_NAMESPACE || !in_array($tagName, self::VOID_ELEMENTS)) { # If the node is a template element, then let the node instead # be the template element's template contents # (a DocumentFragment node). if ( ($n->namespaceURI ?? Parser::HTML_NAMESPACE) === Parser::HTML_NAMESPACE && $n->tagName === "template" && property_exists($n, "content") && $n->content instanceof \DOMDocumentFragment ) { // NOTE: Treat template content as any other document // fragment and just invoke the inner serializer $s .= self::serializeInner($n->content).""; } elseif ($n->hasChildNodes()) { // If the element has children, store its tag name and // continue the loop with its first child; its end // tag will be written out further down $stack[] = $tagName; $n = $n->firstChild; continue; } else { // Otherwise just append the end tag now $s .= ""; } } } # If current node is a Text node elseif ($n instanceof \DOMText) { # If the parent of current node is a style, script, xmp, # iframe, noembed, noframes, or plaintext element, or # if the parent of current node is a noscript element # and scripting is enabled for the node, then append # the value of current node's data IDL attribute literally. $p = $n->parentNode; if ($p instanceof \DOMElement && ($p->namespaceURI ?? Parser::HTML_NAMESPACE) === Parser::HTML_NAMESPACE && in_array($p->tagName, self::RAWTEXT_ELEMENTS)) { // NOTE: scripting is assumed not to be enabled $s .= $n->data; } # Otherwise, append the value of current node's data IDL attribute, escaped as described below. else { $s .= self::escapeString($n->data); } } # If current node is a Comment elseif ($n instanceof \DOMComment) { # Append the literal string "" (U+002D HYPHEN-MINUS, U+002D HYPHEN-MINUS, # U+003E GREATER-THAN SIGN). $s .= ""; } # If current node is a ProcessingInstruction elseif ($n instanceof \DOMProcessingInstruction) { # Append the literal string "). $s .= "target)." ".$n->data.">"; } # If current node is a DocumentType elseif ($n instanceof \DOMDocumentType) { # Append the literal string "" (U+003E GREATER-THAN SIGN). $s .= "name).">"; } // NOTE: Documents and document fragments have no outer content, // so we can just serialize the inner content elseif ($n instanceof \DOMDocument || $n instanceof \DOMDocumentFragment) { return self::serializeInner($n); } else { throw new Exception(Exception::UNSUPPORTED_NODE_TYPE, [get_class($n)]); } // If the current node has no more siblings, go up the tree till a // sibling is found or we've reached the original node while (!$n->nextSibling && $stack) { // Write out the stored end tag each time we go up the tree $tagName = array_pop($stack); $s .= ""; $n = $n->parentNode; } $n = $n->nextSibling; } while ($stack); // Loop until we have traversed the subtree of the target node in full return $s; } /** Serializes the children of an HTML DOM node to a string. This is equivalent to the innerHTML getter * * @param \DOMDocument|\DOMElement|\DOMDocumentFragment $node The node to serialize */ public static function serializeInner(\DOMNode $node): string { # Let s be a string, and initialize it to the empty string. $s = ""; if ($node instanceof \DOMElement && ($node->namespaceURI ?? Parser::HTML_NAMESPACE) === Parser::HTML_NAMESPACE) { # If the node serializes as void, then return the empty string. if (in_array($node->tagName, self::VOID_ELEMENTS)) { return ""; } # If the node is a template element, then let the node instead # be the template element's template contents # (a DocumentFragment node). elseif ($node->tagName === "template" && property_exists($node, "content") && $node->content instanceof \DOMDocumentFragment) { // NOTE: template elements won't necessarily have a content // property because PHP's DOM does not support this natively $node = $node->content; } } if ($node instanceof \DOMElement || $node instanceof \DOMDocument || $node instanceof \DOMDocumentFragment) { # For each child node of the node, in tree order, run the following steps: // NOTE: the steps in question are implemented in the "serialize" routine foreach ($node->childNodes as $n) { $s .= self::serialize($n); } } else { throw new Exception(Exception::UNSUPPORTED_NODE_TYPE, [get_class($node)]); } return $s; } }