append(new \GlobIterator(\MensBeam\HTML\DOM\BASE."tests/cases/Serializer/standard/*.dat", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)); foreach ($files as $file) { if (!in_array(basename($file), $blacklist)) { yield from $this->parseTreeTestFile($file); } } } /** * @dataProvider provideStandardTreeTests * @covers \MensBeam\HTML\DOM\Document::saveHTML * @covers \MensBeam\HTML\DOM\Document::blockElementFilterFactory * @covers \MensBeam\HTML\DOM\Document::serializeNode * @covers \MensBeam\HTML\DOM\Document::__toString * @covers \MensBeam\HTML\DOM\ToString::__toString */ public function testStandardTreeTests(array $data, bool $fragment, string $exp): void { $node = $this->buildTree($data, $fragment); $this->assertSame($exp, (string)$node); } public function provideFormattedTreeTests(): iterable { $files = new \AppendIterator(); $files->append(new \GlobIterator(\MensBeam\HTML\DOM\BASE."tests/cases/Serializer/formatted/*.dat", \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)); foreach ($files as $file) { yield from $this->parseTreeTestFile($file); } } /** * @dataProvider provideFormattedTreeTests * @covers \MensBeam\HTML\DOM\Document::saveHTML * @covers \MensBeam\HTML\DOM\Document::blockElementFilterFactory * @covers \MensBeam\HTML\DOM\Document::serializeNode * @covers \MensBeam\HTML\DOM\Document::__toString * @covers \MensBeam\HTML\DOM\ToString::__toString */ public function testFormattedTreeTests(array $data, bool $fragment, string $exp): void { $node = $this->buildTree($data, $fragment, true); $this->assertSame($exp, (string)$node); } /** * @covers \MensBeam\HTML\DOM\Document::saveHTML * @covers \MensBeam\HTML\DOM\Document::serializeNode */ public function testSerializingDocumentType(): void { $d = new Document(); $dt = $d->implementation->createDocumentType('ook', 'eek', 'ack'); $d->appendChild($dt); $this->assertSame('', $d->saveHTML($dt)); } /** * @covers \MensBeam\HTML\DOM\Document::saveHTML * @covers \MensBeam\HTML\DOM\Document::serializeNode */ public function testSerializingDocumentFragments(): void { $d = new Document(); $d->formatOutput = true; $df = $d->createDocumentFragment(); $df->appendChild($d->createTextNode('ook')); $this->assertSame('ook', (string)$df); $this->assertSame('ook', $d->saveHTML($df)); } /** * @covers \MensBeam\HTML\DOM\Document::saveHTML * @covers \MensBeam\HTML\DOM\Document::serializeNode * @covers \MensBeam\HTML\DOM\ToString::__toString */ public function testSerializingElements(): void { $d = new Document(); $i = $d->createElement('input'); $i->appendChild($d->createTextNode('You should not see this text')); $this->assertSame('', (string)$i); $this->assertSame('', $d->saveHTML($i)); $t = $d->createElement('template'); $t->setAttribute('ook', 'eek'); $t->content->appendChild($d->createTextNode('Ook!')); $this->assertSame('', (string)$t); $this->assertSame('Ook!', $d->saveHTML($t)); } /** * @covers \MensBeam\HTML\DOM\Document::saveHTML * @covers \MensBeam\HTML\DOM\Document::serializeNode * @covers \MensBeam\HTML\DOM\ToString::__toString */ public function testSerializingTextNodes(): void { $d = new Document(); $d->formatOutput = true; $i = $d->createTextNode('test'); $this->assertSame('test', (string)$i); $this->assertSame('test', $d->saveHTML($i)); } public function provideSerializerFailures(): iterable { return [ [ function() { $d = new Document(); $h = $d->createElement('html'); $d2 = new Document(); $d2->saveHTML($h); }, DOMException::class, DOMException::WRONG_DOCUMENT ], [ function() { $d = new Document(); $d->saveHTML($d->createAttribute('fail')); }, Exception::class, Exception::ARGUMENT_TYPE_ERROR ], [ function() { $d = new Document(); $d->saveHTML(new \DOMDocument()); }, Exception::class, Exception::ARGUMENT_TYPE_ERROR ], [ function() { $d = new Document(); $d2 = new \DOMDocument(); $d->saveHTML($d2->createComment('fail')); }, Exception::class, Exception::ARGUMENT_TYPE_ERROR ] ]; } /** * @dataProvider provideSerializerFailures * @covers \MensBeam\HTML\DOM\Document::saveHTML */ public function testSerializerFailures(\Closure $closure, string $exceptionClassName, int $errorCode): void { $this->expectException($exceptionClassName); $this->expectExceptionCode($errorCode); $closure(); } protected function buildTree(array $data, bool $fragment, bool $formatOutput = false): \DOMNode { $document = new Document; $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('/^]*)(?: "([^"]*)" "([^"]*)")?)?>$/', $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)) { $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, $m[2])); $pad += 2; } elseif (preg_match('/^(?:([^" ]+) )?([^"=]+)="((?:[^"]|"(?!$))*)"$/', $d, $m)) { // attribute $ns = strlen((string) $m[1]) ? (array_flip(Parser::NAMESPACE_MAP)[$m[1]] ?? $m[1]) : ""; if ($ns === '') { $cur->setAttribute($m[2], $m[3]); } else { $cur->setAttributeNS($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++; } } }