Completed
Push — master ( 268392...e141f0 )
by Josh
18:59 queued 14:15
created

TemplateHelper::replaceTokens()   D

Complexity

Conditions 13
Paths 57

Size

Total Lines 128
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 57
c 1
b 0
f 0
nc 57
nop 3
dl 0
loc 128
rs 4.9922

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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>';
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
}