File Coverage

blib/lib/XML/EP/Processor/XSLTParser.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             ################################################################################
2             #
3             # Perl module: XML::XSLT
4             #
5             # By Geert Josten, gjosten@sci.kun.nl
6             # and Egon Willighagen, egonw@sci.kun.nl
7             #
8             ################################################################################
9            
10             ######################################################################
11             package XML::EP::Processor::XSLTParser;
12             ######################################################################
13            
14 1     1   1038 use strict;
  1         2  
  1         37  
15 1     1   519 use XML::DOM;
  0            
  0            
16            
17             use XML::DOM ();
18            
19             use vars qw ( $_indent $_indent_incr $debug $warnings );
20             $_indent = 0;
21             $_indent_incr = 1;
22            
23             sub new {
24             my $proto = shift;
25             my $parser = bless {@_}, (ref($proto) || $proto);
26             $parser->__add_default_templates__($parser->{xslDocument})
27             if $parser->{xslDocument};
28             $parser;
29             }
30            
31             sub openproject {
32             my ($parser, $xmlfile, $xslfile) = @_;
33             my $domParser = XML::DOM::Parser->new();
34             $parser->{xslDocument} = $domParser->parsefile($xslfile);
35             $parser->{xmlDocument} = $domParser->parsefile($xmlfile);
36             $parser->__add_default_templates__($parser->{xslDocument});
37             }
38            
39            
40             sub process_project {
41             my ($parser) = @_;
42             $parser->{resultNode} = $parser->{xmlDocument}->createDocumentFragment();
43             my $root_template = $parser->_find_template ('/');
44            
45             if ($root_template) {
46             $parser->_evaluate_template (
47             $root_template, # starting template, the root template
48             $parser->{xmlDocument}, # current XML node, the root
49             '', # current XML selection path, the root
50             $parser->{resultNode} # current result tree node, the root
51             );
52            
53             }
54             $parser->{resultNode};
55             }
56            
57             sub print_result {
58             my ($parser, $file) = @_;
59            
60             my $output = $parser->{resultNode}->toString();
61             $output =~ s/\n\s*\n(\s*)\n/\n$1\n/g; # Substitute multiple empty lines by one
62             $output =~ s/\/\>/ \/\>/g; # Insert a space before all />
63            
64             if ($file) {
65             print $file $output;
66             } else {
67             print $output;
68             }
69             }
70            
71             ######################################################################
72            
73             sub __add_default_templates__ {
74             my $parser = shift;
75             # Add the default templates for match="/" and match="*" #
76             my $root_node = shift;
77            
78             my $stylesheet = $root_node->getElementsByTagName('xsl:stylesheet',0)->item(0);
79             my $first_template = $stylesheet->getElementsByTagName('xsl:template',0)->item(0);
80            
81             my $root_template = $root_node->createElement('xsl:template');
82             $root_template->setAttribute('match','/');
83             $root_template->appendChild ($root_node->createElement('xsl:apply-templates'));
84             $stylesheet->insertBefore($root_template,$first_template);
85            
86             my $any_element_template = $root_node->createElement('xsl:template');
87             $any_element_template->setAttribute('match','*');
88             $any_element_template->appendChild ($root_node->createElement('xsl:apply-templates'));
89             $stylesheet->insertBefore($any_element_template,$first_template);
90             }
91            
92             sub _find_template {
93             my $parser = shift;
94             my $current_xml_selection_path = shift;
95             my $attribute_name = shift;
96             $attribute_name = "match" unless defined $attribute_name;
97            
98             print " "x$_indent,"searching template for \"$current_xml_selection_path\": " if $debug;
99            
100             my $stylesheet = $parser->{xslDocument}->getElementsByTagName('xsl:stylesheet',0)->item(0);
101             my $templates = $stylesheet->getElementsByTagName('xsl:template',0);
102            
103             for (my $i = ($templates->getLength - 1); $i >= 0; $i--) {
104             my $template = $templates->item($i);
105             my $template_attr_value = $template->getAttribute ($attribute_name);
106            
107             if (&__template_matches__ ($template_attr_value, $current_xml_selection_path)) {
108             print "found #$i \"$template_attr_value\"$/" if $debug;
109            
110             return $template;
111             }
112             }
113            
114             print "no template found! $/" if $debug;
115             warn ("No template matching $current_xml_selection_path found !!$/") if $debug;
116             return "";
117             }
118            
119             sub __template_matches__ {
120             my $template = shift;
121             my $path = shift;
122            
123             if ($template ne $path) {
124             if ($path =~ /\/.*(\@\*|\@\w+)$/) {
125             # attribute selection #
126             my $attribute = $1;
127             return ($template eq "\@*" || $template eq $attribute);
128             } elsif ($path =~ /\/(\*|\w+)$/) {
129             # element selection #
130             my $element = $1;
131             return ($template eq "*" || $template eq $element);
132             } else {
133             return "";
134             }
135             } else {
136             return "True";
137             }
138             }
139            
140             sub _evaluate_template {
141             my $parser = shift;
142             my $template = shift;
143             my $current_xml_node = shift;
144             my $current_xml_selection_path = shift;
145             my $current_result_node = shift;
146            
147             print " "x$_indent,"evaluating template content for \"$current_xml_selection_path\": $/" if $debug;
148             $_indent += $_indent_incr;;
149            
150             foreach my $child ($template->getChildNodes) {
151             my $ref = ref $child;
152             print " "x$_indent,"$ref$/" if $debug;
153             $_indent += $_indent_incr;
154            
155             if ($child->getNodeType == ELEMENT_NODE) {
156             $parser->_evaluate_element ($child,
157             $current_xml_node,
158             $current_xml_selection_path,
159             $current_result_node);
160             } elsif ($child->getNodeType == TEXT_NODE) {
161             $parser->_add_node($child, $current_result_node);
162             } else {
163             my $name = $template->getTagName;
164             print " "x$_indent,"Cannot evaluate node $name of type $ref !$/" if $debug;
165             warn ("evaluate-template: Dunno what to do with node of type $ref !!! ($name; $current_xml_selection_path)$/") if $warnings;
166             }
167            
168             $_indent -= $_indent_incr;
169             }
170            
171             $_indent -= $_indent_incr;
172             }
173            
174             sub _add_node {
175             my $parser = shift;
176             my $node = shift;
177             my $parent = shift;
178             my $deep = (shift || "");
179             my $owner = (shift || $parser->{'xmlDocument'});
180            
181             if ($debug) {
182             print " "x$_indent,"adding (deep): " if $deep;
183             print " "x$_indent,"adding (non-deep): " if !$deep;
184             }
185            
186             $node = $node->cloneNode($deep);
187             $node->setOwnerDocument($owner);
188             $parent->appendChild($node);
189            
190             print "done$/" if $debug;
191             }
192            
193             sub _apply_templates {
194             my $parser = shift;
195             my $current_xml_node = shift;
196             my $current_xml_selection_path = shift;
197             my $current_result_node = shift;
198            
199             print " "x$_indent,"applying templates on children of \"$current_xml_selection_path\":$/" if $debug;
200             $_indent += $_indent_incr;
201            
202             foreach my $child ($current_xml_node->getChildNodes) {
203             my $ref = ref $child;
204             print " "x$_indent,"$ref$/" if $debug;
205             $_indent += $_indent_incr;
206            
207             my $child_xml_selection_path = $child->getNodeName;
208             $child_xml_selection_path = "$current_xml_selection_path/$child_xml_selection_path";
209            
210             if ($child->getNodeType == ELEMENT_NODE) {
211             my $template = $parser->_find_template ($child_xml_selection_path);
212            
213             if ($template) {
214            
215             $parser->_evaluate_template ($template,
216             $child,
217             $child_xml_selection_path,
218             $current_result_node);
219             }
220             } elsif ($child->getNodeType == TEXT_NODE) {
221             $parser->_add_node($child, $current_result_node);
222             } elsif ($child->getNodeType == DOCUMENT_TYPE_NODE) {
223             # skip #
224             } elsif ($child->getNodeType == COMMENT_NODE) {
225             # skip #
226             } else {
227             print " "x$_indent,"Cannot apply templates on nodes of type $ref$/" if $debug;
228             warn ("apply-templates: Dunno what to do with nodes of type $ref !!! ($child_xml_selection_path)$/") if $warnings;
229             }
230            
231             $_indent -= $_indent_incr;
232             }
233            
234             $_indent -= $_indent_incr;
235             }
236            
237             sub _evaluate_element {
238             my $parser = shift;
239             my $xsl_node = shift;
240             my $current_xml_node = shift;
241             my $current_xml_selection_path = shift;
242             my $current_result_node = shift;
243            
244             my $xsl_tag = $xsl_node->getTagName;
245             print " "x$_indent,"evaluating element $xsl_tag for \"$current_xml_selection_path\": $/" if $debug;
246             $_indent += $_indent_incr;
247            
248             if ($xsl_tag =~ /^xsl:/i) {
249             if ($xsl_tag =~ /^xsl:apply-templates/i) {
250            
251             $parser->_apply_templates ($current_xml_node,
252             $current_xml_selection_path,
253             $current_result_node);
254             # } elsif ($xsl_tag =~ /^xsl:call-template/i) {
255             # } elsif ($xsl_tag =~ /^xsl:choose/i) {
256             # } elsif ($xsl_tag =~ /^xsl:for-each/i) {
257             # } elsif ($xsl_tag =~ /^xsl:include/i) {
258             # } elsif ($xsl_tag =~ /^xsl:output/i) {
259             # } elsif ($xsl_tag =~ /^xsl:processing-instruction/i) {
260             } elsif ($xsl_tag =~ /^xsl:value-of/i) {
261             $parser->_value_of ($xsl_node, $current_xml_node,
262             $current_xml_selection_path,
263             $current_result_node);
264             } else {
265             $parser->_add_and_recurse ($xsl_node, $current_xml_node,
266             $current_xml_selection_path,
267             $current_result_node);
268             }
269             } else {
270             $parser->_add_and_recurse ($xsl_node, $current_xml_node,
271             $current_xml_selection_path,
272             $current_result_node);
273             }
274            
275             $_indent -= $_indent_incr;
276             }
277            
278             sub _add_and_recurse {
279             my $parser = shift;
280             my $xsl_node = shift;
281             my $current_xml_node = shift;
282             my $current_xml_selection_path = shift;
283             my $current_result_node = shift;
284            
285             $parser->_add_node ($xsl_node, $current_result_node);
286             $parser->_evaluate_template ($xsl_node,
287             $current_xml_node,
288             $current_xml_selection_path,
289             $current_result_node->getLastChild);
290             }
291            
292             sub _value_of {
293             my($parser, $xsl_node, $xml_node, $current_path, $result_node) = @_;
294             my $path = $xsl_node->getAttribute('select');
295             my $start = ($path =~ /^\//) ?
296             $xml_node : $parser->{xmlDocument};
297            
298             my $value = $parser->CollectValues($start, $path);
299             if ($value ne "") {
300             $result_node->appendChild($parser->{xmlDocument}->createTextNode($value));
301             }
302             }
303            
304             sub CollectValues {
305             my($parser, $xmlNode, $path) = @_;
306             if ($path =~ s/^\/\///) {
307             # Beginning with the current node, start a recursive collection
308             return $parser->CollectValuesDeep($xmlNode, $path);
309             }
310             $path =~ s/^\///;
311             if ($path =~ s/^\.\.//) {
312             my $parent = $xmlNode->getParent();
313             return defined $parent ? $parser->CollectValues($parent, $path) : "";
314             }
315             if ($path =~ s/^\.//) {
316             return $parser->CollectValues($xmlNode, $path);
317             }
318             if ($path =~ s/^\@//) {
319             return "" unless $xmlNode->getNodeType() == XML::DOM::ELEMENT_NODE();
320             my $value = $xmlNode->getAttribute($path);
321             return defined $value ? $value : "";
322             }
323             if ($path =~ s/^([\w\-\:\.]+)(?:\[(\d+)\])?//) {
324             my $name = $1;
325             my $index = $2;
326             my @elements = $parser->FindElementsByName($xmlNode, $name);
327             if ($index) { @elements = @elements > $index ? $elements[$index] : () }
328             my $value = "";
329             foreach my $elem (@elements) {
330             $value .= $parser->CollectValues($elem, $path);
331             }
332             return $value;
333             }
334             return "" unless $path eq ""; # Dunno how to handle $path
335             $parser->ElemValue($xmlNode);
336             }
337            
338             sub ElemValue {
339             my($parser, $node) = @_;
340             my $type = $node->getNodeType();
341             if ($type == XML::DOM::ATTRIBUTE_NODE() ||
342             $type == XML::DOM::TEXT_NODE() ||
343             $type == XML::DOM::CDATA_SECTION_NODE()) {
344             $node->getData();
345             } elsif ($type == XML::DOM::ELEMENT_NODE() ||
346             $type == XML::DOM::DOCUMENT_NODE() ||
347             $type == XML::DOM::DOCUMENT_FRAGMENT_NODE()) {
348             my $value = "";
349             for (my $child = $node->getFirstChild(); $child;
350             $child = $child->getNextSibling()) {
351             $value .= $parser->ElemValue($child);
352             }
353             $value;
354             }
355             }
356            
357             sub FindElementsByName {
358             my($parser, $node, $name) = @_;
359             my @result;
360             for (my $child = $node->getFirstChild(); $child;
361             $child = $child->getNextSibling()) {
362             if ($child->getNodeType() == XML::DOM::ELEMENT_NODE()) {
363             push(@result, $child) if $child->getTagName() eq $name;
364             } elsif ($child->getNodeType() == XML::DOM::DOCUMENT_NODE() ||
365             $child->getNodeType() == XML::DOM::DOCUMENT_FRAGMENT_NODE()) {
366             push(@result, $parser->FindElementsByName($child, $name));
367             }
368             }
369             @result;
370             }
371            
372             sub CollectValuesDeep {
373             my($parser, $xmlNode, $path) = @_;
374             my $values = $parser->CollectValues($xmlNode, $path);
375             for (my $child = $xmlNode->getFirstChild(); $child;
376             $child = $child->getNextSibling()) {
377             $values .= $child->CollectValues_deep($xmlNode, $path);
378             }
379             $values;
380             }
381            
382            
383             1;