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