File Coverage

blib/lib/XML/TreePP/Editor.pm
Criterion Covered Total %
statement 248 617 40.1
branch 97 432 22.4
condition 62 240 25.8
subroutine 24 40 60.0
pod 14 14 100.0
total 445 1343 33.1


line stmt bran cond sub pod time code
1             =pod
2              
3             =head1 NAME
4              
5             XML::TreePP::Editor - An editor for an XML::TreePP parsed XML document
6              
7             =head1 SYNOPSIS
8              
9             To use stand-alone:
10              
11             use strict;
12             use XML::TreePP;
13             use XML::TreePP::Editor;
14            
15             my $tpp = XML::TreePP->new();
16             my $tree = $tpp->parse('file.xml');
17             my $tppe = new XML::TreePP::Editor();
18             $tppe->replace( $tree, '/path[2]/element[2]/node', { '-myattribute' => "new value" } );
19             $tppe->insert( $tree, '.', \%{} );
20              
21             =head1 DESCRIPTION
22              
23             This module is used for editing a C parsed XML Document.
24              
25             =head1 REQUIREMENTS
26              
27             The following perl modules are depended on by this module:
28              
29             =over 4
30              
31             =item * XML::TreePP
32              
33             =item * XML::TreePP::XMLPath >= version 0.61
34              
35             =back
36              
37             =head1 Editor PHILOSOPHY
38              
39             =head2 XML Node and Attribute Identification
40              
41             The identification of XML document nodes for modification is handled by the
42             C module.
43              
44             The idenfication of attributes in XML nodes is via the C property
45             in the C module.
46              
47             The idenfication of XML text (or CDATA) nodes is via the C
48             property in the C module.
49              
50             Please review the XMLPath PHILOSOPHY section in it's POD for further
51             information.
52              
53             =head2 C dependency on C
54              
55             The C module has a dependence on C
56             When C and C methods
57             are called without parameters, this module checks to see if either of these
58             objects have been previously created, and links them together.
59              
60             If you provide your own C or C objects, this
61             module does not attempt to link them together. Instead you would want to do
62             it yourself in the following fashion.
63              
64             my $tpp = new XML::TreePP;
65             my $tppx = new XML::TreePP::XMLPath;
66             $tppx->tpp($tpp);
67             my $tppe = new XML::TreePP::Editor( tpp => $tpp, tppx => $tppx );
68              
69             This is essentially similar to how the C and
70             C methods associate the objects.
71              
72             =head1 METHODS
73              
74             =cut
75              
76             package XML::TreePP::Editor;
77              
78 1     1   70392 use 5.005;
  1         6  
  1         60  
79 1     1   8 use warnings;
  1         3  
  1         39  
80 1     1   7 use strict;
  1         2  
  1         44  
81 1     1   6 use Carp;
  1         2  
  1         86  
82 1     1   7 use XML::TreePP;
  1         2  
  1         41  
83 1     1   6 use XML::TreePP::XMLPath 0.61;
  1         30  
  1         56  
84 1     1   6 use Data::Dumper;
  1         2  
  1         76  
85              
86             BEGIN {
87 1     1   7 use vars qw(@ISA @EXPORT @EXPORT_OK);
  1         2  
  1         130  
88 1     1   19 @ISA = qw(Exporter);
89 1         2 @EXPORT = qw();
90 1         3 @EXPORT_OK = qw();
91              
92 1     1   6 use vars qw($REF_NAME);
  1         2  
  1         71  
93 1         2 $REF_NAME = "XML::TreePP::Editor"; # package name
94              
95 1     1   28 use vars qw( $VERSION $DEBUG $TPPKEYS );
  1         2  
  1         69  
96 1         1 $VERSION = '0.13';
97 1         2 $DEBUG = 0;
98 1         9224 $TPPKEYS = "force_array force_hash cdata_scalar_ref user_agent http_lite lwp_useragent base_class elem_class xml_deref first_out last_out indent xml_decl output_encoding utf8_flag attr_prefix text_node_key ignore_error use_ixhash";
99             }
100              
101              
102             =pod
103              
104             =head2 tpp
105              
106             This module is an extension of the C. As such, it uses the
107             module in many different methods to parse XML Docuements, and when the user
108             calls the C and C methods to set and get properties specific to
109             the module.
110              
111             The C, is loaded upon requesting a new object.
112              
113             The caller can override the loaded instance of C in favor of
114             another instance the caller posses, by providing it to this method.
115              
116             Additionally, this module's loaded instance of C can be directly
117             accessed or retrieved through this method.
118              
119             =over 4
120              
121             =item * C
122              
123             An instance of C that this object should use instead of, when needed,
124             loading its own copy. If not provided, the currently loaded instance is
125             returned. If an instance is not loaded, an instance is loaded and then returned.
126              
127             =item * I
128              
129             Returns the result of setting an instance of C in this object.
130             Or returns the internally loaded instance of C.
131             Or loads a new instance of C and returns it.
132              
133             =back
134              
135             $tppe->tpp( new XML::TreePP ); # Sets the XML::TreePP instance to be used by this object
136             my $tppobj = $tppe->tpp(); # Retrieve the currently loaded XML::TreePP instance
137              
138             =cut
139              
140             sub tpp(@) {
141 0 0 0 0 1 0 my $self = shift if ref($_[0]) eq $REF_NAME || undef;
142 0 0       0 if (!defined $self) {
143 0         0 return new XML::TreePP;
144             } else {
145             # If being given the object, set it and return result
146 0 0 0     0 return $self->{'tpp'} = shift if @_ >= 1 && ref($_[0]) eq "XML::TreePP";
147             # If wanting object, and XMLPath object exists, retrieve it, set it, and return it
148 0 0 0     0 if ((defined $self->{'tppx'}) && (ref($self->{'tppx'}) eq "XML::TreePP::XMLPath")) {
149 0         0 $self->{'tpp'} = $self->{'tppx'}->tpp();
150 0         0 return $self->{'tpp'};
151             }
152             # If wanting object and XMLPath object does not exist, create it and return it
153 0         0 $self->{'tpp'} = new XML::TreePP;
154 0         0 return $self->{'tpp'};
155             }
156             }
157              
158              
159             =pod
160              
161             =head2 tppx
162              
163             This module is an extension of the C module. As such,
164             it uses the module in many different methods to access C parsed XML
165             Documents, and when the user calls the C and C methods to set
166             and get properties specific to the module.
167              
168             The C module, is loaded upon requesting a new object.
169              
170             The caller can override the loaded instance of C in favor of
171             another instance the caller posses, by proving it to this method.
172              
173             Additionally, this module's loaded instance of C can be
174             directly accessed or retrieved through this method.
175              
176             =over 4
177              
178             =item * C
179              
180             An instance of C that this object should use instead of,
181             when needed, loading its own copy. If not provided, the currently loaded
182             instance is returned. If an instance is not already loaded, a new instance is
183             loaded and then returned.
184              
185             =item * I
186              
187             Returns the result of setting an instance of C in this object.
188             Or returns the internally loaded instance of C.
189             Or loads a new instance of C and returns it.
190              
191             =back
192              
193             $tppe->tppx( new XML::TreePP::XMLPath ); # Sets the XML::TreePP instance to be used by this object
194             my $tppxobj = $tppe->tppx(); # Retrieve the currently loaded XML::TreePP instance
195              
196             =cut
197              
198             sub tppx(@) {
199 10 50 50 10 1 35 my $self = shift if ref($_[0]) eq $REF_NAME || undef;
200 10 50       22 if (!defined $self) {
201 0         0 return new XML::TreePP::XMLPath;
202             } else {
203             # If being given the object, set it and return result
204 10 50 33     29 return $self->{'tppx'} = shift if @_ >= 1 && ref($_[0]) eq "XML::TreePP::XMLPath";
205             # If wanting object, and XML::TreePP object exists, create it, associate it, and return it
206             # Create
207 10         47 $self->{'tppx'} = new XML::TreePP::XMLPath;
208             # Associate
209 10 50 33     137 if ((defined $self->{'tpp'}) && (ref($self->{'tpp'}) eq "XML::TreePP")) {
210 0         0 $self->{'tppx'}->tpp( $self->{'tpp'} );
211             }
212             # Return
213 10         43 return $self->{'tppx'};
214             }
215             }
216              
217              
218             =pod
219              
220             =head2 set
221              
222             Set the value for a property in this object instance.
223             This method can only be accessed in object oriented style.
224              
225             =over 4
226              
227             =item * C
228              
229             The property to set the value for.
230              
231             =item * C
232              
233             The value of the property to set.
234             If no value is given, the property is deleted.
235              
236             =item * I
237              
238             Returns the result of setting the value of the property, or the result of
239             deleting the property.
240              
241             =back
242              
243             $tppe->set( 'property_name' ); # deletes the property property_name
244             $tppe->set( 'property_name' => 'val' ); # sets the value of property_name
245              
246             =cut
247              
248             sub set(@) {
249 0 0 0 0 1 0 my $self = shift if ref($_[0]) eq $REF_NAME || return undef;
250 0         0 my %args = @_;
251 0         0 while (my ($key,$val) = each %args) {
252 0 0       0 if ( defined $val ) {
253 0         0 $self->{$key} = $val;
254             }
255             else {
256 0         0 delete $self->{$key};
257             }
258             }
259             }
260              
261              
262             =pod
263              
264             =head2 get
265              
266             Retrieve the value set for a property in this object instance.
267             This method can only be accessed in object oriented style.
268              
269             =over 4
270              
271             =item * C
272              
273             The property to get the value for
274              
275             =item * I
276              
277             Returns the value of the property requested
278              
279             =back
280              
281             $tppe->get( 'property_name' );
282              
283             =cut
284              
285             sub get(@) {
286 10 50 50 10 1 33 my $self = shift if ref($_[0]) eq $REF_NAME || return undef;
287 10         11 my $key = shift;
288 10 50       26 return $self->{$key} if exists $self->{$key};
289 10         37 return undef;
290             }
291              
292              
293             =pod
294              
295             =head2 new
296              
297             Create a new object instances of this module.
298              
299             =over 4
300              
301             =item * B
302              
303             An instance of C to be used instead of letting this module load
304             its own.
305              
306             =item * B
307              
308             An instance of C to be used instead of letting this
309             module load its own.
310              
311             =item * B
312              
313             The debug level to set on this object.
314              
315             =item * I
316              
317             An object instance of this module.
318              
319             =back
320              
321             my $cfg = new XML::TreePP::Editor();
322              
323             =cut
324              
325             sub new () {
326 1     1 1 53 my $pkg = shift;
327 1   33     8 my $class = ref($pkg) || $pkg;
328 1         3 my $self = bless {}, $class;
329 1         4 my %args = @_;
330              
331 1 50       5 $self->tpp($args{'tpp'}) if exists $args{'tpp'};
332 1 50       6 $self->tppx($args{'tppx'}) if exists $args{'tppx'};
333 1   33     13 $args{'debug'} ||= $DEBUG;
334 1         6 $self->debug($args{'debug'});
335              
336 1         4 return $self;
337             }
338              
339              
340             =pod
341              
342             =head2 debug
343              
344             Set the debug level
345              
346             =over 4
347              
348             =item * B - optional
349              
350             A value that is >= 0
351              
352             =item * I
353              
354             If passing in B, then the result of setting that value to the object's debug variable.
355              
356             If not passing in B, then the current set value of the object's debug variable.
357              
358             =back
359              
360             $tppe->debug(0); # turn off debug
361             $tppe->debug(9); # turn on debug with value of 9
362             my $debuglevel = $tppe->debug();
363              
364             =cut
365              
366             sub debug {
367 1     1 1 3 my $self = shift;
368 1 50       5 if (@_ == 0) {
369 0 0       0 return $DEBUG if !defined $self->{'_debug'};
370 0         0 return $self->{'_debug'};
371             }
372 1         8 return $self->{'_debug'} = shift;
373             }
374              
375              
376             =pod
377              
378             =head2 modify
379              
380             modify( XMLTree, XMLPath, %OPTIONS )
381              
382             where %options = ( action => %value )
383              
384             and action is one of ( insert, replace, delete, mergeadd, mergereplace, mergedelete, mergeappend )
385              
386             and %value is a XML Node Hash, either a partial node or full node
387              
388             =over 4
389              
390             =item * B
391              
392             The parsed XML Document.
393              
394             =item * B
395              
396             The XML Path to the node, attribute or element to modify
397              
398             =item * B
399              
400             The options for modifying the node.
401              
402             =over 4
403              
404             =item * insert => C<\%node> - insert the new node at XMLPath
405              
406             =item * replace => C<\%node> - replace the node at XMLPath with this new node
407              
408             =item * delete => C - delete the node at the XMLPath
409              
410             =item * mergeadd => C<\%node> - merge this node into the node at XMLPath,
411             only adding elements and attributes that do not exist
412              
413             =item * mergereplace => C<\%node> - merge this node into the node at XMLPath,
414             replacing elements and attributes, and adding them if they do not exist
415              
416             =item * mergedelete => C<\%node> - merge this node into the node at XMLPath,
417             deleting elements and attributes that exist in both nodes
418              
419             =item * mergeappend => C<\%node> - merge this node into the node at XMLPath,
420             appending the values of text elements
421              
422             =back
423              
424             B This method uses the values retrieved from
425             $self->tpp()->get('attr_prefix') and $self->tpp()->get('text_node_key') to
426             define how to interpret how to identify attributes and text (CDATA) nodes.
427              
428             =back
429              
430             Example:
431              
432             my $xmltree => { path => { to => { node => "Brown bears" } } };
433             $tppe->modify( $xmltree, '/path/to/node', mergeappend => { '#text' => " with blue shoes." } )
434             # or: $tppe->modify( $xmltree, '/path/to/node/#text', mergeappend => { '#text' => " with blue shoes." } )
435             # or: $tppe->modify( $xmltree, '/path/to/node/#text', mergeappend => " with blue shoes." )
436             print $xmltree->{'path'}->{'to'}->{'node'};
437            
438             output:
439            
440             Brown bears with blue shoes.
441              
442             =cut
443              
444             sub modify (@) {
445 10 50 50 10 1 18198 my $self = shift if ref($_[0]) eq $REF_NAME || undef;
446 10 50       28 unless (@_ >= 3) { carp 'method modify() requires at least three arguments.'; return undef; }
  0         0  
  0         0  
447 10         14 my $xtree = shift;
448 10         14 my $xmlpath = shift; # XML::TreePP::XMLPath
449 10         25 my %options = @_; # replace=>\%val; insert=>\%val; etc.
450 10         14 my $numAffected = 0;
451              
452 10         11 local $Data::Dumper::Indent = 0;
453 10         11 local $Data::Dumper::Purity = 1;
454 10         56 local $Data::Dumper::Terse = 1;
455              
456 10         14 my ($tpp,$tppx,$xml_text_id,$xml_attr_id);
457              
458 10 50 33     45 if ((defined $self) && (defined $self->get('tpp'))) {
459 0 0       0 $tpp = $self ? $self->tpp() : tpp();
460 0 0       0 $tppx = $self ? $self->tppx() : tppx();
461 0   0     0 $xml_text_id = $tpp->get( 'text_node_key' ) || '#text';
462 0   0     0 $xml_attr_id = $tpp->get( 'attr_prefix' ) || '-';
463             } else {
464 10         16 $xml_text_id = '#text';
465 10         16 $xml_attr_id = '-';
466             }
467              
468             my $whatisnode = sub ($) {
469 6     6   6 my $nodename = shift;
470 6 50       13 return undef if ref($nodename);
471 6 50       17 return "text" if $nodename eq $xml_text_id;
472 6 100       56 return "attribute" if $nodename =~ /^$xml_attr_id\w+$/;
473 4 50       11 return "parent" if $nodename eq '..';
474 4 50       9 return "current" if $nodename eq '.';
475 4         12 return "element";
476 10         43 };
477             my $isnodetype = sub ($) {
478 6     6   11 my $nodename = shift;
479 6         8 my $compare = shift;
480 6 100       12 return 1 if $whatisnode->($nodename) eq $compare;
481 5         31 return 0;
482 10         29 };
483              
484 10     0   24 my $nodeMerge = sub (@) {};
  0         0  
485             $nodeMerge = sub (@) {
486 0     0   0 my $parentnode = shift; # ref - must be HASH ref, or ARRAY if merging to multiple parents
487 0         0 my $childname = shift; # ref->ref # merge into the node with this child name | or append to $stringname of existing child node
488 0         0 my $childpos = shift; # ref->ref->[#] - can be undef # merge into the node at this position, undef to merge into all | or append to $stringname of existing child node at this position
489 0         0 my $stringname = shift; # ref->ref->[#]->name - can be undef # append to this $stringname of the child node
490 0         0 my $value = shift;
491 0         0 my %options = @_;
492 0   0     0 my $mergetype = $options{'mergetype'} || "add"; # add|replace|delete|append
493 0         0 my $result = 0;
494              
495 0 0       0 if (!ref($parentnode)) {
    0          
    0          
496 0         0 croak "Cannot merge a child node to a non referencing parent node.";
497 0         0 return undef;
498             } elsif (ref($parentnode) eq "ARRAY") {
499 0 0       0 if (@{$parentnode} == 0) {
  0         0  
500 0         0 push(@{$parentnode}, {});
  0         0  
501             }
502 0         0 foreach my $single_parentnode (@{$parentnode}) {
  0         0  
503 0         0 my $newresult = $nodeMerge->($single_parentnode,$childname,$childpos,$stringname,$value);
504 0         0 $result += $newresult;
505             }
506             } elsif (ref($parentnode) eq "HASH") {
507             # In every case (but for appending to $stringname), we are merging into an existing child node
508 0         0 my $newchildnode;
509 0 0 0     0 if ((!ref($value)) && (defined $value) && (defined $stringname)) {
    0 0        
    0 0        
510 0         0 $newchildnode = [{ $stringname => $value }];
511             } elsif ((!ref($value)) && (!defined $stringname)) {
512 0         0 $newchildnode = [{ $xml_text_id => $value }];
513             } elsif (ref($value)) {
514 0 0       0 $newchildnode = [ $value ] if ref($value) eq "HASH";
515 0 0       0 $newchildnode = $value if ref($value) eq "ARRAY";
516             } else {
517 0         0 return undef;
518             }
519              
520 0 0       0 if (ref($parentnode->{$childname}) eq "ARRAY") {
521 0 0 0     0 if ((defined $childpos) && ($childpos >= 1) && ($childpos <= @{$parentnode->{$childname}})) {
  0 0 0     0  
522 0 0 0     0 if ((defined $stringname) && ($isnodetype->($stringname, "text"))) { # Make sure we account for { node => } opposed to { node => { #text => } }
    0          
523             # append to #text
524 0 0       0 if (ref($parentnode->{$childname}->[($childpos - 1)]) eq "HASH") {
    0          
525 0         0 $parentnode->{$childname}->[($childpos - 1)]->{$stringname} .= $newchildnode->[0]->{$stringname};
526 0         0 $result++;
527             } elsif (!ref($parentnode->{$childname}->[($childpos - 1)])) {
528 0         0 $parentnode->{$childname}->[($childpos - 1)] .= $newchildnode->[0]->{$stringname};
529 0         0 $result++;
530             }
531             } elsif (defined $stringname) {
532             # do not replace @attributes, parentnode is priority on keeping attribute values - use replace to replace them
533             # $parentnode->{$childname}->[($childpos - 1)]->{$stringname} = $newchildnode->[0]->{$stringname};
534             # $result++;
535             } else {
536 0         0 foreach my $vk (keys %{$newchildnode->[0]}) {
  0         0  
537 0 0       0 if (!exists $parentnode->{$childname}->[($childpos - 1)]->{$vk}) {
    0          
538 0         0 $parentnode->{$childname}->[($childpos - 1)]->{$vk} = $newchildnode->[0]->{$vk};
539 0         0 $result++;
540             } elsif (exists $parentnode->{$childname}->[($childpos - 1)]->{$vk}) {
541 0 0       0 if ($isnodetype->($vk, "text")) {
    0          
    0          
542             # Merge #text/CDATA
543 0         0 $parentnode->{$childname}->[($childpos - 1)]->{$vk} = ($parentnode->{$childname}->[($childpos - 1)]->{$vk} . $newchildnode->[0]->{$vk})
544             } elsif ($isnodetype->($vk, "attribute")) {
545             # Do not replace attributes
546             #$parentnode->{$childname}->[($childpos - 1)]->{$vk} = $newchildnode->[0]->{$vk} if $isnodetype->($vk, "attribute");
547             } elsif (ref($parentnode->{$childname}->[($childpos - 1)]->{$vk}) eq "ARRAY") {
548             # append new merged ones
549 0 0       0 push (@{$parentnode->{$childname}->[($childpos - 1)]->{$vk}},$newchildnode->[0]->{$vk}) if ref($newchildnode->[0]->{$vk}) ne "ARRAY";
  0         0  
550 0 0       0 push (@{$parentnode->{$childname}->[($childpos - 1)]->{$vk}},@{$newchildnode->[0]->{$vk}}) if ref($newchildnode->[0]->{$vk}) eq "ARRAY";
  0         0  
  0         0  
551 0         0 $result++;
552             } else {
553             # convert to array, and append new merged ones
554 0 0       0 $parentnode->{$childname}->[($childpos - 1)]->{$vk} = [$parentnode->{$childname}->[($childpos - 1)]->{$vk},$newchildnode->[0]->{$vk}] if ref($newchildnode->[0]->{$vk}) ne "ARRAY";
555 0 0       0 $parentnode->{$childname}->[($childpos - 1)]->{$vk} = [$parentnode->{$childname}->[($childpos - 1)]->{$vk},@{$newchildnode->[0]->{$vk}}] if ref($newchildnode->[0]->{$vk}) eq "ARRAY";
  0         0  
556 0         0 $result++;
557             }
558             }
559             }
560             }
561             } elsif (!defined $childpos) {
562 0         0 my $i = 0;
563 0         0 while ($i < @{$parentnode->{$childname}}) {
  0         0  
564 0 0 0     0 if ((defined $stringname) && ($isnodetype->($stringname, "text"))) { # Make sure we account for { node => } opposed to { node => { #text => } }
    0          
565 0 0       0 if (ref($parentnode->{$childname}->[($childpos - 1)]) eq "HASH") {
    0          
566 0         0 $parentnode->{$childname}->[$i]->{$stringname} .= $newchildnode->[0]->{$stringname};
567 0         0 $result++;
568             } elsif (!ref($parentnode->{$childname}->[($childpos - 1)])) {
569 0         0 $parentnode->{$childname}->[$i] .= $newchildnode->[0]->{$stringname};
570 0         0 $result++;
571             }
572             } elsif (defined $stringname) {
573 0         0 $parentnode->{$childname}->[$i]->{$stringname} = $newchildnode->[0]->{$stringname};
574 0         0 $result++;
575             } else {
576 0         0 $result += $nodeMerge->($parentnode,$childname,$i,undef,$newchildnode->[0]);
577             }
578 0         0 $i++;
579             }
580             } else {
581 0 0       0 croak "Cannot replace child node, none exists at position $childpos." if !defined $stringname;
582 0 0       0 croak "Cannot replace child node items, none exists at position $childpos." if defined $stringname;
583 0         0 return undef;
584             }
585             } else {
586 0 0 0     0 if ((!defined $childpos) || ($childpos == 1)) {
587 0 0 0     0 if ((defined $stringname) && ($isnodetype->($stringname, "text"))) { # Make sure we account for { node => } opposed to { node => { #text => } }
    0          
588             # The parent node keeps all attribute values, but combines #text or CDATA
589 0 0       0 if (ref($parentnode->{$childname}) eq "HASH") {
    0          
590 0         0 $parentnode->{$childname}->{$stringname} .= $newchildnode->[0]->{$stringname};
591 0         0 $result++;
592             } elsif (!ref($parentnode->{$childname})) {
593 0         0 $parentnode->{$childname} .= $newchildnode->[0];
594 0         0 $result++;
595             }
596             } elsif (defined $stringname) {
597             # The parent node keeps all attribute values - use replace to replace them
598             # $parentnode->{$childname}->{$stringname} = $newchildnode->[0]->{$stringname};
599             } else {
600 0         0 $parentnode->{$childname} = $newchildnode->[0];
601 0         0 $result++;
602             }
603             } else {
604 0 0       0 croak "Cannot replace child node, none exists at position $childpos." if !defined $stringname;
605 0 0       0 croak "Cannot replace child node items, none exists at position $childpos." if defined $stringname;
606 0         0 return undef;
607             }
608             }
609             }
610 0         0 return $result;
611 10         91 };
612              
613 10     0   39 my $nodeMergeActionSingle = sub (@) {};
  0         0  
614             $nodeMergeActionSingle = sub (@) {
615 5     5   9 my $targetnode = shift;
616 5         5 my $mergenode = shift;
617 5         6 my $action = shift; # add | append | replace | delete
618 5         7 my $result = 0;
619             # print Dumper({ targetnode => $targetnode, mergenode => $mergenode, action => $action });
620 5 50 33     38 unless ( (ref($targetnode) eq "HASH") && (ref($mergenode) eq "HASH") && (defined $action) ) {
      33        
621 0         0 return undef;
622             }
623 5         6 foreach my $vk (keys %{$mergenode}) {
  5         12  
624 8 100       27 if ($action eq "mergeadd") {
    100          
    100          
    50          
625 2 50 66     24 if ( (exists $targetnode->{$vk})
    100 66        
      66        
      33        
626             && (ref($targetnode->{$vk})) ) {
627             # do nothing, already exists as a referenced element
628             } elsif ( (exists $targetnode->{$vk})
629             && (!ref($targetnode->{$vk}))
630             && (defined $targetnode->{$vk})
631             && ($targetnode->{$vk} ne "") ) {
632             # do nothing, already exists as text string or CDATA
633             } else {
634 1         3 $targetnode->{$vk} = $mergenode->{$vk};
635 1         3 $result++;
636             }
637             } elsif ($action eq "mergeappend") {
638             # we can only append if the target value and merge value are text or CDATA
639 3 100 66     8 if ( ($isnodetype->($vk, "text"))
      66        
      66        
640             || ($isnodetype->($vk, "attribute"))
641             || ((!ref($targetnode->{$vk})) && ($mergenode->{$vk} =~ /\w+/)) ) {
642 2         6 $targetnode->{$vk} .= $mergenode->{$vk};
643 2         4 $result++;
644             }
645             } elsif ($action eq "mergereplace") {
646 2         4 $targetnode->{$vk} = $mergenode->{$vk};
647 2         4 $result++;
648             } elsif ($action eq "mergedelete") {
649 1 50       3 if (exists $targetnode->{$vk}) {
650 1         3 delete $targetnode->{$vk};
651 1         3 $result++;
652             }
653             }
654             }
655 5         22 return $result;
656 10         34 };
657 10     0   33 my $nodeMergeAction = sub (@) {};
  0         0  
658             $nodeMergeAction = sub (@) {
659 5     5   8 my $parentnode = shift; # ref - must be HASH ref, or ARRAY if merging to multiple parents
660 5         7 my $childname = shift; # ref->ref # merge into the node with this child name | or append to $stringname of existing child node
661 5         5 my $childpos = shift; # ref->ref->[#] - can be undef # merge into the node at this position, undef to merge into all | or append to $stringname of existing child node at this position
662 5         6 my $stringname = shift; # ref->ref->[#]->name - can be undef # append to this $stringname of the child node
663 5         6 my $value = shift;
664 5         13 my %options = @_;
665 5   50     13 my $action = $options{'mergetype'} || "add"; # add|replace|delete|append
666 5         7 my $result = 0;
667              
668 5 50       29 if (!ref($parentnode)) {
    50          
    50          
669 0         0 croak "Cannot merge a child node to a non referencing parent node.";
670 0         0 return undef;
671             } elsif (ref($parentnode) eq "ARRAY") {
672 0 0       0 if (@{$parentnode} == 0) {
  0         0  
673 0         0 push(@{$parentnode}, {});
  0         0  
674             }
675 0         0 foreach my $single_parentnode (@{$parentnode}) {
  0         0  
676 0         0 my $newresult = $nodeMergeAction->($single_parentnode,$childname,$childpos,$stringname,$value);
677 0         0 $result += $newresult;
678             }
679             } elsif (ref($parentnode) eq "HASH") {
680             # In every case (but for appending to $stringname), we are merging into an existing child node
681 5         21 my $newchildnode;
682 5 50 33     35 if ((!ref($value)) && (defined $value) && (defined $stringname)) {
    50 33        
    50 33        
683 0         0 $newchildnode = [{ $stringname => $value }];
684             } elsif ((!ref($value)) && (!defined $stringname)) {
685 0         0 $newchildnode = [{ $xml_text_id => $value }];
686             } elsif (ref($value)) {
687 5 50       14 $newchildnode = [ $value ] if ref($value) eq "HASH";
688 5 50       16 $newchildnode = $value if ref($value) eq "ARRAY";
689             } else {
690 0         0 return undef;
691             }
692              
693 5 50       12 if (ref($parentnode->{$childname}) eq "ARRAY") {
    0          
694 5         7 my @childpositions;
695 5 50 33     25 if ((defined $childpos) && ($childpos >= 1) && ($childpos <= @{$parentnode->{$childname}})) {
  5 0 33     20  
696 5         10 push (@childpositions,$childpos);
697             } elsif (!defined $childpos) {
698 0         0 for (my $i=1; $i <= @{$parentnode->{$childname}}; $i++) {
  0         0  
699 0         0 push (@childpositions,$i);
700             }
701             } else {
702 0 0       0 croak "Cannot replace child node, none exists at position $childpos." if !defined $stringname;
703 0 0       0 croak "Cannot replace child node items, none exists at position $childpos." if defined $stringname;
704 0         0 return undef;
705             }
706 5         9 foreach my $tchildpos (@childpositions) {
707 5 50       17 if (ref($parentnode->{$childname}->[($tchildpos - 1)]) eq "HASH") {
    0          
708 5 50       9 if (defined $stringname) {
709 0         0 $result += $nodeMergeActionSingle->($parentnode->{$childname}->[($tchildpos - 1)], { $stringname => $newchildnode->[0]->{$stringname} }, $action);
710             } else {
711 5         15 $result += $nodeMergeActionSingle->($parentnode->{$childname}->[($tchildpos - 1)], $newchildnode->[0], $action);
712             }
713             } elsif (!ref($parentnode->{$childname}->[($tchildpos - 1)])) {
714 0 0 0     0 if ((defined $stringname) && ($isnodetype->($stringname, "text"))) { # Make sure we account for { node => } opposed to { node => { #text => } }
715             # append to #text
716 0 0       0 if ($action eq "replace") {
    0          
    0          
    0          
717 0         0 $parentnode->{$childname}->[($tchildpos - 1)] = $newchildnode->[0]->{$stringname};
718 0         0 $result++;
719             } elsif ($action eq "append") {
720 0         0 $parentnode->{$childname}->[($tchildpos - 1)] .= $newchildnode->[0]->{$stringname};
721 0         0 $result++;
722             } elsif ($action eq "add") {
723 0 0       0 if ($parentnode->{$childname}->[($tchildpos - 1)] !~ /\w+/) {
724 0         0 $parentnode->{$childname}->[($tchildpos - 1)] = $newchildnode->[0]->{$stringname};
725 0         0 $result++;
726             }
727             } elsif ($action eq "delete") {
728 0         0 $parentnode->{$childname}->[($tchildpos - 1)] = undef;
729 0         0 $result++;
730             }
731             } else {
732 0 0 0     0 if ((defined $parentnode->{$childname}->[($tchildpos - 1)]) && ($parentnode->{$childname}->[($tchildpos - 1)] =~ /\w+/)) {
733 0         0 $parentnode->{$childname}->[($tchildpos - 1)] = { $xml_text_id => $parentnode->{$childname}->[($tchildpos - 1)] };
734             } else {
735 0         0 $parentnode->{$childname}->[($tchildpos - 1)] = {};
736             }
737 0 0       0 if (defined $stringname) {
738 0         0 $result += $nodeMergeActionSingle->($parentnode->{$childname}->[($tchildpos - 1)], { $stringname => $newchildnode->[0]->{$stringname} }, $action);
739             } else {
740 0         0 $result += $nodeMergeActionSingle->($parentnode->{$childname}->[($tchildpos - 1)], $newchildnode->[0], $action);
741             }
742             }
743             }
744             }
745             } elsif (ref($parentnode->{$childname}) eq "HASH") {
746 0 0 0     0 if ( ((defined $childpos) && ($childpos == 1)) || (!defined $childpos) ) {
      0        
747 0 0       0 if (defined $stringname) {
748 0         0 $result += $nodeMergeActionSingle->($parentnode->{$childname}, { $stringname => $newchildnode->[0]->{$stringname} }, $action);
749             } else {
750 0         0 $result += $nodeMergeActionSingle->($parentnode->{$childname}, $newchildnode->[0], $action);
751             }
752             } else {
753 0 0       0 croak "Cannot replace child node, none exists at position $childpos." if !defined $stringname;
754 0 0       0 croak "Cannot replace child node items, none exists at position $childpos." if defined $stringname;
755 0         0 return undef;
756             }
757             } else {
758 0 0 0     0 if ( ((defined $childpos) && ($childpos == 1)) || (!defined $childpos) ) {
      0        
759 0 0 0     0 if ((defined $stringname) && ($isnodetype->($stringname, "text"))) { # Make sure we account for { node => } opposed to { node => { #text => } }
760             # append to #text
761 0 0       0 if ($action eq "replace") {
    0          
    0          
    0          
762 0         0 $parentnode->{$childname} = $newchildnode->[0]->{$stringname};
763 0         0 $result++;
764             } elsif ($action eq "append") {
765 0         0 $parentnode->{$childname} .= $newchildnode->[0]->{$stringname};
766 0         0 $result++;
767             } elsif ($action eq "add") {
768 0 0       0 if ($parentnode->{$childname} !~ /\w+/) {
769 0         0 $parentnode->{$childname} = $newchildnode->[0]->{$stringname};
770 0         0 $result++;
771             }
772             } elsif ($action eq "delete") {
773 0         0 $parentnode->{$childname} = undef;
774 0         0 $result++;
775             }
776             } else {
777 0 0 0     0 if ((defined $parentnode->{$childname}) && ($parentnode->{$childname} =~ /\w+/)) {
778 0         0 $parentnode->{$childname} = { $xml_text_id => $parentnode->{$childname} };
779             } else {
780 0         0 $parentnode->{$childname} = {};
781             }
782 0 0       0 if (defined $stringname) {
783 0         0 $result += $nodeMergeActionSingle->($parentnode->{$childname}, { $stringname => $newchildnode->[0]->{$stringname} }, $action);
784             } else {
785 0         0 $result += $nodeMergeActionSingle->($parentnode->{$childname}, $newchildnode->[0], $action);
786             }
787             }
788             } else {
789 0 0       0 croak "Cannot replace child node, none exists at position $childpos." if !defined $stringname;
790 0 0       0 croak "Cannot replace child node items, none exists at position $childpos." if defined $stringname;
791 0         0 return undef;
792             }
793             }
794             }
795 5         15 return $result;
796 10         112 };
797              
798 10     0   33 my $nodeInsert = sub (@) {};
  0         0  
799             $nodeInsert = sub (@) {
800 1     1   1 my $parentnode = shift; # ref - must be HASH ref, or ARRAY if inserting to multiple parents
801 1         1 my $childname = shift; # ref->ref # insert a new node with this child name | or insert $stringname to this existing node
802 1         2 my $childpos = shift; # ref->ref->[#] - can be undef # insert the new node at this position, undef to append | or insert $stringname to existing node at this position
803 1         1 my $stringname = shift; # ref->ref->[#]->name - can be undef # insert this $stringname with $value to the child node | or append $value if $stringname exists
804 1         2 my $value = shift; # ref->ref->[#]->name = $value # the string value for $stringname if defined | or the value for the node named $childname
805 1         1 my $result = 0;
806              
807 1 50       7 if (!ref($parentnode)) {
    50          
    50          
808 0         0 croak "Cannot insert a child node to a non referencing parent node.";
809 0         0 return undef;
810             } elsif (ref($parentnode) eq "ARRAY") {
811 0 0       0 if (@{$parentnode} == 0) {
  0         0  
812 0         0 push (@{$parentnode}, {});
  0         0  
813             }
814 0         0 foreach my $single_parentnode (@{$parentnode}) {
  0         0  
815 0         0 my $newresult = $nodeInsert->($single_parentnode,$childname,$childpos,$stringname,$value);
816 0         0 $result += $newresult;
817             }
818             } elsif (ref($parentnode) eq "HASH") {
819             # In every case here, we are inserting a new child node
820 1         3 my $newchildnode;
821 1 50 33     11 if ((!ref($value)) && (defined $value) && (defined $stringname)) {
    50 33        
    50 33        
822 0         0 $newchildnode = [{ $stringname => $value }];
823             } elsif ((!ref($value)) && (!defined $stringname)) {
824 0         0 $newchildnode = [{ $xml_text_id => $value }];
825             } elsif (ref($value)) {
826 1 50       5 $newchildnode = [ $value ] if ref($value) eq "HASH";
827 1 50       3 $newchildnode = $value if ref($value) eq "ARRAY";
828             } else {
829 0         0 return undef;
830             }
831              
832 1 50       9 if (!ref($parentnode->{$childname})) {
    50          
    50          
833 0 0       0 if ($parentnode->{$childname} =~ /\w+/) {
834 0         0 my $currentchildnode = { $xml_text_id => $parentnode->{$childname} };
835 0 0       0 if ($childpos == 1) {
836 0         0 $parentnode->{$childname} = [ @{$newchildnode}, $currentchildnode ];
  0         0  
837 0         0 $result += @{$newchildnode};
  0         0  
838             } else {
839 0         0 $parentnode->{$childname} = [ $currentchildnode, @{$newchildnode} ];
  0         0  
840 0         0 $result += @{$newchildnode};
  0         0  
841             }
842             } else {
843 0         0 $parentnode->{$childname} = $newchildnode;
844 0         0 $result += @{$newchildnode};
  0         0  
845             }
846             } elsif (ref($parentnode->{$childname}) eq "HASH") {
847 0 0       0 if ($childpos == 1) {
848 0         0 push (@{$newchildnode}, $parentnode->{$childname});
  0         0  
849 0         0 $result += @{$newchildnode};
  0         0  
850             } else {
851 0         0 unshift (@{$newchildnode}, $parentnode->{$childname});
  0         0  
852 0         0 $result += @{$newchildnode};
  0         0  
853             }
854 0         0 $parentnode->{$childname} = $newchildnode
855             } elsif (ref($parentnode->{$childname}) eq "ARRAY") {
856 1         1 my $size = @{$parentnode->{$childname}};
  1         2  
857 1 50 33     6 if (($childpos >= 1) && ($childpos <= $size)) {
858 1         2 splice(@{$parentnode->{$childname}},($childpos - 1), 0, @{$newchildnode});
  1         3  
  1         3  
859 1         1 $result += @{$newchildnode};
  1         3  
860             } else {
861 0         0 push(@{$parentnode->{$childname}}, @{$newchildnode});
  0         0  
  0         0  
862 0         0 $result += @{$newchildnode};
  0         0  
863             }
864             }
865             }
866 1         2 return $result;
867 10         120 };
868              
869 10     0   36 my $nodeReplace = sub (@) {};
  0         0  
870             $nodeReplace = sub (@) {
871 4     4   7 my $parentnode = shift; # ref - must be HASH ref, or ARRAY if replacing to multiple parents
872 4         6 my $childname = shift; # ref->ref # replace the node with this child name | or replace $stringname of existing child node
873 4         5 my $childpos = shift; # ref->ref->[#] - can be undef # replace the node at this position, undef to replace all | or replace $stringname of existing child node at this position
874 4         5 my $stringname = shift; # ref->ref->[#]->name - can be undef # replace this $stringname of the child node
875 4         5 my $value = shift;
876 4         6 my $result = 0;
877             # print Dumper({ parentnode => $parentnode, childname => $childname, childpos => $childpos, stringname => $stringname, value => $value });
878              
879 4 50       20 if (!ref($parentnode)) {
    50          
    50          
880 0         0 croak "Cannot replace a child node to a non referencing parent node.";
881 0         0 return undef;
882             } elsif (ref($parentnode) eq "ARRAY") {
883 0 0       0 if (@{$parentnode} == 0) {
  0         0  
884 0         0 $parentnode = [{}];
885             }
886 0         0 foreach my $single_parentnode (@{$parentnode}) {
  0         0  
887 0         0 my $newresult = $nodeReplace->($single_parentnode,$childname,$childpos,$stringname,$value);
888 0         0 $result += $newresult;
889             }
890             } elsif (ref($parentnode) eq "HASH") {
891             # In every case (but for replacing $stringname), we are replacing a new child node, having deleted any existing at the same path
892 4         4 my $newchildnode;
893 4 50 33     26 if ((!ref($value)) && (defined $value) && (defined $stringname)) {
    50 33        
      33        
894 0         0 $newchildnode = [{ $stringname => $value }];
895             } elsif ((!ref($value)) && (!defined $stringname)) {
896 0         0 $newchildnode = [{ $xml_text_id => $value }];
897             } else {
898 4 50       12 $newchildnode = [ $value ] if ref($value) eq "HASH";
899 4 50       10 $newchildnode = $value if ref($value) eq "ARRAY";
900             }
901              
902 4 50       11 if (ref($parentnode->{$childname}) eq "ARRAY") {
903 4 50 33     29 if ((defined $childpos) && ($childpos >= 1) && ($childpos <= @{$parentnode->{$childname}})) {
  4 0 33     12  
904 4 50       9 if (defined $stringname) { # Make sure we account for { node => } opposed to { node => { #text => } }
905             # Node could just be CDATA (#text) and not HASH
906 0 0 0     0 if (ref($parentnode->{$childname}->[($childpos - 1)]) eq "HASH") {
    0          
907 0         0 $parentnode->{$childname}->[($childpos - 1)]->{$stringname} = $newchildnode->[0]->{$stringname};
908 0         0 $result++;
909             } elsif (($parentnode->{$childname}->[($childpos - 1)] =~ /\w+/) && ($isnodetype->($stringname, "text"))) {
910 0         0 $parentnode->{$childname}->[($childpos - 1)] = $newchildnode->[0]->{$stringname};
911 0         0 $result++;
912             }
913             } else {
914 4         4 splice(@{$parentnode->{$childname}},($childpos - 1), 1, @{$newchildnode});
  4         9  
  4         7  
915 4         18 $result += @{$newchildnode};
  4         8  
916             }
917             } elsif (!defined $childpos) {
918             # If not $childpos, then all items of node are affected
919 0         0 my $i = 0;
920 0 0       0 if (defined $stringname) { # Make sure we account for { node => } opposed to { node => { #text => } }
921 0         0 while ($i < @{$parentnode->{$childname}}) {
  0         0  
922 0 0       0 print "-replace array $childname $i\n" if $DEBUG;
923             # Node could just be CDATA (#text) and not HASH
924 0 0 0     0 if (ref($parentnode->{$childname}->[$i]) eq "HASH") {
    0          
925 0         0 $parentnode->{$childname}->[$i]->{$stringname} = $newchildnode->[0]->{$stringname};
926 0         0 $result++;
927             } elsif (($parentnode->{$childname}->[$i] =~ /\w+/) && ($isnodetype->($stringname, "text"))) {
928 0         0 $parentnode->{$childname}->[$i] = $newchildnode->[0]->{$stringname};
929 0         0 $result++;
930             }
931 0         0 $i++;
932             }
933             } else {
934 0 0       0 print "-replace ALL $childname $i\n" if $DEBUG;
935 0         0 $parentnode->{$childname} = $newchildnode;
936 0         0 $result++;
937             }
938             } else {
939 0 0       0 croak "Cannot replace child node, none exists at position $childpos." if !defined $stringname;
940 0 0       0 croak "Cannot replace child node items, none exists at position $childpos." if defined $stringname;
941 0         0 return undef;
942             }
943             } else {
944 0 0 0     0 if ((!defined $childpos) || ($childpos == 1)) {
945 0 0       0 if (defined $stringname) { # Make sure we account for { node => } opposed to { node => { #text => } }
946             # print Dumper( { parentnode => $parentnode, childname => $childname, stringname => $stringname, newchildname => $newchildnode } );
947 0 0 0     0 if (ref($parentnode->{$childname}) eq "HASH") {
    0          
948 0         0 $parentnode->{$childname}->{$stringname} = $newchildnode->[0]->{$stringname};
949 0         0 $result++;
950             } elsif (($parentnode->{$childname} =~ /\w+/) && ($isnodetype->($stringname, "text"))) {
951 0         0 $parentnode->{$childname} = $newchildnode->[0]->{$stringname};
952 0         0 $result++;
953             }
954             } else {
955 0         0 $parentnode->{$childname} = $newchildnode;
956 0         0 $result++;
957             }
958             } else {
959 0 0       0 croak "Cannot replace child node, none exists at position $childpos." if !defined $stringname;
960 0 0       0 croak "Cannot replace child node items, none exists at position $childpos." if defined $stringname;
961 0         0 return undef;
962             }
963             }
964             }
965 4         9 return $result;
966 10         119 };
967              
968 10     0   39 my $nodeDelete = sub (@) {};
  0         0  
969             $nodeDelete = sub (@) {
970 4     4   3 my $parentnode = shift; # ref - must be HASH ref, or ARRAY if replacing to multiple parents
971 4         38 my $childname = shift; # ref->[#]->ref # delete the node with this child name | or delete $stringname of existing child node
972 4         6 my $childpos = shift; # ref->[#]->ref->[#] - can be undef # delete the node at this position, undef to delete all | or delete $stringname of existing child node at this position
973 4         5 my $stringname = shift; # ref->[#]->ref->[#]->name - can be undef # delete this $stringname of the child node
974 4         5 my $result = 0;
975              
976 4 50       17 if (!ref($parentnode)) {
    50          
    50          
977 0         0 croak "Cannot delete a child node to a non referencing parent node.";
978 0         0 return undef;
979             } elsif (ref($parentnode) eq "ARRAY") {
980 0 0       0 if (@{$parentnode} == 0) {
  0         0  
981 0         0 $parentnode = [{}];
982             }
983 0         0 foreach my $single_parentnode (@{$parentnode}) {
  0         0  
984 0         0 my $newresult = $nodeDelete->($single_parentnode,$childname,$childpos,$stringname);
985 0         0 $result += $newresult;
986             }
987             } elsif (ref($parentnode) eq "HASH") {
988 4 50       9 if (ref($parentnode->{$childname}) eq "ARRAY") {
989 4 50 33     18 if ((defined $childpos) && ($childpos >= 1) && (($childpos - 1) <= @{$parentnode->{$childname}})) {
  4 0 33     11  
990 4 50       8 if (defined $stringname) { # Make sure we account for { node => } opposed to { node => { #text => } }
991 0 0 0     0 if (ref($parentnode->{$childname}->[($childpos - 1)]) eq "HASH") {
    0          
992 0         0 delete $parentnode->{$childname}->[($childpos - 1)]->{$stringname};
993 0         0 $result++;
994             } elsif (($parentnode->{$childname}->[($childpos - 1)] =~ /\w+/) && ($isnodetype->($stringname, "text"))) {
995 0         0 $parentnode->{$childname}->[($childpos - 1)] = undef;
996 0         0 $result++;
997             }
998             } else {
999             #delete $parentnode->{$childname}->[($childpos - 1)];
1000 4         2 splice (@{$parentnode->{$childname}},($childpos - 1),1);
  4         10  
1001 4         9 $result++;
1002             }
1003             } elsif (!defined $childpos) {
1004 0         0 my $i = 0;
1005 0 0       0 if (defined $stringname) { # Make sure we account for { node => } opposed to { node => { #text => } }
1006 0         0 while ($i < @{$parentnode->{$childname}}) {
  0         0  
1007 0 0 0     0 if (ref($parentnode->{$childname}->[$i]) eq "HASH") {
    0          
1008 0         0 delete $parentnode->{$childname}->[$i]->{$stringname};
1009 0         0 $result++;
1010             } elsif (($parentnode->{$childname}->[$i] =~ /\w+/) && ($isnodetype->($stringname, "text"))) {
1011 0         0 $parentnode->{$childname}->[$i] = undef;
1012 0         0 $result++;
1013             }
1014 0         0 $i++;
1015             }
1016             } else {
1017 0         0 delete $parentnode->{$childname};
1018 0         0 $result++;
1019             }
1020             } else {
1021 0         0 my $num = @{$parentnode->{$childname}};
  0         0  
1022 0 0       0 croak "Cannot delete child node $childname at position $childpos when there is $num." if !defined $stringname;
1023 0 0       0 croak "Cannot delete from child node $childname, none exists at position $childpos when there is $num." if defined $stringname;
1024 0         0 return undef;
1025             }
1026             } else {
1027 0 0 0     0 if ((!defined $childpos) || ($childpos == 1)) {
1028 0 0       0 if (defined $stringname) { # Make sure we account for { node => } opposed to { node => { #text => } }
1029 0         0 delete $parentnode->{$childname}->{$stringname};
1030             } else {
1031 0         0 delete $parentnode->{$childname};
1032             }
1033             } else {
1034 0 0       0 croak "Cannot delete child node, none exists at position $childpos." if !defined $stringname;
1035 0 0       0 croak "Cannot delete from child node, none exists at position $childpos." if defined $stringname;
1036 0         0 return undef;
1037             }
1038             }
1039             }
1040 4         8 return $result;
1041 10         60 };
1042              
1043             # This can be the function $mod->($parent_nodes,insert|replace|delete|mergeadd|mergereplace|mergedelete|mergeappend,$child_path,string,$value);
1044             my $mod = sub (@) {
1045 14     14   16 my $parent_nodes = shift; # @{$parent_nodes}
1046 14         20 my $action = shift; # insert|replace|delete|mergeadd|mergereplace|mergedelete|mergeappend
1047 14         18 my $child_path = shift; # XMLPath
1048 14         18 my $string_element = shift; # @attrname | #text
1049 14         14 my $value = shift; # "value"
1050 14         15 my $numAffected = 0;
1051              
1052 14 50 100     143 if (($action ne "insert") && ($action ne "replace") && ($action ne "delete")
      100        
      100        
      100        
      100        
      66        
1053             && ($action ne "mergeadd") && ($action ne "mergeappend") && ($action ne "mergereplace") && ($action ne "mergedelete") ) {
1054 0         0 croak "Modify only supports insert, replace, merge or delete";
1055             }
1056              
1057             # Extract positional
1058 14         15 my ($positionFilter,$position);
1059 14 50       32 if ($child_path->[1]) {
1060 14 50       65 if ($child_path->[1]->[0]->[0] =~ /^\d*$/) {
1061 14         12 $positionFilter = shift @{$child_path->[1]};
  14         25  
1062 14   50     36 $position = $positionFilter->[0] || undef;
1063             }
1064             }
1065 14         15 foreach my $xref (@{$parent_nodes}) {
  14         22  
1066 14 50       30 if (ref($xref) eq "HASH") {
    0          
1067              
1068 14         15 my @positions;
1069 14 50 33     49 if ((!defined $position) && (ref($xref->{ $child_path->[0] }) eq "ARRAY") && (defined $child_path->[1]) && (@{$child_path->[1]} > 0)) {
  0   33     0  
      0        
1070 0         0 my $ipos = 0;
1071 0         0 while ($xref->{ $child_path->[0] }->[$ipos]) {
1072 0 0       0 if ( my $pass = $tppx->filterXMLDoc($xref->{ $child_path->[0] }->[$ipos], [[ ".", $child_path->[1] ]]) ) {
1073 0         0 push( @positions, ($ipos +1) );
1074             }
1075 0         0 $ipos++;
1076             }
1077             } else {
1078 14         22 push (@positions,$position);
1079             }
1080              
1081 14         16 foreach my $pos (@positions) {
1082 14 100       29 $numAffected += $nodeInsert->($xref,$child_path->[0],$pos,$string_element,$value) if $action eq "insert";
1083 14 100       34 $numAffected += $nodeReplace->($xref,$child_path->[0],$pos,$string_element,$value) if $action eq "replace";
1084 14 100       31 $numAffected += $nodeDelete->($xref,$child_path->[0],$pos,$string_element) if $action eq "delete";
1085 14 100       37 $numAffected += $nodeMergeAction->($xref,$child_path->[0],$pos,$string_element,$value, mergetype => "mergeadd" ) if $action eq "mergeadd";
1086 14 100       34 $numAffected += $nodeMergeAction->($xref,$child_path->[0],$pos,$string_element,$value, mergetype => "mergeappend" ) if $action eq "mergeappend";
1087 14 100       27 $numAffected += $nodeMergeAction->($xref,$child_path->[0],$pos,$string_element,$value, mergetype => "mergereplace" ) if $action eq "mergereplace";
1088 14 100       52 $numAffected += $nodeMergeAction->($xref,$child_path->[0],$pos,$string_element,$value, mergetype => "mergedelete" ) if $action eq "mergedelete";
1089             }
1090              
1091             } elsif (ref($xref) eq "ARRAY") {
1092              
1093 0         0 foreach my $e (@$xref) {
1094 0         0 my @positions;
1095 0 0 0     0 if ((!defined $position) && (ref($e->{ $child_path->[0] }) eq "ARRAY") && (defined $child_path->[1]) && (@{$child_path->[1]} > 0)) {
  0   0     0  
      0        
1096 0         0 my $ipos = 0;
1097 0         0 while ($e->{ $child_path->[0] }->[$ipos]) {
1098 0 0       0 if ( my $pass = $tppx->filterXMLDoc($e->{ $child_path->[0] }->[$ipos], [[ ".", $child_path->[1] ]]) ) {
1099 0         0 push( @positions, ($ipos +1) );
1100             }
1101 0         0 $ipos++;
1102             }
1103             } else {
1104 0         0 push (@positions,$position);
1105             }
1106              
1107 0         0 foreach my $pos (@positions) {
1108 0 0       0 $numAffected += $nodeInsert->($e,$child_path->[0],$pos,$string_element,$value) if $action eq "insert";
1109 0 0       0 $numAffected += $nodeReplace->($e,$child_path->[0],$pos,$string_element,$value) if $action eq "replace";
1110 0 0       0 $numAffected += $nodeDelete->($e,$child_path->[0],$pos,$string_element) if $action eq "delete";
1111 0 0       0 $numAffected += $nodeMergeAction->($e,$child_path->[0],$pos,$string_element,$value, mergetype => "mergeadd" ) if $action eq "mergeadd";
1112 0 0       0 $numAffected += $nodeMergeAction->($e,$child_path->[0],$pos,$string_element,$value, mergetype => "mergeappend" ) if $action eq "mergeappend";
1113 0 0       0 $numAffected += $nodeMergeAction->($e,$child_path->[0],$pos,$string_element,$value, mergetype => "mergereplace" ) if $action eq "mergereplace";
1114 0 0       0 $numAffected += $nodeMergeAction->($e,$child_path->[0],$pos,$string_element,$value, mergetype => "mergedelete" ) if $action eq "mergedelete";
1115             }
1116             }
1117              
1118             }
1119             }
1120 14         64 return $numAffected;
1121 10         125 };
1122              
1123             #pp (\%options);
1124 10         29 my $resultmaps = $self->tppx->filterXMLDoc($xtree, $xmlpath, structure => "ParentMAP");
1125             #pp ({ xmldoc => $xtree, xmlpath => $xmlpath, options => \%options, map => $resultmaps });
1126 10         16601 foreach my $parentmap (@{$resultmaps}) {
  10         24  
1127 10         24 foreach my $action (keys %options) {
1128 10   100     36 my $value = $options{$action} || undef;
1129 10 100       25 if ($action eq "insert") {
1130 1         3 my $child = $parentmap->{'child'}->[0];
1131 1         5 my $child_path = [ $child->{'name'}, [[$child->{'position'}, undef]] ];
1132 1         6 $numAffected += $mod->([$parentmap->{'root'}],$action,$child_path,$child->{'target'},$value);
1133 1         4 next;
1134             }
1135 9 100       26 if ($action eq "delete") {
1136 2         2 foreach my $child (reverse @{$parentmap->{'child'}}) {
  2         5  
1137 4         10 my $tmp_value = eval(Dumper($value));
1138 4         34 my $child_path = [ $child->{'name'}, [[$child->{'position'}, undef]] ];
1139 4         14 $numAffected += $mod->([$parentmap->{'root'}],$action,$child_path,$child->{'target'},$tmp_value);
1140             }
1141 2         6 next;
1142             }
1143 7         10 foreach my $child (@{$parentmap->{'child'}}) {
  7         20  
1144 9         23 my $tmp_value = eval(Dumper($value));
1145 9         87 my $child_path = [ $child->{'name'}, [[$child->{'position'}, undef]] ];
1146 9         35 $numAffected += $mod->([$parentmap->{'root'}],$action,$child_path,$child->{'target'},$tmp_value);
1147             }
1148             }
1149             }
1150              
1151 10         301 return $numAffected;
1152              
1153             }
1154              
1155              
1156             #How do we execute an add() ?
1157             #
1158             #Add will first check to see if xmlpath exists
1159             #If the path does not exist, then the $value is insert()ed into the xmldoc at the path indicated:
1160             # and path is #text - create node if it does not already exist, set #text
1161             # and path is @attribute - create node if it does not already exist, set @attribute
1162             # and path is node, $value is CDATA - create node if it does not already exist, set #text
1163             # and path is node, $value is REF - create the node as $value
1164             #If the path does exist, then we must asssume the $value is to be merge()d:
1165             # and path is #text - FAILURE, #text already exists - use merge to add additional content, or replace to change it
1166             # and path is @attribute - FAILURE, attribute already exists - use replace to change it
1167             # and path is node - FAILURE, node already exists - use insert to add additional nodes, merge to update this one, replace to change this one
1168              
1169             =pod
1170              
1171             =head2 insert
1172              
1173             insert( XMLTree, XMLPath, $value )
1174              
1175             This is the same as modify( XMLTree, XMLPath, insert => $value )
1176              
1177             =cut
1178             sub insert (@) {
1179 0 0 0 0 1   my $self = shift if ref($_[0]) eq $REF_NAME || undef;
1180 0           my $xtree = shift;
1181 0           my $path = shift;
1182 0           my $value = shift;
1183 0 0         return $self->modify( $xtree, $path, insert => $value ) if defined $self;
1184 0           return modify( $xtree, $path, insert => $value );
1185             }
1186              
1187             =pod
1188              
1189             =head2 mergeadd
1190              
1191             mergeadd( XMLTree, XMLPath, $value )
1192              
1193             This is the same as modify( XMLTree, XMLPath, mergeadd => $value )
1194              
1195             =cut
1196             sub mergeadd (@) { # mergeAdd mergeReplace mergeAppend mergeDelete
1197 0 0 0 0 1   my $self = shift if ref($_[0]) eq $REF_NAME || undef;
1198 0           my $xtree = shift;
1199 0           my $path = shift;
1200 0           my $value = shift;
1201 0 0         return $self->modify( $xtree, $path, mergeadd => $value ) if defined $self;
1202 0           return modify( $xtree, $path, mergeadd => $value );
1203             }
1204              
1205             =pod
1206              
1207             =head2 mergereplace
1208              
1209             mergereplace( XMLTree, XMLPath, $value )
1210              
1211             This is the same as modify( XMLTree, XMLPath, mergereplace => $value )
1212              
1213             =cut
1214             sub mergereplace (@) { # mergeAdd mergeReplace mergeAppend mergeDelete
1215 0 0 0 0 1   my $self = shift if ref($_[0]) eq $REF_NAME || undef;
1216 0           my $xtree = shift;
1217 0           my $path = shift;
1218 0           my $value = shift;
1219 0 0         return $self->modify( $xtree, $path, mergereplace => $value ) if defined $self;
1220 0           return modify( $xtree, $path, mergereplace => $value );
1221             }
1222              
1223             =pod
1224              
1225             =head2 mergeappend
1226              
1227             mergeappend( XMLTree, XMLPath, $value )
1228              
1229             This is the same as modify( XMLTree, XMLPath, mergeappend => $value )
1230              
1231             =cut
1232             sub mergeappend (@) { # mergeAdd mergeReplace mergeAppend mergeDelete
1233 0 0 0 0 1   my $self = shift if ref($_[0]) eq $REF_NAME || undef;
1234 0           my $xtree = shift;
1235 0           my $path = shift;
1236 0           my $value = shift;
1237 0 0         return $self->modify( $xtree, $path, mergeappend => $value ) if defined $self;
1238 0           return modify( $xtree, $path, mergeappend => $value );
1239             }
1240              
1241             =pod
1242              
1243             =head2 mergedelete
1244              
1245             mergedelete( XMLTree, XMLPath, $value )
1246              
1247             This is the same as modify( XMLTree, XMLPath, mergedelete => $value )
1248              
1249             =cut
1250             sub mergedelete (@) { # mergeAdd mergeReplace mergeAppend mergeDelete
1251 0 0 0 0 1   my $self = shift if ref($_[0]) eq $REF_NAME || undef;
1252 0           my $xtree = shift;
1253 0           my $path = shift;
1254 0           my $value = shift;
1255 0 0         return $self->modify( $xtree, $path, mergedelete => $value ) if defined $self;
1256 0           return modify( $xtree, $path, mergedelete => $value );
1257             }
1258              
1259             =pod
1260              
1261             =head2 replace
1262              
1263             replace( XMLTree, XMLPath, $value )
1264              
1265             This is the same as modify( XMLTree, XMLPath, replace => $value )
1266              
1267             =cut
1268             sub replace (@) {
1269 0 0 0 0 1   my $self = shift if ref($_[0]) eq $REF_NAME || undef;
1270 0           my $xtree = shift;
1271 0           my $path = shift;
1272 0           my $value = shift;
1273 0 0         return $self->modify( $xtree, $path, replace => $value ) if defined $self;
1274 0           return modify( $xtree, $path, replace => $value );
1275             }
1276              
1277             =pod
1278              
1279             =head2 delete
1280              
1281             delete( XMLTree, XMLPath )
1282              
1283             This is the same as modify( XMLTree, XMLPath, delete => undef )
1284              
1285             =cut
1286             sub delete (@) {
1287 0 0 0 0 1   my $self = shift if ref($_[0]) eq $REF_NAME || undef;
1288 0           my $xtree = shift;
1289 0           my $path = shift;
1290 0 0         return $self->modify( $xtree, $path, delete => undef ) if defined $self;
1291 0           return modify( $xtree, $path, delete => undef );
1292             }
1293              
1294              
1295             1;
1296             __END__