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