123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653 |
- <?php
- namespace Masterminds\HTML5\Tests\Serializer;
- use Masterminds\HTML5\Serializer\OutputRules;
- use Masterminds\HTML5\Serializer\Traverser;
- use Masterminds\HTML5;
- class OutputRulesTest extends \Masterminds\HTML5\Tests\TestCase
- {
- protected $markup = '<!doctype html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <title>Test</title>
- </head>
- <body>
- <p>This is a test.</p>
- </body>
- </html>';
- /**
- * @var HTML5
- */
- protected $html5;
- public function setUp()
- {
- $this->html5 = $this->getInstance();
- }
- /**
- * Using reflection we make a protected method accessible for testing.
- *
- * @param string $name
- * The name of the method on the Traverser class to test
- *
- * @return \ReflectionMethod for the specified method
- */
- public function getProtectedMethod($name)
- {
- $class = new \ReflectionClass('\Masterminds\HTML5\Serializer\OutputRules');
- $method = $class->getMethod($name);
- $method->setAccessible(true);
- return $method;
- }
- public function getTraverserProtectedProperty($name)
- {
- $class = new \ReflectionClass('\Masterminds\HTML5\Serializer\Traverser');
- $property = $class->getProperty($name);
- $property->setAccessible(true);
- return $property;
- }
- public function getOutputRules($options = array())
- {
- $options = $options + $this->html5->getOptions();
- $stream = fopen('php://temp', 'w');
- $dom = $this->html5->loadHTML($this->markup);
- $r = new OutputRules($stream, $options);
- $t = new Traverser($dom, $stream, $r, $options);
- return array(
- $r,
- $stream,
- );
- }
- public function testDocument()
- {
- $dom = $this->html5->loadHTML('<!doctype html><html lang="en"><body>foo</body></html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $r->document($dom);
- $expected = '<!DOCTYPE html>' . PHP_EOL . '<html lang="en"><body>foo</body></html>' . PHP_EOL;
- $this->assertEquals($expected, stream_get_contents($stream, -1, 0));
- }
- public function testEmptyDocument()
- {
- $dom = $this->html5->loadHTML('');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $r->document($dom);
- $expected = '<!DOCTYPE html>' . PHP_EOL;
- $this->assertEquals($expected, stream_get_contents($stream, -1, 0));
- }
- public function testDoctype()
- {
- $dom = $this->html5->loadHTML('<!doctype html><html lang="en"><body>foo</body></html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $m = $this->getProtectedMethod('doctype');
- $m->invoke($r, 'foo');
- $this->assertEquals('<!DOCTYPE html>' . PHP_EOL, stream_get_contents($stream, -1, 0));
- }
- public function testElement()
- {
- $dom = $this->html5->loadHTML(
- '<!doctype html>
- <html lang="en">
- <body>
- <div id="foo" class="bar baz">foo bar baz</div>
- <svg width="150" height="100" viewBox="0 0 3 2">
- <rect width="1" height="2" x="0" fill="#008d46" />
- <rect width="1" height="2" x="1" fill="#ffffff" />
- <rect width="1" height="2" x="2" fill="#d2232c" />
- </svg>
- </body>
- </html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $list = $dom->getElementsByTagName('div');
- $r->element($list->item(0));
- $this->assertEquals('<div id="foo" class="bar baz">foo bar baz</div>', stream_get_contents($stream, -1, 0));
- }
- public function testSerializeWithNamespaces()
- {
- $this->html5 = $this->getInstance(array(
- 'xmlNamespaces' => true,
- ));
- $source = '
- <!DOCTYPE html>
- <html><body id="body" xmlns:x="http://www.prefixed.com">
- <a id="bar1" xmlns="http://www.prefixed.com/bar1">
- <b id="bar4" xmlns="http://www.prefixed.com/bar4"><x:prefixed id="prefixed">xy</x:prefixed></b>
- </a>
- <svg id="svg">svg</svg>
- <c id="bar2" xmlns="http://www.prefixed.com/bar2"></c>
- <div id="div"></div>
- <d id="bar3"></d>
- <xn:d id="bar5" xmlns:xn="http://www.prefixed.com/xn" xmlns="http://www.prefixed.com/bar5_x"><x id="bar5_x">y</x></xn:d>
- </body>
- </html>';
- $dom = $this->html5->loadHTML($source, array(
- 'xmlNamespaces' => true,
- ));
- $this->assertFalse($this->html5->hasErrors(), print_r($this->html5->getErrors(), 1));
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $t->walk();
- $rendered = stream_get_contents($stream, -1, 0);
- $clear = function ($s) {
- return trim(preg_replace('/[\s]+/', ' ', $s));
- };
- $this->assertEquals($clear($source), $clear($rendered));
- }
- public function testElementWithScript()
- {
- $dom = $this->html5->loadHTML(
- '<!doctype html>
- <html lang="en">
- <head>
- <script>
- var $jQ = jQuery.noConflict();
- // Use jQuery via $jQ(...)
- $jQ(document).ready(function () {
- $jQ("#mktFrmSubmit").wrap("<div class=\'buttonSubmit\'></div>");
- $jQ(".buttonSubmit").prepend("<span></span>");
- });
- </script>
- </head>
- <body>
- <div id="foo" class="bar baz">foo bar baz</div>
- </body>
- </html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $script = $dom->getElementsByTagName('script');
- $r->element($script->item(0));
- $this->assertEquals(
- '<script>
- var $jQ = jQuery.noConflict();
- // Use jQuery via $jQ(...)
- $jQ(document).ready(function () {
- $jQ("#mktFrmSubmit").wrap("<div class=\'buttonSubmit\'></div>");
- $jQ(".buttonSubmit").prepend("<span></span>");
- });
- </script>', stream_get_contents($stream, -1, 0));
- }
- public function testElementWithStyle()
- {
- $dom = $this->html5->loadHTML(
- '<!doctype html>
- <html lang="en">
- <head>
- <style>
- body > .bar {
- display: none;
- }
- </style>
- </head>
- <body>
- <div id="foo" class="bar baz">foo bar baz</div>
- </body>
- </html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $style = $dom->getElementsByTagName('style');
- $r->element($style->item(0));
- $this->assertEquals('<style>
- body > .bar {
- display: none;
- }
- </style>', stream_get_contents($stream, -1, 0));
- }
- public function testOpenTag()
- {
- $dom = $this->html5->loadHTML('<!doctype html>
- <html lang="en">
- <body>
- <div id="foo" class="bar baz">foo bar baz</div>
- </body>
- </html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $list = $dom->getElementsByTagName('div');
- $m = $this->getProtectedMethod('openTag');
- $m->invoke($r, $list->item(0));
- $this->assertEquals('<div id="foo" class="bar baz">', stream_get_contents($stream, -1, 0));
- }
- public function testCData()
- {
- $dom = $this->html5->loadHTML('<!doctype html>
- <html lang="en">
- <body>
- <div><![CDATA[bar]]></div>
- </body>
- </html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $list = $dom->getElementsByTagName('div');
- $r->cdata($list->item(0)->childNodes->item(0));
- $this->assertEquals('<![CDATA[bar]]>', stream_get_contents($stream, -1, 0));
- $dom = $this->html5->loadHTML('<!doctype html>
- <html lang="en">
- <body>
- <div id="foo"></div>
- </body>
- </html>');
- $dom->getElementById('foo')->appendChild(new \DOMCdataSection(']]>Foo<[![CDATA test ]]>'));
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $list = $dom->getElementsByTagName('div');
- $r->cdata($list->item(0)->childNodes->item(0));
- $this->assertEquals('<![CDATA[]]]]><![CDATA[>Foo<[![CDATA test ]]]]><![CDATA[>]]>', stream_get_contents($stream, -1, 0));
- }
- public function testComment()
- {
- $dom = $this->html5->loadHTML('<!doctype html>
- <html lang="en">
- <body>
- <div><!-- foo --></div>
- </body>
- </html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $list = $dom->getElementsByTagName('div');
- $r->comment($list->item(0)->childNodes->item(0));
- $this->assertEquals('<!-- foo -->', stream_get_contents($stream, -1, 0));
- $dom = $this->html5->loadHTML('<!doctype html>
- <html lang="en">
- <body>
- <div id="foo"></div>
- </body>
- </html>');
- $dom->getElementById('foo')->appendChild(new \DOMComment('<!-- --> --> Foo -->'));
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $list = $dom->getElementsByTagName('div');
- $r->comment($list->item(0)->childNodes->item(0));
- // Could not find more definitive guidelines on what this should be. Went with
- // what the HTML5 spec says and what \DOMDocument::saveXML() produces.
- $this->assertEquals('<!--<!-- --> --> Foo -->-->', stream_get_contents($stream, -1, 0));
- }
- public function testText()
- {
- $dom = $this->html5->loadHTML('<!doctype html>
- <html lang="en">
- <head>
- <script>baz();</script>
- </head>
- </html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $list = $dom->getElementsByTagName('script');
- $r->text($list->item(0)->childNodes->item(0));
- $this->assertEquals('baz();', stream_get_contents($stream, -1, 0));
- $dom = $this->html5->loadHTML('<!doctype html>
- <html lang="en">
- <head id="foo"></head>
- </html>');
- $foo = $dom->getElementById('foo');
- $foo->appendChild(new \DOMText('<script>alert("hi");</script>'));
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $r->text($foo->firstChild);
- $this->assertEquals('<script>alert("hi");</script>', stream_get_contents($stream, -1, 0));
- }
- public function testNl()
- {
- list($o, $s) = $this->getOutputRules();
- $m = $this->getProtectedMethod('nl');
- $m->invoke($o);
- $this->assertEquals(PHP_EOL, stream_get_contents($s, -1, 0));
- }
- public function testWr()
- {
- list($o, $s) = $this->getOutputRules();
- $m = $this->getProtectedMethod('wr');
- $m->invoke($o, 'foo');
- $this->assertEquals('foo', stream_get_contents($s, -1, 0));
- }
- public function getEncData()
- {
- return array(
- array(
- false,
- '&\'<>"',
- '&\'<>"',
- '&'<>"',
- ),
- array(
- false,
- 'This + is. a < test',
- 'This + is. a < test',
- 'This + is. a < test',
- ),
- array(
- false,
- '.+#',
- '.+#',
- '.+#',
- ),
- array(
- true,
- '.+#\'',
- '.+#\'',
- '.+#'',
- ),
- array(
- true,
- '&".<',
- '&".<',
- '&".<',
- ),
- array(
- true,
- '&\'<>"',
- '&\'<>"',
- '&'<>"',
- ),
- array(
- true,
- "\xc2\xa0\"'",
- ' "\'',
- ' "'',
- ),
- );
- }
- /**
- * Test basic encoding of text.
- *
- * @dataProvider getEncData
- */
- public function testEnc($isAttribute, $test, $expected, $expectedEncoded)
- {
- list($o, $s) = $this->getOutputRules();
- $m = $this->getProtectedMethod('enc');
- $this->assertEquals($expected, $m->invoke($o, $test, $isAttribute));
- list($o, $s) = $this->getOutputRules(array(
- 'encode_entities' => true,
- ));
- $m = $this->getProtectedMethod('enc');
- $this->assertEquals($expectedEncoded, $m->invoke($o, $test, $isAttribute));
- }
- /**
- * Test basic encoding of text.
- *
- * @dataProvider getEncData
- */
- public function testEscape($isAttribute, $test, $expected, $expectedEncoded)
- {
- list($o, $s) = $this->getOutputRules();
- $m = $this->getProtectedMethod('escape');
- $this->assertEquals($expected, $m->invoke($o, $test, $isAttribute));
- }
- public function booleanAttributes()
- {
- return array(
- array('<img alt="" ismap>'),
- array('<img alt="">'),
- array('<input type="radio" readonly>'),
- array('<input type="radio" checked disabled>'),
- array('<input type="checkbox" checked disabled>'),
- array('<input type="radio" value="" checked disabled>'),
- array('<div data-value=""></div>'),
- array('<select disabled></select>'),
- array('<div ng-app></div>'),
- array('<script defer></script>'),
- );
- }
- /**
- * @dataProvider booleanAttributes
- */
- public function testBooleanAttrs($html)
- {
- $dom = $this->html5->loadHTML('<!doctype html><html lang="en"><body>' . $html . '</body></html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $node = $dom->getElementsByTagName('body')->item(0)->firstChild;
- $m = $this->getProtectedMethod('attrs');
- $m->invoke($r, $node);
- $content = stream_get_contents($stream, -1, 0);
- $html = preg_replace('~<[a-z]+(.*)></[a-z]+>~', '\1', $html);
- $html = preg_replace('~<[a-z]+(.*)/?>~', '\1', $html);
- $this->assertEquals($content, $html);
- }
- public function testAttrs()
- {
- $dom = $this->html5->loadHTML('<!doctype html>
- <html lang="en">
- <body>
- <div id="foo" class="bar baz">foo bar baz</div>
- </body>
- </html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $list = $dom->getElementsByTagName('div');
- $m = $this->getProtectedMethod('attrs');
- $m->invoke($r, $list->item(0));
- $content = stream_get_contents($stream, -1, 0);
- $this->assertEquals(' id="foo" class="bar baz"', $content);
- }
- public function testSvg()
- {
- $dom = $this->html5->loadHTML(
- '<!doctype html>
- <html lang="en">
- <body>
- <div id="foo" class="bar baz">foo bar baz</div>
- <svg width="150" height="100" viewBox="0 0 3 2">
- <rect width="1" height="2" x="0" fill="#008d46" />
- <rect width="1" height="2" x="1" fill="#ffffff" />
- <rect width="1" height="2" x="2" fill="#d2232c" />
- <rect id="Bar" x="300" y="100" width="300" height="100" fill="rgb(255,255,0)">
- <animate attributeName="x" attributeType="XML" begin="0s" dur="9s" fill="freeze" from="300" to="0" />
- </rect>
- </svg>
- </body>
- </html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $list = $dom->getElementsByTagName('svg');
- $r->element($list->item(0));
- $contents = stream_get_contents($stream, -1, 0);
- $this->assertRegExp('|<svg width="150" height="100" viewBox="0 0 3 2">|', $contents);
- $this->assertRegExp('|<rect width="1" height="2" x="0" fill="#008d46" />|', $contents);
- $this->assertRegExp('|<rect id="Bar" x="300" y="100" width="300" height="100" fill="rgb\(255,255,0\)">|', $contents);
- }
- public function testMath()
- {
- $dom = $this->html5->loadHTML(
- '<!doctype html>
- <html lang="en">
- <body>
- <div id="foo" class="bar baz">foo bar baz</div>
- <math>
- <mi>x</mi>
- <csymbol definitionURL="http://www.example.com/mathops/multiops.html#plusminus">
- <mo>±</mo>
- </csymbol>
- <mi>y</mi>
- </math>
- </body>
- </html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $list = $dom->getElementsByTagName('math');
- $r->element($list->item(0));
- $content = stream_get_contents($stream, -1, 0);
- $this->assertRegExp('|<math>|', $content);
- $this->assertRegExp('|<csymbol definitionURL="http://www.example.com/mathops/multiops.html#plusminus">|', $content);
- }
- public function testProcessorInstruction()
- {
- $dom = $this->html5->loadHTMLFragment('<?foo bar ?>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $r->processorInstruction($dom->firstChild);
- $content = stream_get_contents($stream, -1, 0);
- $this->assertRegExp('|<\?foo bar \?>|', $content);
- }
- public function testAddressTag()
- {
- $dom = $this->html5->loadHTML(
- '<!doctype html>
- <html lang="en">
- <body>
- <address>
- <a href="../People/Raggett/">Dave Raggett</a>,
- <a href="../People/Arnaud/">Arnaud Le Hors</a>,
- contact persons for the <a href="Activity">W3C HTML Activity</a>
- </address>
- </body>
- </html>');
- $stream = fopen('php://temp', 'w');
- $r = new OutputRules($stream, $this->html5->getOptions());
- $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
- $list = $dom->getElementsByTagName('address');
- $r->element($list->item(0));
- $contents = stream_get_contents($stream, -1, 0);
- $this->assertRegExp('|<address>|', $contents);
- $this->assertRegExp('|<a href="../People/Raggett/">Dave Raggett</a>,|', $contents);
- $this->assertRegExp('|<a href="../People/Arnaud/">Arnaud Le Hors</a>,|', $contents);
- $this->assertRegExp('|contact persons for the <a href="Activity">W3C HTML Activity</a>|', $contents);
- $this->assertRegExp('|</address>|', $contents);
- }
- /**
- * Ensure direct DOM manipulation doesn't break TEXT_RAW elements (iframe, script, etc...).
- */
- public function testHandlingInvalidRawContent()
- {
- $dom = $this->html5->loadHTML(
- '<!doctype html>
- <html lang="en" id="base">
- <body>
- <script id="template" type="x-tmpl-mustache">
- <h1>Hello!</h1>
- </script>
- </body>
- </html>');
- $badNode = $dom->createElement('p', 'Bar');
- // modify the content of the TEXT_RAW element: <script id="template"> appending dom nodes
- $styleElement = $dom->getElementById('template');
- $styleElement->appendChild($badNode);
- $contents = $this->html5->saveHTML($dom);
- $this->assertTrue(false !== strpos($contents, '<script id="template" type="x-tmpl-mustache">
- <h1>Hello!</h1>
- <p>Bar</p></script>'));
- }
- }
|