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