File Coverage

blib/lib/XML/Merge.pm
Criterion Covered Total %
statement 12 12 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 16 16 100.0


line stmt bran cond sub pod time code
1             # 4AIJDCLW: XML::Merge.pm by Pip Stuart to intelligently merge XML documents as parsed XML::XPath objects.
2             package XML::Merge;
3 3     3   16758 use strict;use warnings;use utf8;
  3     3   4  
  3     3   64  
  3         9  
  3         3  
  3         53  
  3         1390  
  3         24  
  3         12  
4             require XML::Tidy ;
5 3     3   99 use base qw( XML::Tidy );
  3         4  
  3         1572  
6             use XML::Tidy ;
7             use Carp;
8             our $VERSION = '1.4';our $d8VS='G7NMEdxm';
9             sub new { my $clas = shift(); my @parm; my $cres = 'main';
10             for(my $indx = 0; $indx < @_; $indx++) { if($_[$indx] =~ /^[-_]?(cres$|conflict_resolution)/ && ($indx + 1) < @_) { $cres = $_[++$indx] ; }
11             else { push(@parm, $_[$indx]); } }
12             my $tdob = XML::Tidy->new(@parm); my $self = bless($tdob, $clas); # self just a new Tidy (XPath) obj blessed into Merge class...
13             $self->{'_object_to_merge'} = undef; $self->{'_conflict_resolution_method'} = $cres; # ... with a few new options
14             # Conflict RESolution method valid values:
15             # 'main' = Main (primary) file wins
16             # 'merg' = Merge file resolves (Last-In wins)
17             # 'warn' = Croak warning about conflict && halt merge
18             # 'test' = Test whether any conflict would occur if merge were performed (0 for no conflict)
19             $self->{'_comment_join_method'} = 'none';
20             # CoMmenT Join method valid values: (no joins are implemented yet)
21             # 'none', 'separate'
22             # 'join', 'combine'
23             # 'jd8s', 'join_with_d8_stamp'
24             # 'jlts', 'join_with_localtime_stamp'
25             $self->{'_id_xpath_list'} = [ # unique ID elements or attributes
26             '@id' ,
27             '@idx' ,
28             '@ndx' ,
29             '@index' ,
30             '@name' ,
31             '@handle' ];
32             return($self); }
33             sub merge {
34             my $self = shift(); my @parm;
35             my $cres = $self->get_conflict_resolution_method();
36             my $cmtj = undef;#$self->get_comment_join_method();
37             my $mdxp = undef;
38             my $msxp = undef;
39             my $mgob = undef;
40             # setup local options
41             for(my $indx = 0; $indx < @_; $indx++) {
42             if ($_[$indx] =~ /^[-_]?(cres$|conflict_resolution)/ && ($indx + 1) < @_) {
43             $cres = $_[++$indx];
44             } elsif($_[$indx] =~ /^[-_]?(cmtj$|comment_join)/ && ($indx + 1) < @_) {
45             $cmtj = $_[++$indx];
46             } elsif($_[$indx] =~ /^[-_]?(mdxp$|merge_destination)/ && ($indx + 1) < @_) {
47             $mdxp = $_[++$indx];
48             } elsif($_[$indx] =~ /^[-_]?(msxp$|merge_source)/ && ($indx + 1) < @_) {
49             $msxp = $_[++$indx];
50             } elsif(ref($_[$indx]) =~ /XML::(XPath|Tidy|Merge)/) {
51             $self->set_object_to_merge($_[$indx]);
52             } else {
53             push(@parm, $_[$indx]);
54             } }
55             $self->set_object_to_merge( XML::Merge->new(@parm) ) if(@parm);
56             $cres = 'merg' if($cres =~ /last/i);
57             $mgob = $self->get_object_to_merge();
58             if($mgob) { my $mnrn; my $mgrn; # traverse main Merge obj && merge w/ object_to_merge according to options
59             # 0a. ck if root node elems have same LocalName but short-circuit root element loading if merge_source or merge_dest
60             if(defined($mdxp) && length($mdxp)) { ($mnrn)= $self->findnodes($mdxp); } else { ($mnrn)= $self->findnodes('/*'); }
61             if(defined($msxp) && length($msxp)) { ($mgrn)= $mgob->findnodes($msxp); } else { ($mgrn)= $mgob->findnodes('/*'); }
62             if($mnrn->getLocalName() eq $mgrn->getLocalName()) { # 1a. ck if each merge root elem has attributes which main doesn't
63             for($mgrn->findnodes('@*')) {
64             my($mnat)= $mnrn->findnodes('@' . $_->getLocalName());
65             # if both root elems have same attribute name with different values...
66             if(defined($mnat)) {
67             # must use Conflict RESolution method to know who's value wins
68             if($mnat->getNodeValue() ne $_->getNodeValue()) {
69             if ($cres eq 'merg') {
70             $mnat->setNodeValue($_->getNodeValue());
71             } elsif($cres eq 'warn') {
72             croak("!*WARN*! Found conflicting attribute:" .
73             $_ ->getLocalName() .
74             "\n main value:" . $mnat->getNodeValue() .
75             "\n merg value:" . $_ ->getNodeValue() .
76             "\n Croaking... please resolve manually.\n");
77             } elsif($cres eq 'test') {
78             return(1); } }
79             } else {
80             $mnrn->appendAttribute($_) unless($cres eq 'test'); } }
81             # 1b. loop through all merge child elems
82             if ($mgrn->findnodes('*')){
83             for($mgrn->findnodes('*')){my $mnmt;
84             my $mtch = 0; # flag to know if already matched
85             my @mgms = (); # save multiple MerG MatcheS
86             for my $idat (@{$self->get_id_xpath_list()}){ # test ID XPaths
87             # if a child merge elem has a matching id, search main for same
88             # my @idns = $_->findnodes($idat); # $mgmt MerG MaTch, $mnmt Merg Node MaTch, @idns ID NodeS, $mmas Merg Match Attr String
89             # for my $mgmt (@idns){my $mmas=$mgmt->toString();$mmas=~ s/^\s+(.*)/$1/;push(@mgms, '@' . $mmas);}}
90             # if(@mgms){
91             # ($mnmt)= $mnrn->findnodes($_->getLocalName() . '[' . join(' and ', @mgms) . ']');
92             # if(defined($mnmt)){ # id matched both main && merg...
93             # $mtch = 1; # was trying to incorporate multiple ID attributes from Kevin here, but not sure how to proceed so just leaving original code for now
94             my($mgmt)= $_->findnodes($idat);
95             if(defined($mgmt)){
96             if ($idat =~ /^\@/) {
97             ($mnmt)= $mnrn->findnodes($_->getLocalName() . '[' . $idat . '="' . $mgmt->getNodeValue() . '"]');
98             } elsif($idat =~ /\[\@\w+\]/) {
99             my $itmp = $idat; my $nval = $mgmt->getNodeValue();
100             $itmp =~ s/(\[\@\w+)\]/$1="$nval"\]/;
101             ($mnmt)= $mnrn->findnodes($itmp);
102             } else {
103             ($mnmt)= $mnrn->findnodes($idat); }
104             if(defined($mnmt)) { # id matched both main && merg...
105             $mtch = 1; # so recursively merge deeper...
106             my $test = $self->_recmerge($mnmt, $_, $cres, $cmtj);
107             return(1) if($cres eq 'test' && $test); } } }
108             if(!$mtch && $mnrn->findnodes($_->getLocalName())) {
109             my($mnmt)= $mnrn->findnodes($_->getLocalName());
110             if(defined($mnmt)) { # plain elem matched both main && merg...
111             my $fail = 0;
112             for my $idat (@{$self->get_id_xpath_list()}) {
113             my($mnat)= $mnmt->findnodes($idat); # MaiN ATtribute
114             my($mgat)= $_ ->findnodes($idat); # MerG ATtribute
115             $fail = 1 if(defined($mnat) || defined($mgat));
116             }
117             unless($fail) { # fail tests if any unique id paths were found
118             $mtch = 1; # so recursively merge deeper...
119             my $test = $self->_recmerge($mnmt, $_, $cres, $cmtj);
120             return(1) if($cres eq 'test' && $test);
121             } } }
122             # if none above matched, append diff child to main root node
123             $mnrn->appendChild($_) unless($mtch || $cres eq 'test'); }
124             } elsif($mgrn->getChildNodes()) { # no kid elems but kid text data node
125             my($mntx)= $mnrn->getChildNodes();
126             my($mgtx)= $mgrn->getChildNodes();
127             if(defined($mgtx) && $mgtx->getNodeType() == TEXT_NODE) {
128             if (!defined($mntx)) {
129             $mnrn->appendChild($mgtx) unless($cres eq 'test');
130             } elsif($cres eq 'merg') {
131             $mntx->setNodeValue($mgtx->getNodeValue());
132             } elsif($cres eq 'warn') {
133             croak("!*WARN*! Found conflicting Root text node:" .
134             $mnrn->getLocalName() .
135             "\n main value:" . $mntx->getNodeValue() .
136             "\n merg value:" . $mgtx->getNodeValue() .
137             "\n Croaking... please resolve manually.\n");
138             } elsif($cres eq 'test') {
139             #return(1); # new text node value is not a merge prob?
140             } } }
141             # 0b. ck if merge root node elem exists somewhere in main
142             } elsif($self->findnodes('//' . $mgrn->getLocalName())) {
143             my($mnmt)= $self->findnodes('//' . $mgrn->getLocalName());
144             # recursively merge main child with merg root
145             my $test = $self->_recmerge($mnmt, $mgrn, $cres, $cmtj);
146             return(1) if($cres eq 'test' && $test);
147             # 0c. just append whole merge doc as last child of main root
148             } elsif($cres ne 'test') {
149             $mnrn->appendChild($mgrn);
150             $mnrn->appendChild($self->Text("\n")); } }
151             return(0); } # false zero 0 test _cres == no conflict, true 1 == conflict
152             sub _recmerge { # recursively merge XML elements
153             my $self = shift(); # merge() already setup all needed _optn values
154             my $mnnd = shift(); # MaiN NoDe
155             my $mgnd = shift(); # MerG NoDe
156             my $cres = shift() || $self->get_conflict_resolution_method();
157             my $cmtj = shift(); # $self->get_comment_join_method();
158             if($mnnd->getLocalName() eq $mgnd->getLocalName()) {
159             for($mgnd->findnodes('@*')) {
160             my($mnat)= $mnnd->findnodes('@' . $_->getLocalName());
161             if(defined($mnat)) {
162             if($mnat->getNodeValue() ne $_->getNodeValue()) {
163             if ($cres eq 'merg') {
164             $mnat->setNodeValue($_->getNodeValue());
165             } elsif($cres eq 'warn') {
166             croak("!*WARN*! Found conflicting Non-Root attribute:" .
167             $_ ->getLocalName() .
168             "\n main value:" . $mnat->getNodeValue() .
169             "\n merg value:" . $_ ->getNodeValue() .
170             "\n Croaking... please resolve manually.\n");
171             } elsif($cres eq 'test') {
172             return(1); } }
173             } else {
174             $mnnd->appendAttribute($_) unless($cres eq 'test'); } }
175             if($mgnd->findnodes('*')) {
176             for($mgnd->findnodes('*')) {
177             my $mtch = 0; # flag to know if already matched
178             for my $idat (@{$self->get_id_xpath_list()}) { # test ID XPaths
179             # if a child merge elem has a matching id, search main for same
180             my($mgmt)= $_->findnodes($idat); # MerG MaTch
181             if(defined($mgmt)) {
182             my $mnmt;
183             if ($idat =~ /^\@/) {
184             ($mnmt)= $mnnd->findnodes($_->getLocalName() . '[' . $idat . '="' . $mgmt->getNodeValue() . '"]');
185             } elsif($idat =~ /\[\@\w+\]/) {
186             my $itmp = $idat; my $nval = $mgmt->getNodeValue();
187             $itmp =~ s/(\[\@\w+)\]/$1="$nval"\]/;
188             ($mnmt)= $mnnd->findnodes($itmp);
189             } else {
190             ($mnmt)= $mnnd->findnodes($idat); }
191             if(defined($mnmt)) { # id matched both main && merg...
192             $mtch = 1; # so recursively merge deeper...
193             my $test = $self->_recmerge($mnmt, $_, $cres, $cmtj);
194             return(1) if($cres eq 'test' && $test); } } }
195             if(!$mtch && $mnnd->findnodes($_->getLocalName())) {
196             my($mnmt)= $mnnd->findnodes($_->getLocalName());
197             if(defined($mnmt)) { # plain elem matched both main && merg...
198             my $fail = 0;
199             for my $idat (@{$self->get_id_xpath_list()}) {
200             my($mnat)= $mnmt->findnodes($idat); # MaiN ATtribute
201             my($mgat)= $_ ->findnodes($idat); # MerG ATtribute
202             $fail = 1 if(defined($mnat) || defined($mgat)); }
203             unless($fail) { # fail tests if any unique id paths were found
204             $mtch = 1; # so recursively merge deeper...
205             my $test = $self->_recmerge($mnmt, $_, $cres, $cmtj);
206             return(1) if($cres eq 'test' && $test); } } }
207             # if none above matched, append diff child to main root node
208             $mnnd->appendChild($_) unless($mtch || $cres eq 'test'); }
209             } elsif($mgnd->getChildNodes()) { # no child elems but child text data node
210             my($mntx)= $mnnd->getChildNodes();
211             my($mgtx)= $mgnd->getChildNodes();
212             if(defined($mgtx) && $mgtx->getNodeType() == TEXT_NODE) {
213             if (!defined($mntx) && $cres ne 'test') {
214             $mnnd->appendChild($mgtx);
215             } elsif($cres eq 'merg') {
216             $mntx->setNodeValue($mgtx->getNodeValue());
217             } elsif($cres eq 'warn') {
218             croak("!*WARN*! Found conflicting Non-Root text node:" .
219             $mnnd->getLocalName() .
220             "\n main value:" . $mntx->getNodeValue() .
221             "\n merg value:" . $mgtx->getNodeValue() .
222             "\n Croaking... please resolve manually.\n");
223             } elsif($cres eq 'test') {
224             #return(1); # new text node value is not a merge prob?
225             } } }
226             } elsif($cres ne 'test') { # append whole merge elem as last kid of main elem
227             $mnnd->appendChild($mgnd);
228             $mnnd->appendChild($self->Text("\n")); }
229             return(0); } # return false for no conflict
230             sub unmerge { # short-hand for writing a certain xpath_loc out then pruning it
231             my $self = shift(); my @parm; my $xplc = undef; my $flnm = undef;
232             # setup local options
233             for(my $indx = 0; $indx < @_; $indx++) {
234             if ($_[$indx] =~ /^[-_]?(flnm$|filename)/ && ($indx + 1) < @_) {
235             $flnm = $_[++$indx];
236             } elsif($_[$indx] =~ /^[-_]?(xplc$|xpath_location)/ && ($indx + 1) < @_) {
237             $xplc = $_[++$indx];
238             } else {
239             push(@parm, $_[$indx]); } }
240             if(@parm) {
241             $flnm = shift(@parm) unless(defined($flnm));
242             $xplc = shift(@parm) unless(defined($xplc)); }
243             if(defined($flnm) && defined($xplc) &&
244             length ($flnm) && length ($xplc)) {
245             $self->write($flnm,
246             $xplc);
247             $self->prune($xplc); } }
248             # Accessors
249             sub get_object_to_merge {my $self=shift(); return($self->{'_object_to_merge' });}
250             sub set_object_to_merge {my $self=shift();$self->{'_object_to_merge' } = shift() if(@_);return($self->{'_object_to_merge' });}
251             sub get_conflict_resolution_method{my $self=shift(); return($self->{'_conflict_resolution_method'});}
252             sub set_conflict_resolution_method{my $self=shift();$self->{'_conflict_resolution_method'} = shift() if(@_);return($self->{'_conflict_resolution_method'});}
253             #ub get_comment_join_method {my $self=shift(); return($self->{'_comment_join_method' });}
254             #ub set_comment_join_method {my $self=shift();$self->{'_comment_join_method' } = shift() if(@_);return($self->{'_comment_join_method' });}
255             sub get_id_xpath_list {my $self=shift(); return($self->{'_id_xpath_list' });}
256             sub set_id_xpath_list {my $self=shift();
257             if(@_) { if(@_ == 1 && ref($_[0]) eq 'ARRAY') { $self->{'_id_xpath_list'} = shift(); }
258             else { $self->{'_id_xpath_list'} = [ @_ ]; } } return($self->{'_id_xpath_list' });}
259             sub DESTROY { } # do nothing but define in case needed later and to calm test warnings
260             8;
261              
262             =encoding utf8
263              
264             =head1 NAME
265              
266             XML::Merge - flexibly merge XML documents
267              
268             =head1 VERSION
269              
270             This documentation refers to version 1.4 of XML::Merge, which was released on Sat Jul 23 14:39:59:48 -0500 2016.
271              
272             =head1 SYNOPSIS
273              
274             #!/usr/bin/perl
275             use strict;use warnings;
276             use utf8;use XML::Merge;
277              
278             # create new XML::Merge object from MainFile.xml
279             my $merge_obj= XML::Merge->new('filename' => 'MainFile.xml');
280              
281             # Merge File2Add.xml into MainFile.xml
282             $merge_obj->merge( 'filename' => 'File2Add.xml');
283              
284             # Tidy up the indenting that resulted from the merge
285             $merge_obj->tidy();
286              
287             # Write out changes back to MainFile.xml
288             $merge_obj->write();
289              
290             =head1 DESCRIPTION
291              
292             This module inherits from L which in turn inherits from
293             L. This ensures that Merge objects' indenting can be
294             tidied up after any merge operation since such modification usually
295             ruins indentation. Polymorphism allows Merge objects to be utilized
296             as normal XML::XPath objects as well.
297              
298             The merging behavior is setup to combine separate XML documents
299             according to certain rules and configurable options. If both
300             documents have root nodes which are elements of the same name, the
301             documents are merged directly. Otherwise, one is merged as a child
302             of the other. An optional XPath location can be specified as the
303             place to perform the merge. If no location is specified, the merge
304             is attempted at the first matching element or is appended as the new
305             last child of the other root if no match is found.
306              
307             =head1 USAGE
308              
309             =head2 new()
310              
311             This is the standard Merge object constructor. It can take the
312             same parameters as an L object constructor to initialize
313             the primary XML document object (the object which subsequent XML
314             documents will be merged into). These parameters can be any one of:
315              
316             'filename' => 'SomeFile.xml'
317             'xml' => $variable_which_holds_a_bunch_of_XML_data
318             'ioref' => $file_InputOutput_reference
319             'context' => $existing_node_at_specified_context_to_become_new_obj
320              
321             Merge's new() can also accept merge-option parameters to
322             override the default merge behavior. These include:
323              
324             'conflict_resolution_method' => 'main', # main file wins
325             'conflict_resolution_method' => 'merg', # merge file wins
326             # 'last-in_wins' is the same as 'merg'
327             'conflict_resolution_method' => 'warn', # croak conflicts
328             'conflict_resolution_method' => 'test', # just test, 1 if conflict
329              
330             =head2 merge()
331              
332             The merge() member function can accept the same L
333             constructor options as new() but this time they are for the
334             temporary file which will be merged into the main object.
335             Merge-options from new() can also be specified and they will only
336             impact one particular invokation of merge(). The specified document
337             will be merged into the primary XML document object according to
338             the following default merge rules:
339              
340             1. If both documents share the same root element name, they are
341             merged directly.
342              
343             2. If they don't share root elements but the temporary merge file's
344             root element is found anywhere within the main file, the merge
345             occurs at the match.
346              
347             3. If no root element match is found, the merge document becomes the
348             new last child of the main file's root element.
349              
350             4. Whenever a deeper level is found with an element of the same name
351             in both documents and either it does not contain any
352             distinguishing attributes or it has attributes which are
353             recognized as 'identifier' (id) attributes (by default, for any
354             element, these are attributes named: 'id', 'idx', 'ndx',
355             'index', 'name', and 'handle'), a corresponding element is
356             searched for to match and merge with.
357              
358             5. Any remaining (non-id) nodes are merged in document order.
359              
360             6. When a conflict arises as non-id attributes or other nodes merge,
361             the specified conflict_resolution_method merge-option is
362             applied (which by default has the main file data persist at the
363             expense of the merging file data).
364              
365             Some of the above rules can be overridden first by the object's
366             merge-options and second by the particular method call's merge-options.
367             Thus, if the default merge-option for conflict resolution is to
368             have the main object win and you use the following constructor:
369              
370             my $merge_obj = XML::Merge->new(
371             'filename' => 'MainFile.xml',
372             'conflict_resolution_method' => 'last-in_wins');
373              
374             ... then any $merge_obj->merge() call would override the
375             default merge behavior by letting the document being merged have
376             priority over the main object's document. However, you could
377             supply additional merge-options in the parameter list of your
378             specific merge() call like:
379              
380             $merge_obj->merge(
381             'filename' => 'File2Add.xml',
382             'conflict_resolution_method' => 'warn');
383              
384             ... to have the latest option override further.
385              
386             The 'test' conflict_resolution_method merge-option does not modify the
387             object at all. It solely returns zero (0) if no conflict was encountered
388             from a temporary attempted merge.
389              
390             It should be used like:
391              
392             for(@files) {
393             if($merge_obj->merge('cres' => 'test', $_)) {
394             croak("Yipes! Conflict with file:$_!\n");
395             } else {
396             $merge_obj->merge($_); # only do it if there are no conflicts
397             }
398             }
399              
400             merge() can also accept another XML::Merge object as a parameter
401             for what to be merged with the main object instead of a filename.
402             An example of this is:
403              
404             $merge_obj->merge($another_merge_obj);
405              
406             Along with the merge options that can be specified in the object
407             constructor, merge() also accepts the following options to specify
408             where to perform the merge relative to:
409              
410             'merge_destination_path' => $main_obj_xpath_location,
411             'merge_source_path' => $merging_obj_xpath_location,
412              
413             =head2 unmerge()
414              
415             The unmerge() member function is a shorthand for calling both write()
416             and prune() on a certain XPath location which should be written out
417             to a disk file before being removed from the Merge object. Please
418             see L for documentation of the inherited write() and prune()
419             member functions.
420              
421             This unmerge() process could be the opposite of merge() if no original
422             elements or attributes overlapped and combined but if combining did
423             happen, this would remove original sections of your primary XML
424             document's data from your Merge object so please use this carefully.
425             It is meant to help separate a giant object (probably the result of
426             myriad merge() calls) back into separate useful well-formed XML
427             documents on disk.
428              
429             unmerge() takes a filename and an xpath_location parameter.
430              
431             =head1 Accessors
432              
433             =head2 get_object_to_merge()
434              
435             Returns the object which was last merged into the main object.
436              
437             =head2 set_object_to_merge()
438              
439             Assigns the object which was last merged into the main object.
440              
441             =head2 get_conflict_resolution_method()
442              
443             Returns the underlying merge-option conflict_resolution_method.
444              
445             =head2 set_conflict_resolution_method()
446              
447             A new value can be provided as a parameter to be assigned
448             as the XML::Merge object's merge-option.
449              
450             =head2 get_id_xpath_list()
451              
452             Returns the underlying id_xpath_list. This is normally just a list
453             of attributes (e.g., '@id', '@idx', '@ndx', '@index', '@name', '@handle')
454             which are unique identifiers for any XML element within merging instance
455             documents. When these attribute names are encountered during a merge(),
456             another element with the same name and attribute value are searched for
457             explicitly in order to align deeper merging and conflict resolution.
458              
459             =head2 set_id_xpath_list()
460              
461             A new list can assigned to the XML::Merge object's id_xpath_list.
462              
463             Please note that this list normally contains XPath attributes so they
464             must be preceded by an at-symbol (@) like: '@example_new_id_attribute'.
465              
466             =head1 CHANGES
467              
468             Revision history for Perl extension XML::Merge:
469              
470             =over 2
471              
472             =item - 1.4 G7NMEdxm Sat Jul 23 14:39:59:48 -0500 2016
473              
474             * inverted conflict resolution 'test' value since true 1 for conflict makes more sense
475              
476             * renumbered t/*.t
477              
478             * updated Makefile.PL and Build.PL to hopefully fix issue L (Thanks Kevin.)
479              
480             * removed DBUG printing
481              
482             * removed PT from VERSION to fix issue L (Thanks ppisar.)
483              
484             * updated license to GPLv3
485              
486             =item - 1.2.75BAJNl Fri May 11 10:19:23:47 2007
487              
488             * added default id @s: idx, ndx, and index
489              
490             =item - 1.2.565EgGd Sun Jun 5 14:42:16:39 2005
491              
492             * added use XML::Tidy to make sure exports are available
493              
494             * removed 02prune.t and moved 03keep.t to 02keep.t ... passing tests is good
495              
496             =item - 1.2.4CCJWiB Sun Dec 12 19:32:44:11 2004
497              
498             * guessing how to fix Darwin test failure @ t/02prune.t first prune() call
499              
500             =item - 1.0.4CAL5IS Fri Dec 10 21:05:18:28 2004
501              
502             * fixed buggy _recmerge
503              
504             =item - 1.0.4CAEU0I Fri Dec 10 14:30:00:18 2004
505              
506             * made accessors for _id_xpath_list
507              
508             * made _id_xpath_list take XPath locations instead of elem names (old _idea)
509              
510             * made test _cres (at Marc's request)
511              
512             * made warn _cres croak
513              
514             * made Merge inherit from Tidy (which inherits from XPath)
515              
516             * separated reload(), strip(), tidy(), prune(), and write() into own
517             XML::Tidy module
518              
519             =item - 1.0.4C2Nf0R Thu Dec 2 23:41:00:27 2004
520              
521             * updated license and prep'd for release
522              
523             =item - 1.0.4C2BcI2 Thu Dec 2 11:38:18:02 2004
524              
525             * updated reload(), strip(), and tidy() to verify _xpob exists
526              
527             =item - 1.0.4C1JHOl Wed Dec 1 19:17:24:47 2004
528              
529             * commented out override stuff since it's probably bad form and dumps crap
530             warnings all over tests and causes them to fail... so I guess just
531             uncomment that stuff if you care to preserve PI's and escapes
532              
533             =item - 1.0.4C1J7gt Wed Dec 1 19:07:42:55 2004
534              
535             * made merge() accept merge_source_xpath and merge_destination_xpath params
536              
537             * made merge() accept other Merge objects
538              
539             * made reload() not clobber basic escapes (by overriding Text toString())
540              
541             * made tidy() not kill processing-instructions (by overriding node_test())
542              
543             * made tidy() not kill comments
544              
545             =item - 1.0.4BOHGjm Wed Nov 24 17:16:45:48 2004
546              
547             * fixed merge() same elems with diff ids bug
548              
549             =item - 1.0.4BNBCZL Tue Nov 23 11:12:35:21 2004
550              
551             * rewrote both merge() and _recmerge() _cres stuff since it was
552             buggy before... so hopefully consistently good now
553              
554             =item - 1.0.4BMJCPm Mon Nov 22 19:12:25:48 2004
555              
556             * fixed merge() for empty elem matching and _cres on text kids
557              
558             =item - 1.0.4BMGTLF Mon Nov 22 16:29:21:15 2004
559              
560             * separated reload() from strip() so that prune() can call it too
561              
562             =item - 1.0.4BM0B3x Mon Nov 22 00:11:03:59 2004
563              
564             * fixed tidy() empty elem bug and implemented prune() and unmerge()
565              
566             =item - 1.0.4BJAZpM Fri Nov 19 10:35:51:22 2004
567              
568             * fixing e() ABSTRACT gen bug
569              
570             =item - 1.0.4BJAMR6 Fri Nov 19 10:22:27:06 2004
571              
572             * fleshed out POD and members
573              
574             =item - 1.0.4AIDqmR Mon Oct 18 13:52:48:27 2004
575              
576             * original version
577              
578             =back
579              
580             =head1 TODO
581              
582             =over 2
583              
584             =item - add Kevin's multiple _idea option where several element attributes are an ID together, from:
585              
586             =item - make namespaces and attributes stay in order after merge()
587              
588             =item - make text append merge option
589              
590             =item - handle comment joins and stamping options
591              
592             =item - support modification-time conflict resolution method
593              
594             =item - add _ignr ignore list of merge XPath locations to not merge (pre-prune())
595              
596             =back
597              
598             =head1 INSTALL
599              
600             From the command shell, please run:
601              
602             `perl -MCPAN -e "install XML::Merge"`
603              
604             or uncompress the package and run the standard:
605              
606             `perl Makefile.PL; make; make test; make install`
607             or if you don't have `make` but Module::Build is installed, try:
608             `perl Build.PL; perl Build; perl Build test; perl Build install`
609              
610             =head1 FILES
611              
612             XML::Merge requires:
613              
614             L to allow errors to croak() from calling sub
615              
616             L to use objects derived from XPath to update XML
617              
618             =head1 LICENSE
619              
620             Most source code should be Free! Code I have lawful authority over is and shall be!
621             Copyright: (c) 2004-2016, Pip Stuart.
622             Copyleft : This software is licensed under the GNU General Public License
623             (version 3 or later). Please consult L
624             for important information about your freedom. This is Free Software: you
625             are free to change and redistribute it. There is NO WARRANTY, to the
626             extent permitted by law. See L for further information.
627              
628             =head1 AUTHOR
629              
630             Pip Stuart
631              
632             =cut
633              
634             # Please see CHANGES for why below remains commented out.
635             ## To not kill Processing Instructions, used to need to fix node_test() test_nt_pi return in XML::XPath::Step.pm first...
636             #package XML::XPath::Step;
637             #use XML::XPath::Parser;
638             #use XML::XPath::Node;
639             #sub node_test {
640             # my $self = shift; my $node = shift;
641             # my $test = $self->{test}; # if node passes test, return true
642             # return 1 if $test == test_nt_node;
643             # if($test == test_any) {
644             # return 1 if $node->isElementNode && defined $node->getName;
645             # }
646             # local $^W;
647             # if($test == test_ncwild) {
648             # return unless $node->isElementNode;
649             # my $match_ns = $self->{pp}->get_namespace($self->{literal}, $node);
650             # if(my $node_nsnode = $node->getNamespace()) {
651             # return 1 if $match_ns eq $node_nsnode->getValue;
652             # }
653             # } elsif($test == test_qname) {
654             # return unless $node->isElementNode;
655             # if($self->{literal} =~ /:/) {
656             # my($prefix, $name) = split(':', $self->{literal}, 2);
657             # my $match_ns = $self->{pp}->get_namespace($prefix, $node);
658             # if(my $node_nsnode = $node->getNamespace()) {
659             # return 1 if($match_ns eq $node_nsnode->getValue && $name eq $node->getLocalName);
660             # }
661             # } else {
662             # return 1 if $node->getName eq $self->{literal};
663             # }
664             # } elsif ($test == test_nt_text) {
665             # return 1 if $node->isTextNode;
666             # } elsif($test == test_nt_comment) {
667             # return 1 if $node->isCommentNode;
668             # } elsif($test == test_nt_pi) {
669             # return unless $node->isPINode;
670             # # EROR was here! $self->{literal} is undefined so can't ->value!
671             # #if(my $val = $self->{literal}->value) {
672             # # return 1 if $node->getTarget eq $val;
673             # #} else {
674             # return 1;
675             # #}
676             # }
677             # return; # fallthrough returns false
678             #}
679             ## ... also update Text nodes' toString() to escape both < && >! ...
680             #package XML::XPath::Node::TextImpl;
681             #sub toString {
682             # my $self = shift; XML::XPath::Node::XMLescape($self->[node_text], '<&>');
683             #}