File Coverage

blib/lib/XML/XPathScript/Processor.pm
Criterion Covered Total %
statement 279 329 84.8
branch 110 164 67.0
condition 40 65 61.5
subroutine 55 64 85.9
pod 23 34 67.6
total 507 656 77.2


line stmt bran cond sub pod time code
1             package XML::XPathScript::Processor;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: XML::XPathScript transformation engine
4             $XML::XPathScript::Processor::VERSION = '2.00';
5 24     24   136 use strict;
  24         43  
  24         577  
6 24     24   101 use warnings;
  24         39  
  24         453  
7 24     24   93 use Carp;
  24         39  
  24         1014  
8              
9 24     24   110 use base qw/ Exporter /;
  24         39  
  24         2561  
10              
11 24     24   8806 use XML::XPathScript::Template;
  24         65  
  24         599  
12 24     24   9509 use Readonly;
  24         74240  
  24         2200  
13              
14             our @EXPORT = qw/
15             $DO_SELF_AS_CHILD
16             $DO_SELF_AND_KIDS
17             $DO_SELF_ONLY
18             $DO_NOT_PROCESS
19             DO_SELF_AND_KIDS
20             DO_SELF_ONLY
21             DO_NOT_PROCESS
22             DO_TEXT_AS_CHILD
23             /;
24              
25             our @EXPORT_OK = qw(
26             $DO_SELF_AS_CHILD
27             $DO_SELF_AND_KIDS
28             $DO_SELF_ONLY
29             $DO_NOT_PROCESS
30             DO_SELF_AND_KIDS
31             DO_SELF_ONLY
32             DO_NOT_PROCESS
33             DO_TEXT_AS_CHILD
34             processor
35             findnodes
36             findvalue
37             findvalues
38             findnodes_as_string
39             xpath_to_string
40             apply_templates
41             matches
42             set_namespace
43             is_element_node
44             is_text_node
45             is_comment_node
46             is_pi_node
47             is_nodelist
48             is_utf8_tainted
49             get_xpath_of_node
50             set_dom
51             get_dom
52             get_parser
53             enable_binmode
54             set_binmode
55             get_binmode
56             set_template
57             get_template
58             set_interpolation
59             get_interpolation
60             set_interpolation_regex
61             get_interpolation_regex
62             );
63              
64 24     24   156 use constant DO_TEXT_AS_CHILD => 2;
  24         43  
  24         1261  
65 24     24   116 use constant DO_SELF_AND_KIDS => 1;
  24         40  
  24         975  
66 24     24   114 use constant DO_SELF_ONLY => -1;
  24         38  
  24         824  
67 24     24   109 use constant DO_NOT_PROCESS => 0;
  24         35  
  24         15953  
68              
69             our( $DO_SELF_AS_CHILD, $DO_SELF_AND_KIDS,
70             $DO_SELF_ONLY, $DO_NOT_PROCESS );
71             Readonly::Scalar $DO_SELF_AS_CHILD => 2;
72             Readonly::Scalar $DO_SELF_AND_KIDS => 1;
73             Readonly::Scalar $DO_SELF_ONLY => -1;
74             Readonly::Scalar $DO_NOT_PROCESS => 0;
75              
76              
77             sub new {
78 64     64 0 106 my $class = shift;
79 64         121 my $self = {};
80 64         103 bless $self, $class;
81 64         229 return $self;
82             # $XML::XPathScript::xp => {doc}
83             # {parser}
84             # {binmode}
85             # {template}
86             # $XML::XPathScript::current->interpolating() {is_interpolating}
87             # $XML::XPathScript::current->{interpolation_regex} {interpolation_regex}
88             }
89              
90             #### accessors #############################################
91            
92             sub processor {
93 73     73 1 1169 return $_[0];
94             }
95              
96             sub set_dom {
97 77     77 1 158 my( $self, $dom ) = @_;
98 77 50       289 my $class = ref( $self->{dom} = $dom )
99             or croak "usage: \$processor->set_dom( \$dom )";
100              
101 77 50       582 if ( $class =~ /((?:XML::)(?:LibXML|XPath))/ ) {
    0          
102 77         225 $self->set_parser( $1 );
103             }
104             elsif ( $class =~ /B::XPath/ ) {
105 0         0 $self->set_parser( 'B::XPath' );
106             }
107             else {
108 0         0 die "no parser assigned to class $class\n";
109             }
110              
111              
112 77         159 return;
113             }
114              
115             sub set_parser {
116 77     77 0 230 my ( $self, $parser ) = @_;
117              
118 77         158 $self->{parser} = $parser;
119 77 50       172 if ( $parser eq 'XML::LibXML' ) {
    0          
    0          
120 77         8922 require XML::XPathScript::Processor::LibXML;
121 77         269 bless $self, 'XML::XPathScript::Processor::LibXML';
122             }
123             elsif ( $parser eq 'XML::XPath' ) {
124 0         0 require XML::XPathScript::Processor::XPath;
125 0         0 bless $self, 'XML::XPathScript::Processor::XPath';
126             }
127             elsif ( $parser eq 'B::XPath' ) {
128 0         0 require XML::XPathScript::Processor::B;
129 0         0 bless $self, 'XML::XPathScript::Processor::B';
130             }
131             else {
132 0         0 die "parser $parser not supported\n";
133             }
134              
135             }
136              
137 0     0 1 0 sub get_dom { $_[0]->{dom} }
138 2     2 1 20 sub get_parser { $_[0]->{parser} }
139 4     4 1 12 sub enable_binmode { $_[0]->{binmode} = 1 }
140 1     1 0 4 sub set_binmode { $_[0]->enable_binmode; }
141 0     0 1 0 sub get_binmode { $_[0]->{binmode} }
142 7     7 1 62 sub set_template { $_[0]->{template} = $_[1] }
143 0     0 1 0 sub get_template { $_[0]->{template} }
144 70     70 1 223 sub set_interpolation { $_[0]->{is_interpolating} = $_[1] }
145 4     4 1 6 sub get_interpolation { $_[0]->{is_interpolating} }
146 65     65 1 134 sub set_interpolation_regex { $_[0]->{interpolation_regex} = $_[1] }
147 0     0 1 0 sub get_interpolation_regex { $_[0]->{interpolation_regex} }
148              
149              
150             # $processor->import_functional( $prefix )
151             # XML::XPathScript::Processor->import_functional( $prefix )
152              
153             sub import_functional {
154 73     73 1 2167 my( $self, $prefix ) = @_;
155              
156 73 50       226 $self or croak "import_functional not called properly";
157            
158             # call as XML::XPathScript::Processor->import_functional
159 73 100       180 $self = XML::XPathScript::Processor->new unless ref $self;
160              
161 73         229 my($caller) = caller;
162              
163 73         316 $self->_export( $caller, $_, $prefix ) for @EXPORT_OK;
164              
165 73         658 return;
166             }
167              
168             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
169              
170             sub _export {
171             # heavily inspired by David James' Class::Exporter
172             # (which is a nice way to say I stole it and
173             # twisted it to my own perverted needs)
174            
175 24     24   178 no strict qw/ refs /;
  24         67  
  24         706  
176 24     24   149 no warnings qw/ uninitialized /;
  24         40  
  24         14549  
177              
178 2628     2628   4596 my( $self, $caller, $export, $prefix ) = @_;
179              
180 2628 100       5565 my $type = $export =~ s/^(\W)// ? $1 : '&';
181 2628         3769 my $export_sym = __PACKAGE__.'::'.$export;
182              
183             #if ( $export =~ /^DO_/ ) {
184             # #warn $caller.'::'.$prefix.$export;
185             # eval "sub ${caller}::$prefix$export () { ". $export->() ." };";
186             #warn &{$caller.'::'.$prefix.$export};
187             # \&XML::XPathScript::Processor::DO_SELF_AND_KIDS;
188             # return;
189             #}
190              
191 2628     18895   11433 *{$caller.'::'.$prefix.$export} = $type eq '&' ? sub { $self->$export(@_) }
  18895         579061  
192 292         712 : $type eq '$' ? \${$export_sym}
193 0         0 : $type eq '@' ? \@{$export_sym}
194 0         0 : $type eq '%' ? \%{$export_sym}
195 2628 0       7328 : $type eq '*' ? *{$export_sym}
  0 0       0  
    0          
    50          
    100          
196             : croak "Can't export symbol: $type$export"
197             ;
198             }
199              
200             ##### stylesheet API #######################################
201              
202             sub findnodes {
203 2584     2584 1 3422 my $self = shift;
204              
205 2584 50       5074 if ($self->{parser} eq 'XML::XPath' ) {
206 0         0 return $self->{dom}->findnodes(@_);
207             }
208              
209 2584         3632 my ($path, $context) = @_;
210 2584 100       4271 $context = $self->{dom} if (!defined $context);
211 2584         4863 return $context->findnodes($path);
212             }
213              
214             sub findvalue {
215 8070     8070 1 9631 my $self = shift;
216 8070 50       15093 if ( $self->{parser} eq 'XML::XPath' ) {
217 0         0 return $self->xpath_to_string(scalar $self->{dom}->findvalue(@_));
218             }
219              
220 8070         11798 my ($path, $context) = @_;
221 8070 100       12346 $context = $self->{dom} if (!defined $context);
222 8070         13789 return $self->xpath_to_string($context->findvalue($path));
223             }
224              
225             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
226              
227             sub xpath_to_string {
228 11566     11566 1 320401 my $self = shift;
229 11566         16359 my ($blob)=@_;
230 11566 100       32339 return $blob unless ref $blob ;
231              
232             # Was simply C<< return "$blob" >> but Perl 5.6.1 seems to have
233             # issues with UTF8-flag used in overloaded stringification :-(
234 1447 50       8322 return $blob->can("data") ? $blob->data()
    100          
235             : $blob->can("value") ? $blob->value()
236             : $blob->string_value()
237             ;
238             }
239              
240             sub findvalues {
241 0     0 1 0 my $self = shift;
242 0         0 my @nodes = $self->findnodes(@_);
243 0         0 map { $self->findvalue('.', $_) } @nodes;
  0         0  
244             }
245              
246             sub findnodes_as_string {
247 0     0 1 0 my $self = shift;
248 0         0 $self->{dom}->findnodes_as_string( @_ )
249             }
250              
251             sub matches {
252 0     0 1 0 my $self = shift;
253 0         0 $self->{dom}->matches(@_)
254             }
255              
256             sub set_namespace
257             {
258 0     0 0 0 my $self = shift;
259 0         0 eval { $self->{dom}->set_namespace(@_) };
  0         0  
260 0 0       0 warn "set_namespace failed: $@" if $@;
261             }
262              
263             sub apply_templates {
264 2962     2962 1 4942 my $self = shift;
265              
266 2962 100       6287 my $params = ref( $_[-1] ) eq 'HASH' ? pop @_ : {} ;
267              
268             # catch the calls to apply_templates()
269 2962 100       5277 @_ = $self->findnodes('/') unless @_;
270              
271 2962 100       7310 unless( ref $_[0] ) { # called with a path to find
272 35         127 @_ = $self->findnodes( @_ );
273             }
274              
275 2962 100       6314 return unless @_;
276              
277 2961         3355 my $retval;
278              
279 24     24   156 no warnings qw/ uninitialized /;
  24         57  
  24         5294  
280             $retval .= $self->translate_node( $_, $params )
281 2961 50       6648 for $self->is_nodelist($_[0]) ? $_[0]->get_nodelist
282             : @_
283             ;
284 2960         7069 return $retval;
285             }
286              
287             sub call_template {
288 0     0 1 0 my ($self,$node,$t,$template)=@_;
289              
290 0 0 0     0 if (defined(my $sub=$template->{testcode})) {
    0 0        
      0        
291 0         0 return &$sub($node,$t);
292             } elsif (exists $t->{prechild} || exists $t->{prechildren} ||
293             exists $t->{postchild} || exists $t->{postchildren}) {
294 0         0 warn "XML::XPathScript::Processor::call_template: cannot handle this sort of templates yet";
295             # Attempt to recover
296 0         0 $t->{pre}="";
297 0         0 $t->{post}="";
298 0         0 return 1;
299             } else {
300 0         0 $t->{pre}=$template->{pre};
301 0         0 $t->{post}=$template->{post};
302 0         0 return 1;
303             };
304             }
305              
306             sub _apply_templates {
307 88     88   123 my $self = shift;
308 88         116 my $params = pop;
309 24     24   155 no warnings 'uninitialized';
  24         49  
  24         2752  
310 88         329 return join '', map $self->translate_node($_,$params), @_;
311             }
312              
313             sub is_utf8_tainted {
314 3240     3240 1 8304 my $self = shift;
315 3240         4471 my ($string) = @_;
316              
317 3240         5604 my $ghost = ($string x 0) .
318             "ab"; # Very quick and conserves UTF-8 flag (and taintedness)
319              
320 24     24   172 $ghost .= do { use bytes; "\xc3" };
  24         67  
  24         124  
  3240         3892  
  3240         4100  
321 24     24   780 $ghost .= do { use bytes; "\xa9" };
  24         69  
  24         62  
  3240         3746  
  3240         3951  
322 24     24   712 my $charlength = do { no bytes; length($ghost) };
  24         40  
  24         97  
  3240         3744  
  3240         4033  
323 24     24   992 my $bytelength = do { use bytes; length($ghost) };
  24         51  
  24         73  
  3240         3414  
  3240         3802  
324              
325 3240 50 66     9131 if ($charlength == 3) {
    100 33        
    50          
326             # The two bytes we added got lumped in core into a single
327             # UTF-8 char. This is a Perl bug (arising e.g. because $string
328             # is tainted, see t/04unicode.t) but we recover gracefully.
329 0         0 return 1;
330             } elsif ($charlength == 4 && $bytelength == 4) {
331 3237         7359 return 0;
332             } elsif ($charlength == 4 && $bytelength == 6) {
333 3         14 return 1; # The bytes were upgraded
334             } else {
335 0         0 die "is_utf8_tainted assertion check failed".
336             " (charlength = $charlength, bytelength=$bytelength)";
337             }
338             }
339              
340             sub get_xpath_of_node {
341 129     129 1 1105 my $self =shift;
342 129         161 my $node = shift;
343              
344             # ugly hacks all over in this function, because the quirky
345             # refcount-proof aliasing (i.e. XML::XPath::Element versus
346             # XML::XPath::ElementImpl) in XML::XPath gets in the way badly
347 129 0 33     533 $node = $$node if $node->isa( 'XML::XPath::Node::Element' )
      33        
348             and not $self->isa( 'XML::XPath::Node::ElementImpl' )
349             and $node =~ /SCALAR/;
350              
351 129 50       543 my $parent = $node->can("parentNode") ? $node->parentNode()
352             : $node->getParentNode()
353             ;
354              
355 129 100       379 return "" unless defined $parent;
356              
357 109         126 my $name;
358 109 100       256 if ($self->is_element_node($node)) {
    50          
    0          
    0          
359 106         199 $name = $node->findvalue('name()');
360             } elsif ($self->is_text_node($node)) {
361 3         7 $name = "text()";
362             } elsif ($self->is_comment_node($node)) {
363 0         0 $name = "comment()";
364             } elsif ($self->is_pi_node($node)) {
365 0         0 $name = "processing-instruction()";
366             } else {
367             # YKYBPTMNW...
368 0         0 return $self->get_xpath_of_node($parent)."/strange-node()";
369             }
370              
371             # ugly hack, part II
372 109 50       3924 my @brothers = map{ ($_->isa( 'XML::XPath::Node::Element' ) ) ? $$_ : $_ } $parent->findnodes("./$name");
  294         3351  
373              
374             # Short-cut for nodes that have an ID. FIXME: not all DTDs use
375             # attribute named "id" as the SGML ID!
376 109 100 100     276 if ($self->is_element_node($node) && (my $id=$self->findvalue('@id',$node))) {
377 4         15 return $self->get_xpath_of_node($parent).sprintf('/%s[@id="%s"]', $name, $id);
378             }
379              
380             # Bug: the matches() function from XML::XPath is hosed, and only
381             # works towards ancestors. We resort to comparing references for
382             # identity. See above for details on the $$self quirk.
383 105 50       307 my $theself=($node =~ m/SCALAR/?$$node:$node);
384              
385 105         9273 for my $i ( 0..$#brothers ) {
386             my $thebrother=($brothers[$i] =~ m/SCALAR/?
387 173 50       877 ${$brothers[$i]}:$brothers[$i]);
  0         0  
388              
389 173 100       14247 return sprintf '%s/%s[%d]', $self->get_xpath_of_node($parent), $name, $i+1
390             if $theself eq $thebrother;
391             };
392              
393 0         0 return $self->get_xpath_of_node($parent)."/$name"."[?]";
394             }
395              
396              
397             ########################## End of exportable stuff ####################
398              
399             sub translate_node {
400 3104     3104 0 4768 my( $self, $node, $params ) = @_;
401              
402             my $retval = $self->is_element_node( $node )
403             ? $self->translate_element_node( $node, $params )
404             : $self->is_text_node($node)
405             ? $self->translate_text_node( $node, $params )
406             : $self->is_comment_node($node)
407             ? $self->translate_comment_node( $node, $params )
408             : $self->is_pi_node($node)
409 3104 0       5877 ? eval { if ($node->getParentNode->getParentNode) {
  0 0       0  
    50          
    100          
    100          
410 0         0 return $node->toString;
411 0         0 } else { '' } }
412             : $self->to_string( $node )
413             ;
414              
415 3104 100 100     12156 if ( $self->{binmode} &&
416             $self->is_utf8_tainted($retval)) {
417 24     24   11428 use Carp qw(confess); # TODO remove this
  24         48  
  24         4989  
418 1         7 confess("Wrong translation by stylesheet".
419             " (result is Unicode-tainted) at ".$self->get_xpath_of_node($node).
420             "\n$retval\n");
421             }
422              
423 3103         8156 return $retval;
424             }
425              
426             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
427              
428             sub translate_text_node {
429 1475     1475 0 2238 my $self = shift;
430 1475         1783 my $node = shift;
431 1475         1586 my $params = shift;
432 1475         1996 my $translations = $self->{template};
433              
434 1475   100     4267 my $trans = $translations->{'#text'} || $translations->{'text()'};
435              
436 1475 100       2906 return $node->toString unless $trans;
437              
438 1418         1768 my $action = $trans->{action};
439              
440 1418         3191 my $t = new XML::XPathScript::Template::Tag;
441 1418         1998 $t->{$_} = $trans->{$_} for keys %{$trans};
  1418         4296  
442 1418 100       2929 if (my $code = $trans->{testcode})
443             {
444 1409         2704 $action = $code->( $node, $t, $params );
445             }
446            
447 24     24   149 no warnings 'uninitialized';
  24         46  
  24         3561  
448 1418 50 66     4984 return if defined($action) and $action == DO_NOT_PROCESS();
449              
450 1418         1722 my $middle;
451 1418 100 100     3646 $middle = $node->toString if defined($action)
452             and $action == DO_TEXT_AS_CHILD();
453              
454             return $self->_transform_tag( $t->{pre}, $node, $t, $params )
455             . $middle
456 1418         2939 . $self->_transform_tag( $t->{post}, $node, $t, $params );
457             }
458              
459             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
460              
461             sub translate_element_node {
462 1617     1617 0 2570 my( $self, $node, $params ) = @_;
463 1617         2485 my $translations = $self->{template};
464              
465 1617         2917 my $node_name = $self->get_node_name( $node );
466              
467 1617         3600 my $namespace = $self->get_namespace( $node );
468              
469 1617         3859 my $trans = XML::XPathScript::Template::resolve( $translations,
470             $namespace, $node_name );
471              
472 1617 100       3158 unless( $trans ) {
473             # no specific and no generic? Okay, okay, return as is...
474 24     24   152 no warnings qw/ uninitialized /;
  24         45  
  24         3305  
475 88         227 my @kids = $self->get_child_nodes( $node );
476 88         803 return $self->start_tag($node)
477             . $self->_apply_templates( @kids, $params )
478             . $self->end_tag($node);
479            
480             }
481              
482 1529         3638 my $t = new XML::XPathScript::Template::Tag;
483 1529         2369 $t->{$_} = $trans->{$_} for keys %{$trans};
  1529         5368  
484              
485             # we officially support 'content', but also allow
486             # 'contents'
487 1529 100 66     5177 if ( my $content = $trans->{content} || $trans->{contents} ) {
488 8         21 return $self->_transform_content( $content, $node, $t, $params );
489             }
490              
491             # by default we do the kids
492 1521         1980 my $dokids = 1;
493 1521         1614 my $search;
494              
495 1521         1843 my $action = $trans->{action};
496            
497 1521 100       2484 if ($trans->{testcode}) {
498 1467         3080 $action = $trans->{testcode}->($node, $t, $params );
499             }
500              
501 24     24   136 no warnings 'uninitialized';
  24         51  
  24         14793  
502 1521 50 66     25039 return if $action =~ /^-?\d+$/ and $action == DO_NOT_PROCESS();
503              
504 1521 100 100     5981 if( defined( $action) and $action !~ /^-?\d+/ ) {
    100          
505             # ah, an xpath expression
506 34         56 $dokids = 0;
507 34         55 $search = $action;
508             }
509             elsif ($action == DO_SELF_ONLY() ) {
510 71         139 $dokids = 0;
511             }
512              
513 1521 100       4293 my @kids = $dokids ? $node->getChildNodes()
    100          
514             : $search ? $node->findnodes($search)
515             : ()
516             ;
517              
518 1521         15008 my @args = ( $node, $t, $params );
519              
520 1521         3951 my $pre = $self->_transform_tag( $t->{pre}, @args );
521 1521 100       2923 $pre .= $self->start_tag( $node , $t->{rename}) if $t->{showtag};
522 1521         3748 $pre .= $self->_transform_tag( $t->{intro}, @args );
523 1521 100       4453 $pre .= $self->_transform_tag( $t->{prechildren}, @args ) if @kids;
524            
525 1521         2132 my $post;
526 1521 100       3385 $post .= $self->_transform_tag( $t->{postchildren}, @args ) if @kids;
527 1521         3555 $post .= $self->_transform_tag( $t->{extro}, @args );
528 1521 100       3147 $post .= $self->end_tag( $node, $t->{rename} ) if $t->{showtag};
529 1521         2572 $post .= $self->_transform_tag( $t->{post}, @args );
530              
531 1521         2019 my $middle;
532              
533 1521         2465 for my $kid ( @kids )
534             {
535 1720         4169 my $kid_is_element = $self->is_element_node( $kid );
536              
537             $middle .= $self->_transform_tag( $trans->{prechild},
538 1720 100       3530 $kid, $t, $params ) if $kid_is_element;
539              
540 1720         3260 $middle .= $self->apply_templates($kid, $params );
541              
542             $middle .= $self->_transform_tag( $trans->{postchild},
543 1720 100       3891 $kid, $t, $params ) if $kid_is_element;
544             }
545            
546 1521         7220 return $pre . $middle . $post;
547             }
548              
549             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
550              
551             sub _transform_content {
552 8     8   18 my ( $self, $content, $node, $t, $params ) = @_;
553              
554 8 50       16 return $content->( $node, $t, $params ) if ref $content eq 'CODE';
555              
556 8         16 my $interpolated = $self->interpolate( $node, $content );
557              
558 8         18 local *STDOUT;
559 8         10 my $output;
560 8 50   1   77 open STDOUT, '>', \$output or die "couldn't redirect STDOUT: $!\n";
  1         7  
  1         1  
  1         6  
561              
562 8         590 my $xps = XML::XPathScript::current();
563              
564 8         18 my $code = $xps->extract( $interpolated );
565              
566 8         20 local $self->{dom} = $node;
567              
568 8         733 eval <<"END_CONTENT";
569             package XML::XPathScript::Template::Content;
570             my \$processor = \$self;
571             my \%params = \%\$params;
572             \$self->import_functional unless exists \&get_template;
573             $code;
574             END_CONTENT
575              
576 8 50       212 die $@ if $@;
577              
578 8         47 return $output;
579             }
580              
581             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
582             # bad terminology, here a tag is 'pre', 'post', etc
583              
584             sub _transform_tag {
585 11890     11890   21122 my ( $self, $tag, $node, $t, $params ) = @_;
586              
587 11890 100       24430 return unless $tag;
588              
589 1992 100       3239 return $tag->( $node, $t, $params ) if ref $tag eq 'CODE';
590              
591 1978         3407 return $self->interpolate( $node, $tag );
592             }
593              
594             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
595              
596             sub translate_comment_node {
597 12     12 0 24 my ( $self, $node, $params ) = @_;
598 12         21 my $translations = $self->{template};
599              
600 12   66     46 my $trans = $translations->{'#comment'} || $translations->{'comment()'};
601              
602 12 50       28 return $node->toString unless $trans;
603              
604 12         70 my $middle = $self->get_text_content( $node );
605              
606 12         45 my $t = new XML::XPathScript::Template::Tag;
607 12         19 $t->{$_} = $trans->{$_} for keys %{$trans};
  12         77  
608              
609 12         21 my $action = $trans->{action};
610              
611 12 100       28 if (my $code = $trans->{testcode})
612             {
613 10         31 $action = $code->( $node, $t, $params );
614 10 100 66     41 if ($action and %$t) {
615 9         28 foreach my $tkey (keys %$t) {
616 26         42 $trans->{$tkey} = $t->{$tkey};
617             }
618             }
619             }
620              
621 24     24   160 no warnings 'uninitialized';
  24         54  
  24         7684  
622 12 100 100     78 return if $action =~ /^-?\d+$/ and $action == $DO_NOT_PROCESS;
623            
624 11 100       28 $middle = undef if $action == $DO_SELF_ONLY;
625             return $self->_transform_tag( $trans->{pre}, $node, $t, $params )
626             . $middle
627 11         33 . $self->_transform_tag( $trans->{post}, $node, $t, $params );
628             }
629              
630             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
631              
632             sub start_tag {
633 104     104 0 251 my( $self, $node, $name ) = @_;
634              
635 104 50 66     344 $name ||= $self->get_qualified_name( $node ) or return;
636              
637 104         219 my $string = '<'.$name;
638              
639             # do we need this for libXML?
640 104 50       287 if( $self->{parser} eq 'XML::XPath' )
641             {
642 0         0 $string .= $_->toString for $node->getNamespaceNodes;
643             }
644              
645 104         273 for my $attr ( $self->get_attributes( $node ) ) {
646 9         110 $string .= $self->get_attribute( $attr );
647             }
648              
649 104         757 $string .= '>';
650              
651 104         383 return $string;
652             }
653              
654             sub end_tag {
655 104     104 0 185 my $self = shift;
656 104 50 66     359 if (my $name = $_[1] || $self->get_qualified_name( $_[0] ) ) {
657 104         504 return "";
658             }
659 0         0 return '';
660             }
661              
662             sub interpolate {
663 1986     1986 0 2833 my ($self, $node, $string) = @_;
664            
665             # if string is empty or no interpolation,
666             # we return
667             return( $string || '' ) unless
668             defined( $string ) and
669 1986 100 50     9073 $self->{is_interpolating};
      66        
670              
671 85         123 my $regex = $self->{interpolation_regex};
672 85         373 $string =~ s/$regex/ $node->findvalue($1) /egs;
  26         519  
673            
674 24     24   161 no warnings 'uninitialized';
  24         67  
  24         1537  
675 85         1536 return $string;
676             }
677              
678             'end of module XML::XPathScript::Processor';
679              
680             __END__