| 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
|
|
|
|
|
|
|
#} |