File Coverage

blib/lib/XML/XSH2/XPathToXML.pm
Criterion Covered Total %
statement 80 199 40.2
branch 38 134 28.3
condition 8 40 20.0
subroutine 10 18 55.5
pod 10 10 100.0
total 146 401 36.4


line stmt bran cond sub pod time code
1             package XML::XSH2::XPathToXML;
2             ###
3             #
4             # Name: XML::XSH2::XPathToXML
5             # Version: 0.05
6             # Description: Parses a list of XPath/value pairs and returns an XML::LibXML note-tree.
7             # Original author: Kurt George Gjerde
8             # Extended by Petr Pajas
9             # Copyright: InterMedia, University of Bergen (2002)
10             # Licence: Same as Perl
11             #
12             ###
13              
14             ### POD at bottom.
15              
16              
17             ###
18              
19 2     2   16 use XML::LibXML;
  2         6  
  2         19  
20 2     2   480 use strict;
  2         4  
  2         65  
21 2     2   12 no warnings qw(uninitialized);
  2         5  
  2         95  
22              
23 2     2   16 use vars qw($VERSION);
  2         4  
  2         8697  
24             $VERSION = '0.05';
25              
26             my $PACKAGE = __PACKAGE__;
27              
28             our ($QUOT,$NAMECHAR,$FIRSTNAMECHAR,$NAME,$STEP,$LAST_STEP,$FILTER,$PREDICATE,$AXIS);
29              
30             # regexps to parse XPath steps
31              
32             $NAMECHAR = '[-_.[:alnum:]]';
33             $FIRSTNAMECHAR = '[-_.[:alpha:]]';
34             $NAME = "(?:${FIRSTNAMECHAR}${NAMECHAR}*(?::${FIRSTNAMECHAR}${NAMECHAR}+)*)";
35              
36             $QUOT = q{(?:'[^']*'|"[^"]*")};
37             $PREDICATE = qr/
38             (?:
39             (?> [^][()"']* ) # non-parens without backtracking
40             |
41             '[^']*' | "[^"]*" # quotes
42             |
43             \[
44             (??{$PREDICATE}) # matching square brackets
45             \]
46             |
47             \(
48             (??{$PREDICATE}) # matching round brackets
49             \)
50             )*
51             /x;
52              
53             $FILTER = qr/(?:\[$PREDICATE\])/;
54              
55             $AXIS=qr{(?:(?:following-sibling|following|preceding|preceding-sibling|parent|ancestor|ancestor-or-self|descendant|self|descendant-or-self|child|namespace)::)?};
56             $STEP = qr{(?:(?:^|/)${AXIS}${NAME}${FILTER}*)};
57             $LAST_STEP = qr{(?:(?:^|/)(?:\@${NAME}|${AXIS}(?:${NAME}|comment[(][)]|text[(][)]|processing-instruction[(](?:\s*"${NAME}"\s*|\s*'${NAME}'\s*)[)]))${FILTER}*)};
58              
59             ### NEW
60             #
61             sub new {
62 8     8 1 38 my ($class, %args) = @_;
63 8         16 my $self = \%args;
64 8         17 bless $self, $class;
65              
66 8   50     58 $self->{version} ||= '1.0';
67 8   50     37 $self->{debug} ||= 0;
68 8 50       25 $self->{maxAutoSiblings} = 256 if not defined $self->{maxAutoSiblings};
69              
70 8         26 $self->init();
71              
72 8         24 return $self;
73             }
74              
75              
76             ### CLOSE
77             #
78             sub close {
79 0     0 1 0 my $self = shift;
80 0         0 undef $self->{tree};
81 0         0 undef $self->{node};
82 0         0 undef $self->{doc};
83             }
84              
85              
86             ### INIT
87             #
88             sub init {
89 8     8 1 12 my $self = shift;
90 8 50       23 unless ($self->{doc}) {
91 8 50       52 if (ref($self->{node})) {
92 8         64 $self->{doc} = $self->{node}->ownerDocument;
93             } else {
94 0         0 $self->{doc} = XML::LibXML::Document->createDocument( $self->{version}, $self->{encoding} );
95 0         0 $self->{doc}->setDocumentElement( $self->{doc}->createElement('root') );
96             }
97             }
98 8   33     33 $self->{node} ||= $self->{doc};
99 8         90 $self->{tree} = {};
100             }
101              
102              
103             ### RESET
104             #
105             sub setContextNode {
106 0     0 1 0 my ($self,$node) = @_;
107 0         0 undef $self->{tree};
108 0 0       0 unless (ref($node)) {
109 0 0       0 if ($self->{XPathContext}) {
110 0         0 $node=$self->{XPathContext}->find($node)->get_node(1);
111             } else {
112 0         0 $node=$self->{node}->find($node)->get_node(1);
113             }
114             }
115 0 0       0 if ($node) {
116 0         0 $self->{node} = $node;
117 0         0 $self->{doc} = $node->ownerDocument;
118             } else {
119 0         0 die "[$PACKAGE] Context node doesn't exist.\n";
120             }
121             }
122              
123              
124             ### RESET
125             #
126             sub reset {
127 0     0 1 0 my ($self,%args) = @_;
128 0         0 $self->close;
129 0         0 $self->init(%args);
130             }
131              
132              
133             ### GET IT
134             #
135 0     0 1 0 sub contextNode { shift->{node} }
136              
137 0     0 1 0 sub document { shift->{doc} }
138              
139 0     0 1 0 sub documentElement { shift->{doc}->documentElement }
140            
141              
142             ### PARSE
143             #
144             sub parse {
145 0     0 1 0 my ($self, $data, $value) = @_;
146            
147 0 0       0 if (ref($data) eq 'HASH') {
    0          
148 0         0 foreach my $xpath (sort keys %{$data}) {
  0         0  
149 0 0       0 print "$xpath\n" if $self->{debug};
150 0         0 $self->createNode( $xpath, $data->{$xpath} );
151             }
152             } elsif (ref($data) eq 'ARRAY') {
153             # preserve order
154 0 0       0 die "[$PACKAGE] Array must have even number of elements.\n" if (@$data % 2);
155 0         0 for (my $i=0; $i<@$data; $i+=2) {
156 0 0       0 print "$data->[$i]\n" if $self->{debug};
157 0         0 $self->createNode( $data->[$i], $data->[$i+1] );
158             }
159             } else {
160 0         0 $self->createNode( $data, $value );
161             }
162            
163 0         0 return $self->{doc}->documentElement();
164             }
165              
166              
167             ### _CREATE NODE
168             #
169             # Creates nodes, text nodes and attributes from an xpath and a value.
170             #
171             # Returns the node created
172             #
173              
174             sub createNode {
175 8     8 1 77 my ($self, $xpath, $value, $context_node) = @_;
176              
177             # strip the uninteresting part
178 8         18 $xpath=~s{^\.\s+/}{};
179              
180             # roughly verify that we have an XPath in a supported form:
181 8 50       5770 die "[$PACKAGE] Can't create nodes based on XPath $xpath\n" unless $xpath =~ m{^(?:\$${NAME}${FILTER}*)?$STEP*$LAST_STEP$};
182             # return;
183              
184 8 100       102 if ($xpath=~s{^/}{}) {
185             # start from the document level
186 3         15 $self->_createSteps($self->{doc},$xpath,$value);
187             } else {
188 5 50       486 if ($xpath =~ s{^(\$${NAME}${FILTER}*)/}{./}) {
189 0         0 $context_node = $1;
190             }
191             # start from the current node
192 5 50 33     29 if ($context_node ne "" and !ref($context_node)) {
193 0 0       0 if ($self->{XPathContext}) {
194 0         0 $context_node=$self->{XPathContext}->find($context_node)->get_node(1);
195             } else {
196 0         0 $context_node=$self->{node}->find($context_node)->get_node(1);
197             }
198 0 0       0 die "[$PACKAGE] Context node doesn't exist.\n" unless ($context_node);
199             } else {
200 5   33     37 $context_node ||= $self->{node};
201             }
202 5         35 $self->_createSteps($context_node,$xpath,$value);
203             }
204             }
205              
206             sub _lookup_namespace {
207 11     11   26 my ($self,$node,$name)=@_;
208 11 50       199 if ($name=~s/^(${NAMECHAR}+)://) {
209 0         0 my $prefix = $1;
210 0         0 my $uri = $node->lookupNamespaceURI($prefix);
211 0 0 0     0 if (!defined($uri) and $self->{XPathContext} and
      0        
212             UNIVERSAL::can($self->{XPathContext},'lookupNs')) {
213 0         0 $uri = $self->{XPathContext}->lookupNs($prefix)
214             }
215 0 0 0     0 if (!defined($uri) and ref($self->{namespaces})) {
216 0         0 $uri = $self->{namespaces}->{ $prefix };
217             }
218 0 0       0 if (!defined($uri)) {
219 0         0 warn "Couldn't find namespace URI for the element ${prefix}:${node}!\n";
220 0         0 return ($prefix.':'.$name,undef);
221             } else {
222             # find the best suitable prefix
223 0         0 my $real_prefix = $node->lookupNamespacePrefix( $uri );
224 0 0 0     0 if ($real_prefix eq "" and !defined($node->lookupNamespaceURI( undef ))) {
225 0         0 $real_prefix = $prefix;
226             }
227 0 0       0 return (($real_prefix ne "") ? $real_prefix.':'.$name : $name , $uri);
228             }
229             } else {
230 11         80 return ($name,undef);
231             }
232             }
233              
234             sub _insertNode {
235 11     11   28 my ( $self, $axis, $node, $next ) = @_;
236 11 100       49 if ($axis =~ /^$|^child::|^descendant(?:-or-self)?::/) {
    50          
    0          
    0          
237 10         55 $node->appendChild( $next );
238             } elsif ($axis =~ /^(following(?:-sibling)?)::/) {
239 1         5 my $parent = $node->parentNode;
240 1 50       3 die "Can't create axis $1 on a document node" unless $parent;
241 1         12 $parent->insertAfter( $next, $node );
242             } elsif ($axis =~ /^(preceding(?:-sibling)?)::/) {
243 0         0 my $parent = $node->parentNode;
244 0 0       0 die "Can't create axis $1 on a document node" unless $parent;
245 0         0 $parent->insertBefore( $next, $node );
246             } elsif ($axis =~ /^(parent|ancestor|ancestor-or-self|self|namespace)::/) {
247 0         0 die "Can't create axis $1";
248             }
249             }
250              
251             sub _createSteps {
252 8     8   78 my ($self,$node,$xpath,$value)=@_;
253              
254 8         25 while ($xpath ne "") {
255             # get the first step
256 12         34 my $step = $xpath;
257 12         18 my $rest;
258 12 100       53 if ($step =~ s{^(.*?)\/(.*)$}{$1}) {
259 4         11 $rest = $2;
260             }
261              
262 12 50       32 print "$xpath : Processing step: $step (remains $rest)\n" if $self->{debug};
263              
264             my $next = $self->{XPathContext} ?
265 12 50       56 $self->{XPathContext}->find($step,$node)->get_node(1) :
266             $node->find($step)->get_node(1);
267              
268 12 100       714 unless ($next) {
269             # auto-create the node(s) implied by the step
270              
271 3 50       644 if ($step =~ /^(?:@|attribute::)($NAME)/) {
    50          
    50          
    50          
272 0         0 my $name = $1;
273 0 0       0 if ($rest eq "") {
274 0 0       0 print "$xpath : auto-creating attribute $name for $step\n" if $self->{debug};
275 0         0 my ($real_name,$uri) = $self->_lookup_namespace($node,$name);
276 0 0 0     0 if (defined($uri) and $uri ne "") {
277 0         0 $node->setAttributeNS($uri,$name,$value);
278 0         0 return $node->getAttributeNodeNS($uri,$name);
279             } else {
280 0         0 $node->setAttribute($name,$value);
281 0         0 return $node->getAttributeNode($name);
282             }
283             } else {
284 0         0 die "[$PACKAGE] XPath steps follow after an attribute: $step/$rest\n";
285             }
286             } elsif ($step =~ /^($AXIS)text\(\)/) {
287 0         0 my $axis = $1;
288 0 0       0 if ($rest eq "") {
289 0 0       0 print "$xpath : auto-creating text() for $step\n" if $self->{debug};
290 0         0 $next = $self->{doc}->createTextNode($value);
291 0         0 $self->_insertNode( $axis, $node, $next );
292 0         0 return $next;
293             } else {
294 0         0 die "[$PACKAGE] XPath steps follow after a text(): $step/$rest\n";
295             }
296             } elsif ($step =~ /^($AXIS)comment\(\)/) {
297 0         0 my $axis = $1;
298 0 0       0 if ($rest eq "") {
299 0 0       0 print "$xpath : auto-creating comment for $step\n" if $self->{debug};
300 0         0 $next = $self->{doc}->createComment($value);
301 0         0 $self->_insertNode( $axis, $node, $next );
302 0         0 return $next;
303             } else {
304 0         0 die "[$PACKAGE] XPath steps follow after a text(): $step/$rest\n";
305             }
306             } elsif ($step =~ /^($AXIS)processing-instruction\((${PREDICATE})\)/o) {
307 0         0 my $axis = $1;
308 0         0 my $name = $2;
309 0 0       0 if ($name=~/^(?:\s*'([^']*)'|"([^"]*)"\s*)$/) {
310 0         0 $name=$1.$2;
311 0 0       0 if ($rest eq "") {
312 0 0       0 print "$xpath : auto-creating comment for $step\n" if $self->{debug};
313 0         0 $next = $self->{doc}->createProcessingInstruction($name,$value);
314 0         0 $self->_insertNode( $axis, $node, $next );
315 0         0 return $next;
316             } else {
317 0         0 die "[$PACKAGE] XPath steps follow after a processing-instruction(): $step/$rest\n";
318             }
319             } else {
320 0         0 die "[$PACKAGE] Can't auto-create PI as specified ($name), use processing-instruction('name')\n";
321             }
322             } else {
323 3         9 my ($name,$axis);
324 3 50       402 if ($step =~ /^($AXIS)($NAME)(?!\()/) {
325 3         14 $axis = $1;
326 3         7 $name = $2;
327             };
328 3 50       14 if ($name eq "") {
329 0         0 die "[$PACKAGE] Can't determine element name from step $step\n";
330             }
331 3         6 my @auto;
332 3         5 do {{
333 11 50       405 print "$xpath : auto-creating element $name for $step\n" if $self->{debug};
  11         29  
334 11 50 33     53 if ($self->{maxAutoSiblings} && @auto>$self->{maxAutoSiblings}) {
335             # unlink all added siblings
336             # print STDERR $self->{doc}->toString(1),"\n @auto\n";
337 0         0 $_->unlinkNode for @auto;
338 0         0 die "[$PACKAGE] Max automatic creation of siblings overflow ($self->{maxAutoSiblings}) for '$xpath', step '$step'!\n";
339             }
340 11         30 my ($real_name,$uri) = $self->_lookup_namespace($node,$name);
341 11 50 33     34 if (defined($uri) and $uri ne "") {
342 0         0 $next = $self->{doc}->createElementNS($uri,$real_name);
343             } else {
344 11         71 $next = $self->{doc}->createElement($real_name);
345             }
346 11 50       42 if ($node == $self->{doc}) {
347 0         0 $node->setDocumentElement( $next );
348             # iterating won't help here
349 0         0 last;
350             } else {
351 11         148 $self->_insertNode( $axis, $node, $next );
352             }
353 11         48 push @auto,$next;
354              
355             $next = $self->{XPathContext} ?
356 11 50       170 $self->{XPathContext}->find($step,$node)->get_node(1) :
357             $node->find($step)->get_node(1);
358             }} while (!$next);
359             }
360             }
361 12         318 $xpath = $rest;
362 12         36 $node = $next;
363             };
364              
365 8 50       51 if ($node) {
366 8 50       49 print "Setting value for $xpath\n" if $self->{debug};
367 8 50       45 if ($node->nodeType == XML::LibXML::XML_ATTRIBUTE_NODE()) {
    50          
368 0         0 $node->setValue($value);
369             } elsif ($node->nodeType != XML::LibXML::XML_ELEMENT_NODE()) {
370 0         0 $node->setData($value);
371             } else {
372 8 50       30 $node->removeChildNodes() if $node->hasChildNodes;
373 8 50 33     23 if (ref($value) and UNIVERSAL::isa($value,'XML::LibXML::Node')) {
374 0         0 $node->appendChild( $value );
375             } else {
376 8 100       40 $node->appendTextNode($value) if $value ne "";
377             }
378             }
379             } else {
380 0         0 warn "No node for XPath ($xpath)\n";
381             }
382 8         127 return $node;
383             }
384              
385             ###
386             #
387             # The following method use a hash to store all the xpaths and their nodes.
388             # Locating a node is purely based on looking up its xpath the way it's been
389             # written. Thus 'somenode[1]' and 'somenode[position()=1]' will be treated as
390             # two different nodes!
391             #
392             # Using proper xpath doc->find to retrieve nodes instead of the hash will
393             # eliminate this problem.
394             #
395             # Anyway:
396             # - if you have a 'node[1]' and a 'node[3]' a 2nd node will not be
397             # created inbetween.
398             # - if you have a 'node[position()=1]' and a 'node[2]' the nodes will
399             # be reversed in the doc (all xpaths are created in alphabetical order).
400             #
401             # ALL OF THESE PROBLEMS HAVE BEEN FIXED IN THE createNode METHOD ABOVE!
402             #
403             #
404             sub _createNode_simple {
405 0     0     my ($self, $xpath, $value) = @_;
406              
407 0           my $name = $xpath;
408 0           $name =~ s{^.*\/(.*)$}{$1};
409 0           $name =~ s{\[.*\]$}{};
410            
411 0           my $parent = $xpath;
412 0           $parent =~ s{^(.*)\/.*$}{$1};
413              
414 0 0 0       if ($parent && !defined $self->{tree}->{$parent}) {
415 0           $self->_createNode_simple($parent, undef);
416             }
417            
418             # Attribute
419 0 0         if ($name =~ /^@/) {
420 0 0         print " addAttribute: $xpath\n" if $self->{debug};
421 0           $self->{tree}->{$parent}->setAttribute($name,$value);
422             }
423             # Element
424             else {
425 0 0         print " createElement: $xpath\n" if $self->{debug};
426              
427 0           $self->{tree}->{$xpath} = $self->{doc}->createElement($name);
428            
429 0 0         if ($parent) {
430 0           $self->{tree}->{$parent}->appendChild( $self->{tree}->{$xpath} );
431             } else {
432 0           $self->{doc}->setDocumentElement( $self->{tree}->{$xpath} );
433             }
434            
435 0 0         $self->{tree}->{$xpath}->appendTextNode($value) if $value;
436             }
437              
438 0           return undef;
439             }
440              
441              
442             ###########
443             1;
444              
445              
446             __END__