code:
<?php

namespace Masterminds\HTML5\Tests;

use Masterminds\HTML5\Elements;

class ElementsTest extends TestCase
{
    public $html5Elements = array(
        'a',
        'abbr',
        'address',
        'area',
        'article',
        'aside',
        'audio',
        'b',
        'base',
        'bdi',
        'bdo',
        'blockquote',
        'body',
        'br',
        'button',
        'canvas',
        'caption',
        'cite',
        'code',
        'col',
        'colgroup',
        'command',
        // "data",
        'datalist',
        'dd',
        'del',
        'details',
        'dfn',
        'dialog',
        'div',
        'dl',
        'dt',
        'em',
        'embed',
        'fieldset',
        'figcaption',
        'figure',
        'footer',
        'form',
        'h1',
        'h2',
        'h3',
        'h4',
        'h5',
        'h6',
        'head',
        'header',
        'hgroup',
        'hr',
        'html',
        'i',
        'iframe',
        'img',
        'input',
        'ins',
        'kbd',
        'keygen',
        'label',
        'legend',
        'li',
        'link',
        'map',
        'mark',
        'menu',
        'meta',
        'meter',
        'nav',
        'noscript',
        'object',
        'ol',
        'optgroup',
        'option',
        'output',
        'p',
        'param',
        'pre',
        'progress',
        'q',
        'rp',
        'rt',
        'ruby',
        's',
        'samp',
        'script',
        'section',
        'select',
        'small',
        'source',
        'span',
        'strong',
        'style',
        'sub',
        'summary',
        'sup',
        'table',
        'tbody',
        'td',
        'textarea',
        'tfoot',
        'th',
        'thead',
        'time',
        'title',
        'tr',
        'track',
        'u',
        'ul',
        'var',
        'video',
        'wbr',
    );

    public $mathmlElements = array(
        'maction',
        'maligngroup',
        'malignmark',
        'math',
        'menclose',
        'merror',
        'mfenced',
        'mfrac',
        'mglyph',
        'mi',
        'mlabeledtr',
        'mlongdiv',
        'mmultiscripts',
        'mn',
        'mo',
        'mover',
        'mpadded',
        'mphantom',
        'mroot',
        'mrow',
        'ms',
        'mscarries',
        'mscarry',
        'msgroup',
        'msline',
        'mspace',
        'msqrt',
        'msrow',
        'mstack',
        'mstyle',
        'msub',
        'msup',
        'msubsup',
        'mtable',
        'mtd',
        'mtext',
        'mtr',
        'munder',
        'munderover',
    );

    public $svgElements = array(
        'a',
        'altGlyph',
        'altGlyphDef',
        'altGlyphItem',
        'animate',
        'animateColor',
        'animateMotion',
        'animateTransform',
        'circle',
        'clipPath',
        'color-profile',
        'cursor',
        'defs',
        'desc',
        'ellipse',
        'feBlend',
        'feColorMatrix',
        'feComponentTransfer',
        'feComposite',
        'feConvolveMatrix',
        'feDiffuseLighting',
        'feDisplacementMap',
        'feDistantLight',
        'feFlood',
        'feFuncA',
        'feFuncB',
        'feFuncG',
        'feFuncR',
        'feGaussianBlur',
        'feImage',
        'feMerge',
        'feMergeNode',
        'feMorphology',
        'feOffset',
        'fePointLight',
        'feSpecularLighting',
        'feSpotLight',
        'feTile',
        'feTurbulence',
        'filter',
        'font',
        'font-face',
        'font-face-format',
        'font-face-name',
        'font-face-src',
        'font-face-uri',
        'foreignObject',
        'g',
        'glyph',
        'glyphRef',
        'hkern',
        'image',
        'line',
        'linearGradient',
        'marker',
        'mask',
        'metadata',
        'missing-glyph',
        'mpath',
        'path',
        'pattern',
        'polygon',
        'polyline',
        'radialGradient',
        'rect',
        'script',
        'set',
        'stop',
        'style',
        'svg',
        'switch',
        'symbol',
        'text',
        'textPath',
        'title',
        'tref',
        'tspan',
        'use',
        'view',
        'vkern',
    );

    public function testIsHtml5Element()
    {
        foreach ($this->html5Elements as $element) {
            $this->assertTrue(Elements::isHtml5Element($element), 'html5 element test failed on: ' . $element);

            $this->assertTrue(Elements::isHtml5Element(strtoupper($element)), 'html5 element test failed on: ' . strtoupper($element));
        }

        $nonhtml5 = array(
            'foo',
            'bar',
            'baz',
        );
        foreach ($nonhtml5 as $element) {
            $this->assertFalse(Elements::isHtml5Element($element), 'html5 element test failed on: ' . $element);

            $this->assertFalse(Elements::isHtml5Element(strtoupper($element)), 'html5 element test failed on: ' . strtoupper($element));
        }
    }

    public function testIsMathMLElement()
    {
        foreach ($this->mathmlElements as $element) {
            $this->assertTrue(Elements::isMathMLElement($element), 'MathML element test failed on: ' . $element);

            // MathML is case sensitive so these should all fail.
            $this->assertFalse(Elements::isMathMLElement(strtoupper($element)), 'MathML element test failed on: ' . strtoupper($element));
        }

        $nonMathML = array(
            'foo',
            'bar',
            'baz',
        );
        foreach ($nonMathML as $element) {
            $this->assertFalse(Elements::isMathMLElement($element), 'MathML element test failed on: ' . $element);
        }
    }

    public function testIsSvgElement()
    {
        foreach ($this->svgElements as $element) {
            $this->assertTrue(Elements::isSvgElement($element), 'SVG element test failed on: ' . $element);

            // SVG is case sensitive so these should all fail.
            $this->assertFalse(Elements::isSvgElement(strtoupper($element)), 'SVG element test failed on: ' . strtoupper($element));
        }

        $nonSVG = array(
            'foo',
            'bar',
            'baz',
        );
        foreach ($nonSVG as $element) {
            $this->assertFalse(Elements::isSvgElement($element), 'SVG element test failed on: ' . $element);
        }
    }

    public function testIsElement()
    {
        foreach ($this->html5Elements as $element) {
            $this->assertTrue(Elements::isElement($element), 'html5 element test failed on: ' . $element);

            $this->assertTrue(Elements::isElement(strtoupper($element)), 'html5 element test failed on: ' . strtoupper($element));
        }

        foreach ($this->mathmlElements as $element) {
            $this->assertTrue(Elements::isElement($element), 'MathML element test failed on: ' . $element);

            // MathML is case sensitive so these should all fail.
            $this->assertFalse(Elements::isElement(strtoupper($element)), 'MathML element test failed on: ' . strtoupper($element));
        }

        foreach ($this->svgElements as $element) {
            $this->assertTrue(Elements::isElement($element), 'SVG element test failed on: ' . $element);

            // SVG is case sensitive so these should all fail. But, there is duplication
            // html5 and SVG. Since html5 is case insensitive we need to make sure
            // it's not a html5 element first.
            if (!in_array($element, $this->html5Elements)) {
                $this->assertFalse(Elements::isElement(strtoupper($element)), 'SVG element test failed on: ' . strtoupper($element));
            }
        }

        $nonhtml5 = array(
            'foo',
            'bar',
            'baz',
        );
        foreach ($nonhtml5 as $element) {
            $this->assertFalse(Elements::isElement($element), 'html5 element test failed on: ' . $element);

            $this->assertFalse(Elements::isElement(strtoupper($element)), 'html5 element test failed on: ' . strtoupper($element));
        }
    }

    public function testElement()
    {
        foreach ($this->html5Elements as $element) {
            $this->assertGreaterThan(0, Elements::element($element));
        }
        $nonhtml5 = array(
            'foo',
            'bar',
            'baz',
        );
        foreach ($nonhtml5 as $element) {
            $this->assertEquals(0, Elements::element($element));
        }
    }

    public function testIsA()
    {
        $this->assertTrue(Elements::isA('script', Elements::KNOWN_ELEMENT));
        $this->assertFalse(Elements::isA('scriptypoo', Elements::KNOWN_ELEMENT));
        $this->assertTrue(Elements::isA('script', Elements::TEXT_RAW));
        $this->assertFalse(Elements::isA('script', Elements::TEXT_RCDATA));

        $voidElements = array(
            'area',
            'base',
            'basefont',
            'bgsound',
            'br',
            'col',
            'command',
            'embed',
            'frame',
            'hr',
            'img',
        );

        foreach ($voidElements as $element) {
            $this->assertTrue(Elements::isA($element, Elements::VOID_TAG), 'Void element test failed on: ' . $element);
        }

        $nonVoid = array(
            'span',
            'a',
            'div',
        );
        foreach ($nonVoid as $tag) {
            $this->assertFalse(Elements::isA($tag, Elements::VOID_TAG), 'Void element test failed on: ' . $tag);
        }

        $blockTags = array(
            'address',
            'article',
            'aside',
            'blockquote',
            'canvas',
            'dd',
            'div',
            'dl',
            'fieldset',
            'figcaption',
            'figure',
            'footer',
            'form',
            'h1',
            'h2',
            'h3',
            'h4',
            'h5',
            'h6',
            'header',
            'hgroup',
            'hr',
            'noscript',
            'ol',
            'output',
            'p',
            'pre',
            'section',
            'table',
            'tfoot',
            'ul',
            'video',
        );

        foreach ($blockTags as $tag) {
            $this->assertTrue(Elements::isA($tag, Elements::BLOCK_TAG), 'Block tag test failed on: ' . $tag);
        }

        $nonBlockTags = array(
            'span',
            'img',
            'label',
        );
        foreach ($nonBlockTags as $tag) {
            $this->assertFalse(Elements::isA($tag, Elements::BLOCK_TAG), 'Block tag test failed on: ' . $tag);
        }
    }

    public function testNormalizeSvgElement()
    {
        $tests = array(
            'foo' => 'foo',
            'altglyph' => 'altGlyph',
            'BAR' => 'bar',
            'fespecularlighting' => 'feSpecularLighting',
            'bAz' => 'baz',
            'foreignobject' => 'foreignObject',
        );

        foreach ($tests as $input => $expected) {
            $this->assertEquals($expected, Elements::normalizeSvgElement($input));
        }
    }

    public function testNormalizeSvgAttribute()
    {
        $tests = array(
            'foo' => 'foo',
            'attributename' => 'attributeName',
            'BAR' => 'bar',
            'limitingconeangle' => 'limitingConeAngle',
            'bAz' => 'baz',
            'patterncontentunits' => 'patternContentUnits',
        );

        foreach ($tests as $input => $expected) {
            $this->assertEquals($expected, Elements::normalizeSvgAttribute($input));
        }
    }

    public function testNormalizeMathMlAttribute()
    {
        $tests = array(
            'foo' => 'foo',
            'definitionurl' => 'definitionURL',
            'BAR' => 'bar',
        );

        foreach ($tests as $input => $expected) {
            $this->assertEquals($expected, Elements::normalizeMathMlAttribute($input));
        }
    }
}


code:
<!doctype html>

    <meta http-equiv="content-type" content="text/html;charset=utf-8" />

Žťčýů

license: mit


code:
<!doctype html>
<html>
<head>
    <meta http-equiv="content-type" content="text/html;charset=windows-1252">
</head>
<body>
    <p>�����</p>
</body>
</html>


code:
<!doctype html>

    <meta charset="utf-8">
    <title>Test</title>

This is a test.


code:
<?php

namespace Masterminds\HTML5\Tests;

use Masterminds\HTML5;

class Html5Test extends TestCase
{
    /**
     * @var HTML5
     */
    private $html5;

    public function setUp()
    {
        $this->html5 = $this->getInstance();
    }

    /**
     * Parse and serialize a string.
     */
    protected function cycle($html)
    {
        $dom = $this->html5->loadHTML('<!DOCTYPE html>' . $html . '');
        $out = $this->html5->saveHTML($dom);

        return $out;
    }

    protected function cycleFragment($fragment)
    {
        $dom = $this->html5->loadHTMLFragment($fragment);
        $out = $this->html5->saveHTML($dom);

        return $out;
    }

    public function testImageTagsInSvg()
    {
        $html = '<!DOCTYPE html>

                            <title>foo</title>

                            <svg>
                                <image height="10" width="10"></image>
                            </svg>

                    ';
        $doc = $this->html5->loadHTML($html);
        $this->assertInstanceOf('DOMElement', $doc->getElementsByTagName('image')->item(0));
        $this->assertEmpty($this->html5->getErrors());
    }

    public function testLoadOptions()
    {
        // doc
        $dom = $this->html5->loadHTML($this->wrap('<t:tag/>'), array(
            'implicitNamespaces' => array('t' => 'http://example.com'),
            'xmlNamespaces' => true,
        ));
        $this->assertInstanceOf('\DOMDocument', $dom);
        $this->assertEmpty($this->html5->getErrors());
        $this->assertFalse($this->html5->hasErrors());

        $xpath = new \DOMXPath($dom);
        $xpath->registerNamespace('t', 'http://example.com');
        $this->assertEquals(1, $xpath->query('//t:tag')->length);

        // doc fragment
        $frag = $this->html5->loadHTMLFragment('<t:tag/>', array(
            'implicitNamespaces' => array('t' => 'http://example.com'),
            'xmlNamespaces' => true,
        ));
        $this->assertInstanceOf('\DOMDocumentFragment', $frag);
        $this->assertEmpty($this->html5->getErrors());
        $this->assertFalse($this->html5->hasErrors());

        $frag->ownerDocument->appendChild($frag);
        $xpath = new \DOMXPath($frag->ownerDocument);
        $xpath->registerNamespace('t', 'http://example.com');
        $this->assertEquals(1, $xpath->query('//t:tag', $frag)->length);
    }

    public function testEncodingUtf8()
    {
        $dom = $this->html5->load(__DIR__ . '/Fixtures/encoding/utf-8.html');
        $this->assertInstanceOf('\DOMDocument', $dom);
        $this->assertEmpty($this->html5->getErrors());
        $this->assertFalse($this->html5->hasErrors());

        $this->assertContains('Žťčýů', $dom->saveHTML());
    }

    public function testEncodingWindows1252()
    {
        $dom = $this->html5->load(__DIR__ . '/Fixtures/encoding/windows-1252.html', array(
            'encoding' => 'Windows-1252',
        ));

        $this->assertInstanceOf('\DOMDocument', $dom);
        $this->assertEmpty($this->html5->getErrors());
        $this->assertFalse($this->html5->hasErrors());

        $dumpedAsUtf8 = mb_convert_encoding($dom->saveHTML(), 'UTF-8', 'Windows-1252');
        $this->assertNotFalse(mb_strpos($dumpedAsUtf8, 'Ž'));
        $this->assertNotFalse(mb_strpos($dumpedAsUtf8, 'è'));
        $this->assertNotFalse(mb_strpos($dumpedAsUtf8, 'ý'));
        $this->assertNotFalse(mb_strpos($dumpedAsUtf8, 'ù'));
    }

    public function testErrors()
    {
        $dom = $this->html5->loadHTML('<xx as>');
        $this->assertInstanceOf('\DOMDocument', $dom);

        $this->assertNotEmpty($this->html5->getErrors());
        $this->assertTrue($this->html5->hasErrors());
    }

    public function testLoad()
    {
        $dom = $this->html5->load(__DIR__ . '/Html5Test.html');
        $this->assertInstanceOf('\DOMDocument', $dom);
        $this->assertEmpty($this->html5->getErrors());
        $this->assertFalse($this->html5->hasErrors());

        $file = fopen(__DIR__ . '/Html5Test.html', 'r');
        $dom = $this->html5->load($file);
        $this->assertInstanceOf('\DOMDocument', $dom);
        $this->assertEmpty($this->html5->getErrors());

        $dom = $this->html5->loadHTMLFile(__DIR__ . '/Html5Test.html');
        $this->assertInstanceOf('\DOMDocument', $dom);
        $this->assertEmpty($this->html5->getErrors());
    }

    public function testLoadHTML()
    {
        $contents = file_get_contents(__DIR__ . '/Html5Test.html');
        $dom = $this->html5->loadHTML($contents);
        $this->assertInstanceOf('\DOMDocument', $dom);
        $this->assertEmpty($this->html5->getErrors());
    }

    public function testLoadHTMLWithComments()
    {
        $contents = '

';

        $dom = $this->html5->loadHTML($contents);
        $this->assertInstanceOf('\DOMDocument', $dom);

        $expected = '<!DOCTYPE html>

';
        $this->assertEquals($expected, $this->html5->saveHTML($dom));
    }

    public function testLoadHTMLFragment()
    {
        $fragment = '

Baz

';
        $dom = $this->html5->loadHTMLFragment($fragment);
        $this->assertInstanceOf('\DOMDocumentFragment', $dom);
        $this->assertEmpty($this->html5->getErrors());
    }

    public function testSaveHTML()
    {
        $dom = $this->html5->load(__DIR__ . '/Html5Test.html');
        $this->assertInstanceOf('\DOMDocument', $dom);
        $this->assertEmpty($this->html5->getErrors());

        $saved = $this->html5->saveHTML($dom);
        $this->assertRegExp('|
This is a test.
|', $saved);
    }

    public function testSaveHTMLFragment()
    {
        $fragment = '

Baz

';
        $dom = $this->html5->loadHTMLFragment($fragment);

        $string = $this->html5->saveHTML($dom);
        $this->assertEquals($fragment, $string);
    }

    public function testSave()
    {
        $dom = $this->html5->load(__DIR__ . '/Html5Test.html');
        $this->assertInstanceOf('\DOMDocument', $dom);
        $this->assertEmpty($this->html5->getErrors());

        // Test resource
        $file = fopen('php://temp', 'w');
        $this->html5->save($dom, $file);
        $content = stream_get_contents($file, -1, 0);
        $this->assertRegExp('|
This is a test.
|', $content);

        // Test file
        $tmpfname = tempnam(sys_get_temp_dir(), 'html5-php');
        $this->html5->save($dom, $tmpfname);
        $content = file_get_contents($tmpfname);
        $this->assertRegExp('|
This is a test.
|', $content);
        unlink($tmpfname);
    }

    // This test reads a document into a dom, turn the dom into a document,
    // then tries to read that document again. This makes sure we are reading,
    // and generating a document that works at a high level.
    public function testItWorks()
    {
        $dom = $this->html5->load(__DIR__ . '/Html5Test.html');
        $this->assertInstanceOf('\DOMDocument', $dom);
        $this->assertEmpty($this->html5->getErrors());

        $saved = $this->html5->saveHTML($dom);

        $dom2 = $this->html5->loadHTML($saved);
        $this->assertInstanceOf('\DOMDocument', $dom2);
        $this->assertEmpty($this->html5->getErrors());
    }

    public function testConfig()
    {
        $html5 = $this->getInstance();
        $options = $html5->getOptions();
        $this->assertEquals(false, $options['encode_entities']);

        $html5 = $this->getInstance(array(
            'foo' => 'bar',
            'encode_entities' => true,
        ));
        $options = $html5->getOptions();
        $this->assertEquals('bar', $options['foo']);
        $this->assertEquals(true, $options['encode_entities']);

        // Need to reset to original so future tests pass as expected.
        // $this->getInstance()->setOption('encode_entities', false);
    }

    public function testSvg()
    {
        $dom = $this->html5->loadHTML(
            '<!doctype html>

foo bar baz

          <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" />
            <text font-family="Verdana" font-size="32">
              <textPath xlink:href="#Foo">
                Test Text.
              </textPath>
            </text>
          </svg>

      ');

        $this->assertEmpty($this->html5->getErrors());

        // Test a mixed case attribute.
        $list = $dom->getElementsByTagName('svg');
        $this->assertNotEmpty($list->length);
        $svg = $list->item(0);
        $this->assertEquals('0 0 3 2', $svg->getAttribute('viewBox'));
        $this->assertFalse($svg->hasAttribute('viewbox'));

        // Test a mixed case tag.
        // Note: getElementsByTagName is not case sensitive.
        $list = $dom->getElementsByTagName('textPath');
        $this->assertNotEmpty($list->length);
        $textPath = $list->item(0);
        $this->assertEquals('textPath', $textPath->tagName);
        $this->assertNotEquals('textpath', $textPath->tagName);

        $html = $this->html5->saveHTML($dom);
        $this->assertRegExp('|<svg width="150" height="100" viewBox="0 0 3 2">|', $html);
        $this->assertRegExp('|<rect width="1" height="2" x="0" fill="#008d46" />|', $html);
    }

    public function testMathMl()
    {
        $dom = $this->html5->loadHTML(
            '<!doctype html>

foo bar baz

          <math>
            <mi>x</mi>
            <csymbol definitionURL="http://www.example.com/mathops/multiops.html#plusminus">
              <mo>±</mo>
            </csymbol>
            <mi>y</mi>
          </math>

      ');

        $this->assertEmpty($this->html5->getErrors());
        $list = $dom->getElementsByTagName('math');
        $this->assertNotEmpty($list->length);

        $list = $dom->getElementsByTagName('div');
        $this->assertNotEmpty($list->length);
        $div = $list->item(0);
        $this->assertEquals('http://example.com', $div->getAttribute('definitionurl'));
        $this->assertFalse($div->hasAttribute('definitionURL'));
        $list = $dom->getElementsByTagName('csymbol');
        $csymbol = $list->item(0);
        $this->assertEquals('http://www.example.com/mathops/multiops.html#plusminus', $csymbol->getAttribute('definitionURL'));
        $this->assertFalse($csymbol->hasAttribute('definitionurl'));

        $html = $this->html5->saveHTML($dom);
        $this->assertRegExp('|<csymbol definitionURL="http://www.example.com/mathops/multiops.html#plusminus">|', $html);
        $this->assertRegExp('|<mi>y</mi>|', $html);
    }

    public function testUnknownElements()
    {
        // The : should not have special handling accourding to section 2.9 of the
        // spec. This is differenant than XML. Since we don't know these elements
        // they are handled as normal elements. Note, to do this is really
        // an invalid example and you should not embed prefixed xml in html5.
        $dom = $this->html5->loadHTMLFragment(
            '<f:rug>
      <f:name>Big rectangle thing</f:name>
      <f:width>40</f:width>
      <f:length>80</f:length>
    </f:rug>
    <sarcasm>um, yeah</sarcasm>');

        $this->assertEmpty($this->html5->getErrors());
        $markup = $this->html5->saveHTML($dom);
        $this->assertRegExp('|<f:name>Big rectangle thing</f:name>|', $markup);
        $this->assertRegExp('|<sarcasm>um, yeah</sarcasm>|', $markup);
    }

    public function testElements()
    {
        // Should have content.
        $res = $this->cycle('
FOO
');
        $this->assertRegExp('|
FOO
|', $res);

        // Should be empty
        $res = $this->cycle('');
        $this->assertRegExp('||', $res);

        // Should have content.
        $res = $this->cycleFragment('
FOO
');
        $this->assertRegExp('|
FOO
|', $res);

        // Should be empty
        $res = $this->cycleFragment('');
        $this->assertRegExp('||', $res);

        // Elements with dashes and underscores
        $res = $this->cycleFragment('<sp-an></sp-an>');
        $this->assertRegExp('|<sp-an></sp-an>|', $res);
        $res = $this->cycleFragment('<sp_an></sp_an>');
        $this->assertRegExp('|<sp_an></sp_an>|', $res);

        // Should have no closing tag.
        $res = $this->cycle('
');
        $this->assertRegExp('|
|', $res);
    }

    public function testAttributes()
    {
        $res = $this->cycle('<use xlink:href="#svg-track" xmlns:xlink="http://www.w3.org/1999/xlink"></use>');
        $this->assertContains('<use xlink:href="#svg-track" xmlns:xlink="http://www.w3.org/1999/xlink"></use>', $res);

        $res = $this->cycle('
FOO
');
        $this->assertRegExp('|
FOO
|', $res);

        // XXX: Note that spec does NOT require attrs in the same order.
        $res = $this->cycle('
FOO
');
        $this->assertRegExp('|
FOO
|', $res);

        $res = $this->cycle('
FOO
');
        $this->assertRegExp('|
FOO
|', $res);

        $res = $this->cycleFragment('
FOO
');
        $this->assertRegExp('|
FOO
|', $res);

        // XXX: Note that spec does NOT require attrs in the same order.
        $res = $this->cycleFragment('
FOO
');
        $this->assertRegExp('|
FOO
|', $res);

        $res = $this->cycleFragment('
FOO
');
        $this->assertRegExp('|
FOO
|', $res);
    }

    public function testPCData()
    {
        $res = $this->cycle('This is a test.');
        $this->assertRegExp('|This is a test.|', $res);

        $res = $this->cycleFragment('This is a test.');
        $this->assertRegExp('|This is a test.|', $res);

        $res = $this->cycle('This
      is
      a
      test.');

        // Check that newlines are there, but don't count spaces.
        $this->assertRegExp('|This\n\s*is\n\s*a\n\s*test.|', $res);

        $res = $this->cycleFragment('This
      is
      a
      test.');

        // Check that newlines are there, but don't count spaces.
        $this->assertRegExp('|This\n\s*is\n\s*a\n\s*test.|', $res);

        $res = $this->cycle('This is a test.');
        $this->assertRegExp('|This is a test.|', $res);

        $res = $this->cycleFragment('This is a test.');
        $this->assertRegExp('|This is a test.|', $res);
    }

    public function testUnescaped()
    {
        $res = $this->cycle('');
        $this->assertRegExp('|2 < 1|', $res);

        $res = $this->cycle('');
        $this->assertRegExp('|div>div>div|', $res);

        $res = $this->cycleFragment('');
        $this->assertRegExp('|2 < 1|', $res);

        $res = $this->cycleFragment('');
        $this->assertRegExp('|div>div>div|', $res);
    }

    public function testEntities()
    {
        $res = $this->cycle('Apples & bananas.');
        $this->assertRegExp('|Apples & bananas.|', $res);

        $res = $this->cycleFragment('Apples & bananas.');
        $this->assertRegExp('|Apples & bananas.|', $res);

        $res = $this->cycleFragment('
R&D
');
        $this->assertRegExp('|R&D|', $res);
    }

    public function testCaseSensitiveTags()
    {
        $dom = $this->html5->loadHTML(
            '<Button color="red">Error</Button>',
            array(
                'xmlNamespaces' => true,
            )
        );
        $out = $this->html5->saveHTML($dom);
        $this->assertRegExp('|<Button color="red">Error</Button>|', $out);
    }

    public function testComment()
    {
        $res = $this->cycle('ab');
        $this->assertRegExp('||', $res);

        $res = $this->cycleFragment('ab');
        $this->assertRegExp('||', $res);
    }

    public function testCDATA()
    {
        $res = $this->cycle('a<![CDATA[ This <is> a test. ]]>b');
        $this->assertRegExp('|<!\[CDATA\[ This <is> a test\. \]\]>|', $res);

        $res = $this->cycleFragment('a<![CDATA[ This <is> a test. ]]>b');
        $this->assertRegExp('|<!\[CDATA\[ This <is> a test\. \]\]>|', $res);
    }
}


code:
<?php
/**
 * @file
 * Test the Scanner. This requires the InputStream tests are all good.
 */

namespace Masterminds\HTML5\Tests\Parser;

use Masterminds\HTML5\Parser\CharacterReference;

class CharacterReferenceTest extends \Masterminds\HTML5\Tests\TestCase
{
    public function testLookupName()
    {
        $this->assertEquals('&', CharacterReference::lookupName('amp'));
        $this->assertEquals('<', CharacterReference::lookupName('lt'));
        $this->assertEquals('>', CharacterReference::lookupName('gt'));
        $this->assertEquals('"', CharacterReference::lookupName('quot'));
        $this->assertEquals('∌', CharacterReference::lookupName('NotReverseElement'));

        $this->assertNull(CharacterReference::lookupName('StinkyCheese'));
    }

    public function testLookupHex()
    {
        $this->assertEquals('<', CharacterReference::lookupHex('3c'));
        $this->assertEquals('<', CharacterReference::lookupHex('003c'));
        $this->assertEquals('&', CharacterReference::lookupHex('26'));
        $this->assertEquals('}', CharacterReference::lookupHex('7d'));
        $this->assertEquals('Σ', CharacterReference::lookupHex('3A3'));
        $this->assertEquals('Σ', CharacterReference::lookupHex('03A3'));
        $this->assertEquals('Σ', CharacterReference::lookupHex('3a3'));
        $this->assertEquals('Σ', CharacterReference::lookupHex('03a3'));
    }

    public function testLookupDecimal()
    {
        $this->assertEquals('&', CharacterReference::lookupDecimal(38));
        $this->assertEquals('&', CharacterReference::lookupDecimal('38'));
        $this->assertEquals('<', CharacterReference::lookupDecimal(60));
        $this->assertEquals('Σ', CharacterReference::lookupDecimal(931));
        $this->assertEquals('Σ', CharacterReference::lookupDecimal('0931'));
    }
}


code:
<?php
/**
 * @file
 * Test the Tree Builder.
 */

namespace Masterminds\HTML5\Tests\Parser;

use Masterminds\HTML5\Parser\Scanner;
use Masterminds\HTML5\Parser\Tokenizer;
use Masterminds\HTML5\Parser\DOMTreeBuilder;

/**
 * These tests are functional, not necessarily unit tests.
 */
class DOMTreeBuilderTest extends \Masterminds\HTML5\Tests\TestCase
{
    protected $errors = array();

    /**
     * Convenience function for parsing.
     */
    protected function parse($string, array $options = array())
    {
        $treeBuilder = new DOMTreeBuilder(false, $options);
        $scanner = new Scanner($string);
        $parser = new Tokenizer($scanner, $treeBuilder);

        $parser->parse();
        $this->errors = $treeBuilder->getErrors();

        return $treeBuilder->document();
    }

    /**
     * Utility function for parsing a fragment of HTML5.
     */
    protected function parseFragment($string)
    {
        $treeBuilder = new DOMTreeBuilder(true);
        $scanner = new Scanner($string);
        $parser = new Tokenizer($scanner, $treeBuilder);

        $parser->parse();
        $this->errors = $treeBuilder->getErrors();

        return $treeBuilder->fragment();
    }

    public function testDocument()
    {
        $html = '<!DOCTYPE html>';
        $doc = $this->parse($html);

        $this->assertInstanceOf('\DOMDocument', $doc);
        $this->assertEquals('html', $doc->documentElement->tagName);
        $this->assertEquals('http://www.w3.org/1999/xhtml', $doc->documentElement->namespaceURI);
    }

    public function testBareAmpersand()
    {
        $html = "<!doctype html>

        ";
        $doc = $this->parse($html);

        $this->assertEmpty($this->errors);
        $this->assertXmlStringEqualsXmlString('
        <!DOCTYPE html>

        ', $doc->saveXML());
    }

    public function testBareAmpersandNotAllowedInAttributes()
    {
        $html = "<!doctype html>

        ";
        $doc = $this->parse($html);

        $this->assertCount(2, $this->errors);
        $this->assertXmlStringEqualsXmlString('
        <!DOCTYPE html>

        ', $doc->saveXML());
    }

    public function testBareAmpersandNotAllowedInBody()
    {
        $html = '<!doctype html>

                a&b
                a&=
                a&=c
                a&=9
                a&+
                a& -- valid

        ';
        $doc = $this->parse($html);

        $this->assertCount(5, $this->errors);
        $this->assertXmlStringEqualsXmlString('
        <!DOCTYPE html>

                a&b
                a&=
                a&=c
                a&=9
                a&+
                a& -- valid

        ', $doc->saveXML());
    }

    public function testStrangeCapitalization()
    {
        $html = '<!doctype html>

                <Title>Hello, world!</TitlE>

            TheBody
        ';
        $doc = $this->parse($html);

        $this->assertInstanceOf('\DOMDocument', $doc);
        $this->assertEquals('html', $doc->documentElement->tagName);

        $xpath = new \DOMXPath($doc);
        $xpath->registerNamespace('x', 'http://www.w3.org/1999/xhtml');

        $this->assertEquals('Hello, world!', $xpath->query('//x:title')->item(0)->nodeValue);
        $this->assertEquals('foo', $xpath->query('//x:script')->item(0)->nodeValue);
    }

    public function testDocumentWithDisabledNamespaces()
    {
        $html = '<!DOCTYPE html>';
        $doc = $this->parse($html, array('disable_html_ns' => true));

        $this->assertInstanceOf('\DOMDocument', $doc);
        $this->assertEquals('html', $doc->documentElement->tagName);
        $this->assertNull($doc->documentElement->namespaceURI);
    }

    public function testDocumentWithATargetDocument()
    {
        $targetDom = new \DOMDocument();

        $html = '<!DOCTYPE html>';
        $doc = $this->parse($html, array('target_document' => $targetDom));

        $this->assertInstanceOf('\DOMDocument', $doc);
        $this->assertSame($doc, $targetDom);
        $this->assertEquals('html', $doc->documentElement->tagName);
    }

    public function testDocumentFakeAttrAbsence()
    {
        $html = '<!DOCTYPE html>foo';
        $doc = $this->parse($html, array('xmlNamespaces' => true));

        $xp = new \DOMXPath($doc);
        $this->assertEquals(0, $xp->query('//@html5-php-fake-id-attribute')->length);
    }

    public function testFragment()
    {
        $html = '
test
test2';
        $doc = $this->parseFragment($html);

        $this->assertInstanceOf('\DOMDocumentFragment', $doc);
        $this->assertTrue($doc->hasChildNodes());
        $this->assertEquals('div', $doc->childNodes->item(0)->tagName);
        $this->assertEquals('test', $doc->childNodes->item(0)->textContent);
        $this->assertEquals('span', $doc->childNodes->item(1)->tagName);
        $this->assertEquals('test2', $doc->childNodes->item(1)->textContent);
    }

    public function testElements()
    {
        $html = '<!DOCTYPE html><title></title>';
        $doc = $this->parse($html);
        $root = $doc->documentElement;

        $this->assertEquals('html', $root->tagName);
        $this->assertEquals('html', $root->localName);
        $this->assertEquals('html', $root->nodeName);

        $this->assertEquals(2, $root->childNodes->length);
        $kids = $root->childNodes;

        $this->assertEquals('head', $kids->item(0)->tagName);
        $this->assertEquals('body', $kids->item(1)->tagName);

        $head = $kids->item(0);
        $this->assertEquals(1, $head->childNodes->length);
        $this->assertEquals('title', $head->childNodes->item(0)->tagName);
    }

    public function testImplicitNamespaces()
    {
        $dom = $this->parse('<!DOCTYPE html>foo');
        $a = $dom->getElementsByTagName('a')->item(0);
        $attr = $a->getAttributeNode('xlink:href');
        $this->assertEquals('http://www.w3.org/1999/xlink', $attr->namespaceURI);

        $dom = $this->parse('<!DOCTYPE html>foo');
        $a = $dom->getElementsByTagName('a')->item(0);
        $attr = $a->getAttributeNode('xml:base');
        $this->assertEquals('http://www.w3.org/XML/1998/namespace', $attr->namespaceURI);
    }

    public function testCustomImplicitNamespaces()
    {
        $dom = $this->parse('<!DOCTYPE html>foo', array(
            'implicitNamespaces' => array(
                't' => 'http://www.example.com',
            ),
        ));
        $a = $dom->getElementsByTagName('a')->item(0);
        $attr = $a->getAttributeNode('t:href');
        $this->assertEquals('http://www.example.com', $attr->namespaceURI);

        $dom = $this->parse('<!DOCTYPE html><t:a>foo</t:a>', array(
            'implicitNamespaces' => array(
                't' => 'http://www.example.com',
            ),
        ));
        $list = $dom->getElementsByTagNameNS('http://www.example.com', 'a');
        $this->assertEquals(1, $list->length);
    }

    public function testXmlNamespaces()
    {
        $dom = $this->parse(
            '<!DOCTYPE html>
            <t:body xmlns:t="http://www.example.com">
                foo

foo

          ', array(
                'xmlNamespaces' => true,
            ));
        $a = $dom->getElementsByTagName('a')->item(0);
        $attr = $a->getAttributeNode('t:href');
        $this->assertEquals('http://www.example.com', $attr->namespaceURI);

        $list = $dom->getElementsByTagNameNS('http://www.example.com', 'body');
        $this->assertEquals(1, $list->length);
    }

    public function testXmlNamespaceNesting()
    {
        $dom = $this->parse(
            '<!DOCTYPE html>

                    <x:prefixed id="prefixed"/>

                <svg id="svg"></svg>
                <c id="bar2" xmlns="http://www.prefixed.com/bar2"></c>

                <d id="bar3"></d>
                <xn:d xmlns:xn="http://www.prefixed.com/xn" xmlns="http://www.prefixed.com/bar5_x" id="bar5"><x id="bar5_x"/></xn:d>

          ', array(
                'xmlNamespaces' => true,
            ));

        $this->assertEmpty($this->errors);

        $div = $dom->getElementById('div');
        $this->assertEquals('http://www.w3.org/1999/xhtml', $div->namespaceURI);

        $body = $dom->getElementById('body');
        $this->assertEquals('http://www.w3.org/1999/xhtml', $body->namespaceURI);

        $bar1 = $dom->getElementById('bar1');
        $this->assertEquals('http://www.prefixed.com/bar1', $bar1->namespaceURI);

        $bar2 = $dom->getElementById('bar2');
        $this->assertEquals('http://www.prefixed.com/bar2', $bar2->namespaceURI);

        $bar3 = $dom->getElementById('bar3');
        $this->assertEquals('http://www.w3.org/1999/xhtml', $bar3->namespaceURI);

        $bar4 = $dom->getElementById('bar4');
        $this->assertEquals('http://www.prefixed.com/bar4', $bar4->namespaceURI);

        $svg = $dom->getElementById('svg');
        $this->assertEquals('http://www.w3.org/2000/svg', $svg->namespaceURI);

        $prefixed = $dom->getElementById('prefixed');
        $this->assertEquals('http://www.prefixed.com', $prefixed->namespaceURI);

        $prefixed = $dom->getElementById('bar5');
        $this->assertEquals('http://www.prefixed.com/xn', $prefixed->namespaceURI);

        $prefixed = $dom->getElementById('bar5_x');
        $this->assertEquals('http://www.prefixed.com/bar5_x', $prefixed->namespaceURI);
    }

    public function testMoveNonInlineElements()
    {
        $doc = $this->parse('
line1

line2
');
        $this->assertEquals('
line1

line2', $doc->saveXML($doc->documentElement), 'Move non-inline elements outside of inline containers.');

        $doc = $this->parse('
line1
line2

');
        $this->assertEquals('
line1

line2
', $doc->saveXML($doc->documentElement), 'Move non-inline elements outside of inline containers.');
    }

    public function testAttributes()
    {
        $html = "<!DOCTYPE html>

      <title></title>

      ";
        $doc = $this->parse($html);
        $root = $doc->documentElement;

        $body = $root->GetElementsByTagName('body')->item(0);
        $this->assertEquals('body', $body->tagName);
        $this->assertTrue($body->hasAttributes());
        $this->assertEquals('a', $body->getAttribute('id'));
        $this->assertEquals('b c', $body->getAttribute('class'));

        $body2 = $doc->getElementById('a');
        $this->assertEquals('body', $body2->tagName);
        $this->assertEquals('a', $body2->getAttribute('id'));
    }

    public function testSVGAttributes()
    {
        $html = "<!DOCTYPE html>

      <svg width='150' viewbox='2'>
      <rect textlength='2'/>
      <animatecolor>foo</animatecolor>
      </svg>
      ";
        $doc = $this->parse($html);
        $root = $doc->documentElement;

        $svg = $root->getElementsByTagName('svg')->item(0);
        $this->assertTrue($svg->hasAttribute('viewBox'));

        $rect = $root->getElementsByTagName('rect')->item(0);
        $this->assertTrue($rect->hasAttribute('textLength'));

        $ac = $root->getElementsByTagName('animateColor');
        $this->assertEquals(1, $ac->length);
    }

    public function testMathMLAttribute()
    {
        $html = '<!doctype html>

          <math>
            <mi>x</mi>
            <csymbol definitionurl="http://www.example.com/mathops/multiops.html#plusminus">
              <mo>±</mo>
            </csymbol>
            <mi>y</mi>
          </math>

      ';

        $doc = $this->parse($html);
        $root = $doc->documentElement;

        $csymbol = $root->getElementsByTagName('csymbol')->item(0);
        $this->assertTrue($csymbol->hasAttribute('definitionURL'));
    }

    public function testMissingHtmlTag()
    {
        $html = '<!DOCTYPE html><title>test</title>';
        $doc = $this->parse($html);

        $this->assertEquals('html', $doc->documentElement->tagName);
        $this->assertEquals('title', $doc->documentElement->childNodes->item(0)->tagName);
    }

    public function testComment()
    {
        $html = '';

        $doc = $this->parse($html);

        $comment = $doc->documentElement->childNodes->item(0);
        $this->assertEquals(XML_COMMENT_NODE, $comment->nodeType);
        $this->assertEquals('Hello World.', $comment->data);

        $html = '';
        $doc = $this->parse($html);

        $comment = $doc->childNodes->item(1);
        $this->assertEquals(XML_COMMENT_NODE, $comment->nodeType);
        $this->assertEquals('Hello World.', $comment->data);

        $comment = $doc->childNodes->item(2);
        $this->assertEquals(XML_ELEMENT_NODE, $comment->nodeType);
        $this->assertEquals('html', $comment->tagName);
    }

    public function testCDATA()
    {
        $html = '<!DOCTYPE html><math><![CDATA[test]]></math>';
        $doc = $this->parse($html);

        $wrapper = $doc->getElementsByTagName('math')->item(0);
        $this->assertEquals(1, $wrapper->childNodes->length);
        $cdata = $wrapper->childNodes->item(0);
        $this->assertEquals(XML_CDATA_SECTION_NODE, $cdata->nodeType);
        $this->assertEquals('test', $cdata->data);
    }

    public function testText()
    {
        $html = '<!DOCTYPE html><math>test</math>';
        $doc = $this->parse($html);

        $wrapper = $doc->getElementsByTagName('math')->item(0);
        $this->assertEquals(1, $wrapper->childNodes->length);
        $data = $wrapper->childNodes->item(0);
        $this->assertEquals(XML_TEXT_NODE, $data->nodeType);
        $this->assertEquals('test', $data->data);

        // The DomTreeBuilder has special handling for text when in before head mode.
        $html = '<!DOCTYPE html>
    Foo';
        $doc = $this->parse($html);
        $this->assertEquals('Line 0, Col 0: Unexpected text. Ignoring: Foo', $this->errors[0]);
        $headElement = $doc->documentElement->firstChild;
        $this->assertEquals('head', $headElement->tagName);
    }

    public function testParseErrors()
    {
        $html = '<!DOCTYPE html><math><![CDATA[test';
        $doc = $this->parse($html);

        // We're JUST testing that we can access errors. Actual testing of
        // error messages happen in the Tokenizer's tests.
        $this->assertGreaterThan(0, count($this->errors));
        $this->assertTrue(is_string($this->errors[0]));
    }

    public function testProcessingInstruction()
    {
        // Test the simple case, which is where PIs are inserted into the DOM.
        $doc = $this->parse('<!DOCTYPE html><?foo bar?>');
        $this->assertEquals(1, $doc->documentElement->childNodes->length);
        $pi = $doc->documentElement->firstChild;
        $this->assertInstanceOf('\DOMProcessingInstruction', $pi);
        $this->assertEquals('foo', $pi->nodeName);
        $this->assertEquals('bar', $pi->data);

        // Leading xml PIs should be ignored.
        $doc = $this->parse('<?xml version="1.0"?><!DOCTYPE html>');

        $this->assertEquals(2, $doc->childNodes->length);
        $this->assertInstanceOf('\DOMDocumentType', $doc->childNodes->item(0));
        $this->assertInstanceOf('\DOMElement', $doc->childNodes->item(1));
    }

    public function testAutocloseP()
    {
        $html = '<!DOCTYPE html>

';
        $doc = $this->parse($html);

        $p = $doc->getElementsByTagName('p')->item(0);
        $this->assertEquals(0, $p->childNodes->length);
        $this->assertEquals('figure', $p->nextSibling->tagName);
    }

    public function testAutocloseLI()
    {
        $html = '<!doctype html>

Foo
Bar
Baz

      ';

        $doc = $this->parse($html);
        $length = $doc->getElementsByTagName('ul')->item(0)->childNodes->length;
        $this->assertEquals(3, $length);
    }

    public function testMathML()
    {
        $html = '<!doctype html>

          <math xmlns="http://www.w3.org/1998/Math/MathML">
            <mi>x</mi>
            <csymbol definitionurl="http://www.example.com/mathops/multiops.html#plusminus">
              <mo>±</mo>
            </csymbol>
            <mi>y</mi>
          </math>

      ';

        $doc = $this->parse($html);
        $math = $doc->getElementsByTagName('math')->item(0);
        $this->assertEquals('math', $math->tagName);
        $this->assertEquals('math', $math->nodeName);
        $this->assertEquals('math', $math->localName);
        $this->assertEquals('http://www.w3.org/1998/Math/MathML', $math->namespaceURI);
    }

    public function testSVG()
    {
        $html = '<!doctype html>

          <svg width="150" height="100" viewBox="0 0 3 2" xmlns="http://www.w3.org/2000/svg">
            <rect width="1" height="2" x="2" fill="#d2232c" />
            <text font-family="Verdana" font-size="32">
              <textpath xlink:href="#Foo">
                Test Text.
              </textPath>
            </text>
          </svg>

      ';

        $doc = $this->parse($html);
        $svg = $doc->getElementsByTagName('svg')->item(0);
        $this->assertEquals('svg', $svg->tagName);
        $this->assertEquals('svg', $svg->nodeName);
        $this->assertEquals('svg', $svg->localName);
        $this->assertEquals('http://www.w3.org/2000/svg', $svg->namespaceURI);

        $textPath = $doc->getElementsByTagName('textPath')->item(0);
        $this->assertEquals('textPath', $textPath->tagName);
    }

    public function testNoScript()
    {
        $html = '<!DOCTYPE html>No JS';
        $doc = $this->parse($html);
        $this->assertEmpty($this->errors);
        $noscript = $doc->getElementsByTagName('noscript')->item(0);
        $this->assertEquals('noscript', $noscript->tagName);

        $html = '<!DOCTYPE html>
No JS
';
        $doc = $this->parse($html);
        $this->assertEmpty($this->errors);
        $p = $doc->getElementsByTagName('p')->item(0);
        $this->assertEquals('p', $p->tagName);
    }

    /**
     * Regression for issue #13.
     */
    public function testRegressionHTMLNoBody()
    {
        $html = '<!DOCTYPE html>Test';
        $doc = $this->parse($html);
        $span = $doc->getElementById('test');

        $this->assertEmpty($this->errors);

        $this->assertEquals('span', $span->tagName);
        $this->assertEquals('Test', $span->textContent);
    }

    public function testInstructionProcessor()
    {
        $string = '<!DOCTYPE html><?foo bar ?>';

        $treeBuilder = new DOMTreeBuilder();
        $is = new InstructionProcessorMock();
        $treeBuilder->setInstructionProcessor($is);

        $scanner = new Scanner($string);
        $parser = new Tokenizer($scanner, $treeBuilder);

        $parser->parse();
        $dom = $treeBuilder->document();
        $div = $dom->getElementsByTagName('div')->item(0);

        $this->assertEquals(1, $is->count);
        $this->assertEquals('foo', $is->name);
        $this->assertEquals('bar ', $is->data);
        $this->assertEquals('div', $div->tagName);
        $this->assertEquals('foo', $div->textContent);
    }

    public function testSelectGroupedOptions()
    {
        $html = <<<EOM
<!DOCTYPE html>

        <title>testSelectGroupedOptions</title>

        <select>
            <optgroup id="first" label="first">
                <option value="foo">foo</option>
                <option value="bar">bar</option>
                <option value="baz">baz</option>
            </optgroup>
            <optgroup id="second" label="second">
                <option value="lorem">lorem</option>
                <option value="ipsum">ipsum</option>
            </optgroup>
         </select>

EOM;
        $dom = $this->parse($html);

        $this->assertSame(3, $dom->getElementById('first')->getElementsByTagName('option')->length);
        $this->assertSame(2, $dom->getElementById('second')->getElementsByTagName('option')->length);
    }

    public function testVoidTag()
    {
        $html = <<<EOM
<!DOCTYPE html>

        <title>testVoidTag</title>
        <meta>
        <meta>

EOM;

        $dom = $this->parse($html);
        $this->assertSame(2, $dom->getElementsByTagName('meta')->length);
        $this->assertSame(0, $dom->getElementsByTagName('meta')->item(0)->childNodes->length);
        $this->assertSame(0, $dom->getElementsByTagName('meta')->item(1)->childNodes->length);
    }

    public function testIgnoreSelfClosingTag()
    {
        $html = <<<EOM
<!DOCTYPE html>

        <title>testIllegalSelfClosingTag</title>

Hello, World!

EOM;

        $dom = $this->parse($html);
        $this->assertSame(1, $dom->getElementsByTagName('div')->item(0)->childNodes->length);
    }

    public function testIAudioInParagraph()
    {
        $html = <<<EOM
<!DOCTYPE html>

        <title>testIllegalSelfClosingTag</title>

        <audio preload="none" controls="controls">
            <source src="https://example.com/test.mp3" type="audio/mpeg" />
            Your browser does not support the audio element.
        </audio>

>

EOM;

        $dom = $this->parse($html);
        $audio = $dom->getElementsByTagName('audio')->item(0);

        $this->assertSame('p', $audio->parentNode->nodeName);
        $this->assertSame(3, $audio->childNodes->length);
    }
}


code:
<?php

namespace Masterminds\HTML5\Tests\Parser;

use Masterminds\HTML5\Elements;
use Masterminds\HTML5\Parser\EventHandler;

/**
 * This testing class gathers events from a parser and builds a stack of events.
 * It is useful for checking the output of a tokenizer.
 *
 * IMPORTANT:
 *
 * The startTag event also kicks the parser into TEXT_RAW when it encounters
 * script or pre tags. This is to match the behavior required by the HTML5 spec,
 * which says that the tree builder must tell the tokenizer when to switch states.
 */
class EventStack implements EventHandler
{
    protected $stack;

    public function __construct()
    {
        $this->stack = array();
    }

    /**
     * Get the event stack.
     */
    public function events()
    {
        return $this->stack;
    }

    public function depth()
    {
        return count($this->stack);
    }

    public function get($index)
    {
        return $this->stack[$index];
    }

    protected function store($event, $data = null)
    {
        $this->stack[] = array(
            'name' => $event,
            'data' => $data,
        );
    }

    public function doctype($name, $type = 0, $id = null, $quirks = false)
    {
        $args = array(
            $name,
            $type,
            $id,
            $quirks,
        );
        $this->store('doctype', $args);
    }

    public function startTag($name, $attributes = array(), $selfClosing = false)
    {
        $args = func_get_args();
        $this->store('startTag', $args);
        if ('pre' == $name || 'script' == $name) {
            return Elements::TEXT_RAW;
        }
    }

    public function endTag($name)
    {
        $this->store('endTag', array(
            $name,
        ));
    }

    public function comment($cdata)
    {
        $this->store('comment', array(
            $cdata,
        ));
    }

    public function cdata($data)
    {
        $this->store('cdata', func_get_args());
    }

    public function text($cdata)
    {
        // fprintf(STDOUT, "Received TEXT event with: " . $cdata);
        $this->store('text', array(
            $cdata,
        ));
    }

    public function eof()
    {
        $this->store('eof');
    }

    public function parseError($msg, $line, $col)
    {
        // throw new EventStackParseError(sprintf("%s (line %d, col %d)", $msg, $line, $col));
        // $this->store(sprintf("%s (line %d, col %d)", $msg, $line, $col));
        $this->store('error', func_get_args());
    }

    public function processingInstruction($name, $data = null)
    {
        $this->store('pi', func_get_args());
    }
}


code:
<?php

namespace Masterminds\HTML5\Tests\Parser;

class EventStackError extends \Exception
{
}


code:
<?php

namespace Masterminds\HTML5\Tests\Parser;

class InstructionProcessorMock implements \Masterminds\HTML5\InstructionProcessor
{
    public $name = null;

    public $data = null;

    public $count = 0;

    public function process(\DOMElement $element, $name, $data)
    {
        $this->name = $name;
        $this->data = $data;
        ++$this->count;

        $div = $element->ownerDocument->createElement('div');
        $div->nodeValue = 'foo';

        $element->appendChild($div);

        return $div;
    }
}


code:
<?php
/**
 * @file
 * Test the Scanner. This requires the InputStream tests are all good.
 */

namespace Masterminds\HTML5\Tests\Parser;

use Masterminds\HTML5\Parser\StringInputStream;
use Masterminds\HTML5\Parser\Scanner;

class ScannerTest extends \Masterminds\HTML5\Tests\TestCase
{
    /**
     * A canary test to make sure the basics are setup and working.
     */
    public function testConstructDeprecated()
    {
        $is = new StringInputStream('abc');
        $s = new Scanner($is);

        $this->assertInstanceOf('\Masterminds\HTML5\Parser\Scanner', $s);
    }

    public function testConstruct()
    {
        $this->assertInstanceOf('\Masterminds\HTML5\Parser\Scanner', new Scanner('abc'));
    }

    public function testNextDeprecated()
    {
        $s = new Scanner(new StringInputStream('abc'));

        $this->assertEquals('b', $s->next());
        $this->assertEquals('c', $s->next());
    }

    public function testNext()
    {
        $s = new Scanner('abc');

        $this->assertEquals('b', $s->next());
        $this->assertEquals('c', $s->next());
    }

    public function testPosition()
    {
        $s = new Scanner('abc');

        $this->assertEquals(0, $s->position());

        $s->next();
        $this->assertEquals(1, $s->position());
    }

    public function testPeek()
    {
        $s = new Scanner('abc');

        $this->assertEquals('b', $s->peek());

        $s->next();
        $this->assertEquals('c', $s->peek());
    }

    public function testCurrent()
    {
        $s = new Scanner('abc');

        // Before scanning the string begins the current is empty.
        $this->assertEquals('a', $s->current());

        $c = $s->next();
        $this->assertEquals('b', $s->current());

        // Test movement through the string.
        $c = $s->next();
        $this->assertEquals('c', $s->current());
    }

    public function testUnconsume()
    {
        $s = new Scanner('abcdefghijklmnopqrst');

        // Get initial position.
        $s->next();
        $start = $s->position();

        // Move forward a bunch of positions.
        $amount = 7;
        for ($i = 0; $i < $amount; ++$i) {
            $s->next();
        }

        // Roll back the amount we moved forward.
        $s->unconsume($amount);

        $this->assertEquals($start, $s->position());
    }

    public function testGetHex()
    {
        $s = new Scanner('ab13ck45DE*');

        $this->assertEquals('ab13c', $s->getHex());

        $s->next();
        $this->assertEquals('45DE', $s->getHex());
    }

    public function testGetAsciiAlpha()
    {
        $s = new Scanner('abcdef1%mnop*');

        $this->assertEquals('abcdef', $s->getAsciiAlpha());

        // Move past the 1% to scan the next group of text.
        $s->next();
        $s->next();
        $this->assertEquals('mnop', $s->getAsciiAlpha());
    }

    public function testGetAsciiAlphaNum()
    {
        $s = new Scanner('abcdef1ghpo#mn94op');

        $this->assertEquals('abcdef1ghpo', $s->getAsciiAlphaNum());

        // Move past the # to scan the next group of text.
        $s->next();
        $this->assertEquals('mn94op', $s->getAsciiAlphaNum());
    }

    public function testGetNumeric()
    {
        $s = new Scanner('1784a 45 9867 #');

        $this->assertEquals('1784', $s->getNumeric());

        // Move past the 'a ' to scan the next group of text.
        $s->next();
        $s->next();
        $this->assertEquals('45', $s->getNumeric());
    }

    public function testCurrentLine()
    {
        $s = new Scanner("1784a\n45\n9867 #\nThis is a test.");

        $this->assertEquals(1, $s->currentLine());

        // Move to the next line.
        $s->getAsciiAlphaNum();
        $s->next();
        $this->assertEquals(2, $s->currentLine());
    }

    public function testColumnOffset()
    {
        $s = new Scanner("1784a a\n45 9867 #\nThis is a test.");

        // Move the pointer to the space.
        $s->getAsciiAlphaNum();
        $this->assertEquals(5, $s->columnOffset());

        // We move the pointer ahead. There must be a better way to do this.
        $s->next();
        $s->next();
        $s->next();
        $s->next();
        $s->next();
        $s->next();
        $this->assertEquals(3, $s->columnOffset());
    }

    public function testRemainingChars()
    {
        $string = "\n45\n9867 #\nThis is a test.";
        $s = new Scanner("1784a\n45\n9867 #\nThis is a test.");

        $s->getAsciiAlphaNum();
        $this->assertEquals($string, $s->remainingChars());
    }
}


code:
<?php

namespace Masterminds\HTML5\Tests\Parser;

use Masterminds\HTML5\Parser\UTF8Utils;
use Masterminds\HTML5\Parser\Scanner;
use Masterminds\HTML5\Parser\Tokenizer;

class TokenizerTest extends \Masterminds\HTML5\Tests\TestCase
{
    // ================================================================
    // Additional assertions.
    // ================================================================

    /**
     * Tests that an event matches both the event type and the expected value.
     *
     * @param string $type
     *                        Expected event type
     * @param string $expects
     *                        The value expected in $event['data'][0]
     */
    public function assertEventEquals($type, $expects, $event)
    {
        $this->assertEquals($type, $event['name'], "Event $type for " . print_r($event, true));
        if (is_array($expects)) {
            $this->assertEquals($expects, $event['data'], "Event $type should equal " . print_r($expects, true) . ': ' . print_r($event, true));
        } else {
            $this->assertEquals($expects, $event['data'][0], "Event $type should equal $expects: " . print_r($event, true));
        }
    }

    /**
     * Assert that a given event is 'error'.
     */
    public function assertEventError($event)
    {
        $this->assertEquals('error', $event['name'], 'Expected error for event: ' . print_r($event, true));
    }

    /**
     * Asserts that all of the tests are good.
     *
     * This loops through a map of tests/expectations and runs a few assertions on each test.
     *
     * Checks:
     * - depth (if depth is > 0)
     * - event name
     * - matches on event 0.
     */
    protected function isAllGood($name, $depth, $tests, $debug = false)
    {
        foreach ($tests as $try => $expects) {
            if ($debug) {
                fprintf(STDOUT, "%s expects %s\n", $try, print_r($expects, true));
            }
            $e = $this->parse($try);
            if ($depth > 0) {
                $this->assertEquals($depth, $e->depth(), "Expected depth $depth for test $try." . print_r($e, true));
            }
            $this->assertEventEquals($name, $expects, $e->get(0));
        }
    }

    // ================================================================
    // Utility functions.
    // ================================================================
    public function testParse()
    {
        list($tok, $events) = $this->createTokenizer('');

        $tok->parse();
        $e1 = $events->get(0);

        $this->assertEquals(1, $events->Depth());
        $this->assertEquals('eof', $e1['name']);
    }

    public function testWhitespace()
    {
        $spaces = '    ';
        list($tok, $events) = $this->createTokenizer($spaces);

        $tok->parse();

        $this->assertEquals(2, $events->depth());

        $e1 = $events->get(0);

        $this->assertEquals('text', $e1['name']);
        $this->assertEquals($spaces, $e1['data'][0]);
    }

    public function testCharacterReference()
    {
        $good = array(
            '&' => '&',
            '<' => '<',
            '&' => '&',
            '&' => '&',
        );
        $this->isAllGood('text', 2, $good);

        // Test with broken charref
        $str = '&foo';
        $events = $this->parse($str);
        $e1 = $events->get(0);
        $this->assertEquals('error', $e1['name']);

        $str = '&#xfoo';
        $events = $this->parse($str);
        $e1 = $events->get(0);
        $this->assertEquals('error', $e1['name']);

        $str = '&#foo';
        $events = $this->parse($str);
        $e1 = $events->get(0);
        $this->assertEquals('error', $e1['name']);

        // FIXME: Once the text processor is done, need to verify that the
        // tokens are transformed correctly into text.
    }

    public function testBogusComment()
    {
        $bogus = array(
            '</+this is a bogus comment. +>',
            '<!+this is a bogus comment. !>',
            '<!D OCTYPE foo bar>',
            '<!DOCTYEP foo bar>',
            '<![CADATA[ TEST ]]>',
            '<![CDATA Hello ]]>',
            '<![CDATA[ Hello [[>',
            '<!CDATA[[ test ]]>',
            '<![CDATA[',
            '<![CDATA[hellooooo hello',
            '<? Hello World ?>',
            '<? Hello World',
        );
        foreach ($bogus as $str) {
            $events = $this->parse($str);
            $this->assertEventError($events->get(0));
            $this->assertEventEquals('comment', $str, $events->get(1));
        }
    }

    public function testEndTag()
    {
        $succeed = array(
            '' => 'a',
            '</test>' => 'test',
            '</test
      >' => 'test',
            '</thisIsTheTagThatDoesntEndItJustGoesOnAndOnMyFriend>' => 'thisisthetagthatdoesntenditjustgoesonandonmyfriend',
            // See 8.2.4.10, which requires this and does not say error.
            '</a' => 'aisAllGood('endTag', 2, $succeed);

        // Recoverable failures
        $fail = array(
            '' => 'a',
            '</a ' => 'a',
            '</a <b <c>' => 'a',
            '' => 'a',
            ' 'a',
        );
        foreach ($fail as $test => $result) {
            $events = $this->parse($test);
            $this->assertEquals(3, $events->depth());
            // Should have triggered an error.
            $this->assertEventError($events->get(0));
            // Should have tried to parse anyway.
            $this->assertEventEquals('endTag', $result, $events->get(1));
        }

        // BogoComments
        $comments = array(
            '</>' => '</>',
            '</ >' => '</ >',
            '' => '',
        );
        foreach ($comments as $test => $result) {
            $events = $this->parse($test);
            $this->assertEquals(3, $events->depth());

            // Should have triggered an error.
            $this->assertEventError($events->get(0));

            // Should have tried to parse anyway.
            $this->assertEventEquals('comment', $result, $events->get(1));
        }
    }

    public function testComment()
    {
        $good = array(
            '' => 'easy',
            '' => ' 1 > 0 ',
            '' => ' --$i ',
            '' => '--$i',
            "" => "\nHello World.\na",
            '' => ' ' => '',
            '',
            '' => '<![CDATA[not a comment]]>',
        );
        foreach ($good as $test => $expects) {
            $events = $this->parse($test);
            $this->assertEventEquals('startTag', 'script', $events->get(0));
            $this->assertEventEquals('text', $expects, $events->get(1));
            $this->assertEventEquals('endTag', 'script', $events->get(2));
        }

        $bad = array(
            ' '&</script',
            '


code:
<?php
/**
 * @file
 * Test the Tree Builder's special-case rules.
 */

namespace Masterminds\HTML5\Tests\Parser;

use Masterminds\HTML5\Parser\TreeBuildingRules;
use Masterminds\HTML5\Parser\Tokenizer;
use Masterminds\HTML5\Parser\Scanner;
use Masterminds\HTML5\Parser\DOMTreeBuilder;

/**
 * These tests are functional, not necessarily unit tests.
 */
class TreeBuildingRulesTest extends \Masterminds\HTML5\Tests\TestCase
{
    const HTML_STUB = '<!DOCTYPE html><title>test</title>%s';

    /**
     * Convenience function for parsing.
     */
    protected function parse($string)
    {
        $treeBuilder = new DOMTreeBuilder();
        $scanner = new Scanner($string);
        $parser = new Tokenizer($scanner, $treeBuilder);

        $parser->parse();

        return $treeBuilder->document();
    }

    /**
     * Convenience function for parsing fragments.
     */
    protected function parseFragment($string)
    {
        $events = new DOMTreeBuilder(true);
        $scanner = new Scanner($string);
        $parser = new Tokenizer($scanner, $events);

        $parser->parse();

        return $events->fragment();
    }

    public function testTDFragment()
    {
        $frag = $this->parseFragment('This is a test of the HTML5 parser	');

        $td = $frag->childNodes->item(0);

        $this->assertEquals(1, $frag->childNodes->length);
        $this->assertEquals('td', $td->tagName);
        $this->assertEquals('This is a test of the HTML5 parser', $td->nodeValue);
    }

    public function testHasRules()
    {
        $doc = new \DOMDocument('1.0');
        $engine = new TreeBuildingRules($doc);

        $this->assertTrue($engine->hasRules('li'));
        $this->assertFalse($engine->hasRules('imaginary'));
    }

    public function testHandleLI()
    {
        $html = sprintf(self::HTML_STUB, '

test
test2
');
        $doc = $this->parse($html);

        $list = $doc->getElementById('a');

        $this->assertEquals(2, $list->childNodes->length);
        foreach ($list->childNodes as $ele) {
            $this->assertEquals('li', $ele->tagName);
        }
    }

    public function testHandleDT()
    {
        $html = sprintf(self::HTML_STUB, '

Hello
Hi
');
        $doc = $this->parse($html);

        $list = $doc->getElementById('a');

        $this->assertEquals(2, $list->childNodes->length);
        $this->assertEquals('dt', $list->firstChild->tagName);
        $this->assertEquals('dd', $list->lastChild->tagName);
    }

    public function testHandleOptionGroupAndOption()
    {
        $html = sprintf(self::HTML_STUB, '<optgroup id="foo" label="foo" ><option value="foo">bar</option></optgroup>');
        $doc = $this->parse($html);

        $list = $doc->getElementById('foo');

        $this->assertEquals(1, $list->childNodes->length);

        $option = $list->childNodes->item(0);
        $this->assertEquals('option', $option->tagName);
    }

    public function testTable()
    {
        $html = sprintf(self::HTML_STUB, '

foobarbaz');
        $doc = $this->parse($html);

        $list = $doc->getElementById('a');

        $this->assertEquals(3, $list->childNodes->length);
        $this->assertEquals('th', $list->firstChild->tagName);
        $this->assertEquals('td', $list->lastChild->tagName);
    }
}


code:
<?php

namespace Masterminds\HTML5\Tests\Parser;

use Masterminds\HTML5\Parser\UTF8Utils;

class UTF8UtilsTest extends \Masterminds\HTML5\Tests\TestCase
{
    public function testConvertToUTF8()
    {
        $out = UTF8Utils::convertToUTF8('éàa', 'ISO-8859-1');
        $this->assertEquals('Ã©Ã a', $out);
    }

    /**
     * @todo add tests for invalid codepoints
     */
    public function testCheckForIllegalCodepoints()
    {
        $smoke = 'Smoke test';
        $err = UTF8Utils::checkForIllegalCodepoints($smoke);
        $this->assertEmpty($err);

        $data = "Foo Bar \0 Baz";
        $errors = UTF8Utils::checkForIllegalCodepoints($data);
        $this->assertContains('null-character', $errors);
    }
}


code:
<?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>

        <meta charset="utf-8">
        <title>Test</title>

This is a test.

    ';

    /**
     * @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>foo');

        $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 . 'foo' . 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>foo');

        $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>

foo bar baz

        <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>

    ');

        $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('
foo bar baz
', stream_get_contents($stream, -1, 0));
    }

    public function testSerializeWithNamespaces()
    {
        $this->html5 = $this->getInstance(array(
            'xmlNamespaces' => true,
        ));

        $source = '
            <!DOCTYPE html>

                        <x:prefixed id="prefixed">xy</x:prefixed>

                    <svg id="svg">svg</svg>
                    <c id="bar2" xmlns="http://www.prefixed.com/bar2"></c>

                    <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>

            ';

        $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>

foo bar baz

    ');

        $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(
            '', stream_get_contents($stream, -1, 0));
    }

    public function testElementWithStyle()
    {
        $dom = $this->html5->loadHTML(
            '<!doctype html>

foo bar baz

    ');

        $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('', stream_get_contents($stream, -1, 0));
    }

    public function testOpenTag()
    {
        $dom = $this->html5->loadHTML('<!doctype html>

foo bar baz

    ');

        $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('
', stream_get_contents($stream, -1, 0));
    }

    public function testCData()
    {
        $dom = $this->html5->loadHTML('<!doctype html>

<![CDATA[bar]]>

    ');

        $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>

    ');

        $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>

    ');

        $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('', stream_get_contents($stream, -1, 0));

        $dom = $this->html5->loadHTML('<!doctype 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>

    ');

        $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>

    ');
        $foo = $dom->getElementById('foo');
        $foo->appendChild(new \DOMText(''));

        $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(''),
            array(''),
            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('

'),
            array('<select disabled></select>'),
            array('

'),
            array(''),
        );
    }

    /**
     * @dataProvider booleanAttributes
     */
    public function testBooleanAttrs($html)
    {
        $dom = $this->html5->loadHTML('<!doctype html>' . $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>

foo bar baz

    ');

        $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>

foo bar baz

        <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>

    ');

        $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>

foo bar baz

        <math>
          <mi>x</mi>
          <csymbol definitionURL="http://www.example.com/mathops/multiops.html#plusminus">
            <mo>±</mo>
          </csymbol>
          <mi>y</mi>
        </math>

    ');

        $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>

        <address>
            Dave Raggett,
            Arnaud Le Hors,
            contact persons for the W3C HTML Activity
        </address>

    ');

        $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('|Dave Raggett,|', $contents);
        $this->assertRegExp('|Arnaud Le Hors,|', $contents);
        $this->assertRegExp('|contact persons for the W3C HTML Activity|', $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>

');

        $badNode = $dom->createElement('p', 'Bar');

        // modify the content of the TEXT_RAW element: '));
    }
}


code:
<?php

namespace Masterminds\HTML5\Tests\Serializer;

use Masterminds\HTML5\Serializer\OutputRules;
use Masterminds\HTML5\Serializer\Traverser;

class TraverserTest extends \Masterminds\HTML5\Tests\TestCase
{
    protected $markup = '<!doctype html>

        <meta charset="utf-8">
        <title>Test</title>

This is a test.

    ';

    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 \ReflectionMethod for the specified method
     */
    public function getProtectedMethod($name)
    {
        $class = new \ReflectionClass('\Masterminds\HTML5\Serializer\Traverser');
        $method = $class->getMethod($name);
        $method->setAccessible(true);

        return $method;
    }

    public function getTraverser()
    {
        $stream = fopen('php://temp', 'w');

        $dom = $this->html5->loadHTML($this->markup);
        $t = new Traverser($dom, $stream, $html5->getOptions());

        // We return both the traverser and stream so we can pull from it.
        return array(
            $t,
            $stream,
        );
    }

    public function testConstruct()
    {
        // The traverser needs a place to write the output to. In our case we
        // use a stream in temp space.
        $stream = fopen('php://temp', 'w');

        $html5 = $this->getInstance();

        $r = new OutputRules($stream, $this->html5->getOptions());
        $dom = $this->html5->loadHTML($this->markup);

        $t = new Traverser($dom, $stream, $r, $html5->getOptions());

        $this->assertInstanceOf('\Masterminds\HTML5\Serializer\Traverser', $t);
    }

    public function testFragmentDeprecated()
    {
        $html = 'foo
bar
';
        $input = new \Masterminds\HTML5\Parser\StringInputStream($html);
        $dom = $this->html5->parseFragment($input);

        $this->assertInstanceOf('\DOMDocumentFragment', $dom);

        $stream = fopen('php://temp', 'w');
        $r = new OutputRules($stream, $this->html5->getOptions());
        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
        $t->walk();

        $this->assertEquals($html, stream_get_contents($stream, -1, 0));
    }

    public function testFragment()
    {
        $html = 'foo
bar
';
        $dom = $this->html5->parseFragment($html);

        $this->assertInstanceOf('\DOMDocumentFragment', $dom);

        $stream = fopen('php://temp', 'w');
        $r = new OutputRules($stream, $this->html5->getOptions());
        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
        $t->walk();

        $this->assertEquals($html, stream_get_contents($stream, -1, 0));
    }

    public function testProcessorInstructionDeprecated()
    {
        $html = '<?foo bar ?>';
        $input = new \Masterminds\HTML5\Parser\StringInputStream($html);
        $dom = $this->html5->parseFragment($input);

        $this->assertInstanceOf('\DOMDocumentFragment', $dom);

        $stream = fopen('php://temp', 'w');
        $r = new OutputRules($stream, $this->html5->getOptions());

        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
        $t->walk();

        $this->assertEquals($html, stream_get_contents($stream, -1, 0));
    }

    public function testProcessorInstruction()
    {
        $html = '<?foo bar ?>';
        $dom = $this->html5->parseFragment($html);

        $this->assertInstanceOf('\DOMDocumentFragment', $dom);

        $stream = fopen('php://temp', 'w');
        $r = new OutputRules($stream, $this->html5->getOptions());

        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
        $t->walk();

        $this->assertEquals($html, stream_get_contents($stream, -1, 0));
    }
}


code:
<?php

namespace Masterminds\HTML5\Tests;

use Masterminds\HTML5;
use PHPUnit\Framework\TestCase as BaseTestCase;

class TestCase extends BaseTestCase
{
    const DOC_OPEN = '<!DOCTYPE html><title>test</title>';

    const DOC_CLOSE = '';

    public function testFoo()
    {
        // Placeholder. Why is PHPUnit emitting warnings about no tests?
    }

    public function getInstance(array $options = array())
    {
        return new HTML5($options);
    }

    protected function wrap($fragment)
    {
        return self::DOC_OPEN . $fragment . self::DOC_CLOSE;
    }
}


code:
<?php

require __DIR__ . '/../../vendor/autoload.php';

$iterations = isset($argv[1]) ? $argv[1] : 100;

$html5 = new Masterminds\HTML5();
$content = file_get_contents(__DIR__ . '/example.html');
$dom = $html5->loadHTML($content);

$samples = array();
for ($i = 0; $i < $iterations; ++$i) {
    $t = microtime(true);
    $dom = $html5->loadHTML($content);
    $samples[] = microtime(true) - $t;
}
$time = array_sum($samples) / count($samples);
echo 'Loading: ' . ($time * 1000) . "\n";

$samples = array();
for ($i = 0; $i < $iterations; ++$i) {
    $t = microtime(true);
    $html5->saveHTML($dom);
    $samples[] = microtime(true) - $t;
}
$time = array_sum($samples) / count($samples);
echo 'Writing: ' . ($time * 1000) . "\n";

exit(0);


code:
[*]
indent_style = tab
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
charset = utf-8


code:
.DS_Store
/nbproject
composer.phar
/vendor/
/tmp
.idea/
/bin/test


code:
language: php
php:
  - 5.4
  - 5.5
  - 5.6
  - 7.0
  - 7.1
  - 7.2
  - nightly
env:
  - COMPOSER_REQUIRE=""
  - COMPOSER_REQUIRE="composer require masterminds/html5"
install:
  - $COMPOSER_REQUIRE
before_script: composer install