PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / vendor / masterminds / html5 / src / HTML5 / Serializer / OutputRules.php

OutputRules.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.0, at vendor/masterminds/html5/src/HTML5/Serializer/OutputRules.php

536 lines 15.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @file
4 * The rules for generating output in the serializer.
5 *
6 * These output rules are likely to generate output similar to the document that
7 * was parsed. It is not intended to output exactly the document that was parsed.
8 */
9
10 namespace Masterminds\HTML5\Serializer;
11
12 use Masterminds\HTML5\Elements;
13
14 /**
15 * Generate the output html5 based on element rules.
16 */
17 class OutputRules implements RulesInterface
18 {
19 const NAMESPACE_HTML = 'http://www.w3.org/1999/xhtml';
20
21 const NAMESPACE_MATHML = 'http://www.w3.org/1998/Math/MathML';
22
23 const NAMESPACE_SVG = 'http://www.w3.org/2000/svg';
24
25 const NAMESPACE_XLINK = 'http://www.w3.org/1999/xlink';
26
27 const NAMESPACE_XML = 'http://www.w3.org/XML/1998/namespace';
28
29 const NAMESPACE_XMLNS = 'http://www.w3.org/2000/xmlns/';
30
31 /**
32 * Holds the HTML5 element names that causes a namespace switch.
33 *
34 * @var array
35 */
36 protected $implicitNamespaces = array(
37 self::NAMESPACE_HTML,
38 self::NAMESPACE_SVG,
39 self::NAMESPACE_MATHML,
40 self::NAMESPACE_XML,
41 self::NAMESPACE_XMLNS,
42 );
43
44 const IM_IN_HTML = 1;
45
46 const IM_IN_SVG = 2;
47
48 const IM_IN_MATHML = 3;
49
50 protected $traverser;
51
52 protected $encode = false;
53
54 protected $out;
55
56 protected $outputMode;
57
58 private $xpath;
59
60 protected $nonBooleanAttributes = array(
61 /*
62 array(
63 'nodeNamespace'=>'http://www.w3.org/1999/xhtml',
64 'attrNamespace'=>'http://www.w3.org/1999/xhtml',
65
66 'nodeName'=>'img', 'nodeName'=>array('img', 'a'),
67 'attrName'=>'alt', 'attrName'=>array('title', 'alt'),
68 ),
69 */
70 array(
71 'nodeNamespace' => 'http://www.w3.org/1999/xhtml',
72 'attrName' => array('href',
73 'hreflang',
74 'http-equiv',
75 'icon',
76 'id',
77 'keytype',
78 'kind',
79 'label',
80 'lang',
81 'language',
82 'list',
83 'maxlength',
84 'media',
85 'method',
86 'name',
87 'placeholder',
88 'rel',
89 'rows',
90 'rowspan',
91 'sandbox',
92 'spellcheck',
93 'scope',
94 'seamless',
95 'shape',
96 'size',
97 'sizes',
98 'span',
99 'src',
100 'srcdoc',
101 'srclang',
102 'srcset',
103 'start',
104 'step',
105 'style',
106 'summary',
107 'tabindex',
108 'target',
109 'title',
110 'type',
111 'value',
112 'width',
113 'border',
114 'charset',
115 'cite',
116 'class',
117 'code',
118 'codebase',
119 'color',
120 'cols',
121 'colspan',
122 'content',
123 'coords',
124 'data',
125 'datetime',
126 'default',
127 'dir',
128 'dirname',
129 'enctype',
130 'for',
131 'form',
132 'formaction',
133 'headers',
134 'height',
135 'accept',
136 'accept-charset',
137 'accesskey',
138 'action',
139 'align',
140 'alt',
141 'bgcolor',
142 ),
143 ),
144 array(
145 'nodeNamespace' => 'http://www.w3.org/1999/xhtml',
146 'xpath' => 'starts-with(local-name(), \'data-\')',
147 ),
148 );
149
150 const DOCTYPE = '<!DOCTYPE html>';
151
152 public function __construct($output, $options = array())
153 {
154 if (isset($options['encode_entities'])) {
155 $this->encode = $options['encode_entities'];
156 }
157
158 $this->outputMode = static::IM_IN_HTML;
159 $this->out = $output;
160 }
161
162 public function addRule(array $rule)
163 {
164 $this->nonBooleanAttributes[] = $rule;
165 }
166
167 public function setTraverser(Traverser $traverser)
168 {
169 $this->traverser = $traverser;
170
171 return $this;
172 }
173
174 public function unsetTraverser()
175 {
176 $this->traverser = null;
177
178 return $this;
179 }
180
181 public function document($dom)
182 {
183 $this->doctype();
184 if ($dom->documentElement) {
185 foreach ($dom->childNodes as $node) {
186 $this->traverser->node($node);
187 }
188 $this->nl();
189 }
190 }
191
192 protected function doctype()
193 {
194 $this->wr(static::DOCTYPE);
195 $this->nl();
196 }
197
198 /**
199 * @param \DOMElement $ele
200 */
201 public function element($ele)
202 {
203 $name = $ele->tagName;
204
205 // Per spec:
206 // If the element has a declared namespace in the HTML, MathML or
207 // SVG namespaces, we use the lname instead of the tagName.
208 if ($this->traverser->isLocalElement($ele)) {
209 $name = $ele->localName;
210 }
211
212 // If we are in SVG or MathML there is special handling.
213 // Using if/elseif instead of switch because it's faster in PHP.
214 if ('svg' == $name) {
215 $this->outputMode = static::IM_IN_SVG;
216 $name = Elements::normalizeSvgElement($name);
217 } elseif ('math' == $name) {
218 $this->outputMode = static::IM_IN_MATHML;
219 }
220
221 $this->openTag($ele);
222 // The tag is already self-closed (`<svg />` or `<math />`) in `openTag` if there are no child nodes.
223 $handledAsVoidTag = $this->outputMode !== static::IM_IN_HTML && !$ele->hasChildNodes();
224
225 if (Elements::isA($name, Elements::TEXT_RAW)) {
226 foreach ($ele->childNodes as $child) {
227 if ($child instanceof \DOMCharacterData) {
228 $this->wr($child->data);
229 } elseif ($child instanceof \DOMElement) {
230 $this->element($child);
231 }
232 }
233 } else {
234 // Handle children.
235 if ($ele->hasChildNodes()) {
236 $this->traverser->children($ele->childNodes);
237 }
238
239 // Close out the SVG or MathML special handling.
240 if ('svg' == $name || 'math' == $name) {
241 $this->outputMode = static::IM_IN_HTML;
242 }
243 }
244
245 // If not unary, add a closing tag.
246 if (!$handledAsVoidTag && !Elements::isA($name, Elements::VOID_TAG)) {
247 $this->closeTag($ele);
248 }
249 }
250
251 /**
252 * Write a text node.
253 *
254 * @param \DOMText $ele The text node to write.
255 */
256 public function text($ele)
257 {
258 if (isset($ele->parentNode) && isset($ele->parentNode->tagName) && Elements::isA($ele->parentNode->localName, Elements::TEXT_RAW)) {
259 $this->wr($ele->data);
260
261 return;
262 }
263
264 // FIXME: This probably needs some flags set.
265 $this->wr($this->enc($ele->data));
266 }
267
268 public function cdata($ele)
269 {
270 // This encodes CDATA.
271 $this->wr($ele->ownerDocument->saveXML($ele));
272 }
273
274 public function comment($ele)
275 {
276 // These produce identical output.
277 // $this->wr('<!--')->wr($ele->data)->wr('-->');
278 $this->wr($ele->ownerDocument->saveXML($ele));
279 }
280
281 public function processorInstruction($ele)
282 {
283 $this->wr('<?')
284 ->wr($ele->target)
285 ->wr(' ')
286 ->wr($ele->data)
287 ->wr('?>');
288 }
289
290 /**
291 * Write the namespace attributes.
292 *
293 * @param \DOMNode $ele The element being written.
294 */
295 protected function namespaceAttrs($ele)
296 {
297 if (!$this->xpath || $this->xpath->document !== $ele->ownerDocument) {
298 $this->xpath = new \DOMXPath($ele->ownerDocument);
299 }
300
301 foreach ($this->xpath->query('namespace::*[not(.=../../namespace::*)]', $ele) as $nsNode) {
302 if (!in_array($nsNode->nodeValue, $this->implicitNamespaces)) {
303 $this->wr(' ')->wr($nsNode->nodeName)->wr('="')->wr($nsNode->nodeValue)->wr('"');
304 }
305 }
306 }
307
308 /**
309 * Write the opening tag.
310 *
311 * Tags for HTML, MathML, and SVG are in the local name. Otherwise, use the
312 * qualified name (8.3).
313 *
314 * @param \DOMNode $ele The element being written.
315 */
316 protected function openTag($ele)
317 {
318 $this->wr('<')->wr($this->traverser->isLocalElement($ele) ? $ele->localName : $ele->tagName);
319
320 $this->attrs($ele);
321 $this->namespaceAttrs($ele);
322
323 if ($this->outputMode == static::IM_IN_HTML) {
324 $this->wr('>');
325 } // If we are not in html mode we are in SVG, MathML, or XML embedded content.
326 else {
327 if ($ele->hasChildNodes()) {
328 $this->wr('>');
329 } // If there are no children this is self closing.
330 else {
331 $this->wr(' />');
332 }
333 }
334 }
335
336 protected function attrs($ele)
337 {
338 // FIXME: Needs support for xml, xmlns, xlink, and namespaced elements.
339 if (!$ele->hasAttributes()) {
340 return $this;
341 }
342
343 // TODO: Currently, this always writes name="value", and does not do
344 // value-less attributes.
345 $map = $ele->attributes;
346 $len = $map->length;
347 for ($i = 0; $i < $len; ++$i) {
348 $node = $map->item($i);
349 $val = $this->enc($node->value, true);
350
351 // XXX: The spec says that we need to ensure that anything in
352 // the XML, XMLNS, or XLink NS's should use the canonical
353 // prefix. It seems that DOM does this for us already, but there
354 // may be exceptions.
355 $name = $node->nodeName;
356
357 // Special handling for attributes in SVG and MathML.
358 // Using if/elseif instead of switch because it's faster in PHP.
359 if ($this->outputMode == static::IM_IN_SVG) {
360 $name = Elements::normalizeSvgAttribute($name);
361 } elseif ($this->outputMode == static::IM_IN_MATHML) {
362 $name = Elements::normalizeMathMlAttribute($name);
363 }
364
365 $this->wr(' ')->wr($name);
366
367 if ((isset($val) && '' !== $val) || $this->nonBooleanAttribute($node)) {
368 $this->wr('="')->wr($val)->wr('"');
369 }
370 }
371 }
372
373 protected function nonBooleanAttribute(\DOMAttr $attr)
374 {
375 $ele = $attr->ownerElement;
376 foreach ($this->nonBooleanAttributes as $rule) {
377 if (isset($rule['nodeNamespace']) && $rule['nodeNamespace'] !== $ele->namespaceURI) {
378 continue;
379 }
380 if (isset($rule['attNamespace']) && $rule['attNamespace'] !== $attr->namespaceURI) {
381 continue;
382 }
383 if (isset($rule['nodeName']) && !is_array($rule['nodeName']) && $rule['nodeName'] !== $ele->localName) {
384 continue;
385 }
386 if (isset($rule['nodeName']) && is_array($rule['nodeName']) && !in_array($ele->localName, $rule['nodeName'], true)) {
387 continue;
388 }
389 if (isset($rule['attrName']) && !is_array($rule['attrName']) && $rule['attrName'] !== $attr->localName) {
390 continue;
391 }
392 if (isset($rule['attrName']) && is_array($rule['attrName']) && !in_array($attr->localName, $rule['attrName'], true)) {
393 continue;
394 }
395 if (isset($rule['xpath'])) {
396 $xp = $this->getXPath($attr);
397 if (isset($rule['prefixes'])) {
398 foreach ($rule['prefixes'] as $nsPrefix => $ns) {
399 $xp->registerNamespace($nsPrefix, $ns);
400 }
401 }
402 if (!$xp->evaluate($rule['xpath'], $attr)) {
403 continue;
404 }
405 }
406
407 return true;
408 }
409
410 return false;
411 }
412
413 private function getXPath(\DOMNode $node)
414 {
415 if (!$this->xpath) {
416 $this->xpath = new \DOMXPath($node->ownerDocument);
417 }
418
419 return $this->xpath;
420 }
421
422 /**
423 * Write the closing tag.
424 *
425 * Tags for HTML, MathML, and SVG are in the local name. Otherwise, use the
426 * qualified name (8.3).
427 *
428 * @param \DOMNode $ele The element being written.
429 */
430 protected function closeTag($ele)
431 {
432 if ($this->outputMode == static::IM_IN_HTML || $ele->hasChildNodes()) {
433 $this->wr('</')->wr($this->traverser->isLocalElement($ele) ? $ele->localName : $ele->tagName)->wr('>');
434 }
435 }
436
437 /**
438 * Write to the output.
439 *
440 * @param string $text The string to put into the output
441 *
442 * @return $this
443 */
444 protected function wr($text)
445 {
446 fwrite($this->out, (string) $text);
447
448 return $this;
449 }
450
451 /**
452 * Write a new line character.
453 *
454 * @return $this
455 */
456 protected function nl()
457 {
458 fwrite($this->out, PHP_EOL);
459
460 return $this;
461 }
462
463 /**
464 * Encode text.
465 *
466 * When encode is set to false, the default value, the text passed in is
467 * escaped per section 8.3 of the html5 spec. For details on how text is
468 * escaped see the escape() method.
469 *
470 * When encoding is set to true the text is converted to named character
471 * references where appropriate. Section 8.1.4 Character references of the
472 * html5 spec refers to using named character references. This is useful for
473 * characters that can't otherwise legally be used in the text.
474 *
475 * The named character references are listed in section 8.5.
476 *
477 * @see http://www.w3.org/TR/2013/CR-html5-20130806/syntax.html#named-character-references True encoding will turn all named character references into their entities.
478 * This includes such characters as +.# and many other common ones. By default
479 * encoding here will just escape &'<>".
480 *
481 * @param string $text Text to encode.
482 * @param bool $attribute True if we are encoding an attribute, false otherwise.
483 *
484 * @return string The encoded text.
485 */
486 protected function enc($text, $attribute = false)
487 {
488 // Escape the text rather than convert to named character references.
489 if (!$this->encode) {
490 return $this->escape($text, $attribute);
491 }
492
493 return htmlentities($text, ENT_HTML5 | ENT_SUBSTITUTE | ENT_QUOTES, 'UTF-8', false);
494 }
495
496 /**
497 * Escape test.
498 *
499 * According to the html5 spec section 8.3 Serializing HTML fragments, text
500 * within tags that are not style, script, xmp, iframe, noembed, and noframes
501 * need to be properly escaped.
502 *
503 * The & should be converted to &amp;, no breaking space unicode characters
504 * converted to &nbsp;, when in attribute mode the " should be converted to
505 * &quot;, and when not in attribute mode the < and > should be converted to
506 * &lt; and &gt;.
507 *
508 * @see http://www.w3.org/TR/2013/CR-html5-20130806/syntax.html#escapingString
509 *
510 * @param string $text Text to escape.
511 * @param bool $attribute True if we are escaping an attrubute, false otherwise.
512 */
513 protected function escape($text, $attribute = false)
514 {
515 // Not using htmlspecialchars because, while it does escaping, it doesn't
516 // match the requirements of section 8.5. For example, it doesn't handle
517 // non-breaking spaces.
518 if ($attribute) {
519 $replace = array(
520 '"' => '&quot;',
521 '&' => '&amp;',
522 "\xc2\xa0" => '&nbsp;',
523 );
524 } else {
525 $replace = array(
526 '<' => '&lt;',
527 '>' => '&gt;',
528 '&' => '&amp;',
529 "\xc2\xa0" => '&nbsp;',
530 );
531 }
532
533 return strtr($text, $replace);
534 }
535 }
536