File Coverage

blib/lib/XML/XUpdate/LibXML.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             # $Id: LibXML.pm,v 1.15 2005/05/12 15:04:58 pajas Exp $
2              
3             package XML::XUpdate::LibXML;
4              
5 1     1   2886 use XML::LibXML;
  0            
  0            
6             use XML::LibXML::XPathContext;
7             use strict;
8             use vars qw(@ISA $debug $VERSION);
9              
10             BEGIN {
11             $debug=0;
12             $VERSION = '0.6.0';
13             }
14              
15             sub strip_space {
16             my ($text)=@_;
17             $text=~s/^\s*//;
18             $text=~s/\s*$//;
19             return $text;
20             }
21              
22             sub new {
23             my $class=(ref($_[0]) || $_[0]);
24             my $var_pool = {};
25             my $xpc = XML::LibXML::XPathContext->new();
26             $xpc->registerVarLookupFunc(\&_get_var,$var_pool);
27             return bless [$var_pool,
28             "http://www.xmldb.org/xupdate",
29             $xpc
30             ], $class;
31             }
32              
33             sub registerNs {
34             my ($self,$prefix, $uri)=@_;
35             $self->[2]->registerNs($prefix,$uri);
36             }
37              
38             sub init {
39             my ($self,$doc)=@_;
40             $self->[2]->setContextNode($doc);
41             }
42              
43             sub _context {
44             my ($self,$name,$value)=@_;
45             return $self->[2];
46             }
47              
48             sub _set_var {
49             my ($self,$name,$value)=@_;
50             print STDERR "DEBUG: Storing $name as ",ref($value),"\n" if $debug;
51             $self->[0]->{$name}=$value;
52             }
53              
54             sub _get_var {
55             my ($data,$name)=@_;
56             return $data->{$name};
57             }
58              
59             sub set_namespace {
60             my ($self,$URI)=@_;
61             $self->[1]=$URI;
62             }
63              
64             sub namespace {
65             my ($self)=@_;
66             return $self->[1];
67             }
68              
69             sub process {
70             my ($self,$dom,$updoc)=@_;
71             return unless ref($self);
72              
73             $self->init($dom);
74             print STDERR "DEBUG: Updating ",$dom->nodeName,"\n" if $debug;
75             foreach my $command ($updoc->getDocumentElement()->childNodes()) {
76              
77             if ($command->nodeType == XML::LibXML::XML_ELEMENT_NODE) {
78             if (lc($command->getNamespaceURI()) eq $self->namespace()) {
79             print STDERR "DEBUG: applying ",$command->toString(),"\n" if $debug;
80             $self->xupdate_command($dom,$command);
81             } else {
82             print STDERR "DEBUG: Ignorint element ",$command->toString(),"\n" if $debug;
83             }
84             }
85              
86             }
87             }
88              
89             sub get_text {
90             my ($self,$node)=@_;
91             my $text="";
92             foreach ($node->childNodes()) {
93             if ($_->nodeType() == XML::LibXML::XML_TEXT_NODE ||
94             $_->nodeType() == XML::LibXML::XML_CDATA_SECTION_NODE) {
95             $text.=$_->getData();
96             }
97             }
98             return strip_space($text);
99             }
100              
101             sub add_attribute {
102             my ($self, $node, $attr_node)=@_;
103             $node->setAttributeNS($attr_node->getNamespaceURI,
104             $attr_node->getName(),
105             $attr_node->getValue);
106             }
107              
108             sub append {
109             my ($self,$node,$results)=@_;
110             foreach (@$results) {
111             if ($_->nodeType == XML::LibXML::XML_ATTRIBUTE_NODE) {
112             $self->add_attribute($node,$_);
113             } else {
114             $node->appendChild($_);
115             }
116             }
117             }
118              
119             sub insert_after {
120             my ($self,$node,$results)=@_;
121              
122             if ($node->nodeType == XML::LibXML::XML_ATTRIBUTE_NODE) {
123             $self->append($node->getOwnerElement(),$results);
124             } else {
125             foreach (reverse @$results) {
126             if ($_->nodeType == XML::LibXML::XML_ATTRIBUTE_NODE) {
127             $self->add_attribute($node->parentNode(),$_);
128             } else {
129             $node->parentNode()->insertAfter($_,$node);
130             }
131             }
132             }
133             }
134              
135             sub insert_before {
136             my ($self,$node,$results)=@_;
137             if ($node->nodeType == XML::LibXML::XML_ATTRIBUTE_NODE) {
138             $self->append($node->getOwnerElement(),$results);
139             } else {
140             foreach (@$results) {
141             if ($_->nodeType == XML::LibXML::XML_ATTRIBUTE_NODE) {
142             $self->add_attribute($node->parentNode(),$_);
143             } else {
144             $node->parentNode()->insertBefore($_,$node);
145             }
146             }
147             }
148             }
149              
150             sub append_child {
151             my ($self,$node,$results,$child)=@_;
152             return unless @$results;
153             if ($child ne "") {
154             # XUpdate WD is weird:
155             # child=1 should mean make the new node 1st child
156             # child=last() should mean make new node last child
157             # but if there are n children before insertion,
158             # last() evaluates to n but they want the new node
159             # to be (n+1)th.
160              
161             # so we must add it first, then calculate the position:
162             my $ctxt = $self->_context();
163             $self->append($node,$results);
164             my ($ref)=$ctxt->findnodes("node()[$child]",$node);
165             return unless $ref;
166             # check whether we should move results before $ref node
167             foreach (@$results) {
168             return if $ref->isSameNode($_);
169             }
170             # now move them
171             foreach (@$results) {
172             $_->unbindNode();
173             $node->insertBefore($_,$ref);
174             }
175             } else {
176             $self->append($node,$results);
177             }
178             }
179              
180             sub update {
181             my ($self,$node,$results)=@_;
182              
183             if ($node->nodeType == XML::LibXML::XML_TEXT_NODE ||
184             $node->nodeType == XML::LibXML::XML_CDATA_SECTION_NODE) {
185             $self->insert_after($node,$results);
186             $node->unbindNode();
187             } elsif ($node->nodeType == XML::LibXML::XML_ATTRIBUTE_NODE ||
188             $node->nodeType == XML::LibXML::XML_PI_NODE) {
189             $node->setValue(strip_space(join "", map { $_->to_literal() } @$results));
190             } elsif ($node->nodeType == XML::LibXML::XML_ELEMENT_NODE) {
191             foreach ($node->childNodes()){
192             $_->unbindNode();
193             }
194             $self->append($node,$results);
195             }
196             }
197              
198             sub remove {
199             my ($self, $node)=@_;
200             $node->unbindNode();
201             }
202              
203             sub rename {
204             my ($self,$node,$name)=@_;
205             $node->setName($name);
206             }
207              
208             sub process_instructions {
209             my ($self, $dom, $command)=@_;
210              
211             my @result=();
212             foreach my $inst ($command->childNodes()) {
213             print STDERR "DEBUG: Instruction ",$command->toString(),"\n" if $debug;
214             if ( $inst->nodeType == XML::LibXML::XML_ELEMENT_NODE ) {
215             if ( $inst->getLocalName() eq 'element' ) {
216             my $new;
217             if ($inst->hasAttribute('namespace') and
218             $inst->getAttribute('name')=~/:/) {
219             $new=$dom->getOwnerDocument()->createElementNS(
220             $inst->getAttribute('namespace'),
221             $inst->getAttribute('name')
222             );
223             } else {
224             $new=$dom->getOwnerDocument()->createElement($inst->getAttribute('name'));
225             }
226             $self->append($new,$self->process_instructions($dom,$inst));
227             push @result,$new;
228             } elsif ( $inst->getLocalName() eq 'attribute' ) {
229             if ($inst->hasAttribute('namespace') and
230             $inst->getAttribute('name')=~/:/) {
231             my $att=
232             $dom->getOwnerDocument()->
233             createAttributeNS(
234             $inst->getAttribute('namespace'),
235             $inst->getAttribute('name')
236             );
237             $att->setValue($self->get_text($inst));
238             push @result,$att;
239             } else {
240             my $att=
241             $dom->getOwnerDocument()->
242             createAttribute(
243             $inst->getAttribute('name')
244             );
245             $att->setValue($self->get_text($inst));
246             push @result,$att;
247             }
248             } elsif ( $inst->getLocalName() eq 'text' ) {
249             push @result,$dom->getOwnerDocument()->createTextNode($self->get_text($inst));
250             } elsif ( $inst->getLocalName() eq 'processing-instruction' ) {
251             push @result,$dom->getOwnerDocument()->createProcessingInstruction(
252             $inst->getAttribute('name'),
253             $self->get_text($inst)
254             );
255             } elsif ( $inst->getLocalName() eq 'comment' ) {
256             push @result,$dom->getOwnerDocument()->createComment($self->get_text($inst));
257             } elsif ( $inst->getLocalName() eq 'value-of' ) {
258             my $value=$self->get_select($dom,$inst);
259             if ($value->isa('XML::LibXML::NodeList')) {
260             push @result, map { $_->cloneNode(1) }$value->get_nodelist;
261             } else {
262             push @result,$dom->getOwnerDocument()->createTextNode($value->to_literal());
263             }
264             } else {
265             # not in XUpdate DTD but in examples of XUpdate WD
266             push @result,$dom->getOwnerDocument()->importNode($inst)
267             unless (lc($inst->getNamespaceURI) eq $self->namespace());
268             }
269             } elsif ( $inst->nodeType == XML::LibXML::XML_CDATA_SECTION_NODE ||
270             $inst->nodeType == XML::LibXML::XML_TEXT_NODE) {
271             push @result,$dom->getOwnerDocument()->importNode($inst);
272             }
273             }
274             return \@result;
275             }
276              
277             sub get_select {
278             my ($self,$dom,$node)=@_;
279             my $xpath=$node->getAttribute('select');
280             if ($xpath eq "") {
281             die "Error: Required attribute select is missing or empty at:\n".
282             $node->toString()."\nAborting!\n";
283             }
284             return $self->_context->find($xpath);
285             }
286              
287             sub get_test {
288             my ($self,$dom,$node)=@_;
289             my $xpath=$node->getAttribute('test');
290             if ($xpath eq "") {
291             die "Error: Required attribute test is missing or empty at:\n".
292             $node->toString()."\nAborting!\n";
293             }
294             return $self->_context->find($xpath);
295             }
296              
297             sub xupdate_command {
298             my ($self,$dom,$command)=@_;
299             return unless ($command->getType == XML::LibXML::XML_ELEMENT_NODE);
300             if ($command->getLocalName() eq 'variable') {
301             my $select=$self->get_select($dom,$command);
302             $self->_set_var($command->getAttribute('name'), $select);
303             } elsif ($command->getLocalName() eq 'if') {
304             # xu:if
305             my $test=$self->get_test($dom,$command);
306             if ($test) {
307             print STDERR "DEBUG: Conditional execution of ",$dom->nodeName,"\n" if $debug;
308             foreach my $subcommand ($command->childNodes()) {
309            
310             if ($subcommand->nodeType == XML::LibXML::XML_ELEMENT_NODE) {
311             if (lc($subcommand->getNamespaceURI()) eq $self->namespace()) {
312             print STDERR "DEBUG: Applying ",$subcommand->toString(),"\n" if $debug;
313             $self->xupdate_command($dom,$subcommand);
314             } else {
315             print STDERR "DEBUG: Ignoring element ",$subcommand->toString(),"\n" if $debug;
316             }
317             }
318            
319             }
320             }
321             } else {
322             my $select=$self->get_select($dom,$command);
323             if ($select->isa('XML::LibXML::NodeList')) {
324             my @refnodes=$select->get_nodelist();
325             if (@refnodes) {
326            
327             # xu:insert-after
328             if ($command->getLocalName eq 'insert-after') {
329              
330             foreach (@refnodes) {
331             $self->insert_after($_,
332             $self->process_instructions($dom,$command));
333             }
334              
335             # xu:insert-before
336             } elsif ($command->getLocalName eq 'insert-before') {
337              
338             foreach (@refnodes) {
339             $self->insert_before($_,
340             $self->process_instructions($dom,$command));
341             }
342              
343             # xu:append
344             } elsif ($command->getLocalName eq 'append') {
345              
346             foreach (@refnodes) {
347             my $results=$self->process_instructions($dom,$command);
348             my $child=$command->getAttribute('child');
349             $self->append_child($_,$results,$child);
350             }
351              
352             # xu:update
353             } elsif ($command->getLocalName eq 'update') {
354              
355             foreach (@refnodes) {
356             my $results=$self->process_instructions($dom,$command);
357             # Well, XUpdate WD is not very specific about this.
358             # The content of this element should be PCDATA only.
359             # I'm extending WD by allowing instruction list.
360             $self->update($_,$results);
361             }
362              
363             # xu:remove
364             } elsif ($command->getLocalName eq 'remove') {
365              
366             foreach (@refnodes) {
367             $self->remove($_);
368             }
369              
370             # xu:rename
371             } elsif ($command->getLocalName eq 'rename') {
372              
373             foreach (@refnodes) {
374             $self->rename($_,$self->get_text($command));
375             }
376              
377             }
378             }
379             } else {
380             die "XPath does not lead to a nodelist: ",$command->getAttribute('select'),"\n";
381             }
382             }
383             }
384              
385             1;
386              
387             __END__