Completed
Push — master ( 41967d...dd3832 )
by Josh
21:07
created

TemplateHelper::getElementsByRegexp()   C

Complexity

Conditions 8
Paths 27

Size

Total Lines 38
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 15
c 1
b 0
f 0
nc 27
nop 2
dl 0
loc 38
rs 5.3846
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2016 The s9e Authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\TextFormatter\Configurator\Helpers;
9
10
use DOMAttr;
11
use DOMCharacterData;
12
use DOMDocument;
13
use DOMElement;
14
use DOMNode;
15
use DOMProcessingInstruction;
16
use DOMXPath;
17
use RuntimeException;
18
use s9e\TextFormatter\Configurator\Exceptions\InvalidXslException;
19
use s9e\TextFormatter\Configurator\Helpers\RegexpBuilder;
20
21
abstract class TemplateHelper
22
{
23
	/**
24
	* XSL namespace
25
	*/
26
	const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
27
28
	/**
29
	* Return all attributes (literal or generated) that match given regexp
30
	*
31
	* @param  DOMDocument $dom    Document
32
	* @param  string      $regexp Regexp
33
	* @return array               Array of DOMNode instances
34
	*/
35
	public static function getAttributesByRegexp(DOMDocument $dom, $regexp)
36
	{
37
		$xpath = new DOMXPath($dom);
38
		$nodes = [];
39
40
		// Get literal attributes
41
		foreach ($xpath->query('//@*') as $attribute)
42
		{
43
			if (preg_match($regexp, $attribute->name))
44
			{
45
				$nodes[] = $attribute;
46
			}
47
		}
48
49
		// Get generated attributes
50
		foreach ($xpath->query('//xsl:attribute') as $attribute)
51
		{
52
			if (preg_match($regexp, $attribute->getAttribute('name')))
53
			{
54
				$nodes[] = $attribute;
55
			}
56
		}
57
58
		// Get attributes created with <xsl:copy-of/>
59
		foreach ($xpath->query('//xsl:copy-of') as $node)
60
		{
61
			$expr = $node->getAttribute('select');
62
63
			if (preg_match('/^@(\\w+)$/', $expr, $m)
64
			 && preg_match($regexp, $m[1]))
65
			{
66
				$nodes[] = $node;
67
			}
68
		}
69
70
		return $nodes;
71
	}
72
73
	/**
74
	* Return all DOMNodes whose content is CSS
75
	*
76
	* @param  DOMDocument $dom Document
77
	* @return array            Array of DOMNode instances
78
	*/
79
	public static function getCSSNodes(DOMDocument $dom)
80
	{
81
		$regexp = '/^style$/i';
82
		$nodes  = array_merge(
83
			self::getAttributesByRegexp($dom, $regexp),
84
			self::getElementsByRegexp($dom, '/^style$/i')
85
		);
86
87
		return $nodes;
88
	}
89
90
	/**
91
	* Return all elements (literal or generated) that match given regexp
92
	*
93
	* @param  DOMDocument $dom    Document
94
	* @param  string      $regexp Regexp
95
	* @return array               Array of DOMNode instances
96
	*/
97
	public static function getElementsByRegexp(DOMDocument $dom, $regexp)
98
	{
99
		$xpath = new DOMXPath($dom);
100
		$nodes = [];
101
102
		// Get literal attributes
103
		foreach ($xpath->query('//*') as $element)
104
		{
105
			if (preg_match($regexp, $element->localName))
106
			{
107
				$nodes[] = $element;
108
			}
109
		}
110
111
		// Get generated elements
112
		foreach ($xpath->query('//xsl:element') as $element)
113
		{
114
			if (preg_match($regexp, $element->getAttribute('name')))
115
			{
116
				$nodes[] = $element;
117
			}
118
		}
119
120
		// Get elements created with <xsl:copy-of/>
121
		// NOTE: this method of creating elements is disallowed by default
122
		foreach ($xpath->query('//xsl:copy-of') as $node)
123
		{
124
			$expr = $node->getAttribute('select');
125
126
			if (preg_match('/^\\w+$/', $expr)
127
			 && preg_match($regexp, $expr))
128
			{
129
				$nodes[] = $node;
130
			}
131
		}
132
133
		return $nodes;
134
	}
135
136
	/**
137
	* Return all DOMNodes whose content is JavaScript
138
	*
139
	* @param  DOMDocument $dom Document
140
	* @return array            Array of DOMNode instances
141
	*/
142
	public static function getJSNodes(DOMDocument $dom)
143
	{
144
		$regexp = '/^(?>data-s9e-livepreview-postprocess$|on)/i';
145
		$nodes  = array_merge(
146
			self::getAttributesByRegexp($dom, $regexp),
147
			self::getElementsByRegexp($dom, '/^script$/i')
148
		);
149
150
		return $nodes;
151
	}
152
153
	/**
154
	* Get the regexp used to remove meta elements from the intermediate representation
155
	*
156
	* @param  array  $templates
157
	* @return string
158
	*/
159
	public static function getMetaElementsRegexp(array $templates)
160
	{
161
		$exprs = [];
162
163
		// Coalesce all templates and load them into DOM
164
		$xsl = '<xsl:template xmlns:xsl="http://www.w3.org/1999/XSL/Transform">' . implode('', $templates) . '</xsl:template>';
165
		$dom = new DOMDocument;
166
		$dom->loadXML($xsl);
167
		$xpath = new DOMXPath($dom);
168
169
		// Collect the values of all the "match", "select" and "test" attributes of XSL elements
170
		$query = '//xsl:*/@*[contains("matchselectest", name())]';
171
		foreach ($xpath->query($query) as $attribute)
172
		{
173
			$exprs[] = $attribute->value;
174
		}
175
176
		// Collect the XPath expressions used in all the attributes of non-XSL elements
177
		$query = '//*[namespace-uri() != "' . self::XMLNS_XSL . '"]/@*';
178
		foreach ($xpath->query($query) as $attribute)
179
		{
180
			foreach (AVTHelper::parse($attribute->value) as $token)
181
			{
182
				if ($token[0] === 'expression')
183
				{
184
					$exprs[] = $token[1];
185
				}
186
			}
187
		}
188
189
		// Names of the meta elements
190
		$tagNames = [
191
			'e' => true,
192
			'i' => true,
193
			's' => true
194
		];
195
196
		// In the highly unlikely event the meta elements are rendered, we remove them from the list
197
		foreach (array_keys($tagNames) as $tagName)
198
		{
199
			if (isset($templates[$tagName]) && $templates[$tagName] !== '')
200
			{
201
				unset($tagNames[$tagName]);
202
			}
203
		}
204
205
		// Create a regexp that matches the tag names used as element names, e.g. "s" in "//s" but
206
		// not in "@s" or "$s"
207
		$regexp = '(\\b(?<![$@])(' . implode('|', array_keys($tagNames)) . ')(?!-)\\b)';
208
209
		// Now look into all of the expressions that we've collected
210
		preg_match_all($regexp, implode("\n", $exprs), $m);
211
212
		foreach ($m[0] as $tagName)
213
		{
214
			unset($tagNames[$tagName]);
215
		}
216
217
		if (empty($tagNames))
218
		{
219
			// Always-false regexp
220
			return '((?!))';
221
		}
222
223
		return '(<' . RegexpBuilder::fromList(array_keys($tagNames)) . '>[^<]*</[^>]+>)';
224
	}
225
226
	/**
227
	* Return all elements (literal or generated) that match given regexp
228
	*
229
	* Will return all <param/> descendants of <object/> and all attributes of <embed/> whose name
230
	* matches given regexp. This method will NOT catch <param/> elements whose 'name' attribute is
231
	* set via an <xsl:attribute/>
232
	*
233
	* @param  DOMDocument $dom    Document
234
	* @param  string      $regexp
235
	* @return array               Array of DOMNode instances
236
	*/
237
	public static function getObjectParamsByRegexp(DOMDocument $dom, $regexp)
238
	{
239
		$xpath = new DOMXPath($dom);
240
		$nodes = [];
241
242
		// Collect attributes from <embed/> elements
243
		foreach (self::getAttributesByRegexp($dom, $regexp) as $attribute)
244
		{
245
			if ($attribute->nodeType === XML_ATTRIBUTE_NODE)
246
			{
247
				if (strtolower($attribute->parentNode->localName) === 'embed')
248
				{
249
					$nodes[] = $attribute;
250
				}
251
			}
252
			elseif ($xpath->evaluate('ancestor::embed', $attribute))
253
			{
254
				// Assuming <xsl:attribute/> or <xsl:copy-of/>
255
				$nodes[] = $attribute;
256
			}
257
		}
258
259
		// Collect <param/> descendants of <object/> elements
260
		foreach ($dom->getElementsByTagName('object') as $object)
261
		{
262
			foreach ($object->getElementsByTagName('param') as $param)
263
			{
264
				if (preg_match($regexp, $param->getAttribute('name')))
265
				{
266
					$nodes[] = $param;
267
				}
268
			}
269
		}
270
271
		return $nodes;
272
	}
273
274
	/**
275
	* Return a list of parameters in use in given XSL
276
	*
277
	* @param  string $xsl XSL source
278
	* @return array       Alphabetically sorted list of unique parameter names
279
	*/
280
	public static function getParametersFromXSL($xsl)
281
	{
282
		$paramNames = [];
283
284
		// Wrap the XSL in boilerplate code because it might not have a root element
285
		$xsl = '<xsl:stylesheet xmlns:xsl="' . self::XMLNS_XSL . '">'
286
		     . '<xsl:template>'
287
		     . $xsl
288
		     . '</xsl:template>'
289
		     . '</xsl:stylesheet>';
290
291
		$dom = new DOMDocument;
292
		$dom->loadXML($xsl);
293
294
		$xpath = new DOMXPath($dom);
295
296
		// Start by collecting XPath expressions in XSL elements
297
		$query = '//xsl:*/@match | //xsl:*/@select | //xsl:*/@test';
298
		foreach ($xpath->query($query) as $attribute)
299
		{
300
			foreach (XPathHelper::getVariables($attribute->value) as $varName)
301
			{
302
				// Test whether this is the name of a local variable
303
				$varQuery = 'ancestor-or-self::*/'
304
				          . 'preceding-sibling::xsl:variable[@name="' . $varName . '"]';
305
306
				if (!$xpath->query($varQuery, $attribute)->length)
307
				{
308
					$paramNames[] = $varName;
309
				}
310
			}
311
		}
312
313
		// Collecting XPath expressions in attribute value templates
314
		$query = '//*[namespace-uri() != "' . self::XMLNS_XSL . '"]'
315
		       . '/@*[contains(., "{")]';
316
		foreach ($xpath->query($query) as $attribute)
317
		{
318
			$tokens = AVTHelper::parse($attribute->value);
319
320
			foreach ($tokens as $token)
321
			{
322
				if ($token[0] !== 'expression')
323
				{
324
					continue;
325
				}
326
327
				foreach (XPathHelper::getVariables($token[1]) as $varName)
328
				{
329
					// Test whether this is the name of a local variable
330
					$varQuery = 'ancestor-or-self::*/'
331
					          . 'preceding-sibling::xsl:variable[@name="' . $varName . '"]';
332
333
					if (!$xpath->query($varQuery, $attribute)->length)
334
					{
335
						$paramNames[] = $varName;
336
					}
337
				}
338
			}
339
		}
340
341
		// Dedupe and sort names
342
		$paramNames = array_unique($paramNames);
343
		sort($paramNames);
344
345
		return $paramNames;
346
	}
347
348
	/**
349
	* Return all DOMNodes whose content is an URL
350
	*
351
	* NOTE: it will also return HTML4 nodes whose content is an URI
352
	*
353
	* @param  DOMDocument $dom Document
354
	* @return array            Array of DOMNode instances
355
	*/
356
	public static function getURLNodes(DOMDocument $dom)
357
	{
358
		$regexp = '/(?>^(?>action|background|c(?>ite|lassid|odebase)|data|formaction|href|icon|longdesc|manifest|p(?>luginspage|oster|rofile)|usemap)|src)$/i';
359
		$nodes  = self::getAttributesByRegexp($dom, $regexp);
360
361
		/**
362
		* @link http://helpx.adobe.com/flash/kb/object-tag-syntax-flash-professional.html
363
		* @link http://www.sitepoint.com/control-internet-explorer/
364
		*/
365
		foreach (self::getObjectParamsByRegexp($dom, '/^(?:dataurl|movie)$/i') as $param)
366
		{
367
			$node = $param->getAttributeNode('value');
368
			if ($node)
369
			{
370
				$nodes[] = $node;
371
			}
372
		}
373
374
		return $nodes;
375
	}
376
377
	/**
378
	* Highlight the source of a node inside of a template
379
	*
380
	* @param  DOMNode $node    Node to highlight
381
	* @param  string  $prepend HTML to prepend
382
	* @param  string  $append  HTML to append
383
	* @return string           Template's source, as HTML
384
	*/
385
	public static function highlightNode(DOMNode $node, $prepend, $append)
386
	{
387
		// Add a unique token to the node
388
		$uniqid = uniqid('_');
389
		if ($node instanceof DOMAttr)
390
		{
391
			$node->value .= $uniqid;
392
		}
393
		elseif ($node instanceof DOMElement)
394
		{
395
			$node->setAttribute($uniqid, '');
396
		}
397
		elseif ($node instanceof DOMCharacterData
398
		     || $node instanceof DOMProcessingInstruction)
399
		{
400
			$node->data .= $uniqid;
401
		}
402
403
		$dom = $node->ownerDocument;
404
		$dom->formatOutput = true;
405
406
		$docXml = self::innerXML($dom->documentElement);
407
		$docXml = trim(str_replace("\n  ", "\n", $docXml));
408
409
		$nodeHtml = htmlspecialchars(trim($dom->saveXML($node)));
410
		$docHtml  = htmlspecialchars($docXml);
411
412
		// Enclose the node's representation in our hilighting HTML
413
		$html = str_replace($nodeHtml, $prepend . $nodeHtml . $append, $docHtml);
414
415
		// Remove the unique token from HTML and from the node
416
		if ($node instanceof DOMAttr)
417
		{
418
			$node->value = substr($node->value, 0, -strlen($uniqid));
419
			$html = str_replace($uniqid, '', $html);
420
		}
421
		elseif ($node instanceof DOMElement)
422
		{
423
			$node->removeAttribute($uniqid);
424
			$html = str_replace(' ' . $uniqid . '=&quot;&quot;', '', $html);
425
		}
426
		elseif ($node instanceof DOMCharacterData
427
		     || $node instanceof DOMProcessingInstruction)
428
		{
429
			$node->data .= $uniqid;
430
			$html = str_replace($uniqid, '', $html);
431
		}
432
433
		return $html;
434
	}
435
436
	/**
437
	* Load a template as an xsl:template node
438
	*
439
	* Will attempt to load it as XML first, then as HTML as a fallback. Either way, an xsl:template
440
	* node is returned
441
	*
442
	* @param  string      $template
443
	* @return DOMDocument
444
	*/
445
	public static function loadTemplate($template)
446
	{
447
		$dom = self::loadTemplateAsXML($template);
448
		if ($dom)
449
		{
450
			return $dom;
451
		}
452
453
		$dom = self::loadTemplateAsXML(self::fixEntities($template));
454
		if ($dom)
455
		{
456
			return $dom;
457
		}
458
459
		// If the template contains an XSL element, abort now. Otherwise, try reparsing it as HTML
460
		if (strpos($template, '<xsl:') !== false)
461
		{
462
			$error = libxml_get_last_error();
463
			throw new InvalidXslException($error->message);
464
		}
465
466
		return self::loadTemplateAsHTML($template);
467
	}
468
469
	/**
470
	* Replace simple templates (in an array, in-place) with a common template
471
	*
472
	* In some situations, renderers can take advantage of multiple tags having the same template. In
473
	* any configuration, there's almost always a number of "simple" tags that are rendered as an
474
	* HTML element of the same name with no HTML attributes. For instance, the system tag "p" used
475
	* for paragraphs, "B" tags used for "b" HTML elements, etc... This method replaces those
476
	* templates with a common template that uses a dynamic element name based on the tag's name,
477
	* either its nodeName or localName depending on whether the tag is namespaced, and normalized to
478
	* lowercase using XPath's translate() function
479
	*
480
	* @param  array<string> &$templates Associative array of [tagName => template]
481
	* @param  integer       $minCount
482
	* @return void
483
	*/
484
	public static function replaceHomogeneousTemplates(array &$templates, $minCount = 3)
485
	{
486
		$tagNames = [];
487
488
		// Prepare the XPath expression used for the element's name
489
		$expr = 'name()';
490
491
		// Identify "simple" tags, whose template is one element of the same name. Their template
492
		// can be replaced with a dynamic template shared by all the simple tags
493
		foreach ($templates as $tagName => $template)
494
		{
495
			// Generate the element name based on the tag's localName, lowercased
496
			$elName = strtolower(preg_replace('/^[^:]+:/', '', $tagName));
497
498
			if ($template === '<' . $elName . '><xsl:apply-templates/></' . $elName . '>')
499
			{
500
				$tagNames[] = $tagName;
501
502
				// Use local-name() if any of the tags are namespaced
503
				if (strpos($tagName, ':') !== false)
504
				{
505
					$expr = 'local-name()';
506
				}
507
			}
508
		}
509
510
		// We only bother replacing their template if there are at least $minCount simple tags.
511
		// Otherwise it only makes the stylesheet bigger
512
		if (count($tagNames) < $minCount)
513
		{
514
			return;
515
		}
516
517
		// Generate a list of uppercase characters from the tags' names
518
		$chars = preg_replace('/[^A-Z]+/', '', count_chars(implode('', $tagNames), 3));
519
520
		if (is_string($chars) && $chars !== '')
521
		{
522
			$expr = 'translate(' . $expr . ",'" . $chars . "','" . strtolower($chars) . "')";
523
		}
524
525
		// Prepare the common template
526
		$template = '<xsl:element name="{' . $expr . '}">'
527
		          . '<xsl:apply-templates/>'
528
		          . '</xsl:element>';
529
530
		// Replace the templates
531
		foreach ($tagNames as $tagName)
532
		{
533
			$templates[$tagName] = $template;
534
		}
535
	}
536
537
	/**
538
	* Replace parts of a template that match given regexp
539
	*
540
	* Treats attribute values as plain text. Replacements within XPath expression is unsupported.
541
	* The callback must return an array with two elements. The first must be either of 'expression',
542
	* 'literal' or 'passthrough', and the second element depends on the first.
543
	*
544
	*  - 'expression' indicates that the replacement must be treated as an XPath expression such as
545
	*    '@foo', which must be passed as the second element.
546
	*  - 'literal' indicates a literal (plain text) replacement, passed as its second element.
547
	*  - 'passthrough' indicates that the replacement should the tag's content. It works differently
548
	*    whether it is inside an attribute's value or a text node. Within an attribute's value, the
549
	*    replacement will be the text content of the tag. Within a text node, the replacement
550
	*    becomes an <xsl:apply-templates/> node.
551
	*
552
	* @param  string   $template Original template
553
	* @param  string   $regexp   Regexp for matching parts that need replacement
554
	* @param  callback $fn       Callback used to get the replacement
555
	* @return string             Processed template
556
	*/
557
	public static function replaceTokens($template, $regexp, $fn)
558
	{
559
		if ($template === '')
560
		{
561
			return $template;
562
		}
563
564
		$dom   = self::loadTemplate($template);
565
		$xpath = new DOMXPath($dom);
566
567
		// Replace tokens in attributes
568
		foreach ($xpath->query('//@*') as $attribute)
569
		{
570
			// Generate the new value
571
			$attrValue = preg_replace_callback(
572
				$regexp,
573
				function ($m) use ($fn, $attribute)
574
				{
575
					$replacement = $fn($m, $attribute);
576
577
					if ($replacement[0] === 'expression')
578
					{
579
						return '{' . $replacement[1] . '}';
580
					}
581
					elseif ($replacement[0] === 'passthrough')
582
					{
583
						return '{.}';
584
					}
585
					else
586
					{
587
						// Literal replacement
588
						return $replacement[1];
589
					}
590
				},
591
				$attribute->value
592
			);
593
594
			// Replace the attribute value
595
			$attribute->value = htmlspecialchars($attrValue, ENT_COMPAT, 'UTF-8');
596
		}
597
598
		// Replace tokens in text nodes
599
		foreach ($xpath->query('//text()') as $node)
600
		{
601
			preg_match_all(
602
				$regexp,
603
				$node->textContent,
604
				$matches,
605
				PREG_SET_ORDER | PREG_OFFSET_CAPTURE
606
			);
607
608
			if (empty($matches))
609
			{
610
				continue;
611
			}
612
613
			// Grab the node's parent so that we can rebuild the text with added variables right
614
			// before the node, using DOM's insertBefore(). Technically, it would make more sense
615
			// to create a document fragment, append nodes then replace the node with the fragment
616
			// but it leads to namespace redeclarations, which looks ugly
617
			$parentNode = $node->parentNode;
618
619
			$lastPos = 0;
620
			foreach ($matches as $m)
621
			{
622
				$pos = $m[0][1];
623
624
				// Catch-up to current position
625
				if ($pos > $lastPos)
626
				{
627
					$parentNode->insertBefore(
628
						$dom->createTextNode(
629
							substr($node->textContent, $lastPos, $pos - $lastPos)
630
						),
631
						$node
632
					);
633
				}
634
				$lastPos = $pos + strlen($m[0][0]);
635
636
				// Remove the offset data from the array, keep only the content of captures so that
637
				// $_m contains the same data that preg_match() or preg_replace() would return
638
				$_m = [];
639
				foreach ($m as $capture)
640
				{
641
					$_m[] = $capture[0];
642
				}
643
644
				// Get the replacement for this token
645
				$replacement = $fn($_m, $node);
646
647
				if ($replacement[0] === 'expression')
648
				{
649
					// Expressions are evaluated in a <xsl:value-of/> node
650
					$parentNode
651
						->insertBefore(
652
							$dom->createElementNS(self::XMLNS_XSL, 'xsl:value-of'),
653
							$node
654
						)
655
						->setAttribute('select', $replacement[1]);
656
				}
657
				elseif ($replacement[0] === 'passthrough')
658
				{
659
					// Passthrough token, replace with <xsl:apply-templates/>
660
					$parentNode->insertBefore(
661
						$dom->createElementNS(self::XMLNS_XSL, 'xsl:apply-templates'),
662
						$node
663
					);
664
				}
665
				else
666
				{
667
					// Literal replacement
668
					$parentNode->insertBefore($dom->createTextNode($replacement[1]), $node);
669
				}
670
			}
671
672
			// Append the rest of the text
673
			$text = substr($node->textContent, $lastPos);
674
			if ($text > '')
675
			{
676
				$parentNode->insertBefore($dom->createTextNode($text), $node);
677
			}
678
679
			// Now remove the old text node
680
			$parentNode->removeChild($node);
681
		}
682
683
		return self::saveTemplate($dom);
684
	}
685
686
	/**
687
	* Serialize a loaded template back into a string
688
	*
689
	* NOTE: removes the root node created by loadTemplate()
690
	*
691
	* @param  DOMDocument $dom
692
	* @return string
693
	*/
694
	public static function saveTemplate(DOMDocument $dom)
695
	{
696
		return self::innerXML($dom->documentElement);
697
	}
698
699
	/**
700
	* Replace HTML entities and unescaped ampersands in given template
701
	*
702
	* @param  string $template
703
	* @return string
704
	*/
705
	protected static function fixEntities($template)
706
	{
707
		return preg_replace_callback(
708
			'(&(?!quot;|amp;|apos;|lt;|gt;)\\w+;)',
709
			function ($m)
710
			{
711
				return html_entity_decode($m[0], ENT_NOQUOTES, 'UTF-8');
712
			},
713
			preg_replace('(&(?![A-Za-z0-9]+;|#\\d+;|#x[A-Fa-f0-9]+;))', '&amp;', $template)
714
		);
715
	}
716
717
	/**
718
	* Get the XML content of an element
719
	*
720
	* @param  DOMElement $element
721
	* @return string
722
	*/
723
	protected static function innerXML(DOMElement $element)
724
	{
725
		// Serialize the XML then remove the outer element
726
		$xml = $element->ownerDocument->saveXML($element);
727
728
		$pos = 1 + strpos($xml, '>');
729
		$len = strrpos($xml, '<') - $pos;
730
731
		// If the template is empty, return an empty string
732
		if ($len < 1)
733
		{
734
			return '';
735
		}
736
737
		$xml = substr($xml, $pos, $len);
738
739
		return $xml;
740
	}
741
742
	/**
743
	* Load given HTML template in a DOM document
744
	*
745
	* @param  string      $template Original template
746
	* @return DOMDocument
747
	*/
748
	protected static function loadTemplateAsHTML($template)
749
	{
750
		$dom  = new DOMDocument;
751
		$html = '<?xml version="1.0" encoding="utf-8" ?><html><body><div>' . $template . '</div></body></html>';
752
753
		$useErrors = libxml_use_internal_errors(true);
754
		$dom->loadHTML($html);
755
		libxml_use_internal_errors($useErrors);
756
757
		// Now dump the thing as XML then reload it with the proper root element
758
		$xml = '<?xml version="1.0" encoding="utf-8" ?><xsl:template xmlns:xsl="' . self::XMLNS_XSL . '">' . self::innerXML($dom->documentElement->firstChild->firstChild) . '</xsl:template>';
1 ignored issue
show
Compatibility introduced by
$dom->documentElement->firstChild->firstChild of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
759
760
		$useErrors = libxml_use_internal_errors(true);
761
		$dom->loadXML($xml);
762
		libxml_use_internal_errors($useErrors);
763
764
		return $dom;
765
	}
766
767
	/**
768
	* Load given XSL template in a DOM document
769
	*
770
	* @param  string           $template Original template
771
	* @return bool|DOMDocument           DOMDocument on success, FALSE otherwise
772
	*/
773
	protected static function loadTemplateAsXML($template)
774
	{
775
		$xml = '<?xml version="1.0" encoding="utf-8" ?><xsl:template xmlns:xsl="' . self::XMLNS_XSL . '">' . $template . '</xsl:template>';
776
777
		$useErrors = libxml_use_internal_errors(true);
778
		$dom       = new DOMDocument;
779
		$success   = $dom->loadXML($xml);
780
		libxml_use_internal_errors($useErrors);
781
782
		return ($success) ? $dom : false;
783
	}
784
}