Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.90% covered (warning)
86.90%
73 / 84
70.00% covered (warning)
70.00%
7 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
TestFormatter
86.90% covered (warning)
86.90%
73 / 84
70.00% covered (warning)
70.00%
7 / 10
45.96
0.00% covered (danger)
0.00%
0 / 1
 startDocument
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doctype
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 characters
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formatCharacters
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 element
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 formatElement
93.10% covered (success)
93.10%
27 / 29
0.00% covered (danger)
0.00%
0 / 1
13.06
 comment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formatComment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formatDOMNode
84.21% covered (warning)
84.21%
16 / 19
0.00% covered (danger)
0.00%
0 / 1
12.57
 formatDOMElement
72.73% covered (warning)
72.73%
16 / 22
0.00% covered (danger)
0.00%
0 / 1
9.30
1<?php
2
3namespace Wikimedia\RemexHtml\Serializer;
4
5use Wikimedia\RemexHtml\DOM\DOMFormatter;
6use Wikimedia\RemexHtml\DOM\DOMUtils;
7use Wikimedia\RemexHtml\HTMLData;
8use Wikimedia\RemexHtml\Tokenizer\Attribute;
9
10/**
11 * A Formatter which is used to format documents in (almost) the way they
12 * appear in the html5lib tests. A little bit of post-processing is required
13 * in the PHPUnit tests.
14 */
15class TestFormatter implements Formatter, DOMFormatter {
16    private static $attrNamespaces = [
17        HTMLData::NS_XML => 'xml',
18        HTMLData::NS_XLINK => 'xlink',
19        HTMLData::NS_XMLNS => 'xmlns',
20    ];
21
22    public function startDocument( $fragmentNamespace, $fragmentName ) {
23        return '';
24    }
25
26    public function doctype( $name, $public, $system ) {
27        $ret = "<!DOCTYPE $name";
28        if ( $public !== '' || $system !== '' ) {
29            $ret .= " \"$public\" \"$system\"";
30        }
31        $ret .= ">\n";
32        return $ret;
33    }
34
35    public function characters( SerializerNode $parent, $text, $start, $length ) {
36        return $this->formatCharacters( substr( $text, $start, $length ) );
37    }
38
39    private function formatCharacters( $text ) {
40        return '"' .
41            str_replace( "\n", "<EOL>", $text ) .
42            "\"\n";
43    }
44
45    public function element( SerializerNode $parent, SerializerNode $node, $contents ) {
46        return $this->formatElement( $node->namespace, $node->name,
47            $node->attrs->getObjects(), $contents );
48    }
49
50    private function formatElement( $namespace, $name, $attrs, $contents ) {
51        $name = DOMUtils::uncoerceName( $name );
52        if ( $namespace === HTMLData::NS_HTML ) {
53            $tagName = $name;
54        } elseif ( $namespace === HTMLData::NS_SVG ) {
55            $tagName = "svg $name";
56        } elseif ( $namespace === HTMLData::NS_MATHML ) {
57            $tagName = "math $name";
58        } else {
59            $tagName = $name;
60        }
61        $ret = "<$tagName>\n";
62        $sortedAttrs = $attrs;
63        ksort( $sortedAttrs, SORT_STRING );
64        foreach ( $sortedAttrs as $attrName => $attr ) {
65            $localName = DOMUtils::uncoerceName( $attr->localName );
66            if ( $attr->namespaceURI === null
67                // @phan-suppress-next-line PhanUndeclaredProperty
68                || isset( $attr->reallyNoNamespace )
69            ) {
70                $prefix = '';
71            } elseif ( isset( self::$attrNamespaces[$attr->namespaceURI] ) ) {
72                $prefix = self::$attrNamespaces[$attr->namespaceURI] . ' ';
73            } else {
74                $prefix = '';
75            }
76            $ret .= "  $prefix$localName=\"{$attr->value}\"\n";
77        }
78        if ( $contents !== null && $contents !== '' ) {
79            $contents = preg_replace( '/^/m', '  ', $contents );
80        } else {
81            $contents = '';
82        }
83        if ( $namespace === HTMLData::NS_HTML && $name === 'template' ) {
84            if ( $contents === '' ) {
85                $contents = "  content\n";
86            } else {
87                $contents = "  content\n" . preg_replace( '/^/m', '  ', $contents );
88            }
89        }
90        $ret .= $contents;
91        return $ret;
92    }
93
94    public function comment( SerializerNode $parent, $text ) {
95        return $this->formatComment( $text );
96    }
97
98    private function formatComment( $text ) {
99        return "<!-- $text -->\n";
100    }
101
102    public function formatDOMNode( \DOMNode $node ) {
103        $contents = '';
104        if ( $node->firstChild ) {
105            foreach ( $node->childNodes as $child ) {
106                $contents .= $this->formatDOMNode( $child );
107            }
108        }
109
110        switch ( $node->nodeType ) {
111            case XML_ELEMENT_NODE:
112                '@phan-var \DOMElement $node'; /** @var \DOMElement $node */
113                return $this->formatDOMElement( $node, $contents );
114
115            case XML_DOCUMENT_NODE:
116            case XML_DOCUMENT_FRAG_NODE:
117                return $contents;
118
119            case XML_TEXT_NODE:
120            case XML_CDATA_SECTION_NODE:
121                '@phan-var \DOMCharacterData $node'; /** @var \DOMCharacterData $node */
122                return $this->formatCharacters( $node->data );
123
124            case XML_COMMENT_NODE:
125                '@phan-var \DOMComment $node'; /** @var \DOMComment $node */
126                return $this->formatComment( $node->data );
127
128            case XML_DOCUMENT_TYPE_NODE:
129                '@phan-var \DOMDocumentType $node'; /** @var \DOMDocumentType $node */
130                return $this->doctype( $node->name, $node->publicId, $node->systemId );
131
132            case XML_PI_NODE:
133            default:
134                return '';
135        }
136    }
137
138    public function formatDOMElement( \DOMElement $node, $content ) {
139        $attrs = [];
140        foreach ( $node->attributes as $attr ) {
141            $prefix = null;
142            switch ( $attr->namespaceURI ) {
143                case HTMLData::NS_XML:
144                    $prefix = 'xml';
145                    $qName = 'xml:' . $attr->localName;
146                    break;
147                case HTMLData::NS_XMLNS:
148                    if ( $attr->localName === 'xmlns' ) {
149                        $qName = 'xmlns';
150                    } else {
151                        $prefix = 'xmlns';
152                        $qName = 'xmlns:' . $attr->localName;
153                    }
154                    break;
155                case HTMLData::NS_XLINK:
156                    $prefix = 'xlink';
157                    $qName = 'xlink:' . $attr->localName;
158                    break;
159                default:
160                    if ( strlen( $attr->prefix ) ) {
161                        $qName = $attr->prefix . ':' . $attr->localName;
162                    } else {
163                        $prefix = $attr->prefix;
164                        $qName = $attr->localName;
165                    }
166            }
167
168            $attrs[$qName] = new Attribute( $qName, $attr->namespaceURI, $prefix,
169                $attr->localName, $attr->value );
170        }
171
172        return $this->formatElement( $node->namespaceURI, $node->nodeName, $attrs, $content );
173    }
174}