File Coverage

blib/lib/XML/LibXML/Tools.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package XML::LibXML::Tools;
2              
3             BEGIN {
4 5     5   192403 @XML::LibXML::Tools::ISA = qw( Exporter );
5 5         24 @XML::LibXML::Tools::EXPORT = qw( BEFORE AFTER TO );
6 5         114 $XML::LibXML::Tools::VERSION = '1.00';
7             }
8              
9 5     5   51 use strict;
  5         9  
  5         167  
10 5     5   28 use Exporter;
  5         19  
  5         265  
11              
12             our $croak = 1;
13              
14 5     5   6197 use XML::LibXML;
  0            
  0            
15             use Carp;
16              
17             use constant BEFORE => "before";
18             use constant AFTER => "after";
19             use constant TO => "to";
20             use constant DEEP => 1;
21              
22             use Class::AccessorMaker {
23             # object wide settings
24             defaultLocation => TO,
25              
26             # object holders
27             objectDom => "",
28             storeDom => 1,
29              
30             # error handling
31             error => undef,
32             errorMsg => undef,
33             croakOnError => 1,
34             showPath => 0,
35              
36             }, "new_init";
37              
38              
39             sub init {
40             my ($self) = @_;
41              
42             $self->croakOnError($croak);
43             return $self;
44             }
45              
46             # prevent endless recursion.
47             my @already_seen = ();
48              
49             # ==========================================================================
50             # ERRORS AND SUCH
51             # ==========================================================================
52              
53             sub addError{
54             my $self = shift;
55             my $string = shift;
56              
57             $self->errorMsg( ( ($self->errorMsg()) ? ($self->errorMsg()."\n") : "") . $string );
58             $self->error(1);
59              
60             if ( $self->croakOnError ) {
61             my @caller = caller(1);
62             my @calledby = caller(2);
63             my ($caller) = $calledby[3]||'';
64             $caller =~ s/XML::LibXML::Tools:://;
65             croak("$string - $caller ($caller[1] line: $caller[2])");
66             }
67              
68             return($self);
69             }
70              
71             sub resetError{
72             my $self = shift;
73             $self->error(0);
74             $self->errorMsg("");
75             return($self);
76             }
77              
78             sub checkParams {
79             my $self = shift;
80             my %param = @_;
81              
82             if ( my @missing = grep { !defined $param{params}->{$_} } @{$param{required}} ) {
83             $self->addError("Missing required parameter(s) ".join(", ", @missing));
84              
85             return undef;
86             }
87              
88             # shouldn't be here, but prevents warnings;
89             $param{params}->{depth} ||= 0;
90              
91             return 1;
92             }
93              
94             sub resetHandlers {
95             my $self = shift;
96             my $caller = [caller(1)]->[1];
97              
98             if ( $caller !~ /LibXML::Tools/ ) {
99             @already_seen = ();
100             $self->resetError;
101             }
102             }
103              
104             # ==========================================================================
105             # COMPLEX -> DOM
106             # ==========================================================================
107              
108             sub complex2Dom {
109             my $self = shift;
110             my %param = @_;
111             $param{depth} ||= 0;
112              
113             warn "-" x $param{depth}, "complex2Dom\n" if $self->showPath;
114              
115             my $prev = $self->showPath;
116             $self->showPath($param{showpath}) if exists $param{showpath};
117              
118             # reset recursion safety
119             $self->resetHandlers();
120              
121             # create ourselves a Dom.
122             my $dom = XML::LibXML::Document->new();
123             $dom->setEncoding( "utf-8" );
124              
125             # find root element
126             my $ref = $param{data};
127             my $rootName = shift @$ref;
128             $ref = shift @$ref;
129              
130             if ( my $type = ref($rootName) ) {
131             if ( $type =~ /ARRAY/ ) {
132             $rootName = shift @$rootName;
133             } elsif ( $type =~ /XML::LibXML/ ) {
134             # to complex.
135             $self->addError("complex2Dom: To complex! ($rootName)");
136             } else {
137             $self->addError("complex2Dom: No rootname! ($rootName)");
138             croak($self->errorMsg);
139             }
140             }
141              
142             # create and set it.
143             {
144             eval {
145             local $SIG{__DIE__} = sub { };
146             $dom->setDocumentElement($dom->createElement( $rootName ));
147             };
148             chomp $@;
149             $self->addError("complex2Dom: $@") if $@;
150             }
151             my $root = $dom->getDocumentElement;
152             my $res = $self->array2Dom( %param,
153             data => $ref,
154             depth => $param{depth}+1,
155             dom => $dom,
156             node => $root );
157              
158             # fake 1-node document support
159             if( ref($res) eq "XML::LibXML::Text" ) {
160             $root->addChild($res);
161             }
162              
163             $self->objectDom($dom) if !$self->objectDom && $self->storeDom;
164              
165             $self->showPath($prev);
166             return ($self->error) ? undef : $dom;
167             }
168              
169             sub array2Dom{
170             my $self = shift;
171             my %param = @_;
172              
173             $param{dom} ||= $self->objectDom;
174             $self->checkParams( params => \%param,
175             required => [ qw(data dom node) ],
176             ) || return undef;
177              
178             $self->resetHandlers;
179              
180             my $type = ref($param{data});
181              
182             if ( $type and $self->alreadySeen($param{data}) ) {
183             warn "Circular reference for $type at array2Dom\n";
184             return;
185             }
186              
187             my $prev = $self->showPath;
188             $self->showPath($param{showpath}) if exists $param{showpath};
189              
190             warn "-" x $param{depth}, "array2Dom\n" if $self->showPath;
191              
192             if( $type eq "ARRAY" ){
193             my $first = 1;
194             my @tmp = @{$param{data}};
195              
196             while ( @tmp ) {
197             my $key = shift @tmp;
198              
199             $key = '' if( !defined $key );
200             my $val = shift @tmp;
201              
202             if( my $attr = isAttribute($key) ){
203             my $newNode = $param{dom}->createAttribute($attr, $val);
204             $self->addNode( %param,
205             depth => $param{depth}+1,
206             newNode => $newNode );
207              
208             } elsif( my $cmnt = isComment($key) ) {
209             my $element = $param{dom}->createComment( $cmnt );
210             $self->addNode( %param,
211             depth => $param{depth}+1,
212             newNode => $element );
213              
214             } elsif ( my $type = ref($key) ) {
215             $self->domAdd(%param,
216             depth => $param{depth}+1,
217             data => [ $key ]);
218              
219             } else {
220             if( defined $val ) {
221             my $element = $param{dom}->createElement($key);
222             $param{node}->addChild($element);
223              
224             # also support XML-doms!
225             if( ref($val) =~ /XML::LibXML::Document|XML::LibXML::DocumentFragment/ ) {
226             my $content = "";
227             if( ref($val) =~ /XML::LibXML::Document$/ ) {
228             $content = $val->getDocumentElement->cloneNode( DEEP );
229             $content->setOwnerDocument($param{dom});
230             } else {
231             $content = $val;
232             }
233              
234             $element->addChild($content);
235              
236             } else {
237             my $res = $self->array2Dom( %param,
238             depth => $param{depth}+1,
239             data => $val,
240             node => $element);
241             }
242              
243             } else {
244             # key _is_ value!
245             $self->addNode(%param,
246             depth => $param{depth}+1,
247             newNode => $self->makeXMLFragment( %param,
248             depth => $param{depth}+1,
249             data => $key || '' ) );
250             }
251             }
252             }
253              
254             return($param{node});
255              
256             } elsif ($type eq "SCALAR") {
257             $self->array2Dom( %param,
258             depth => $param{depth}+1,
259             data => scalar($param{data}) );
260              
261             } elsif ( $type =~ /XML::LibXML/ ) {
262             $self->addNode ( %param,
263             depth => $param{depth}+1,
264             newNode => $self->makeXMLFragment( %param,
265             depth => $param{depth}+1 ) );
266              
267             } else{
268             # it's a true scalar! -> return the value!
269             $self->ref2TextNode( %param, depth => $param{depth}+1 );
270             }
271              
272             $self->showPath($prev);
273             return ($self->error) ? undef : 1;
274             }
275              
276             # ==========================================================================
277             # DOM MANIPULATION
278             # ==========================================================================
279              
280             sub ref2TextNode {
281             my $self = shift;
282             my %param = @_;
283              
284             $param{dom} ||= $self->objectDom;
285             $self->checkParams( params => \%param,
286             required => [ qw(data dom node) ] ) || return undef;
287              
288             my $prev = $self->showPath;
289             $self->showPath($param{showpath}) if exists $param{showpath};
290             warn "-" x $param{depth}, "ref2TextNode\n" if $self->showPath;
291              
292             # some times, some where an element is mistaken for a text - correct
293             # that mistake here.
294              
295             my $textNode;
296             if ( ref($param{data}) !~ /XML::LibXML/ ) {
297             $textNode = $param{dom}->createTextNode($param{data});
298              
299             } elsif ( ref($param{data}) =~ /DocumentFragment|Element/ ) {
300             $textNode = $param{data};
301              
302             } elsif ( ref($param{data}) =~ /NodeList/ ) {
303             $textNode = $param{data}->shift;
304              
305             } else {
306             $self->addError("ref2TextNode: unknown reference type (".
307             ref($param{data}).") - can't make node");
308             }
309              
310             # reset location -> TO for text nodes
311             $self->addNode( %param,
312             location => TO,
313             depth => $param{depth}+1,
314             newNode => $textNode );
315              
316             $self->showPath($prev);
317              
318             return ($self->error) ? undef : 1;
319             }
320              
321             sub makeXMLFragment {
322             my $self = shift;
323             my %param = @_;
324              
325             $param{dom} ||= $self->objectDom;
326             $self->checkParams( params => \%param,
327             required => [ qw(dom data) ]) || return undef;
328              
329             my $prev = $self->showPath;
330             $self->showPath($param{showpath}) if exists $param{showpath};
331              
332             warn "-" x $param{depth}, "makeXMLFragment\n" if $self->showPath;
333              
334             if ( ref($param{data}) =~ /DocumentFragment/ ) {
335             # for fragments, elements and nodes be content.
336             $self->showPath($prev);
337             return $param{data}
338              
339             } elsif ( ref($param{data}) =~ /NodeList/ ) {
340             $param{data} = $param{data}->shift->cloneNode( DEEP );
341              
342             } elsif ( ref($param{data}) =~ /Element|Node/ ) {
343             $self->showPath($prev);
344             return $param{data}->cloneNode( DEEP );
345              
346             }
347              
348             if ( ref($param{data}) =~ /ARRAY/ ) {
349             # make array-value a DOM
350             $param{data} = $self->complex2Dom( %param,
351             data => $param{data},
352             depth => $param{depth}+1 );
353             }
354              
355             my $type = ref($param{data});
356             my $content = "";
357              
358             if ( $type =~ /XML::LibXML::Document$/ ) {
359             $content = $param{data}->getDocumentElement->cloneNode( DEEP );
360             $content->setOwnerDocument($param{dom})
361             } elsif ( $type =~ /DocumentFragment|Element/ ) {
362             $content = $param{data};
363              
364             } else {
365             $content = $param{dom}->createTextNode($param{data});
366             }
367              
368             $self->showPath($prev);
369              
370             return ($self->error) ? undef : $content;
371             }
372              
373             sub domUpdate {
374             my $self = shift;
375             my %param = @_;
376              
377             $param{dom} ||= $self->objectDom;
378             $param{dom} || $self->addError("domUpdate : No DOM supplied!");
379              
380             if ( !$param{node} and $param{xpath} ) {
381             $param{node} = $param{dom}->findnodes($param{xpath})->shift;
382             $param{node} || $self->addError("Couldn't execute XPATH on supplied DOM ($param{dom}) in domUpdate");
383             }
384              
385             $self->checkParams( params => \%param,
386             required => [ qw(dom node data) ]) || return undef;
387              
388             my $prev = $self->showPath;
389             $self->showPath($param{showpath}) if exists $param{showpath};
390              
391             warn "-" x $param{depth}, "domUpdate\n" if $self->showPath;
392              
393             # reset recursion limiter.
394             $self->resetHandlers;
395              
396             my @tmp = @{$param{data}};
397             while (@tmp) {
398             my $key = shift @tmp;
399              
400             my $value;
401             $value = shift @tmp if ( !ref($key) );
402              
403             if( my $attr = isAttribute($key) ){
404             # update attribute of tag
405             $param{node}->removeAttribute( $attr );
406              
407             my $newNode = $param{dom}->createAttribute($attr, $value);
408             $self->addNode( %param,
409             depth => $param{depth}+1,
410             newNode => $newNode );
411              
412             } elsif ( isComment($key) ) {
413             # make error - but don't croak
414             my $prev = $self->croakOnError; $self->croakOnError(0);
415             $self->addError("domUpdate doesn't support comments. Referting to domAdd");
416             $self->croakOnError($prev);
417              
418             $self->domAdd(%param,
419             depth => $param{depth}+1,
420             data => [ $key ]);
421              
422             } else {
423             # update value element of tag
424             my $type = ref($key);
425              
426             if ( !$type ) {
427             my $parent = $param{node}->findnodes("$key");
428             my $node = $parent->shift();
429              
430             if (!$node) {
431             # perhaps adding it is possible...
432             $self->domAdd( %param,
433             depth => $param{depth}+1,
434             data => [ $key => $value ] );
435             next;
436             }
437              
438             my ($elname) = $key =~ /([^\/]*)$/;
439             my $element = $param{dom}->createElement( $elname );
440             my $content = $self->makeXMLFragment(%param,
441             depth => $param{depth}+1,
442             data => $value);
443              
444             $element->addChild($content);
445             $node->replaceNode($element);
446              
447             } elsif ( $type =~ /NodeList$/ ) {
448              
449             foreach my $node ( $key->get_nodelist ) {
450             # add or replace?
451             my ($parentNode, $addOrReplace);
452             eval {
453             local $SIG{__DIE__} = sub {};
454             $addOrReplace = ($parentNode = $param{node}->findnodes($node->nodeName)->shift)
455             ? "replaceNode"
456             : "addChild";
457             };
458             chomp($@); $self->addError("domUpdate: $@") if $@;
459              
460             $addOrReplace ||= "addChild";
461             $parentNode ||= $param{node};
462              
463             my $newNode = $node->cloneNode( DEEP );
464             $parentNode->$addOrReplace($newNode);
465             }
466              
467             } elsif ( $type =~ /XML::LibXML/ ) {
468             my $content = $self->makeXMLFragment(%param,
469             depth => $param{depth}+1,
470             data => $key);
471             my $node = $content->findnodes("/")->shift;
472              
473             # add or replace?
474             my ($parentNode, $addOrReplace);
475             eval {
476             local $SIG{__DIE__} = sub {};
477             $addOrReplace = ($parentNode = $param{node}->findnodes($node->nodeName)->shift)
478             ? "replaceNode"
479             : "addChild";
480             };
481             chomp($@); $self->addError("domUpdate: $@") if $@;
482              
483             $addOrReplace ||= "addChild";
484             $parentNode ||= $param{node};
485              
486             $parentNode->$addOrReplace($content);
487              
488             } else {
489             $self->addError("Unknown type at domUpdate");
490             }
491             }
492             }
493              
494             $self->showPath($prev);
495              
496             return ($self->error) ? undef : 1;
497             }
498              
499             sub domAdd {
500             my $self = shift;
501             my %param = @_;
502              
503             $param{dom} ||= $self->objectDom;
504             if ( !$param{node} and $param{xpath} ) {
505             $param{node} = $param{dom}->findnodes($param{xpath})->shift;
506             $param{node} || $self->addError("Couldn't execute XPATH on supplied DOM in domUpdate");
507             }
508              
509             $self->checkParams( params => \%param,
510             required => [ qw(dom node data) ]) || return undef;
511              
512             my $prev = $self->showPath;
513             $self->showPath($param{showpath}) if exists $param{showpath};
514              
515             warn "-" x $param{depth}, "domAdd\n" if $self->showPath;
516              
517             # reset recursion limiter.
518             $self->resetHandlers;
519              
520             my @tmp = @{$param{data}};
521             while (@tmp) {
522             my $key = shift @tmp;
523              
524             my $value;
525             $value = shift @tmp if ( !ref($key) );
526              
527             if ( my $attr = isAttribute($key) ) {
528             # update attribute of tag
529             $param{node}->removeAttribute( $attr );
530              
531             my $newNode = $param{dom}->createAttribute($attr, $value);
532             $self->addNode( %param,
533             depth => $param{depth}+1,
534             newNode => $newNode );
535              
536             } elsif ( my $comment = isComment($key) ) {
537             $self->addNode( %param,
538             depth => $param{depth}+1,
539             newNode => $param{dom}->createComment( $comment ) );
540              
541             } else {
542             # get the type.
543             my $type = ref ($key);
544              
545             # make a dom for arrays
546             if ( $type =~ /ARRAY/ ) {
547             $key = $self->complex2Dom( %param,
548             data => $key,
549             depth => $param{depth}+1);
550             $type = ref ( $key );
551             }
552              
553             if ( $type =~ /NodeList/ ) {
554             ## adding OR moving a list of Nodes.
555             while ( my $node = $key->shift ) {
556             my $newNode = $node->cloneNode( DEEP );
557             $self->addNode( %param,
558             depth => $param{depth}+1,
559             newNode => $newNode );
560             }
561              
562             } elsif ( $type =~ /XML::LibXML::Document$/ ) {
563             ## adding a dom.
564             my $newNode = $key->getDocumentElement->cloneNode( DEEP );
565             $newNode->setOwnerDocument($param{dom});
566             $self->addNode( %param,
567             depth => $param{depth}+1,
568             newNode => $newNode );
569              
570             } elsif ( $type =~ /XML::LibXML::/ ) {
571             ## adding a fragment, element or node
572             my $newNode = $key->cloneNode( DEEP );
573             $self->addNode( %param,
574             depth => $param{depth}+1,
575             newNode => $newNode );
576              
577             } elsif ( !$type ) {
578             # perhaps it is a xml-scalar-ref
579             if ( $key =~ /SCALAR/ ) {
580             my $pkey;
581             eval {
582             local $SIG{__DIE__} = sub {};
583             $pkey = $key->nodeName;
584             };
585             $key = $pkey if !$@;
586             }
587              
588             if ( ref($value) ) {
589             my $array = $value;
590             if ( $key ) { $array = [$key => $value] }
591              
592             $self->addNode( %param,
593             depth => $param{depth}+1,
594             newNode =>
595             $self->makeXMLFragment( %param,
596             depth => $param{depth}+1,
597             data => $array )
598             );
599             } else {
600             my $newNode = $param{dom}->createElement( $key );
601             $self->ref2TextNode( %param,
602             node => $newNode,
603             depth => $param{depth}+1,
604             data => $value );
605             $self->addNode( %param,
606             depth => $param{depth}+1,
607             newNode => $newNode );
608             }
609             } else {
610             $self->addError("Couldn't determine what to do with $key");
611             }
612              
613             }
614             }
615              
616             $self->showPath($prev);
617             return ($self->error) ? undef : 1;
618             }
619              
620             sub addNode {
621             my $self = shift;
622             my %param = @_;
623              
624             $param{dom} ||= $self->objectDom;
625             $self->checkParams( params => \%param,
626             required => [ qw(dom node newNode) ]) || return undef;
627              
628             $param{location} ||= $self->defaultLocation;
629              
630             my $prev = $self->showPath;
631             $self->showPath($param{showpath}) if exists $param{showpath};
632              
633             warn "-" x $param{depth}, "addNode\n" if $self->showPath;
634              
635             if ( $param{node}->isSameNode ( $param{dom}->getDocumentElement )
636             and $param{location} ne TO ) {
637             $self->addError("addNode: can't add BEFORE or AFTER the root element");
638             }
639              
640             if ( $param{location} eq TO ) {
641             $param{node}->addChild($param{newNode});
642              
643             } elsif ( $param{location} eq AFTER ) {
644             my $parentNode = $param{node}->parentNode;
645             $parentNode->insertAfter( $param{newNode}, $param{node} );
646              
647             } elsif ( $param{location} eq BEFORE ) {
648             my $parentNode = $param{node}->parentNode;
649             $parentNode->insertBefore( $param{newNode}, $param{node} );
650              
651             } else {
652             $self->addError("Unknown adding location: $param{location} at addNode");
653             $self->showPath($prev);
654             return undef;
655             }
656              
657             $self->showPath($prev);
658             return ($self->error) ? undef : 1;
659             }
660              
661             sub domDelete {
662             my $self = shift;
663             my %param = @_;
664              
665             # reset recursion (just for safety)
666             $self->resetHandlers;
667              
668             $param{dom} ||= $self->objectDom;
669             if ( !$param{node} and $param{xpath} ) {
670             $param{node} = $param{dom}->findnodes($param{xpath})->shift;
671             $param{node} || $self->addError("Couldn't execute XPATH on supplied DOM in domDelete");
672             }
673              
674             # allow shorthand
675             $param{deleteXPath} = $param{delete} if ( !$param{deleteXPath} and $param{delete} );
676              
677             $self->checkParams( params => \%param,
678             required => [ qw(dom node deleteXPath) ]) || return undef;
679              
680              
681             my $prev = $self->showPath;
682             $self->showPath($param{showpath}) if exists $param{showpath};
683             warn "-" x $param{depth}, "domDelete\n" if $self->showPath;
684              
685             # remove
686             my $set = $param{node}->findnodes($param{deleteXPath});
687             foreach my $deleteNode ( $set->get_nodelist ) {
688              
689             if ( $deleteNode->nodeType == 2 ) {
690             # attributes need special treatment
691             $deleteNode->unbindNode;
692              
693             } else {
694             $param{node}->removeChild($deleteNode);
695             }
696             }
697              
698             $self->showPath($prev);
699             return ($self->error) ? undef : 1;
700             }
701              
702             # ==========================================================================
703             # USEFULL TOOLS?
704             # ==========================================================================
705              
706             sub analyseXpath {
707             my ( $self, $xpath ) = @_;
708              
709             # provides last part,number of xpath, parent
710             # eg /newlsetter[1]/section[2]/parot[2]
711             #
712             # returns ('parot', 2, '/newlsetter[1]/section[2]')
713             #
714              
715             my $index;
716             if ( $xpath =~ s/\[(\d+)\]$// ) {
717             $index = $1;
718             }
719              
720             my ($parent, $part) = $xpath =~ /^(.*)\/([^\/]+)$/;
721              
722             return($part, $index, $parent);
723             }
724              
725             sub attribute{
726             my $self = shift;
727             my $name = shift;
728             my $val = shift;
729              
730             return( "$name=attr" => $val);
731             }
732              
733             sub isAttribute {
734             my ($name) = shift =~ /^(.*)=attr$/;
735             return $name;
736             }
737              
738             sub comment {
739             my $self = shift;
740             my $comment = shift;
741              
742             return( "$comment=comment" => undef );
743             }
744              
745             sub isComment {
746             my ($comment) = shift =~ /^(.*)=comment$/m;
747             $comment || return undef;
748             # comments look better with surrounding spaces
749             $comment = " " . $comment if $comment !~ /^\s/;
750             $comment .= " " if $comment !~ /\s$/;
751              
752             return $comment;
753             }
754              
755             sub alreadySeen {
756             my $self = shift;
757             my $ref = shift;
758              
759             # returns 1 if already seen
760             # undef if not yet seen
761             #
762              
763             if ( grep { $ref == $_ } @already_seen ) {
764             return 1;
765             } else {
766             push @already_seen , $ref;
767             }
768              
769             return undef;
770             }
771              
772             sub DESTROY {
773             my $self = $_[0];
774              
775             $self = undef;
776             return undef;
777             }
778              
779             1;
780              
781             __END__