J. King
3 years ago
20 changed files with 1926 additions and 135 deletions
@ -0,0 +1,50 @@ |
|||
<?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\Parser; |
|||
|
|||
use MensBeam\HTML\Parser; |
|||
|
|||
trait AttributeSetter { |
|||
public function elementSetAttribute(\DOMElement $element, ?string $namespaceURI, string $qualifiedName, string $value): void { |
|||
if ($namespaceURI === Parser::XMLNS_NAMESPACE) { |
|||
// NOTE: We create attribute nodes so that xmlns attributes |
|||
// don't get lost; otherwise they cannot be serialized. |
|||
// Furthermore we create the attribute node in a temporary |
|||
// document to avoid some related PHP bugs |
|||
$d = new \DOMDocument; |
|||
$d->appendChild($d->createElement("html")); |
|||
try { |
|||
$a = $d->createAttributeNS($namespaceURI, $qualifiedName); |
|||
// @codeCoverageIgnoreStart |
|||
} catch (\DOMException $e) { |
|||
// The attribute name is invalid for XML 1.0 Second Edition |
|||
// Replace any offending characters with "UHHHHHH" where H are the |
|||
// uppercase hexadecimal digits of the character's code point |
|||
// NOTE: This case is never encountered by the parser |
|||
$qualifiedName = self::coerceName($qualifiedName, true); |
|||
$a = $d->createAttributeNS($namespaceURI, $qualifiedName); |
|||
} |
|||
// @codeCoverageIgnoreEnd |
|||
$a->value = self::escapeString($value, true); |
|||
$element->setAttributeNodeNS($element->ownerDocument->importNode($a)); |
|||
} else { |
|||
try { |
|||
$element->setAttributeNS($namespaceURI, $qualifiedName, $value); |
|||
} catch (\DOMException $e) { |
|||
// The attribute name is invalid for XML 1.0 Second Edition |
|||
// Replace any offending characters with "UHHHHHH" where H are the |
|||
// uppercase hexadecimal digits of the character's code point |
|||
$qualifiedName = self::coerceName($qualifiedName, ($namespaceURI !== null)); |
|||
$element->setAttributeNS($namespaceURI, $qualifiedName, $value); |
|||
$this->mangledAttributes = true; |
|||
} |
|||
if ($qualifiedName === "id" && $namespaceURI === null) { |
|||
$element->setIdAttribute($qualifiedName, true); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,232 @@ |
|||
<?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\Parser; |
|||
|
|||
use MensBeam\HTML\Parser; |
|||
|
|||
abstract class Serializer { |
|||
use NameCoercion; |
|||
|
|||
protected const VOID_ELEMENTS = ["basefont", "bgsound", "frame", "keygen", "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]; |
|||
protected const RAWTEXT_ELEMENTS = ["style", "script", "xmp", "iframe", "noembed", "noframes", "plaintext"]; |
|||
|
|||
/** Serializes an HTML DOM node to a string. This is equivalent to the outerHTML getter |
|||
* |
|||
* @param \DOMDocument|\DOMElement|\DOMText|\DOMComment|\DOMProcessingInstruction|\DOMDocumentFragment|\DOMDocumentType $node The node to serialize |
|||
*/ |
|||
public static function serializeOuter(\DOMNode $node): string { |
|||
$s = ""; |
|||
$stack = []; |
|||
$n = $node; |
|||
do { |
|||
# If current node is an Element |
|||
if ($n instanceof \DOMElement) { |
|||
# If current node is an element in the HTML namespace, |
|||
# the MathML namespace, or the SVG namespace, then let |
|||
# tagname be current node's local name. Otherwise, let |
|||
# tagname be current node's qualified name. |
|||
if (in_array($n->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)."</$tagName>"; |
|||
} 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 .= "</$tagName>"; |
|||
} |
|||
} |
|||
} |
|||
# 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+003C LESS-THAN SIGN, |
|||
# U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS, |
|||
# U+002D HYPHEN-MINUS), followed by the value of current |
|||
# node's data IDL attribute, followed by the literal |
|||
# string "-->" (U+002D HYPHEN-MINUS, U+002D HYPHEN-MINUS, |
|||
# U+003E GREATER-THAN SIGN). |
|||
$s .= "<!--".$n->data."-->"; |
|||
} |
|||
# If current node is a ProcessingInstruction |
|||
elseif ($n instanceof \DOMProcessingInstruction) { |
|||
# Append the literal string "<?" (U+003C LESS-THAN SIGN,
|
|||
# U+003F QUESTION MARK), followed by the value of |
|||
# current node's target IDL attribute, followed by a |
|||
# single U+0020 SPACE character, followed by the value |
|||
# of current node's data IDL attribute, followed by a |
|||
# single U+003E GREATER-THAN SIGN character (>). |
|||
$s .= "<?".self::uncoerceName($n->target)." ".$n->data.">";
|
|||
} |
|||
# If current node is a DocumentType |
|||
elseif ($n instanceof \DOMDocumentType) { |
|||
# Append the literal string "<!DOCTYPE" (U+003C LESS-THAN SIGN, |
|||
# U+0021 EXCLAMATION MARK, U+0044 LATIN CAPITAL LETTER D, |
|||
# U+004F LATIN CAPITAL LETTER O, U+0043 LATIN CAPITAL LETTER C, |
|||
# U+0054 LATIN CAPITAL LETTER T, U+0059 LATIN CAPITAL LETTER Y, |
|||
# U+0050 LATIN CAPITAL LETTER P, U+0045 LATIN CAPITAL LETTER E), |
|||
# followed by a space (U+0020 SPACE), followed by the value |
|||
# of current node's name IDL attribute, followed by the |
|||
# literal string ">" (U+003E GREATER-THAN SIGN). |
|||
$s .= "<!DOCTYPE ".trim($n->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 .= "</$tagName>"; |
|||
$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 "serializeOuter" routine |
|||
foreach ($node->childNodes as $n) { |
|||
$s .= self::serializeOuter($n); |
|||
} |
|||
} else { |
|||
throw new Exception(Exception::UNSUPPORTED_NODE_TYPE, [get_class($node)]); |
|||
} |
|||
return $s; |
|||
} |
|||
} |
@ -0,0 +1,258 @@ |
|||
<?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\Parser\Exception; |
|||
use MensBeam\HTML\Parser; |
|||
use MensBeam\HTML\Parser\AttributeSetter; |
|||
use MensBeam\HTML\Parser\NameCoercion; |
|||
use MensBeam\HTML\Parser\Serializer; |
|||
|
|||
/** @covers \MensBeam\HTML\Parser\Serializer */ |
|||
class TestSerializer extends \PHPUnit\Framework\TestCase { |
|||
use NameCoercion, AttributeSetter; |
|||
|
|||
/** @dataProvider provideStandardTreeTests */ |
|||
public function testStandardTreeTests(array $data, bool $fragment, string $exp): void { |
|||
$node = $this->buildTree($data, $fragment); |
|||
$this->assertSame($exp, Serializer::serializeOuter($node)); |
|||
} |
|||
|
|||
public function provideStandardTreeTests(): iterable { |
|||
$blacklist = []; |
|||
$files = new \AppendIterator(); |
|||
$files->append(new \GlobIterator(\MensBeam\HTML\Parser\BASE."tests/cases/serializer/*.dat", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)); |
|||
foreach ($files as $file) { |
|||
if (!in_array(basename($file), $blacklist)) { |
|||
yield from $this->parseTreeTestFile($file); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** @dataProvider provideTemplateTests */ |
|||
public function testSerializeADecoratedTemplate(?string $ns, bool $content, bool $fragment, bool $text, string $exp): void { |
|||
$d = new \DOMDocument; |
|||
$t = $d->createElementNS($ns, "template"); |
|||
$t->appendChild($d->createTextNode("EEK")); |
|||
if ($content) { |
|||
$t->content = null; |
|||
if ($fragment) { |
|||
$f = $d->createDocumentFragment(); |
|||
$t->content = $f; |
|||
if ($text) { |
|||
$f->appendChild($d->createTextNode("OOK")); |
|||
} |
|||
} |
|||
} |
|||
$exp1 = $exp; |
|||
$exp2 = "<template>$exp</template>"; |
|||
$this->assertSame($exp1, Serializer::serializeInner($t)); |
|||
$this->assertSame($exp2, Serializer::serializeOuter($t)); |
|||
} |
|||
|
|||
public function provideTemplateTests(): iterable { |
|||
return [ |
|||
[null, false, false, false, "EEK"], |
|||
[null, true, false, false, "EEK"], |
|||
[null, true, true, false, ""], |
|||
[null, true, true, true, "OOK"], |
|||
[Parser::HTML_NAMESPACE, false, false, false, "EEK"], |
|||
[Parser::HTML_NAMESPACE, true, false, false, "EEK"], |
|||
[Parser::HTML_NAMESPACE, true, true, false, ""], |
|||
[Parser::HTML_NAMESPACE, true, true, true, "OOK"], |
|||
]; |
|||
} |
|||
|
|||
/** @dataProvider provideEmptyElementTests */ |
|||
public function testInnerSerializeEmptyElement(string $tagName, ?string $ns, string $exp): void { |
|||
$d = new \DOMDocument; |
|||
$e = $d->createElementNS($ns, $tagName); |
|||
$e->appendChild($d->createTextNode("EEK")); |
|||
$this->assertSame($exp, Serializer::serializeInner($e)); |
|||
} |
|||
|
|||
public function provideEmptyElementTests(): iterable { |
|||
return [ |
|||
["basefont", null, ""], |
|||
["bgsound", null, ""], |
|||
["frame", null, ""], |
|||
["keygen", null, ""], |
|||
["area", null, ""], |
|||
["base", null, ""], |
|||
["br", null, ""], |
|||
["col", null, ""], |
|||
["embed", null, ""], |
|||
["hr", null, ""], |
|||
["img", null, ""], |
|||
["input", null, ""], |
|||
["link", null, ""], |
|||
["meta", null, ""], |
|||
["param", null, ""], |
|||
["source", null, ""], |
|||
["track", null, ""], |
|||
["wbr", null, ""], |
|||
["basefont", Parser::HTML_NAMESPACE, ""], |
|||
["bgsound", Parser::HTML_NAMESPACE, ""], |
|||
["frame", Parser::HTML_NAMESPACE, ""], |
|||
["keygen", Parser::HTML_NAMESPACE, ""], |
|||
["area", Parser::HTML_NAMESPACE, ""], |
|||
["base", Parser::HTML_NAMESPACE, ""], |
|||
["br", Parser::HTML_NAMESPACE, ""], |
|||
["col", Parser::HTML_NAMESPACE, ""], |
|||
["embed", Parser::HTML_NAMESPACE, ""], |
|||
["hr", Parser::HTML_NAMESPACE, ""], |
|||
["img", Parser::HTML_NAMESPACE, ""], |
|||
["input", Parser::HTML_NAMESPACE, ""], |
|||
["link", Parser::HTML_NAMESPACE, ""], |
|||
["meta", Parser::HTML_NAMESPACE, ""], |
|||
["param", Parser::HTML_NAMESPACE, ""], |
|||
["source", Parser::HTML_NAMESPACE, ""], |
|||
["track", Parser::HTML_NAMESPACE, ""], |
|||
["wbr", Parser::HTML_NAMESPACE, ""], |
|||
["basefont", Parser::SVG_NAMESPACE, "EEK"], |
|||
["bgsound", Parser::SVG_NAMESPACE, "EEK"], |
|||
["frame", Parser::SVG_NAMESPACE, "EEK"], |
|||
["keygen", Parser::SVG_NAMESPACE, "EEK"], |
|||
["area", Parser::SVG_NAMESPACE, "EEK"], |
|||
["base", Parser::SVG_NAMESPACE, "EEK"], |
|||
["br", Parser::SVG_NAMESPACE, "EEK"], |
|||
["col", Parser::SVG_NAMESPACE, "EEK"], |
|||
["embed", Parser::SVG_NAMESPACE, "EEK"], |
|||
["hr", Parser::SVG_NAMESPACE, "EEK"], |
|||
["img", Parser::SVG_NAMESPACE, "EEK"], |
|||
["input", Parser::SVG_NAMESPACE, "EEK"], |
|||
["link", Parser::SVG_NAMESPACE, "EEK"], |
|||
["meta", Parser::SVG_NAMESPACE, "EEK"], |
|||
["param", Parser::SVG_NAMESPACE, "EEK"], |
|||
["source", Parser::SVG_NAMESPACE, "EEK"], |
|||
["track", Parser::SVG_NAMESPACE, "EEK"], |
|||
["wbr", Parser::SVG_NAMESPACE, "EEK"], |
|||
]; |
|||
} |
|||
|
|||
public function testOuterSerializeAnInvalidNode(): void { |
|||
$d = new \DOMDocument; |
|||
$a = $d->createAttribute("oops"); |
|||
$this->expectExceptionObject(new Exception(Exception::UNSUPPORTED_NODE_TYPE, [\DOMAttr::class])); |
|||
Serializer::serializeOuter($a); |
|||
} |
|||
|
|||
public function testInnerSerializeAnInvalidNode(): void { |
|||
$d = new \DOMDocument; |
|||
$t = $d->createTextNode("OOPS"); |
|||
$this->expectExceptionObject(new Exception(Exception::UNSUPPORTED_NODE_TYPE, [\DOMText::class])); |
|||
Serializer::serializeInner($t); |
|||
} |
|||
|
|||
protected function buildTree(array $data, bool $fragment, bool $formatOutput = false): \DOMNode { |
|||
$document = new \DOMDocument; |
|||
$document->formatOutput = $formatOutput; |
|||
if ($fragment) { |
|||
$document->appendChild($document->createElement("html")); |
|||
$out = $document->createDocumentFragment(); |
|||
} else { |
|||
$out = $document; |
|||
} |
|||
$cur = $out; |
|||
$pad = 2; |
|||
// process each line in turn |
|||
for ($l = 0; $l < sizeof($data); $l++) { |
|||
preg_match('/^(\|\s+)(.+)/', $data[$l], $m); |
|||
// pop any parents as long as the padding of the line is less than the expected padding |
|||
$p = strlen((string) $m[1]); |
|||
assert($p >= 2 && $p <= $pad && !($p % 2), new \Exception("Input data is invalid on line ".($l + 1))); |
|||
while ($p < $pad) { |
|||
$pad -= 2; |
|||
$cur = $cur->parentNode; |
|||
} |
|||
// act based upon what the rest of the line looks like |
|||
$d = $m[2]; |
|||
if (preg_match('/^<!-- (.*?) -->$/', $d, $m)) { |
|||
// comment |
|||
$cur->appendChild($document->createComment($m[1])); |
|||
} elseif (preg_match('/^<!DOCTYPE(?: ([^ >]*)(?: "([^"]*)" "([^"]*)")?)?>$/', $d, $m)) { |
|||
// doctype |
|||
$name = strlen((string) ($m[1] ?? "")) ? $m[1] : " "; |
|||
$public = strlen((string) ($m[2] ?? "")) ? $m[2] : ""; |
|||
$system = strlen((string) ($m[3] ?? "")) ? $m[3] : ""; |
|||
$cur->appendChild($document->implementation->createDocumentType($name, $public, $system)); |
|||
} elseif (preg_match('/^<\?([^ ]+) ([^>]*)>$/', $d, $m)) { |
|||
// processing instruction |
|||
$cur->appendChild($document->createProcessingInstruction($m[1], $m[2])); |
|||
} elseif (preg_match('/^<(?:([^ ]+) )?([^>]+)>$/', $d, $m)) { |
|||
// element |
|||
$ns = strlen((string) $m[1]) ? (array_flip(Parser::NAMESPACE_MAP)[$m[1]] ?? $m[1]) : null; |
|||
$cur = $cur->appendChild($document->createElementNS($ns, self::coerceName($m[2]))); |
|||
$pad += 2; |
|||
} elseif (preg_match('/^(?:([^" ]+) )?([^"=]+)="((?:[^"]|"(?!$))*)"$/', $d, $m)) { |
|||
// attribute |
|||
$ns = strlen((string) $m[1]) ? (array_flip(Parser::NAMESPACE_MAP)[$m[1]] ?? $m[1]) : ""; |
|||
$this->elementSetAttribute($cur, $ns, $m[2], $m[3]); |
|||
} elseif (preg_match('/^"((?:[^"]|"(?!$))*)("?)$/', $d, $m)) { |
|||
// text |
|||
$t = $m[1]; |
|||
while (!strlen((string) $m[2])) { |
|||
preg_match('/^((?:[^"]|"(?!$))*)("?)$/', $data[++$l], $m); |
|||
$t .= "\n".$m[1]; |
|||
} |
|||
$cur->appendChild($document->createTextNode($t)); |
|||
} else { |
|||
throw new \Exception("Input data is invalid on line ".($l + 1)); |
|||
} |
|||
} |
|||
return $out; |
|||
} |
|||
|
|||
protected function parseTreeTestFile(string $file): \Generator { |
|||
$index = 0; |
|||
$l = 0; |
|||
$lines = array_map(function($v) { |
|||
return rtrim($v, "\n"); |
|||
}, file($file)); |
|||
while ($l < sizeof($lines)) { |
|||
$pos = $l + 1; |
|||
assert(in_array($lines[$l], ["#document", "#fragment"]), new \Exception("Test $file #$index does not start with #document or #fragment tag at line ".($l + 1))); |
|||
$fragment = $lines[$l] === "#fragment"; |
|||
// collect the test input |
|||
$data = []; |
|||
for (++$l; $l < sizeof($lines); $l++) { |
|||
if (preg_match('/^#(script-(on|off)|output)$/', $lines[$l])) { |
|||
break; |
|||
} |
|||
$data[] = $lines[$l]; |
|||
} |
|||
// set the script mode, if present |
|||
assert(preg_match('/^#(script-(on|off)|output)$/', $lines[$l]) === 1, new \Exception("Test $file #$index follows data with something other than script flag or output at line ".($l + 1))); |
|||
$script = null; |
|||
if ($lines[$l] === "#script-off") { |
|||
$script = false; |
|||
$l++; |
|||
} elseif ($lines[$l] === "#script-on") { |
|||
$script = true; |
|||
$l++; |
|||
} |
|||
// collect the output string |
|||
$exp = []; |
|||
assert($lines[$l] === "#output", new \Exception("Test $file #$index follows input with something other than output at line ".($l + 1))); |
|||
for (++$l; $l < sizeof($lines); $l++) { |
|||
if ($lines[$l] === "" && in_array(($lines[$l + 1] ?? ""), ["#document", "#fragment"])) { |
|||
break; |
|||
} |
|||
assert(preg_match('/^([^#]|$)/', $lines[$l]) === 1, new \Exception("Test $file #$index contains unrecognized data after output at line ".($l + 1))); |
|||
$exp[] = $lines[$l]; |
|||
} |
|||
$exp = implode("\n", $exp); |
|||
if (!$script) { |
|||
yield basename($file)." #$index (line $pos)" => [$data, $fragment, $exp]; |
|||
} |
|||
$l++; |
|||
$index++; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,99 @@ |
|||
HTML DOM serialization tests |
|||
============================ |
|||
|
|||
The format of these tests is essentially the format of html5lib's tree |
|||
construction tests in reverse. There are, however, important differences, |
|||
so the format is documented in full here. |
|||
|
|||
Each file containing tree construction tests consists of any number of |
|||
tests separated by two newlines (LF) and a single newline before the end |
|||
of the file. For instance: |
|||
|
|||
[TEST]LF |
|||
LF |
|||
[TEST]LF |
|||
LF |
|||
[TEST]LF |
|||
|
|||
Where [TEST] is the following format: |
|||
|
|||
Each test begins with a line reading `#document` or `#fragment`; subsequent |
|||
lines represent the document or document fragment (respectively) used as |
|||
input, until a line is encountered which reads `#output`, `#script-on`, |
|||
or `#script-off`. |
|||
|
|||
Each DOM node in the input is written on its own line beginning with the |
|||
characters "| " (a vertical bar followed by a single space); lines which begin |
|||
with other characters are a continuation of the previous line. Attributes |
|||
are treated as distinct nodes and have their own entries. There is no escape |
|||
mechanism: all input is literal, including newlines and quotation marks. Two |
|||
spaces are used to denote each level of nesting. For example: |
|||
|
|||
| node |
|||
| child node |
|||
continuation of child node |
|||
| grandchild node |
|||
| child node |
|||
| attribute node of child |
|||
| grandchild node |
|||
|
|||
The different types of nodes are: |
|||
|
|||
- Element nodes in the form `<body>` for an element in the HTML namespace, |
|||
or `<svg svg>` for an element in a foreign namespace. Qualified names are |
|||
written as usual e.g. `<math math:math>`, though such elements are not |
|||
produced by the parser |
|||
- Attribute nodes in the form `id="value"` or e.g. `xml xml:id="value"`, with |
|||
a quotation mark immediately followed by a newline marking the end of the |
|||
attribute value (in other words, attribute values may contain literal |
|||
quotation marks) |
|||
- Text nodes in the form `"text data"`; like attributes, only a quotation mark |
|||
followed a newline marks the end of text data |
|||
- Comment nodes of the form `<!-- comment data -->`; the space characters are |
|||
padding and are not part of the comment data |
|||
- Document type nodes in the form `<!DOCTYPE html "public" "system">`, or |
|||
`<!DOCTYPE html>` or simply `<!DOCTYPE>` depending on its contents |
|||
- Processing instructions in the form `<?target PI data>`. Processing |
|||
instructions are not generated by the HTML parser, but may appear in |
|||
documents by other means |
|||
|
|||
Namespaces are represented by the following short names: |
|||
|
|||
| Name | URL | |
|||
|-------|--------------------------------------| |
|||
| xml | http://www.w3.org/XML/1998/namespace | |
|||
| xmlns | http://www.w3.org/2000/xmlns/ | |
|||
| xlink | http://www.w3.org/1999/xlink | |
|||
| math | http://www.w3.org/1998/Math/MathML | |
|||
| svg | http://www.w3.org/2000/svg | |
|||
|
|||
Other namespaces may also appear; these should be interpreted as literal URLs. |
|||
|
|||
After the input block either `#script-on` or `#script-off` may appear. These |
|||
signal that the test should be run with scripting on or off, respectively. If |
|||
neither line is present, the test should be run in both modes. |
|||
|
|||
Finally, `#output` marks the beginning of output. All subsequent text is |
|||
literal characters until two consecutive newlines following by either |
|||
`#document` or `#fragment` are seen. |
|||
|
|||
Below is a complete example: |
|||
|
|||
#document |
|||
| <!-- This is longer than most tests --> |
|||
| <!DOCTYPE html "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> |
|||
| <html> |
|||
| lang="en" |
|||
| <head> |
|||
| <body> |
|||
| style="font-family: "Times New Roman"" |
|||
| <svg svg> |
|||
| xml xml:id="image" |
|||
| <div> |
|||
| "This is a text node. |
|||
It has an embedded newline. It is in fact pretty "busy" and has |
|||
multiple newlines. |
|||
|
|||
And even a blank line." |
|||
| <!-- This comment also |
|||
has a newline --> |
@ -0,0 +1,33 @@ |
|||
#fragment |
|||
| <fake_ns test:test> |
|||
#output |
|||
<test:test></test:test> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| test💩test="test" |
|||
#output |
|||
<span test💩test="test"></span> |
|||
|
|||
#fragment |
|||
| <wbr> |
|||
| "You should not see this text." |
|||
#output |
|||
<wbr> |
|||
|
|||
#fragment |
|||
| <wbr> |
|||
| class="test" |
|||
#output |
|||
<wbr class="test"> |
|||
|
|||
#fragment |
|||
| <poop💩> |
|||
#output |
|||
<poop💩></poop💩> |
|||
|
|||
#fragment |
|||
| <test> |
|||
| poop💩="soccer" |
|||
#output |
|||
<test poop💩="soccer"></test> |
@ -0,0 +1,34 @@ |
|||
#document |
|||
| <html> |
|||
#output |
|||
<html></html> |
|||
|
|||
#document |
|||
| <!DOCTYPE html> |
|||
| <html> |
|||
#output |
|||
<!DOCTYPE html><html></html> |
|||
|
|||
#document |
|||
| <!DOCTYPE html "public" "system"> |
|||
| <html> |
|||
#output |
|||
<!DOCTYPE html><html></html> |
|||
|
|||
#document |
|||
| <!DOCTYPE test> |
|||
| <html> |
|||
#output |
|||
<!DOCTYPE test><html></html> |
|||
|
|||
#document |
|||
| <!DOCTYPE> |
|||
| <html> |
|||
#output |
|||
<!DOCTYPE ><html></html> |
|||
|
|||
#document |
|||
| <html> |
|||
| <?php echo "Hello world!"; ?> |
|||
#output |
|||
<html><?php echo "Hello world!"; ?></html> |
@ -0,0 +1,913 @@ |
|||
#fragment |
|||
| <span> |
|||
#output |
|||
<span></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
#output |
|||
<span><a></a></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| b="c" |
|||
#output |
|||
<span><a b="c"></a></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| b="&" |
|||
#output |
|||
<span><a b="&"></a></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| b=" " |
|||
#output |
|||
<span><a b=" "></a></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| b=""" |
|||
#output |
|||
<span><a b="""></a></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| b="<" |
|||
#output |
|||
<span><a b="<"></a></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| b=">" |
|||
#output |
|||
<span><a b=">"></a></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| href="javascript:"<>"" |
|||
#output |
|||
<span><a href="javascript:"<>""></a></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <svg svg> |
|||
| xlink xlink:href="a" |
|||
#output |
|||
<span><svg xlink:href="a"></svg></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <svg svg> |
|||
| xmlns xmlns:svg="test" |
|||
#output |
|||
<span><svg xmlns:svg="test"></svg></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| "a" |
|||
#output |
|||
<span>a</span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| "&" |
|||
#output |
|||
<span>&</span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| " " |
|||
#output |
|||
<span> </span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| "<" |
|||
#output |
|||
<span><</span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| ">" |
|||
#output |
|||
<span>></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| """ |
|||
#output |
|||
<span>"</span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <style> |
|||
| "<&>" |
|||
#output |
|||
<span><style><&></style></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <script> |
|||
| type="test" |
|||
| "<&>" |
|||
#output |
|||
<span><script type="test"><&></script></span> |
|||
|
|||
#fragment |
|||
| <script> |
|||
| type="test" |
|||
| "<&>" |
|||
#output |
|||
<script type="test"><&></script> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <xmp> |
|||
| "<&>" |
|||
#output |
|||
<span><xmp><&></xmp></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <iframe> |
|||
| "<&>" |
|||
#output |
|||
<span><iframe><&></iframe></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <noembed> |
|||
| "<&>" |
|||
#output |
|||
<span><noembed><&></noembed></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <noframes> |
|||
| "<&>" |
|||
#output |
|||
<span><noframes><&></noframes></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <noscript> |
|||
| "<&>" |
|||
#script-off |
|||
#output |
|||
<span><noscript><&></noscript></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <noscript> |
|||
| "<&>" |
|||
#script-on |
|||
#output |
|||
<span><noscript><&></noscript></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <!-- data --> |
|||
#output |
|||
<span><!--data--></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| <b> |
|||
| <c> |
|||
| <d> |
|||
| "e" |
|||
| <f> |
|||
| <g> |
|||
| "h" |
|||
#output |
|||
<span><a><b><c></c></b><d>e</d><f><g>h</g></f></a></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| b="c" |
|||
#output |
|||
<span b="c"></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <svg svg> |
|||
| xml xml:foo="test" |
|||
#output |
|||
<span><svg xml:foo="test"></svg></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <svg svg> |
|||
| xml abc:foo="test" |
|||
#output |
|||
<span><svg xml:foo="test"></svg></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <svg svg> |
|||
| xmlns xmlns:foo="test" |
|||
#output |
|||
<span><svg xmlns:foo="test"></svg></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <svg svg> |
|||
| xmlns xmlns="test" |
|||
#output |
|||
<span><svg xmlns="test"></svg></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <svg svg> |
|||
| fake_ns abc:def="test" |
|||
#output |
|||
<span><svg abc:def="test"></svg></span> |
|||
|
|||
#fragment |
|||
| <pre> |
|||
| " |
|||
" |
|||
#output |
|||
<pre> |
|||
</pre> |
|||
|
|||
#fragment |
|||
| <pre> |
|||
| "a |
|||
" |
|||
#output |
|||
<pre>a |
|||
</pre> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <pre> |
|||
| " |
|||
" |
|||
#output |
|||
<span><pre> |
|||
</pre></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <pre> |
|||
| "a |
|||
" |
|||
#output |
|||
<span><pre>a |
|||
</pre></span> |
|||
|
|||
#fragment |
|||
| <textarea> |
|||
| " |
|||
" |
|||
#output |
|||
<textarea> |
|||
</textarea> |
|||
|
|||
#fragment |
|||
| <textarea> |
|||
| "a |
|||
" |
|||
#output |
|||
<textarea>a |
|||
</textarea> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <textarea> |
|||
| " |
|||
" |
|||
#output |
|||
<span><textarea> |
|||
</textarea></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <textarea> |
|||
| "a |
|||
" |
|||
#output |
|||
<span><textarea>a |
|||
</textarea></span> |
|||
|
|||
#fragment |
|||
| <listing> |
|||
| " |
|||
" |
|||
#output |
|||
<listing> |
|||
</listing> |
|||
|
|||
#fragment |
|||
| <listing> |
|||
| "a |
|||
" |
|||
#output |
|||
<listing>a |
|||
</listing> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <listing> |
|||
| " |
|||
" |
|||
#output |
|||
<span><listing> |
|||
</listing></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <listing> |
|||
| "a |
|||
" |
|||
#output |
|||
<span><listing>a |
|||
</listing></span> |
|||
|
|||
#fragment |
|||
| <area> |
|||
#output |
|||
<area> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <area> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><area><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <area> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><area><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <area> |
|||
#output |
|||
<span><a>test</a><b></b><area></span> |
|||
|
|||
#fragment |
|||
| <base> |
|||
#output |
|||
<base> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <base> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><base><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <base> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><base><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <base> |
|||
#output |
|||
<span><a>test</a><b></b><base></span> |
|||
|
|||
#fragment |
|||
| <basefont> |
|||
#output |
|||
<basefont> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <basefont> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><basefont><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <basefont> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><basefont><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <basefont> |
|||
#output |
|||
<span><a>test</a><b></b><basefont></span> |
|||
|
|||
#fragment |
|||
| <bgsound> |
|||
#output |
|||
<bgsound> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <bgsound> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><bgsound><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <bgsound> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><bgsound><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <bgsound> |
|||
#output |
|||
<span><a>test</a><b></b><bgsound></span> |
|||
|
|||
#fragment |
|||
| <br> |
|||
#output |
|||
<br> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <br> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><br><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <br> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><br><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <br> |
|||
#output |
|||
<span><a>test</a><b></b><br></span> |
|||
|
|||
#fragment |
|||
| <col> |
|||
#output |
|||
<col> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <col> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><col><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <col> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><col><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <col> |
|||
#output |
|||
<span><a>test</a><b></b><col></span> |
|||
|
|||
#fragment |
|||
| <embed> |
|||
#output |
|||
<embed> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <embed> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><embed><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <embed> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><embed><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <embed> |
|||
#output |
|||
<span><a>test</a><b></b><embed></span> |
|||
|
|||
#fragment |
|||
| <frame> |
|||
#output |
|||
<frame> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <frame> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><frame><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <frame> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><frame><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <frame> |
|||
#output |
|||
<span><a>test</a><b></b><frame></span> |
|||
|
|||
#fragment |
|||
| <hr> |
|||
#output |
|||
<hr> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <hr> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><hr><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <hr> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><hr><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <hr> |
|||
#output |
|||
<span><a>test</a><b></b><hr></span> |
|||
|
|||
#fragment |
|||
| <img> |
|||
#output |
|||
<img> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <img> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><img><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <img> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><img><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <img> |
|||
#output |
|||
<span><a>test</a><b></b><img></span> |
|||
|
|||
#fragment |
|||
| <input> |
|||
#output |
|||
<input> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <input> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><input><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <input> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><input><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <input> |
|||
#output |
|||
<span><a>test</a><b></b><input></span> |
|||
|
|||
#fragment |
|||
| <keygen> |
|||
#output |
|||
<keygen> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <keygen> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><keygen><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <keygen> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><keygen><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <keygen> |
|||
#output |
|||
<span><a>test</a><b></b><keygen></span> |
|||
|
|||
#fragment |
|||
| <link> |
|||
#output |
|||
<link> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <link> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><link><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <link> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><link><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <link> |
|||
#output |
|||
<span><a>test</a><b></b><link></span> |
|||
|
|||
#fragment |
|||
| <meta> |
|||
#output |
|||
<meta> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <meta> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><meta><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <meta> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><meta><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <meta> |
|||
#output |
|||
<span><a>test</a><b></b><meta></span> |
|||
|
|||
#fragment |
|||
| <param> |
|||
#output |
|||
<param> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <param> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><param><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <param> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><param><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <param> |
|||
#output |
|||
<span><a>test</a><b></b><param></span> |
|||
|
|||
#fragment |
|||
| <source> |
|||
#output |
|||
<source> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <source> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><source><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <source> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><source><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <source> |
|||
#output |
|||
<span><a>test</a><b></b><source></span> |
|||
|
|||
#fragment |
|||
| <track> |
|||
#output |
|||
<track> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <track> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><track><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <track> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><track><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <track> |
|||
#output |
|||
<span><a>test</a><b></b><track></span> |
|||
|
|||
#fragment |
|||
| <wbr> |
|||
#output |
|||
<wbr> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <wbr> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
#output |
|||
<span><wbr><a>test</a><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <wbr> |
|||
| <b> |
|||
#output |
|||
<span><a>test</a><wbr><b></b></span> |
|||
|
|||
#fragment |
|||
| <span> |
|||
| <a> |
|||
| "test" |
|||
| <b> |
|||
| <wbr> |
|||
#output |
|||
<span><a>test</a><b></b><wbr></span> |
Loading…
Reference in new issue