File Coverage

lib/XML/DOM/Lite/XSLT.pm
Criterion Covered Total %
statement 135 302 44.7
branch 63 178 35.3
condition 22 68 32.3
subroutine 20 26 76.9
pod 0 21 0.0
total 240 595 40.3


line stmt bran cond sub pod time code
1             package XML::DOM::Lite::XSLT;
2              
3 7     7   38 use XML::DOM::Lite::XPath;
  7         14  
  7         183  
4 7     7   34 use XML::DOM::Lite::Constants qw(:all);
  7         15  
  7         1466  
5 7     7   39 use Carp qw(confess);
  7         13  
  7         340  
6              
7 7     7   39 use warnings;
  7         13  
  7         218  
8 7     7   33 use strict;
  7         11  
  7         33807  
9              
10             our $DEBUG = 0;
11              
12 0     0 0 0 sub new { bless { }, $_[0] }
13              
14             sub process {
15 1     1 0 225 my ($self, $xmlDoc, $stylesheet) = @_;
16 1         4 return xsltProcess($xmlDoc, $stylesheet);
17             }
18              
19             sub xsltProcess {
20 1     1 0 1 my ($xmlDoc, $stylesheet) = @_;
21              
22 1 50       5 $DEBUG && warn('XML STYLESHEET:');
23 1 50       3 $DEBUG && warn(xmlText($stylesheet));
24 1 50       3 $DEBUG && warn('XML INPUT:');
25 1 50       2 $DEBUG && warn(xmlText($xmlDoc));
26              
27 1         5 my $output = $xmlDoc->createDocumentFragment();
28 1         11 xsltProcessContext(XML::DOM::Lite::XPath::ExprContext->new($xmlDoc), $stylesheet, $output);
29              
30 1         7 my $ret = xmlText($output);
31              
32 1 50       4 $DEBUG && warn('HTML OUTPUT:');
33 1 50       3 $DEBUG && warn($ret);
34              
35 1         5 return $ret;
36             }
37              
38             sub xsltProcessContext {
39 10     10 0 45 my ($input, $template, $output) = @_;
40 10         30 my @nodename = split(/:/, $template->nodeName);
41 10 100 66     46 if (@nodename == 1 or $nodename[0] ne 'xsl') {
42 2         22 xsltPassThrough($input, $template, $output);
43              
44             } else {
45 8 50 66     162 if ($nodename[1] eq 'apply-imports') {
    100          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    100          
    100          
    50          
    50          
    0          
    0          
    0          
    0          
46 0         0 warn('not implemented: ' . $nodename[1]);
47             } elsif ($nodename[1] eq 'apply-templates') {
48 2         6 my $select = xmlGetAttribute($template, 'select');
49 2         3 my $nodes;
50 2 50       5 if ($select) {
51 2         5 $nodes = xpathEval($select, $input)->nodeSetValue();
52             } else {
53 0         0 $nodes = $input->{node}->childNodes;
54             }
55              
56 2         7 my $sortContext = $input->clone($nodes->[0], 0, $nodes);
57 2         6 xsltWithParam($sortContext, $template);
58 2         15 xsltSort($sortContext, $template);
59              
60 2         5 my $mode = xmlGetAttribute($template, 'mode');
61 2         7 my $top = $template->ownerDocument->documentElement;
62 2         8 for (my $i = 0; $i < $top->childNodes->length; ++$i) {
63 4         10 my $c = $top->childNodes->[$i];
64 4 50 33     23 if ($c->nodeType == ELEMENT_NODE and
      50        
      50        
      33        
65             $c->nodeName eq 'xsl:template' and
66             ($c->getAttribute('mode') || '') eq ($mode || '')) {
67 4         6 for (my $j = 0; $j < @{$sortContext->{nodelist}}; ++$j) {
  6         32  
68 2         3 my $nj = $sortContext->{nodelist}->[$j];
69 2         6 xsltProcessContext($sortContext->clone($nj, $j), $c, $output);
70             }
71             }
72             }
73              
74             } elsif ($nodename[1] eq 'attribute') {
75 0         0 my $nameexpr = xmlGetAttribute($template, 'name');
76 0         0 my $name = xsltAttributeValue($nameexpr, $input);
77 0         0 my $node = $output->ownerDocument->createDocumentFragment();
78 0         0 xsltChildNodes($input, $template, $node);
79 0         0 my $value = xmlValue($node);
80 0         0 $output->setAttribute($name, $value);
81              
82             } elsif ($nodename[1] eq 'attribute-set') {
83 0         0 warn('not implemented: ' . $nodename[1]);
84              
85             } elsif ($nodename[1] eq 'call-template') {
86 0         0 my $name = xmlGetAttribute($template, 'name');
87 0         0 my $top = $template->ownerDocument->documentElement;
88              
89 0         0 my $paramContext = $input->clone();
90 0         0 xsltWithParam($paramContext, $template);
91              
92 0         0 for (my $i = 0; $i < $top->childNodes->length; ++$i) {
93 0         0 my $c = $top->childNodes->[$i];
94 0 0 0     0 if ($c->nodeType == ELEMENT_NODE and
      0        
95             $c->nodeName eq 'xsl:template' and
96             $c->getAttribute('name') eq $name) {
97 0         0 xsltChildNodes($paramContext, $c, $output);
98 0         0 last;
99             }
100             }
101             } elsif ($nodename[1] eq 'choose') {
102 0         0 xsltChoose($input, $template, $output);
103              
104             } elsif ($nodename[1] eq 'comment') {
105 0         0 my $node = $output->ownerDocument->createDocumentFragment();
106 0         0 xsltChildNodes($input, $template, $node);
107 0         0 my $commentData = xmlValue($node);
108 0         0 my $commentNode = $output->ownerDocument->createComment($commentData);
109 0         0 $output->appendChild($commentNode);
110              
111             } elsif ($nodename[1] eq 'copy') {
112 0 0       0 if ($input->{node}->nodeType == ELEMENT_NODE) {
    0          
113 0         0 my $node = $output->ownerDocument->createElement($input->{node}->nodeName);
114 0         0 $output->appendChild($node);
115 0         0 xsltChildNodes($input, $template, $node);
116              
117             } elsif ($input->{node}->nodeType == ATTRIBUTE_NODE) {
118 0         0 my $node = $output->ownerDocument->createAttribute($input->{node}->nodeName);
119 0         0 $node->nodeValue = $input->{node}->nodeValue;
120 0         0 $output->setAttribute($node);
121             }
122              
123             } elsif ($nodename[1] eq 'copy-of') {
124 0         0 my $select = xmlGetAttribute($template, 'select');
125 0         0 my $value = xpathEval($select, $input);
126 0 0       0 if ($value->{type} eq 'node-set') {
127 0         0 my $nodes = $value->nodeSetValue();
128 0         0 for (my $i = 0; $i < @$nodes; ++$i) {
129 0         0 xsltCopyOf($output, $nodes->[$i]);
130             }
131              
132             } else {
133 0         0 my $node = $output->ownerDocument->createTextNode($value->stringValue());
134 0         0 $output->appendChild($node);
135             }
136              
137             } elsif ($nodename[1] eq 'decimal-format') {
138 0         0 warn('not implemented: ' . $nodename[1]);
139              
140             } elsif ($nodename[1] eq 'element') {
141 0         0 my $nameexpr = xmlGetAttribute($template, 'name');
142 0         0 my $name = xsltAttributeValue($nameexpr, $input);
143 0         0 my $node = $output->ownerDocument->createElement($name);
144 0         0 $output->appendChild($node);
145 0         0 xsltChildNodes($input, $template, $node);
146              
147             } elsif ($nodename[1] eq 'fallback') {
148 0         0 warn('not implemented: ' . $nodename[1]);
149              
150             } elsif ($nodename[1] eq 'for-each') {
151 0         0 my $sortContext = $input->clone();
152 0         0 xsltSort($sortContext, $template);
153 0         0 xsltForEach($sortContext, $template, $output);
154              
155             } elsif ($nodename[1] eq 'if') {
156 0         0 my $test = xmlGetAttribute($template, 'test');
157 0 0       0 if (xpathEval($test, $input)->booleanValue()) {
158 0         0 xsltChildNodes($input, $template, $output);
159             }
160              
161             } elsif ($nodename[1] eq 'import') {
162 0         0 warn('not implemented: ' . $nodename[1]);
163              
164             } elsif ($nodename[1] eq 'include') {
165 0         0 warn('not implemented: ' . $nodename[1]);
166              
167             } elsif ($nodename[1] eq 'key') {
168 0         0 warn('not implemented: ' . $nodename[1]);
169              
170             } elsif ($nodename[1] eq 'message') {
171 0         0 warn('not implemented: ' . $nodename[1]);
172              
173             } elsif ($nodename[1] eq 'namespace-alias') {
174 0         0 warn('not implemented: ' . $nodename[1]);
175              
176             } elsif ($nodename[1] eq 'number') {
177 0         0 warn('not implemented: ' . $nodename[1]);
178              
179             } elsif ($nodename[1] eq 'otherwise') {
180 0         0 warn('not implemented: ' . $nodename[1]);
181              
182             } elsif ($nodename[1] eq 'output') {
183              
184             } elsif ($nodename[1] eq 'preserve-space') {
185 0         0 warn('not implemented: ' . $nodename[1]);
186              
187             } elsif ($nodename[1] eq 'processing-instruction') {
188 0         0 warn('not implemented: ' . $nodename[1]);
189              
190             } elsif ($nodename[1] eq 'sort') {
191              
192             } elsif ($nodename[1] eq 'strip-space') {
193 0         0 warn('not implemented: ' . $nodename[1]);
194              
195             } elsif ($nodename[1] eq 'stylesheet' or $nodename[1] eq 'transform') {
196 1         5 xsltChildNodes($input, $template, $output);
197              
198             } elsif ($nodename[1] eq 'template') {
199 4         11 my $match = xmlGetAttribute($template, 'match');
200 4 100 66     23 if ($match and xpathMatch($match, $input)) {
201 3         29 xsltChildNodes($input, $template, $output);
202             }
203              
204             } elsif ($nodename[1] eq 'text') {
205 0         0 my $text = xmlValue($template);
206 0         0 my $node = $output->ownerDocument->createTextNode($text);
207 0         0 $output->appendChild($node);
208              
209             } elsif ($nodename[1] eq 'value-of') {
210 1         3 my $select = xmlGetAttribute($template, 'select');
211 1         5 my $value = xpathEval($select, $input)->stringValue();
212 1 50       6 unless ($output->ownerDocument) { die 'no ownerDocument for '.Dumper($output) }
  0         0  
213 1         4 my $node = $output->ownerDocument->createTextNode($value);
214 1         4 $output->appendChild($node);
215              
216             } elsif ($nodename[1] eq 'param') {
217 0         0 xsltVariable($input, $template, 0);
218              
219             } elsif ($nodename[1] eq 'variable') {
220 0         0 xsltVariable($input, $template, 1);
221              
222             } elsif ($nodename[1] eq 'when') {
223 0         0 warn('error if here: ' . $nodename[1]);
224              
225             } elsif ($nodename[1] eq 'with-param') {
226 0         0 warn('error if here: ' . $nodename[1]);
227              
228             } else {
229 0         0 warn('error if here: ' . $nodename[1]);
230             }
231             }
232             }
233              
234             sub xsltWithParam {
235 2     2 0 4 my ($input, $template) = @_;
236 2         6 for (my $i = 0; $i < $template->childNodes->length; ++$i) {
237 0         0 my $c = $template->childNodes->[$i];
238 0 0 0     0 if ($c->nodeType == ELEMENT_NODE and $c->nodeName eq 'xsl:with-param') {
239 0         0 xsltVariable($input, $c, 1);
240             }
241             }
242             }
243              
244             sub xsltSort {
245 2     2 0 3 my ($input, $template) = @_;
246 2         4 my $sort = [];
247 2         7 for (my $i = 0; $i < $template->childNodes->length; ++$i) {
248 0         0 my $c = $template->childNodes->[$i];
249 0 0 0     0 if ($c->nodeType == ELEMENT_NODE and $c->nodeName eq 'xsl:sort') {
250 0         0 my $select = xmlGetAttribute($c, 'select');
251 0         0 my $expr = xpathParse($select);
252 0   0     0 my $type = xmlGetAttribute($c, 'data-type') || 'text';
253 0   0     0 my $order = xmlGetAttribute($c, 'order') || 'ascending';
254 0         0 push(@$sort, { expr=> $expr, type=> $type, order=> $order });
255             }
256             }
257              
258 2         5 xpathSort($input, $sort);
259             }
260              
261             sub xsltVariable {
262 0     0 0 0 my ($input, $template, $override) = @_;
263            
264 0         0 my $name = xmlGetAttribute($template, 'name');
265 0         0 my $select = xmlGetAttribute($template, 'select');
266              
267 0         0 my $value;
268              
269 0 0       0 if ($template->childNodes->length > 0) {
    0          
270 0         0 my $root = $input->{node}->ownerDocument->createDocumentFragment();
271 0         0 xsltChildNodes($input, $template, $root);
272 0         0 $value = new NodeSetValue([$root]);
273              
274             } elsif ($select) {
275 0         0 $value = xpathEval($select, $input);
276              
277             } else {
278 0         0 $value = new StringValue('');
279             }
280              
281 0 0 0     0 if ($override || !$input->getVariable($name)) {
282 0         0 $input->setVariable($name, $value);
283             }
284             }
285              
286              
287             sub xsltChoose {
288 0     0 0 0 my ($input, $template, $output) = @_;
289 0         0 for (my $i = 0; $i < $template->childNodes->length; ++$i) {
290 0         0 my $childNode = $template->childNodes->[$i];
291 0 0       0 if ($childNode->nodeType != ELEMENT_NODE) {
    0          
    0          
292 0         0 next;
293              
294             } elsif ($childNode->nodeName eq 'xsl:when') {
295 0         0 my $test = xmlGetAttribute($childNode, 'test');
296 0 0       0 if (xpathEval($test, $input)->booleanValue()) {
297 0         0 xsltChildNodes($input, $childNode, $output);
298 0         0 last;
299             }
300              
301             } elsif ($childNode->nodeName eq 'xsl:otherwise') {
302 0         0 xsltChildNodes($input, $childNode, $output);
303 0         0 last;
304             }
305             }
306             }
307              
308              
309             sub xsltForEach {
310 0     0 0 0 my ($input, $template, $output) = @_;
311 0         0 my $select = xmlGetAttribute($template, 'select');
312 0         0 my $nodes = xpathEval($select, $input)->nodeSetValue();
313 0         0 for (my $i = 0; $i < @$nodes; ++$i) {
314 0         0 my $context = $input->clone($nodes->[$i], $i, $nodes);
315 0         0 xsltChildNodes($context, $template, $output);
316             }
317             }
318              
319              
320             sub xsltChildNodes {
321 6     6 0 10 my ($input, $template, $output, $foo) = @_;
322 6         15 my $context = $input->clone();
323 6         10 foreach my $c (@{$template->childNodes}) {
  6         17  
324 7         98 xsltProcessContext($context, $c, $output);
325             }
326             }
327              
328              
329             sub xsltPassThrough {
330 2     2 0 4 my ($input, $template, $output) = @_;
331 2 50       9 if ($template->nodeType == TEXT_NODE) {
    100          
332 0 0       0 if (xsltPassText($template)) {
333 0         0 my $node = $output->ownerDocument->createTextNode($template->nodeValue);
334 0         0 $output->appendChild($node);
335             }
336              
337             } elsif ($template->nodeType == ELEMENT_NODE) {
338 1         3 my $node = $output->ownerDocument->createElement($template->nodeName);
339 1         6 for (my $i = 0; $i < $template->attributes->length; ++$i) {
340 1         4 my $a = $template->attributes->[$i];
341 1 50       10 if ($a) {
342 1         3 my $name = $a->nodeName;
343 1         5 my $value = xsltAttributeValue($a->nodeValue, $input);
344 1         5 $node->setAttribute($name, $value);
345             }
346             }
347 1         5 $output->appendChild($node);
348 1         3 xsltChildNodes($input, $template, $node);
349              
350             } else {
351 1         5 xsltChildNodes($input, $template, $output);
352             }
353             }
354              
355             sub xsltPassText {
356 0     0 0 0 my ($template) = @_;
357 0 0       0 unless ($template->nodeValue =~ /^\s*$/) {
358 0         0 return 1;
359             }
360              
361 0         0 my $element = $template->parentNode;
362 0 0       0 if ($element->nodeName eq 'xsl:text') {
363 0         0 return 1;
364             }
365              
366 0   0     0 while ($element and $element->nodeType == ELEMENT_NODE) {
367 0         0 my $xmlspace = $element->getAttribute('xml:space');
368 0 0       0 if ($xmlspace) {
369 0 0       0 if ($xmlspace eq 'default') {
    0          
370 0         0 return 0;
371             } elsif ($xmlspace eq 'preserve') {
372 0         0 return 1;
373             }
374             }
375              
376 0         0 $element = $element->parentNode;
377             }
378              
379 0         0 return 0;
380             }
381              
382             sub xsltAttributeValue {
383 1     1 0 2 my ($value, $context) = @_;
384 1         3 my $parts = [ split(/{/, $value) ];
385 1 50       4 if (@$parts == 1) {
386 1         3 return $value;
387             }
388              
389 0         0 my $ret = '';
390 0         0 for (my $i = 0; $i < @$parts; ++$i) {
391 0         0 my $rp = [ split(/}/, $parts->[$i]) ];
392 0 0       0 if (@$rp != 2) {
393 0         0 $ret .= $parts->[$i];
394 0         0 next;
395             }
396              
397 0         0 my $val = xpathEval($rp->[0], $context)->stringValue();
398 0         0 $ret .= ($val . $rp->[1]);
399             }
400              
401 0         0 return $ret;
402             }
403              
404              
405             sub xmlGetAttribute {
406 9     9 0 13 my ($node, $name) = @_;
407 9         22 my $value = $node->getAttribute($name);
408 9 100       17 if ($value) {
409 7         15 return xmlResolveEntities($value);
410             } else {
411 2         5 return $value;
412             }
413             }
414              
415              
416             sub xsltCopyOf {
417 0     0 0 0 my ($dst, $src) = @_;
418 0 0 0     0 if ($src->nodeType == TEXT_NODE) {
    0          
    0          
    0          
419 0         0 my $node = $dst->ownerDocument->createTextNode($src->nodeValue);
420 0         0 $dst->appendChild($node);
421              
422             } elsif ($src->nodeType == ATTRIBUTE_NODE) {
423 0         0 $dst->setAttribute($src->nodeName, $src->nodeValue);
424              
425             } elsif ($src->nodeType == ELEMENT_NODE) {
426 0         0 my $node = $dst->ownerDocument->createElement($src->nodeName);
427 0         0 $dst->appendChild($node);
428              
429 0         0 for (my $i = 0; $i < $src->attributes->length; ++$i) {
430 0         0 xsltCopyOf($node, $src->attributes->[$i]);
431             }
432              
433 0         0 for (my $i = 0; $i < $src->childNodes->length; ++$i) {
434 0         0 xsltCopyOf($node, $src->childNodes->[$i]);
435             }
436              
437             } elsif ($src->nodeType == DOCUMENT_FRAGMENT_NODE or
438             $src->nodeType == DOCUMENT_NODE) {
439 0         0 for (my $i = 0; $i < $src->childNodes->length; ++$i) {
440 0         0 xsltCopyOf($dst, $src->childNodes->[$i]);
441             }
442             }
443             }
444              
445             sub xpathParse {
446 7     7 0 9 my ($match) = @_;
447 7         26 return XML::DOM::Lite::XPath->parse($match);
448             }
449              
450             sub xpathMatch {
451 4     4 0 7 my ($match, $context) = @_;
452 4         8 my $expr = xpathParse($match);
453              
454 4         6 my $ret;
455 4 50 66     25 if ($expr->{steps} and (not $expr->{absolute})
  2   66     11  
      33        
      33        
456 0         0 and (@{$expr->{steps}} == 1)
457             and ($expr->{steps}->[0]->{axis} eq 'child')
458             and (@{$expr->{steps}->[0]->{predicate}} == 0)) {
459 0         0 $ret = $expr->{steps}->[0]->{nodetest}->evaluate($context)->booleanValue();
460             } else {
461              
462 4         5 $ret = 0;
463 4         6 my $node = $context->{node};
464              
465 4   100     20 while ((not $ret) and $node) {
466 6         21 my $result = $expr->evaluate($context->clone($node,0,[$node]))->nodeSetValue();
467 6         24 for (my $i = 0; $i < @$result; ++$i) {
468 4 100       13 if ($result->[$i] == $context->{node}) {
469 3         4 $ret = 1;
470 3         3 last;
471             }
472             }
473 6         19 $node = $node->parentNode;
474             }
475             }
476              
477 4         24 return $ret;
478             }
479              
480             sub xpathSort {
481 2     2 0 8 return XML::DOM::Lite::XPath::xpathSort(@_);
482             }
483              
484             sub xpathEval {
485 3     3 0 5 my ($select, $context) = @_;
486 3         7 my $expr = xpathParse($select);
487 3         9 my $ret = $expr->evaluate($context);
488 3         10 return $ret;
489             }
490              
491             sub xmlText {
492 3     3 0 4 my ($node) = @_;
493 3         7 my $ret = '';
494 3 100 33     7 if ($node->nodeType == TEXT_NODE) {
    100          
    50          
495 1         3 $ret .= $node->nodeValue;
496              
497             } elsif ($node->nodeType == ELEMENT_NODE) {
498 1         3 $ret .= '<' . $node->nodeName;
499 1         5 for (my $i = 0; $i < $node->attributes->length; ++$i) {
500 1         3 my $a = $node->attributes->[$i];
501 1 50 33     6 if ($a and $a->nodeName and $a->nodeValue) {
      33        
502 1         21 $ret .= ' ' . $a->nodeName;
503 1         5 $ret .= '="' . $a->nodeValue . '"';
504             }
505             }
506              
507 1 50       4 if ($node->childNodes->length == 0) {
508 0         0 $ret .= '/>';
509              
510             } else {
511 1         2 $ret .= '>';
512 1         4 for (my $i = 0; $i < $node->childNodes->length; ++$i) {
513 1         4 $ret .= xmlText($node->childNodes->[$i]);
514             }
515 1         3 $ret .= 'nodeName . '>';
516             }
517              
518             } elsif ($node->nodeType == DOCUMENT_NODE or
519             $node->nodeType == DOCUMENT_FRAGMENT_NODE) {
520 1         4 for (my $i = 0; $i < $node->childNodes->length; ++$i) {
521 1         5 $ret .= xmlText($node->childNodes->[$i]);
522             }
523             }
524              
525 3         11 return $ret;
526             }
527              
528             sub xmlResolveEntities {
529 7     7 0 9 my ($s) = @_;
530              
531 7         17 my $parts = [ split(/&/, $s) ];
532              
533 7         13 my $ret = $parts->[0];
534 7         17 for (my $i = 1; $i < @$parts; ++$i) {
535 0         0 my $rp = [ split(/;/, $parts->[$i]) ];
536 0 0       0 if (@$rp == 1) {
537 0         0 $ret .= $parts->[$i];
538 0         0 next;
539             }
540            
541 0         0 my $ch;
542 0 0       0 if ($rp->[0] eq 'lt') {
    0          
    0          
    0          
    0          
    0          
543 0         0 $ch = '<';
544             } elsif ($rp->[0] eq 'gt') {
545 0         0 $ch = '>';
546             } elsif ($rp->[0] eq 'amp') {
547 0         0 $ch = '&';
548             } elsif ($rp->[0] eq 'quot') {
549 0         0 $ch = '"';
550             } elsif ($rp->[0] eq 'apos') {
551 0         0 $ch = "'";
552             } elsif ($rp->[0] eq 'nbsp') {
553 0         0 $ch = ' '; # "\x160"
554             } else {
555 0         0 warn 'unknown entity '.$rp->[0];
556             #my span = window.document.createElement('span');
557             #span.innerHTML = '&' + rp[0] + '; ';
558             #ch = span.childNodes[0].nodeValue.charAt(0);
559             }
560 0         0 $ret .= ($ch . $rp->[1]);
561             }
562              
563 7         19 return $ret;
564             }
565              
566             1;
567              
568             __END__