blib/lib/XML/XSLT.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 7 | 9 | 77.7 |
branch | n/a | ||
condition | n/a | ||
subroutine | 3 | 3 | 100.0 |
pod | n/a | ||
total | 10 | 12 | 83.3 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | ############################################################################## | ||||||
2 | # | ||||||
3 | # Perl module: XML::XSLT | ||||||
4 | # | ||||||
5 | # By Geert Josten, gjosten@sci.kun.nl | ||||||
6 | # and Egon Willighagen, egonw@sci.kun.nl | ||||||
7 | # and Jonathan Stowe |
||||||
8 | # | ||||||
9 | ############################################################################### | ||||||
10 | |||||||
11 | =head1 NAME | ||||||
12 | |||||||
13 | XML::XSLT - A perl module for processing XSLT | ||||||
14 | |||||||
15 | =cut | ||||||
16 | |||||||
17 | ###################################################################### | ||||||
18 | package XML::XSLT; | ||||||
19 | ###################################################################### | ||||||
20 | |||||||
21 | 27 | 27 | 185936 | use strict; | |||
27 | 62 | ||||||
27 | 1253 | ||||||
22 | 27 | 27 | 158 | use warnings; | |||
27 | 52 | ||||||
27 | 1025 | ||||||
23 | |||||||
24 | 27 | 27 | 20193 | use XML::DOM 1.25; | |||
0 | |||||||
0 | |||||||
25 | use XML::DOM::XPath; | ||||||
26 | use LWP::Simple qw(get); | ||||||
27 | use URI; | ||||||
28 | use Cwd; | ||||||
29 | use File::Basename qw(dirname); | ||||||
30 | use Carp; | ||||||
31 | |||||||
32 | # Namespace constants | ||||||
33 | |||||||
34 | use constant NS_XSLT => 'http://www.w3.org/1999/XSL/Transform'; | ||||||
35 | use constant NS_XHTML => 'http://www.w3.org/TR/xhtml1/strict'; | ||||||
36 | |||||||
37 | use vars qw ( $VERSION @ISA @EXPORT_OK $AUTOLOAD ); | ||||||
38 | |||||||
39 | $VERSION = '0.50_5'; | ||||||
40 | |||||||
41 | @ISA = qw( Exporter ); | ||||||
42 | @EXPORT_OK = qw( &transform &serve ); | ||||||
43 | |||||||
44 | my %deprecation_used; | ||||||
45 | |||||||
46 | ###################################################################### | ||||||
47 | # PUBLIC DEFINITIONS | ||||||
48 | |||||||
49 | =head1 SYNOPSIS | ||||||
50 | |||||||
51 | use XML::XSLT; | ||||||
52 | |||||||
53 | my $xslt = XML::XSLT->new ($xsl, warnings => 1); | ||||||
54 | |||||||
55 | $xslt->transform ($xmlfile); | ||||||
56 | print $xslt->toString; | ||||||
57 | |||||||
58 | $xslt->dispose(); | ||||||
59 | |||||||
60 | =head1 DESCRIPTION | ||||||
61 | |||||||
62 | This module implements the W3C's XSLT specification. The goal is full | ||||||
63 | implementation of this spec, but we have not yet achieved | ||||||
64 | that. However, it already works well. See L |
||||||
65 | the current status of each command. | ||||||
66 | |||||||
67 | XML::XSLT makes use of XML::DOM and LWP::Simple, while XML::DOM | ||||||
68 | uses XML::Parser. Therefore XML::Parser, XML::DOM and LWP::Simple | ||||||
69 | have to be installed properly for XML::XSLT to run. | ||||||
70 | |||||||
71 | =head1 Specifying Sources | ||||||
72 | |||||||
73 | The stylesheets and the documents may be passed as filenames, file | ||||||
74 | handles regular strings, string references or DOM-trees. Functions | ||||||
75 | that require sources (e.g. new), will accept either a named parameter | ||||||
76 | or simply the argument. | ||||||
77 | |||||||
78 | Either of the following are allowed: | ||||||
79 | |||||||
80 | my $xslt = XML::XSLT->new($xsl); | ||||||
81 | my $xslt = XML::XSLT->new(Source => $xsl); | ||||||
82 | |||||||
83 | In documentation, the named parameter `Source' is always shown, but it | ||||||
84 | is never required. | ||||||
85 | |||||||
86 | =head2 METHODS | ||||||
87 | |||||||
88 | =over 4 | ||||||
89 | |||||||
90 | =cut | ||||||
91 | |||||||
92 | =item new(Source => $xml [, %args]) | ||||||
93 | |||||||
94 | Returns a new XSLT parser object. Valid flags are: | ||||||
95 | |||||||
96 | =over 2 | ||||||
97 | |||||||
98 | =item DOMparser_args | ||||||
99 | |||||||
100 | Hashref of arguments to pass to the XML::DOM::Parser object's parse | ||||||
101 | method. | ||||||
102 | |||||||
103 | =item variables | ||||||
104 | |||||||
105 | Hashref of variables and their values for the stylesheet. | ||||||
106 | |||||||
107 | =item base | ||||||
108 | |||||||
109 | Base of URL for file inclusion. | ||||||
110 | |||||||
111 | =item debug | ||||||
112 | |||||||
113 | Turn on debugging messages. | ||||||
114 | |||||||
115 | =item warnings | ||||||
116 | |||||||
117 | Turn on warning messages. | ||||||
118 | |||||||
119 | =item indent | ||||||
120 | |||||||
121 | Starting amount of indention for debug messages. Defaults to 0. | ||||||
122 | |||||||
123 | =item indent_incr | ||||||
124 | |||||||
125 | Amount to indent each level of debug message. Defaults to 1. | ||||||
126 | |||||||
127 | =back | ||||||
128 | |||||||
129 | =cut | ||||||
130 | |||||||
131 | sub new | ||||||
132 | { | ||||||
133 | my $class = shift; | ||||||
134 | my $self = bless {}, $class; | ||||||
135 | my %args = $self->__parse_args(@_); | ||||||
136 | |||||||
137 | $self->{DEBUG} = defined $args{debug} ? $args{debug} : ""; | ||||||
138 | no strict 'subs'; | ||||||
139 | |||||||
140 | if ( $self->{DEBUG} ) | ||||||
141 | { | ||||||
142 | *__PACKAGE__::debug = \&debug; | ||||||
143 | } | ||||||
144 | else | ||||||
145 | { | ||||||
146 | *__PACKAGE__::debug = sub {}; | ||||||
147 | } | ||||||
148 | |||||||
149 | use strict 'subs'; | ||||||
150 | |||||||
151 | $self->{INDENT} = defined $args{indent} ? $args{indent} : 0; | ||||||
152 | $self->{PARSER} = XML::DOM::Parser->new(); | ||||||
153 | $self->{PARSER_ARGS} = | ||||||
154 | defined $args{DOMparser_args} ? $args{DOMparser_args} : {}; | ||||||
155 | $self->{VARIABLES} = defined $args{variables} ? $args{variables} : {}; | ||||||
156 | $self->debug(join ' ', keys %{$self->{VARIABLES}}); | ||||||
157 | $self->{WARNINGS} = defined $args{warnings} ? $args{warnings} : 0; | ||||||
158 | $self->{INDENT_INCR} = defined $args{indent_incr} ? $args{indent_incr} : 1; | ||||||
159 | $self->{XSL_BASE} = | ||||||
160 | defined $args{base} ? $args{base} : 'file://' . cwd . '/'; | ||||||
161 | $self->{XML_BASE} = | ||||||
162 | defined $args{base} ? $args{base} : 'file://' . cwd . '/'; | ||||||
163 | |||||||
164 | $self->use_deprecated( $args{use_deprecated} ) | ||||||
165 | if exists $args{use_deprecated}; | ||||||
166 | |||||||
167 | $self->debug("creating parser object:"); | ||||||
168 | |||||||
169 | $self->_indent(); | ||||||
170 | $self->open_xsl(%args); | ||||||
171 | $self->_outdent(); | ||||||
172 | |||||||
173 | return $self; | ||||||
174 | } | ||||||
175 | |||||||
176 | sub use_deprecated | ||||||
177 | { | ||||||
178 | my ( $self, $use_deprecated ) = @_; | ||||||
179 | |||||||
180 | if ( defined $use_deprecated ) | ||||||
181 | { | ||||||
182 | $self->{USE_DEPRECATED} = $use_deprecated; | ||||||
183 | } | ||||||
184 | |||||||
185 | return $self->{USE_DEPRECATED} || 0; | ||||||
186 | } | ||||||
187 | |||||||
188 | sub DESTROY { } # Cuts out random dies on includes | ||||||
189 | |||||||
190 | =item default_xml_version | ||||||
191 | |||||||
192 | Gets and/or sets the default XML version used in the output documents, | ||||||
193 | this will almost certainly want to be 1.0 | ||||||
194 | |||||||
195 | =cut | ||||||
196 | |||||||
197 | sub default_xml_version | ||||||
198 | { | ||||||
199 | my ( $self, $xml_version ) = @_; | ||||||
200 | |||||||
201 | if ( defined $xml_version ) | ||||||
202 | { | ||||||
203 | $self->{DEFAULT_XML_VERSION} = $xml_version; | ||||||
204 | } | ||||||
205 | |||||||
206 | return $self->{DEFAULT_XML_VERSION} ||= '1.0'; | ||||||
207 | } | ||||||
208 | |||||||
209 | =item serve(Source => $xml [, %args]) | ||||||
210 | |||||||
211 | Processes the given XML through the stylesheet. Returns a string | ||||||
212 | containg the result. Example: | ||||||
213 | |||||||
214 | use XML::XSLT qw(serve); | ||||||
215 | |||||||
216 | $xslt = XML::XSLT->new($xsl); | ||||||
217 | print $xslt->serve $xml; | ||||||
218 | |||||||
219 | =over 4 | ||||||
220 | |||||||
221 | =item http_headers | ||||||
222 | |||||||
223 | If true, then prepends the appropriate HTTP headers (e.g. Content-Type, | ||||||
224 | Content-Length); | ||||||
225 | |||||||
226 | Defaults to true. | ||||||
227 | |||||||
228 | =item xml_declaration | ||||||
229 | |||||||
230 | If true, then the result contains the appropriate header. | ||||||
231 | |||||||
232 | Defaults to true. | ||||||
233 | |||||||
234 | =item xml_version | ||||||
235 | |||||||
236 | The version of the XML. | ||||||
237 | |||||||
238 | Defaults to 1.0. | ||||||
239 | |||||||
240 | =item doctype | ||||||
241 | |||||||
242 | The type of DOCTYPE this document is. Defaults to SYSTEM. | ||||||
243 | |||||||
244 | =back | ||||||
245 | |||||||
246 | =cut | ||||||
247 | |||||||
248 | sub serve | ||||||
249 | { | ||||||
250 | my $self = shift; | ||||||
251 | my $class = ref $self || croak "Not a method call"; | ||||||
252 | my %args = $self->__parse_args(@_); | ||||||
253 | my $ret; | ||||||
254 | |||||||
255 | $args{http_headers} = 1 unless defined $args{http_headers}; | ||||||
256 | $args{xml_declaration} = 1 unless defined $args{xml_declaration}; | ||||||
257 | $args{xml_version} = $self->default_xml_version() | ||||||
258 | unless defined $args{xml_version}; | ||||||
259 | $args{doctype} = 'SYSTEM' unless defined $args{doctype}; | ||||||
260 | $args{clean} = 0 unless defined $args{clean}; | ||||||
261 | |||||||
262 | $ret = $self->transform( $args{Source} )->toString; | ||||||
263 | |||||||
264 | if ( $args{clean} ) | ||||||
265 | { | ||||||
266 | eval { require HTML::Clean }; | ||||||
267 | |||||||
268 | if ($@) | ||||||
269 | { | ||||||
270 | CORE::warn("Not passing through HTML::Clean -- install the module"); | ||||||
271 | } | ||||||
272 | else | ||||||
273 | { | ||||||
274 | my $hold = HTML::Clean->new( \$ret ); | ||||||
275 | $hold->strip; | ||||||
276 | $ret = ${ $hold->data }; | ||||||
277 | } | ||||||
278 | } | ||||||
279 | |||||||
280 | if ( my $doctype = $self->doctype() ) | ||||||
281 | { | ||||||
282 | $ret = $doctype . "\n" . $ret; | ||||||
283 | } | ||||||
284 | |||||||
285 | if ( $args{xml_declaration} ) | ||||||
286 | { | ||||||
287 | $ret = $self->xml_declaration() . "\n" . $ret; | ||||||
288 | } | ||||||
289 | |||||||
290 | if ( $args{http_headers} ) | ||||||
291 | { | ||||||
292 | $ret = | ||||||
293 | "Content-Type: " | ||||||
294 | . $self->media_type() . "\n" | ||||||
295 | . "Content-Length: " | ||||||
296 | . length($ret) . "\n\n" | ||||||
297 | . $ret; | ||||||
298 | } | ||||||
299 | |||||||
300 | return $ret; | ||||||
301 | } | ||||||
302 | |||||||
303 | =item xml_declaration | ||||||
304 | |||||||
305 | Will return an XML declaration element based on the output encoding and | ||||||
306 | XML version. | ||||||
307 | |||||||
308 | =cut | ||||||
309 | |||||||
310 | sub xml_declaration | ||||||
311 | { | ||||||
312 | my ( $self, $xml_version, $output_encoding ) = @_; | ||||||
313 | |||||||
314 | $xml_version ||= $self->default_xml_version(); | ||||||
315 | $output_encoding ||= $self->output_encoding(); | ||||||
316 | |||||||
317 | return qq{}; | ||||||
318 | } | ||||||
319 | |||||||
320 | =item output_encoding | ||||||
321 | |||||||
322 | Gets and/or sets the output encoding that is used in the xml declaration | ||||||
323 | and elsewhere (default: UTF-8) | ||||||
324 | |||||||
325 | =cut | ||||||
326 | |||||||
327 | # defaulting blindly to UTF-8 is a bug, this should also be used to | ||||||
328 | # appropriately set the encoding of the output. | ||||||
329 | # | ||||||
330 | sub output_encoding | ||||||
331 | { | ||||||
332 | my ( $self, $encoding ) = @_; | ||||||
333 | |||||||
334 | if ( defined $encoding ) | ||||||
335 | { | ||||||
336 | $self->{OUTPUT_ENCODING} = $encoding; | ||||||
337 | } | ||||||
338 | |||||||
339 | return exists $self->{OUTPUT_ENCODING} ? $self->{OUTPUT_ENCODING} : 'UTF-8'; | ||||||
340 | } | ||||||
341 | |||||||
342 | sub doctype_system | ||||||
343 | { | ||||||
344 | my ( $self, $doctype ) = @_; | ||||||
345 | |||||||
346 | if ( defined $doctype ) | ||||||
347 | { | ||||||
348 | $self->{DOCTYPE_SYSTEM} = $doctype; | ||||||
349 | } | ||||||
350 | |||||||
351 | return $self->{DOCTYPE_SYSTEM}; | ||||||
352 | } | ||||||
353 | |||||||
354 | sub doctype_public | ||||||
355 | { | ||||||
356 | my ( $self, $doctype ) = @_; | ||||||
357 | |||||||
358 | if ( defined $doctype ) | ||||||
359 | { | ||||||
360 | $self->{DOCTYPE_PUBLIC} = $doctype; | ||||||
361 | } | ||||||
362 | |||||||
363 | return $self->{DOCTYPE_PUBLIC}; | ||||||
364 | } | ||||||
365 | |||||||
366 | =item result_document | ||||||
367 | |||||||
368 | An accessor for the XML::DOM object that the transformed document is | ||||||
369 | assembled into. | ||||||
370 | |||||||
371 | =cut | ||||||
372 | |||||||
373 | sub result_document() | ||||||
374 | { | ||||||
375 | my ( $self, $document ) = @_; | ||||||
376 | |||||||
377 | if ( defined $document ) | ||||||
378 | { | ||||||
379 | $self->{RESULT_DOCUMENT} = $document; | ||||||
380 | } | ||||||
381 | |||||||
382 | return $self->{RESULT_DOCUMENT}; | ||||||
383 | } | ||||||
384 | |||||||
385 | sub debug | ||||||
386 | { | ||||||
387 | my $self = shift; | ||||||
388 | my $arg = shift || ""; | ||||||
389 | |||||||
390 | if ($self->{DEBUG} and $self->{DEBUG} > 1 ) | ||||||
391 | { | ||||||
392 | $arg = (caller(1))[3] . ": $arg"; | ||||||
393 | } | ||||||
394 | |||||||
395 | print STDERR " " x $self->{INDENT}, "$arg\n" | ||||||
396 | if $self->{DEBUG}; | ||||||
397 | } | ||||||
398 | |||||||
399 | sub warn | ||||||
400 | { | ||||||
401 | my $self = shift; | ||||||
402 | my $arg = shift || ""; | ||||||
403 | |||||||
404 | print STDERR " " x $self->{INDENT}, "$arg\n" | ||||||
405 | if $self->{DEBUG}; | ||||||
406 | print STDERR "$arg\n" | ||||||
407 | if $self->{WARNINGS} && !$self->{DEBUG}; | ||||||
408 | } | ||||||
409 | |||||||
410 | =item open_xml(Source => $xml [, %args]) | ||||||
411 | |||||||
412 | Gives the XSLT object new XML to process. Returns an XML::DOM object | ||||||
413 | corresponding to the XML. | ||||||
414 | |||||||
415 | =over 4 | ||||||
416 | |||||||
417 | =item base | ||||||
418 | |||||||
419 | The base URL to use for opening documents. | ||||||
420 | |||||||
421 | =item parser_args | ||||||
422 | |||||||
423 | Arguments to pase to the parser. | ||||||
424 | |||||||
425 | =back | ||||||
426 | |||||||
427 | =cut | ||||||
428 | |||||||
429 | sub open_xml | ||||||
430 | { | ||||||
431 | my $self = shift; | ||||||
432 | my $class = ref $self || croak "Not a method call"; | ||||||
433 | my %args = $self->__parse_args(@_); | ||||||
434 | |||||||
435 | if ( defined $self->xml_document() && not $self->{XML_PASSED_AS_DOM} ) | ||||||
436 | { | ||||||
437 | $self->debug("flushing old XML::DOM::Document object..."); | ||||||
438 | $self->xml_document()->dispose; | ||||||
439 | } | ||||||
440 | |||||||
441 | if (ref $args{Source} && UNIVERSAL::isa($args{Source}, 'XML::DOM::Document' ) ) | ||||||
442 | { | ||||||
443 | $self->{XML_PASSED_AS_DOM} = 1; | ||||||
444 | } | ||||||
445 | |||||||
446 | if ( defined $self->result_document() ) | ||||||
447 | { | ||||||
448 | $self->debug("flushing result..."); | ||||||
449 | $self->result_document()->dispose(); | ||||||
450 | } | ||||||
451 | |||||||
452 | $self->debug("opening xml..."); | ||||||
453 | |||||||
454 | $args{parser_args} ||= {}; | ||||||
455 | |||||||
456 | my $xml_document = $self->__open_document( | ||||||
457 | Source => $args{Source}, | ||||||
458 | base => $self->{XML_BASE}, | ||||||
459 | parser_args => { %{ $self->{PARSER_ARGS} }, %{ $args{parser_args} } }, | ||||||
460 | ); | ||||||
461 | |||||||
462 | $self->xml_document($xml_document); | ||||||
463 | |||||||
464 | $self->{XML_BASE} = | ||||||
465 | dirname( URI->new_abs( $args{Source}, $self->{XML_BASE} )->as_string ) | ||||||
466 | . '/'; | ||||||
467 | $self->result_document( $self->xml_document()->createDocumentFragment()); | ||||||
468 | } | ||||||
469 | |||||||
470 | =item xml_document | ||||||
471 | |||||||
472 | Gets and/or sets the XML::DOM object corresponding to the XML document | ||||||
473 | being processed. The document might be altered during processing. | ||||||
474 | |||||||
475 | =cut | ||||||
476 | |||||||
477 | sub xml_document | ||||||
478 | { | ||||||
479 | my ( $self, $xml_document ) = @_; | ||||||
480 | |||||||
481 | if ( defined $xml_document ) | ||||||
482 | { | ||||||
483 | $self->{XML_DOCUMENT} = $xml_document; | ||||||
484 | } | ||||||
485 | |||||||
486 | return $self->{XML_DOCUMENT}; | ||||||
487 | } | ||||||
488 | |||||||
489 | =item open_xsl(Source => $xml, [, %args]) | ||||||
490 | |||||||
491 | Gives the XSLT object a new stylesheet to use in processing XML. | ||||||
492 | Returns an XML::DOM object corresponding to the stylesheet. Any | ||||||
493 | arguments present are passed to the XML::DOM::Parser. | ||||||
494 | |||||||
495 | =over 4 | ||||||
496 | |||||||
497 | =item base | ||||||
498 | |||||||
499 | The base URL to use for opening documents. | ||||||
500 | |||||||
501 | =item parser_args | ||||||
502 | |||||||
503 | Arguments to pase to the parser. | ||||||
504 | |||||||
505 | =back | ||||||
506 | |||||||
507 | =cut | ||||||
508 | |||||||
509 | sub open_xsl | ||||||
510 | { | ||||||
511 | my $self = shift; | ||||||
512 | my $class = ref $self || croak "Not a method call"; | ||||||
513 | my %args = $self->__parse_args(@_); | ||||||
514 | |||||||
515 | |||||||
516 | |||||||
517 | $self->xsl_document()->dispose | ||||||
518 | if not $self->{XSL_PASSED_AS_DOM} | ||||||
519 | and defined $self->xsl_document(); | ||||||
520 | |||||||
521 | |||||||
522 | |||||||
523 | if ( ref $args{Source} && UNIVERSAL::isa($args{Source}, 'XML::DOM::Document' )) | ||||||
524 | { | ||||||
525 | $self->{XSL_PASSED_AS_DOM} = 1 | ||||||
526 | } | ||||||
527 | |||||||
528 | |||||||
529 | # open new document # open new document | ||||||
530 | $self->debug("opening xsl..."); | ||||||
531 | |||||||
532 | $args{parser_args} ||= {}; | ||||||
533 | |||||||
534 | my $xsl_document = $self->__open_document( | ||||||
535 | Source => $args{Source}, | ||||||
536 | base => $self->{XSL_BASE}, | ||||||
537 | parser_args => { %{ $self->{PARSER_ARGS} }, %{ $args{parser_args} } }, | ||||||
538 | ); | ||||||
539 | |||||||
540 | |||||||
541 | $self->{ORIG_XSL_DOC} = $xsl_document; | ||||||
542 | |||||||
543 | $self->xsl_document($xsl_document); | ||||||
544 | |||||||
545 | $self->{XSL_BASE} = | ||||||
546 | dirname( URI->new_abs( $args{Source}, $self->{XSL_BASE} )->as_string ) | ||||||
547 | . '/'; | ||||||
548 | |||||||
549 | $self->__preprocess_stylesheet; | ||||||
550 | } | ||||||
551 | |||||||
552 | =item xsl_document | ||||||
553 | |||||||
554 | Gets and/or sets the XML::DOM object corresponding to the XSLT document | ||||||
555 | that is being used for processing, this will be altered during processing | ||||||
556 | so should not be an object that needs to be reused elsewhere. | ||||||
557 | |||||||
558 | =cut | ||||||
559 | |||||||
560 | sub xsl_document | ||||||
561 | { | ||||||
562 | my ( $self, $xsl_document ) = @_; | ||||||
563 | |||||||
564 | if ( defined $xsl_document ) | ||||||
565 | { | ||||||
566 | $self->{XSL_DOCUMENT} = $xsl_document; | ||||||
567 | } | ||||||
568 | |||||||
569 | return $self->{XSL_DOCUMENT}; | ||||||
570 | } | ||||||
571 | |||||||
572 | # Argument parsing with backwards compatibility. | ||||||
573 | sub __parse_args | ||||||
574 | { | ||||||
575 | my $self = shift; | ||||||
576 | my %args; | ||||||
577 | |||||||
578 | if ( @_ % 2 ) | ||||||
579 | { | ||||||
580 | $args{Source} = shift; | ||||||
581 | %args = ( %args, @_ ); | ||||||
582 | } | ||||||
583 | else | ||||||
584 | { | ||||||
585 | %args = @_; | ||||||
586 | if ( not exists $args{Source} ) | ||||||
587 | { | ||||||
588 | my $name = [ caller(1) ]->[3]; | ||||||
589 | carp | ||||||
590 | "Argument syntax of call to $name deprecated. See the documentation for $name" | ||||||
591 | unless $self->use_deprecated($args{use_deprecated}) | ||||||
592 | or exists $deprecation_used{$name}; | ||||||
593 | $deprecation_used{$name} = 1; | ||||||
594 | %args = (); | ||||||
595 | $args{Source} = shift; | ||||||
596 | shift; | ||||||
597 | %args = ( %args, @_ ); | ||||||
598 | } | ||||||
599 | } | ||||||
600 | |||||||
601 | return %args; | ||||||
602 | } | ||||||
603 | |||||||
604 | # private auxiliary function # | ||||||
605 | sub __my_tag_compression | ||||||
606 | { | ||||||
607 | my ( $tag, $elem ) = @_; | ||||||
608 | |||||||
609 | =begin internal_docs | ||||||
610 | |||||||
611 | __my_tag_compression__( $tag, $elem ) | ||||||
612 | |||||||
613 | A function for DOM::XML::setTagCompression to determine the style for printing | ||||||
614 | of empty tags and empty container tags. | ||||||
615 | |||||||
616 | XML::XSLT implements an XHTML-friendly style. | ||||||
617 | |||||||
618 | Allow tag to be preceded by a namespace: ([\w\.]+\:){0,1} | ||||||
619 | |||||||
620 | -> |
||||||
621 | |||||||
622 | or | ||||||
623 | |||||||
624 | |
||||||
625 | |||||||
626 | Empty tag list obtained from: | ||||||
627 | |||||||
628 | http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd | ||||||
629 | |||||||
630 | According to "Appendix C. HTML Compatibility Guidelines", | ||||||
631 | C.3 Element Minimization and Empty Element Content | ||||||
632 | |||||||
633 | Given an empty instance of an element whose content model is not EMPTY | ||||||
634 | (for example, an empty title or paragraph) do not use the minimized form | ||||||
635 | (e.g. use and not ). |
||||||
636 | |||||||
637 | However, the tag is processed like an empty tag here! |
||||||
638 | |||||||
639 | Tags allowed: | ||||||
640 | |||||||
641 | base meta link hr br param img area input col | ||||||
642 | |||||||
643 | Special Case: p (even though it violates C.3) | ||||||
644 | |||||||
645 | The tags are matched in order of expected common occurence. | ||||||
646 | |||||||
647 | =end internal_docs | ||||||
648 | |||||||
649 | =cut | ||||||
650 | |||||||
651 | $tag = [ split ':', $tag ]->[1] if index( $tag, ':' ) >= 0; | ||||||
652 | return 2 if $tag =~ m/^(p|br|img|hr|input|meta|base|link|param|area|col)$/i; | ||||||
653 | |||||||
654 | # Print other empty tags like this: |
||||||
655 | return 1; | ||||||
656 | } | ||||||
657 | |||||||
658 | # private auxiliary function # | ||||||
659 | sub __preprocess_stylesheet | ||||||
660 | { | ||||||
661 | my $self = $_[0]; | ||||||
662 | |||||||
663 | $self->debug("preprocessing stylesheet..."); | ||||||
664 | |||||||
665 | $self->__get_first_element; | ||||||
666 | $self->__extract_namespaces; | ||||||
667 | $self->__get_stylesheet; | ||||||
668 | |||||||
669 | # Why is this here when __get_first_element does, apparently, the same thing? | ||||||
670 | # Because, in __get_stylesheet we warp the document. | ||||||
671 | $self->__expand_xsl_includes; | ||||||
672 | $self->_top_xsl_node( $self->xsl_document()->getFirstChild ); | ||||||
673 | $self->__extract_top_level_variables; | ||||||
674 | |||||||
675 | $self->__add_default_templates; | ||||||
676 | $self->__cache_templates; # speed optim | ||||||
677 | |||||||
678 | $self->__set_xsl_output; | ||||||
679 | } | ||||||
680 | |||||||
681 | sub _top_xsl_node | ||||||
682 | { | ||||||
683 | my ( $self, $top_xsl_node ) = @_; | ||||||
684 | |||||||
685 | if ( defined $top_xsl_node ) | ||||||
686 | { | ||||||
687 | $self->{TOP_XSL_NODE} = $top_xsl_node; | ||||||
688 | } | ||||||
689 | |||||||
690 | return $self->{TOP_XSL_NODE}; | ||||||
691 | } | ||||||
692 | |||||||
693 | # private auxiliary function # | ||||||
694 | |||||||
695 | sub __get_stylesheet | ||||||
696 | { | ||||||
697 | my $self = shift; | ||||||
698 | my $stylesheet; | ||||||
699 | my $xsl_ns = $self->xsl_ns(); | ||||||
700 | my $xsl = $self->xsl_document(); | ||||||
701 | |||||||
702 | foreach my $child ( $xsl->getElementsByTagName( '*', 0 ) ) | ||||||
703 | { | ||||||
704 | my ( $ns, $tag ) = split( ':', $child->getTagName() ); | ||||||
705 | if ( not defined $tag ) | ||||||
706 | { | ||||||
707 | $tag = $ns; | ||||||
708 | $ns = $self->default_ns(); | ||||||
709 | } | ||||||
710 | if ( $tag eq 'stylesheet' || $tag eq 'transform' ) | ||||||
711 | { | ||||||
712 | if ( my $attributes = $child->getAttributes() ) | ||||||
713 | { | ||||||
714 | my $version = $attributes->getNamedItem('version'); | ||||||
715 | |||||||
716 | $self->xslt_version( $version->getNodeValue() ) if $version; | ||||||
717 | } | ||||||
718 | |||||||
719 | $stylesheet = $child; | ||||||
720 | last; | ||||||
721 | } | ||||||
722 | } | ||||||
723 | |||||||
724 | if ( !$stylesheet ) | ||||||
725 | { | ||||||
726 | |||||||
727 | # stylesheet is actually one complete template! | ||||||
728 | # put it in a template-element | ||||||
729 | |||||||
730 | $stylesheet = $xsl->createElement("${xsl_ns}stylesheet"); | ||||||
731 | my $template = $xsl->createElement("${xsl_ns}template"); | ||||||
732 | $template->setAttribute( 'match', "/" ); | ||||||
733 | |||||||
734 | my $template_content = $xsl->getElementsByTagName( '*', 0 )->item(0); | ||||||
735 | $xsl->replaceChild( $stylesheet, $template_content ); | ||||||
736 | $stylesheet->appendChild($template); | ||||||
737 | $template->appendChild($template_content); | ||||||
738 | } | ||||||
739 | |||||||
740 | $self->xsl_document($stylesheet); | ||||||
741 | } | ||||||
742 | |||||||
743 | sub xslt_version | ||||||
744 | { | ||||||
745 | my ( $self, $xslt_version ) = @_; | ||||||
746 | |||||||
747 | if ( defined $xslt_version ) | ||||||
748 | { | ||||||
749 | $self->{XSLT_VERSION} = $xslt_version; | ||||||
750 | } | ||||||
751 | |||||||
752 | return $self->{XSLT_VERSION} ||= '1.0'; | ||||||
753 | } | ||||||
754 | |||||||
755 | # private auxiliary function # | ||||||
756 | sub __get_first_element | ||||||
757 | { | ||||||
758 | my ($self) = @_; | ||||||
759 | my $node = $self->xsl_document()->getFirstChild(); | ||||||
760 | |||||||
761 | $node = $node->getNextSibling until $node->isa( 'XML::DOM::Element' ); | ||||||
762 | |||||||
763 | $self->_top_xsl_node($node); | ||||||
764 | } | ||||||
765 | |||||||
766 | # private auxiliary function # | ||||||
767 | sub __extract_namespaces | ||||||
768 | { | ||||||
769 | my ($self) = @_; | ||||||
770 | |||||||
771 | my $attr = $self->_top_xsl_node()->getAttributes; | ||||||
772 | if ( defined $attr ) | ||||||
773 | { | ||||||
774 | foreach | ||||||
775 | my $attribute ( $self->_top_xsl_node()->getAttributes->getValues ) | ||||||
776 | { | ||||||
777 | my ( $pre, $post ) = split( ":", $attribute->getName, 2 ); | ||||||
778 | my $value = $attribute->getValue; | ||||||
779 | |||||||
780 | # Take care of namespaces | ||||||
781 | if ( $pre eq 'xmlns' and not defined $post ) | ||||||
782 | { | ||||||
783 | $self->default_ns(''); | ||||||
784 | |||||||
785 | $self->{NAMESPACE}->{ $self->default_ns() }->{namespace} = | ||||||
786 | $value; | ||||||
787 | $self->xsl_ns('') | ||||||
788 | if $value eq NS_XSLT; | ||||||
789 | $self->debug( | ||||||
790 | "Namespace `" . $self->default_ns() . "' = `$value'" ); | ||||||
791 | } | ||||||
792 | elsif ( $pre eq 'xmlns' ) | ||||||
793 | { | ||||||
794 | $self->{NAMESPACE}->{$post}->{namespace} = $value; | ||||||
795 | $self->xsl_ns("$post:") | ||||||
796 | if $value eq NS_XSLT; | ||||||
797 | $self->debug("Namespace `$post:' = `$value'"); | ||||||
798 | } | ||||||
799 | else | ||||||
800 | { | ||||||
801 | $self->default_ns(''); | ||||||
802 | } | ||||||
803 | |||||||
804 | # Take care of versions | ||||||
805 | if ( $pre eq "version" and not defined $post ) | ||||||
806 | { | ||||||
807 | $self->{NAMESPACE}->{ $self->default_ns() }->{version} = $value; | ||||||
808 | $self->debug( "Version for namespace `" | ||||||
809 | . $self->default_ns() | ||||||
810 | . "' = `$value'" ); | ||||||
811 | } | ||||||
812 | elsif ( $pre eq "version" ) | ||||||
813 | { | ||||||
814 | $self->{NAMESPACE}->{$post}->{version} = $value; | ||||||
815 | $self->debug("Version for namespace `$post:' = `$value'"); | ||||||
816 | } | ||||||
817 | } | ||||||
818 | } | ||||||
819 | if ( not defined $self->default_ns() ) | ||||||
820 | { | ||||||
821 | my ($dns) = split( ':', $self->_top_xsl_node()->getTagName ); | ||||||
822 | $self->default_ns($dns); | ||||||
823 | } | ||||||
824 | $self->debug( "Default Namespace: `" . $self->default_ns() . "'" ); | ||||||
825 | $self->xsl_ns( $self->default_ns() ) unless $self->xsl_ns(); | ||||||
826 | |||||||
827 | $self->debug( "XSL Namespace: `" . $self->xsl_ns() . "'" ); | ||||||
828 | |||||||
829 | # ** FIXME: is this right? | ||||||
830 | $self->{NAMESPACE}->{ $self->default_ns() }->{namespace} ||= NS_XHTML; | ||||||
831 | } | ||||||
832 | |||||||
833 | =item default_ns | ||||||
834 | |||||||
835 | Gets and/or sets the default namespace to be used in the XSL | ||||||
836 | |||||||
837 | =cut | ||||||
838 | |||||||
839 | sub default_ns | ||||||
840 | { | ||||||
841 | my ( $self, $default_ns ) = @_; | ||||||
842 | |||||||
843 | if ( defined $default_ns ) | ||||||
844 | { | ||||||
845 | $self->{DEFAULT_NS} = $default_ns; | ||||||
846 | } | ||||||
847 | return exists $self->{DEFAULT_NS} ? $self->{DEFAULT_NS} : undef; | ||||||
848 | } | ||||||
849 | |||||||
850 | sub xsl_ns | ||||||
851 | { | ||||||
852 | my ( $self, $prefix ) = @_; | ||||||
853 | |||||||
854 | if ( defined $prefix ) | ||||||
855 | { | ||||||
856 | $prefix .= ':' unless $prefix =~ /:$/; | ||||||
857 | $self->{XSL_NS} = $prefix; | ||||||
858 | } | ||||||
859 | return $self->{XSL_NS}; | ||||||
860 | } | ||||||
861 | |||||||
862 | # private auxiliary function # | ||||||
863 | sub __expand_xsl_includes | ||||||
864 | { | ||||||
865 | my $self = shift; | ||||||
866 | |||||||
867 | $self->debug("IN INCLUDE"); | ||||||
868 | $self->debug($self->xsl_ns()); | ||||||
869 | foreach my $include_node ( $self->xsl_document() # _top_xsl_node() | ||||||
870 | ->getElementsByTagName( $self->xsl_ns() . "include" ) ) | ||||||
871 | { | ||||||
872 | my $include_file = $include_node->getAttribute('href'); | ||||||
873 | |||||||
874 | $self->debug("including - $include_file"); | ||||||
875 | die "include tag carries no selection!" | ||||||
876 | unless defined $include_file; | ||||||
877 | |||||||
878 | my $include_doc; | ||||||
879 | my $tmp_doc; | ||||||
880 | eval { | ||||||
881 | $tmp_doc = | ||||||
882 | $self->__open_by_filename( $include_file, $self->{XSL_BASE} ); | ||||||
883 | $include_doc = $tmp_doc->getFirstChild(); | ||||||
884 | #$tmp_doc->removeChild($include_doc); | ||||||
885 | }; | ||||||
886 | die "parsing of $include_file failed: $@" | ||||||
887 | if $@; | ||||||
888 | |||||||
889 | $self->debug("inserting `$include_file'"); | ||||||
890 | #$self->xsl_document()->setOwnerDocument($include_doc); | ||||||
891 | $include_doc->setOwnerDocument( $self->{ORIG_XSL_DOC} ); | ||||||
892 | $self->xsl_document()->replaceChild( $include_doc, $include_node ); | ||||||
893 | #$include_doc->dispose; | ||||||
894 | } | ||||||
895 | } | ||||||
896 | |||||||
897 | # private auxiliary function # | ||||||
898 | sub __extract_top_level_variables | ||||||
899 | { | ||||||
900 | my $self = $_[0]; | ||||||
901 | |||||||
902 | $self->debug("Extracting variables"); | ||||||
903 | foreach my $child ( $self->xsl_document()->getChildNodes() ) | ||||||
904 | { | ||||||
905 | next unless $child->getNodeType() == ELEMENT_NODE; | ||||||
906 | my $name = $child->getNodeName(); | ||||||
907 | my ( $ns, $tag ) = split( ':', $name ); | ||||||
908 | |||||||
909 | $self->debug("$ns $tag"); | ||||||
910 | # ( $tag eq '' && $self->xsl_ns() eq '' ) | ||||||
911 | # || $self->xsl_ns() eq $ns ) | ||||||
912 | if (1) | ||||||
913 | { | ||||||
914 | $tag = $ns if $tag eq ''; | ||||||
915 | |||||||
916 | $self->debug($tag); | ||||||
917 | if ( $tag eq 'variable' || $tag eq 'param' ) | ||||||
918 | { | ||||||
919 | |||||||
920 | my $name = $child->getAttribute("name"); | ||||||
921 | if ( exists $self->{VARIABLES}->{$name} ) | ||||||
922 | { | ||||||
923 | $self->debug( | ||||||
924 | "$tag $name already set to '$self->{VARIABLES}->{$name}'"); | ||||||
925 | } | ||||||
926 | elsif ($name) | ||||||
927 | { | ||||||
928 | $self->debug("got $tag called $name"); | ||||||
929 | my $value = $child->getAttributeNode("select"); | ||||||
930 | if ( !defined $value ) | ||||||
931 | { | ||||||
932 | $self->debug("evaluating variable from child nodes"); | ||||||
933 | if ( $child->getChildNodes()->getLength() ) | ||||||
934 | { | ||||||
935 | my $result = XML::DOM::DocumentFragment->new(); | ||||||
936 | |||||||
937 | #$self->xml_document()->createDocumentFragment; | ||||||
938 | $self->_evaluate_template( $child, $self->xml_document(), | ||||||
939 | '', $result ); | ||||||
940 | $value = $self->__string__($result); | ||||||
941 | $result->dispose(); | ||||||
942 | } | ||||||
943 | } | ||||||
944 | else | ||||||
945 | { | ||||||
946 | $self->debug("Trying to get a literal"); | ||||||
947 | $value = $value->getValue(); | ||||||
948 | if ( $value =~ /^'([^']*)'$/m ) | ||||||
949 | { | ||||||
950 | $value = $1; | ||||||
951 | } | ||||||
952 | } | ||||||
953 | if ( defined $value ) | ||||||
954 | { | ||||||
955 | $self->debug("Setting $tag `$name' = `$value'"); | ||||||
956 | $self->{VARIABLES}->{$name} = $value; | ||||||
957 | } | ||||||
958 | } | ||||||
959 | else | ||||||
960 | { | ||||||
961 | |||||||
962 | # Required, so we die (http://www.w3.org/TR/xslt#variables) | ||||||
963 | die "$tag tag carries no name!"; | ||||||
964 | } | ||||||
965 | } | ||||||
966 | } | ||||||
967 | } | ||||||
968 | } | ||||||
969 | |||||||
970 | # private auxiliary function # | ||||||
971 | sub __add_default_templates | ||||||
972 | { | ||||||
973 | my $self = $_[0]; | ||||||
974 | my $doc = $self->_top_xsl_node()->getOwnerDocument; | ||||||
975 | |||||||
976 | # create template for '*' and '/' | ||||||
977 | my $elem_template = $doc->createElement( $self->xsl_ns() . "template" ); | ||||||
978 | $elem_template->setAttribute( 'match', '*|/' ); | ||||||
979 | |||||||
980 | # |
||||||
981 | $elem_template->appendChild( | ||||||
982 | $doc->createElement( $self->xsl_ns() . "apply-templates" ) ); | ||||||
983 | |||||||
984 | # create template for 'text()' and '@*' | ||||||
985 | my $attr_template = $doc->createElement( $self->xsl_ns() . "template" ); | ||||||
986 | $attr_template->setAttribute( 'match', 'text()|@*' ); | ||||||
987 | |||||||
988 | # |
||||||
989 | $attr_template->appendChild( | ||||||
990 | $doc->createElement( $self->xsl_ns() . "value-of" ) ); | ||||||
991 | $attr_template->getFirstChild->setAttribute( 'select', '.' ); | ||||||
992 | |||||||
993 | # create template for 'processing-instruction()' and 'comment()' | ||||||
994 | my $pi_template = $doc->createElement( $self->xsl_ns() . "template" ); | ||||||
995 | $pi_template->setAttribute( 'match', 'processing-instruction()|comment()' ); | ||||||
996 | |||||||
997 | $self->debug("adding default templates to stylesheet"); | ||||||
998 | |||||||
999 | # add them to the stylesheet | ||||||
1000 | $self->xsl_document()->insertBefore( $pi_template, $self->_top_xsl_node ); | ||||||
1001 | $self->xsl_document() | ||||||
1002 | ->insertBefore( $attr_template, $self->_top_xsl_node() ); | ||||||
1003 | $self->xsl_document() | ||||||
1004 | ->insertBefore( $elem_template, $self->_top_xsl_node() ); | ||||||
1005 | } | ||||||
1006 | |||||||
1007 | =item | ||||||
1008 | |||||||
1009 | Returns the templates from the XSL document. | ||||||
1010 | |||||||
1011 | =cut | ||||||
1012 | |||||||
1013 | sub templates | ||||||
1014 | { | ||||||
1015 | my ( $self, $templates ) = @_; | ||||||
1016 | |||||||
1017 | if ( defined $templates ) | ||||||
1018 | { | ||||||
1019 | $self->{TEMPLATE} = $templates; | ||||||
1020 | } | ||||||
1021 | |||||||
1022 | $self->debug("templates() called from : " . (caller(1))[3]); | ||||||
1023 | unless ( exists $self->{TEMPLATE} ) | ||||||
1024 | { | ||||||
1025 | $self->{TEMPLATE} = []; | ||||||
1026 | my $xsld = $self->xsl_document(); | ||||||
1027 | my $tag = $self->xsl_ns() . 'template'; | ||||||
1028 | |||||||
1029 | $self->debug("getting $tag"); | ||||||
1030 | @{ $self->{TEMPLATE} } = $xsld->getElementsByTagName($tag); | ||||||
1031 | } | ||||||
1032 | |||||||
1033 | return wantarray ? @{ $self->{TEMPLATE} } : $self->{TEMPLATE}; | ||||||
1034 | } | ||||||
1035 | |||||||
1036 | # private auxiliary function # | ||||||
1037 | sub __cache_templates | ||||||
1038 | { | ||||||
1039 | my $self = $_[0]; | ||||||
1040 | |||||||
1041 | # pre-cache template names and matches # | ||||||
1042 | # reversing the template order is much more efficient # | ||||||
1043 | |||||||
1044 | foreach my $template ( reverse $self->templates() ) | ||||||
1045 | { | ||||||
1046 | next unless $template->getParentNode(); | ||||||
1047 | if ( $template->getParentNode->getTagName =~ | ||||||
1048 | /^([\w\.\-]+\:){0,1}(stylesheet|transform|include)/ ) | ||||||
1049 | { | ||||||
1050 | my $match = $template->getAttribute('match') || ''; | ||||||
1051 | my $name = $template->getAttribute('name') || ''; | ||||||
1052 | push( @{ $self->{TEMPLATE_MATCH} }, $match ); | ||||||
1053 | push( @{ $self->{TEMPLATE_NAME} }, $name ); | ||||||
1054 | } | ||||||
1055 | } | ||||||
1056 | } | ||||||
1057 | |||||||
1058 | |||||||
1059 | =item xsl_output_method | ||||||
1060 | |||||||
1061 | Get or set the | ||||||
1062 | 'text' and 'xml' | ||||||
1063 | |||||||
1064 | =cut | ||||||
1065 | |||||||
1066 | sub xsl_output_method | ||||||
1067 | { | ||||||
1068 | my ( $self, $method) = @_; | ||||||
1069 | |||||||
1070 | if (defined $method and $method =~ /(?:html|text|xml)/ ) | ||||||
1071 | { | ||||||
1072 | $self->{METHOD} = $method; | ||||||
1073 | } | ||||||
1074 | |||||||
1075 | return exists $self->{METHOD} ? $self->{METHOD} : 'xml'; | ||||||
1076 | } | ||||||
1077 | |||||||
1078 | # private auxiliary function # | ||||||
1079 | sub __set_xsl_output | ||||||
1080 | { | ||||||
1081 | my $self = $_[0]; | ||||||
1082 | |||||||
1083 | # default settings | ||||||
1084 | $self->media_type('text/xml'); | ||||||
1085 | |||||||
1086 | # extraction of top-level xsl:output tag | ||||||
1087 | my ($output) = | ||||||
1088 | $self->xsl_document() | ||||||
1089 | ->getElementsByTagName( $self->xsl_ns() . "output", 0 ); | ||||||
1090 | |||||||
1091 | if ( defined $output ) | ||||||
1092 | { | ||||||
1093 | |||||||
1094 | # extraction and processing of the attributes | ||||||
1095 | my $attribs = $output->getAttributes; | ||||||
1096 | my $media = $attribs->getNamedItem('media-type'); | ||||||
1097 | my $method = $attribs->getNamedItem('method'); | ||||||
1098 | $self->media_type( $media->getNodeValue ) if defined $media; | ||||||
1099 | $self->xsl_output_method($method->getNodeValue) if defined $method; | ||||||
1100 | |||||||
1101 | if ( my $omit = $attribs->getNamedItem('omit-xml-declaration') ) | ||||||
1102 | { | ||||||
1103 | if ( $omit->getNodeValue() =~ /^(yes|no)$/ ) | ||||||
1104 | { | ||||||
1105 | $self->omit_xml_declaration($1); | ||||||
1106 | } | ||||||
1107 | else | ||||||
1108 | { | ||||||
1109 | |||||||
1110 | # I would say that this should be fatal | ||||||
1111 | # Perhaps there should be a 'strict' option to the constructor | ||||||
1112 | |||||||
1113 | my $m = | ||||||
1114 | qq{Wrong value for attribute "omit-xml-declaration" in\n\t} | ||||||
1115 | . $self->xsl_ns() | ||||||
1116 | . qq{output, should be "yes" or "no"}; | ||||||
1117 | $self->warn($m); | ||||||
1118 | } | ||||||
1119 | } | ||||||
1120 | |||||||
1121 | unless ( $self->omit_xml_declaration() ) | ||||||
1122 | { | ||||||
1123 | my $output_ver = $attribs->getNamedItem('version'); | ||||||
1124 | my $output_enc = $attribs->getNamedItem('encoding'); | ||||||
1125 | $self->output_version( $output_ver->getNodeValue ) | ||||||
1126 | if defined $output_ver; | ||||||
1127 | $self->output_encoding( $output_enc->getNodeValue ) | ||||||
1128 | if defined $output_enc; | ||||||
1129 | |||||||
1130 | if ( not $self->output_version() || not $self->output_encoding() ) | ||||||
1131 | { | ||||||
1132 | $self->warn( | ||||||
1133 | qq{Expected attributes "version" and "encoding" in\n\t} | ||||||
1134 | . $self->xsl_ns() | ||||||
1135 | . "output" ); | ||||||
1136 | } | ||||||
1137 | } | ||||||
1138 | my $doctype_public = $attribs->getNamedItem('doctype-public'); | ||||||
1139 | my $doctype_system = $attribs->getNamedItem('doctype-system'); | ||||||
1140 | |||||||
1141 | my $dp = defined $doctype_public ? $doctype_public->getNodeValue : ''; | ||||||
1142 | |||||||
1143 | $self->doctype_public($dp); | ||||||
1144 | |||||||
1145 | my $ds = defined $doctype_system ? $doctype_system->getNodeValue : ''; | ||||||
1146 | $self->doctype_system($ds); | ||||||
1147 | |||||||
1148 | # cdata-section-elements should only be used if the output type | ||||||
1149 | # is XML but as we are not checking that right now ... | ||||||
1150 | |||||||
1151 | my $cdata_section = $attribs->getNamedItem('cdata-section-elements'); | ||||||
1152 | |||||||
1153 | if ( defined $cdata_section ) | ||||||
1154 | { | ||||||
1155 | my $cdata_sections = []; | ||||||
1156 | @{$cdata_sections} = split /\s+/, $cdata_section->getNodeValue(); | ||||||
1157 | $self->cdata_sections($cdata_sections); | ||||||
1158 | } | ||||||
1159 | } | ||||||
1160 | else | ||||||
1161 | { | ||||||
1162 | $self->debug("Default Output options being used"); | ||||||
1163 | } | ||||||
1164 | } | ||||||
1165 | |||||||
1166 | sub omit_xml_declaration | ||||||
1167 | { | ||||||
1168 | my ( $self, $omit_xml_declaration ) = @_; | ||||||
1169 | |||||||
1170 | if ( defined $omit_xml_declaration ) | ||||||
1171 | { | ||||||
1172 | if ( $omit_xml_declaration =~ /^(yes|no)$/ ) | ||||||
1173 | { | ||||||
1174 | $self->{OMIT_XML_DECL} = ( $1 eq 'yes' ); | ||||||
1175 | } | ||||||
1176 | else | ||||||
1177 | { | ||||||
1178 | $self->{OMIT_XML_DECL} = $omit_xml_declaration ? 1 : 0; | ||||||
1179 | } | ||||||
1180 | } | ||||||
1181 | |||||||
1182 | return exists $self->{OMIT_XML_DECL} ? $self->{OMIT_XML_DECL} : 0; | ||||||
1183 | } | ||||||
1184 | |||||||
1185 | =item cdata_sections | ||||||
1186 | |||||||
1187 | Get or set the element names supplied via the cdata-section-elements | ||||||
1188 | attribute (i.e. a space separated list of element names.) | ||||||
1189 | |||||||
1190 | =cut | ||||||
1191 | |||||||
1192 | sub cdata_sections | ||||||
1193 | { | ||||||
1194 | my ( $self, $cdata_sections ) = @_; | ||||||
1195 | |||||||
1196 | if ( defined $cdata_sections ) | ||||||
1197 | { | ||||||
1198 | $self->{CDATA_SECTIONS} = $cdata_sections; | ||||||
1199 | } | ||||||
1200 | |||||||
1201 | $self->{CDATA_SECTIONS} = [] unless exists $self->{CDATA_SECTIONS}; | ||||||
1202 | |||||||
1203 | return wantarray() ? @{ $self->{CDATA_SECTIONS} } : $self->{CDATA_SECTIONS}; | ||||||
1204 | } | ||||||
1205 | |||||||
1206 | sub is_cdata_section | ||||||
1207 | { | ||||||
1208 | my ( $self, $element ) = @_; | ||||||
1209 | |||||||
1210 | my %cdata_sections; | ||||||
1211 | |||||||
1212 | my @cdata_temp = $self->cdata_sections(); | ||||||
1213 | @cdata_sections{@cdata_temp} = (1) x @cdata_temp; | ||||||
1214 | |||||||
1215 | my $tagname; | ||||||
1216 | |||||||
1217 | if ( defined $element and ref($element) and ref($element) eq 'XML::DOM' ) | ||||||
1218 | { | ||||||
1219 | $tagname = $element->getTagName(); | ||||||
1220 | } | ||||||
1221 | else | ||||||
1222 | { | ||||||
1223 | $tagname = $element; | ||||||
1224 | } | ||||||
1225 | |||||||
1226 | # Will need to do namespace checking on this really | ||||||
1227 | |||||||
1228 | return exists $cdata_sections{$tagname} ? 1 : 0; | ||||||
1229 | } | ||||||
1230 | |||||||
1231 | =item output_version | ||||||
1232 | |||||||
1233 | Gets and/or sets the XML version that will be used for the output | ||||||
1234 | (defaults to default_xml_version()) | ||||||
1235 | |||||||
1236 | =cut | ||||||
1237 | |||||||
1238 | sub output_version | ||||||
1239 | { | ||||||
1240 | my ( $self, $output_version ) = @_; | ||||||
1241 | |||||||
1242 | if ( defined $output_version ) | ||||||
1243 | { | ||||||
1244 | $self->{OUTPUT_VERSION} = $output_version; | ||||||
1245 | } | ||||||
1246 | |||||||
1247 | return exists $self->{OUTPUT_VERSION} | ||||||
1248 | ? $self->{OUTPUT_VERSION} | ||||||
1249 | : $self->default_xml_version(); | ||||||
1250 | } | ||||||
1251 | |||||||
1252 | sub __get_attribute_sets | ||||||
1253 | { | ||||||
1254 | my ($self) = @_; | ||||||
1255 | |||||||
1256 | my $doc = $self->xsl_document(); | ||||||
1257 | my $nsp = $self->xsl_ns(); | ||||||
1258 | my $tagname = $nsp . 'attribute-set'; | ||||||
1259 | my %inc; | ||||||
1260 | my @included; | ||||||
1261 | foreach my $attribute_set ( $doc->getElementsByTagName( $tagname, 0 ) ) | ||||||
1262 | { | ||||||
1263 | my $attribs = $attribute_set->getAttributes(); | ||||||
1264 | next unless defined $attribs; | ||||||
1265 | my $name_attr = $attribs->getNamedItem('name'); | ||||||
1266 | next unless defined $name_attr; | ||||||
1267 | my $name = $name_attr->getValue(); | ||||||
1268 | $self->debug("processing attribute-set $name"); | ||||||
1269 | |||||||
1270 | if ( my $uas = $attribs->getNamedItem('use-attribute-sets') ) | ||||||
1271 | { | ||||||
1272 | $self->_indent(); | ||||||
1273 | $inc{$name} = $uas->getValue(); | ||||||
1274 | $self->debug("Attribute set $name includes $inc{$name}"); | ||||||
1275 | push @included, $name; | ||||||
1276 | $self->_outdent(); | ||||||
1277 | } | ||||||
1278 | |||||||
1279 | my $attr_set = {}; | ||||||
1280 | |||||||
1281 | my $tagname = $nsp . 'attribute'; | ||||||
1282 | |||||||
1283 | foreach | ||||||
1284 | my $attribute ( $attribute_set->getElementsByTagName( $tagname, 0 ) ) | ||||||
1285 | { | ||||||
1286 | my $attribs = $attribute->getAttributes(); | ||||||
1287 | next unless defined $attribs; | ||||||
1288 | my $name_attr = $attribs->getNamedItem('name'); | ||||||
1289 | next unless defined $name_attr; | ||||||
1290 | my $attr_name = $name_attr->getValue(); | ||||||
1291 | $self->debug("Processing attribute $attr_name"); | ||||||
1292 | if ($attr_name) | ||||||
1293 | { | ||||||
1294 | my $result = $self->xml_document()->createDocumentFragment(); | ||||||
1295 | $self->_evaluate_template( $attribute, $self->xml_document(), | ||||||
1296 | '/', $result ); # might need variables | ||||||
1297 | my $value = | ||||||
1298 | $self->fix_attribute_value( $self->__string__($result) ); | ||||||
1299 | $attr_set->{$attr_name} = $value; | ||||||
1300 | $result->dispose(); | ||||||
1301 | $self->debug("Adding attribute $attr_name with value $value"); | ||||||
1302 | } | ||||||
1303 | } | ||||||
1304 | |||||||
1305 | $self->__attribute_set_( $name, $attr_set ); | ||||||
1306 | |||||||
1307 | } | ||||||
1308 | foreach my $as (@included ) | ||||||
1309 | { | ||||||
1310 | $self->_indent(); | ||||||
1311 | $self->debug("adding attributes from $inc{$as} to $as"); | ||||||
1312 | my %fix = (%{$self->__attribute_set_($as)},%{$self->__attribute_set_($inc{$as})}); | ||||||
1313 | $self->__attribute_set_($as,\%fix); | ||||||
1314 | $self->_outdent(); | ||||||
1315 | } | ||||||
1316 | } | ||||||
1317 | |||||||
1318 | # Accessor for attribute sets | ||||||
1319 | |||||||
1320 | sub __attribute_set_ | ||||||
1321 | { | ||||||
1322 | my ( $self, $name, $attr_hash ) = @_; | ||||||
1323 | |||||||
1324 | if ( defined $attr_hash && defined $name ) | ||||||
1325 | { | ||||||
1326 | if ( exists $self->{ATTRIBUTE_SETS}->{$name} ) | ||||||
1327 | { | ||||||
1328 | %{$self->{ATTRIBUTE_SETS}->{$name}} = | ||||||
1329 | ( %{$self->{ATTRIBUTE_SETS}->{$name}}, %{$attr_hash}); | ||||||
1330 | } | ||||||
1331 | else | ||||||
1332 | { | ||||||
1333 | $self->{ATTRIBUTE_SETS}->{$name} = $attr_hash; | ||||||
1334 | } | ||||||
1335 | } | ||||||
1336 | |||||||
1337 | return defined $name | ||||||
1338 | && exists $self->{ATTRIBUTE_SETS}->{$name} | ||||||
1339 | ? $self->{ATTRIBUTE_SETS}->{$name} | ||||||
1340 | : undef; | ||||||
1341 | } | ||||||
1342 | |||||||
1343 | sub open_project | ||||||
1344 | { | ||||||
1345 | my $self = shift; | ||||||
1346 | my $xml = shift; | ||||||
1347 | my $xsl = shift; | ||||||
1348 | my ( $xmlflag, $xslflag, %args ) = @_; | ||||||
1349 | |||||||
1350 | carp "open_project is deprecated." | ||||||
1351 | unless $self->use_deprecated() | ||||||
1352 | or exists $deprecation_used{open_project}; | ||||||
1353 | $deprecation_used{open_project} = 1; | ||||||
1354 | |||||||
1355 | $self->debug("opening project:"); | ||||||
1356 | $self->_indent(); | ||||||
1357 | |||||||
1358 | $self->open_xml( $xml, %args ); | ||||||
1359 | $self->open_xsl( $xsl, %args ); | ||||||
1360 | |||||||
1361 | $self->debug("done..."); | ||||||
1362 | $self->_outdent(); | ||||||
1363 | } | ||||||
1364 | |||||||
1365 | =item transform(Source => $xml [, %args]) | ||||||
1366 | |||||||
1367 | Processes the given XML through the stylesheet. Returns an XML::DOM | ||||||
1368 | object corresponding to the transformed XML. Any arguments present | ||||||
1369 | are passed to the XML::DOM::Parser. | ||||||
1370 | |||||||
1371 | =cut | ||||||
1372 | |||||||
1373 | sub transform | ||||||
1374 | { | ||||||
1375 | my $self = shift; | ||||||
1376 | |||||||
1377 | if ( keys %{$self->{VARIABLES}} ) | ||||||
1378 | { | ||||||
1379 | $self->debug("Adding variables"); | ||||||
1380 | push @_,'variables', $self->{VARIABLES}; | ||||||
1381 | } | ||||||
1382 | |||||||
1383 | my %topvariables = $self->__parse_args(@_); | ||||||
1384 | |||||||
1385 | $self->debug("transforming document:"); | ||||||
1386 | $self->_indent(); | ||||||
1387 | |||||||
1388 | $self->open_xml(%topvariables); | ||||||
1389 | |||||||
1390 | $self->debug("done..."); | ||||||
1391 | $self->_outdent(); | ||||||
1392 | |||||||
1393 | # The _get_attribute_set needs an open XML document | ||||||
1394 | |||||||
1395 | $self->_indent(); | ||||||
1396 | $self->__get_attribute_sets(); | ||||||
1397 | $self->_outdent(); | ||||||
1398 | |||||||
1399 | $self->debug("processing project:"); | ||||||
1400 | $self->_indent(); | ||||||
1401 | |||||||
1402 | $self->process(%topvariables); | ||||||
1403 | |||||||
1404 | $self->debug("done!"); | ||||||
1405 | $self->_outdent(); | ||||||
1406 | $self->result_document()->normalize(); | ||||||
1407 | return $self->result_document(); | ||||||
1408 | } | ||||||
1409 | |||||||
1410 | =item process(%variables) | ||||||
1411 | |||||||
1412 | Processes the previously loaded XML through the stylesheet using the | ||||||
1413 | variables set in the argument. | ||||||
1414 | |||||||
1415 | =cut | ||||||
1416 | |||||||
1417 | sub process | ||||||
1418 | { | ||||||
1419 | my ( $self, %topvariables ) = @_; | ||||||
1420 | |||||||
1421 | $self->debug("processing project:"); | ||||||
1422 | $self->_indent(); | ||||||
1423 | |||||||
1424 | my $root_template = $self->_match_template( "match", '/', 1, '' ); | ||||||
1425 | |||||||
1426 | $self->debug(join ' ', keys %topvariables); | ||||||
1427 | %topvariables = ( | ||||||
1428 | defined $topvariables{variables} ? %{$topvariables{variables}} : (), | ||||||
1429 | defined $self->{VARIABLES} | ||||||
1430 | && ref $self->{VARIABLES} | ||||||
1431 | && ref $self->{VARIABLES} eq 'ARRAY' ? @{ $self->{VARIABLES} } : () | ||||||
1432 | ); | ||||||
1433 | |||||||
1434 | $self->debug(join ' ', keys %topvariables); | ||||||
1435 | |||||||
1436 | |||||||
1437 | $self->_evaluate_template( | ||||||
1438 | $root_template, # starting template: the root template | ||||||
1439 | $self->xml_document(), | ||||||
1440 | '', # current XML selection path: the root | ||||||
1441 | $self->result_document(), # current result tree node: the root | ||||||
1442 | { () }, # current known variables: none | ||||||
1443 | \%topvariables # previously known variables: top level variables | ||||||
1444 | ); | ||||||
1445 | |||||||
1446 | $self->debug("done!"); | ||||||
1447 | $self->_outdent(); | ||||||
1448 | } | ||||||
1449 | |||||||
1450 | # Handles deprecations. | ||||||
1451 | sub AUTOLOAD | ||||||
1452 | { | ||||||
1453 | my $self = shift; | ||||||
1454 | my $type = ref($self) || croak "Not a method call"; | ||||||
1455 | my $name = $AUTOLOAD; | ||||||
1456 | $name =~ s/.*://; | ||||||
1457 | |||||||
1458 | my %deprecation = ( | ||||||
1459 | 'output_string' => 'toString', | ||||||
1460 | 'result_string' => 'toString', | ||||||
1461 | 'output' => 'toString', | ||||||
1462 | 'result' => 'toString', | ||||||
1463 | 'result_mime_type' => 'media_type', | ||||||
1464 | 'output_mime_type' => 'media_type', | ||||||
1465 | 'result_tree' => 'to_dom', | ||||||
1466 | 'output_tree' => 'to_dom', | ||||||
1467 | 'transform_document' => 'transform', | ||||||
1468 | 'process_project' => 'process' | ||||||
1469 | ); | ||||||
1470 | |||||||
1471 | if ( exists $deprecation{$name} ) | ||||||
1472 | { | ||||||
1473 | carp "$name is deprecated. Use $deprecation{$name}" | ||||||
1474 | unless $self->use_deprecated() | ||||||
1475 | or exists $deprecation_used{$name}; | ||||||
1476 | $deprecation_used{$name} = 1; | ||||||
1477 | eval qq{return \$self->$deprecation{$name}(\@_)}; | ||||||
1478 | } | ||||||
1479 | else | ||||||
1480 | { | ||||||
1481 | croak "$name: No such method name"; | ||||||
1482 | } | ||||||
1483 | } | ||||||
1484 | |||||||
1485 | sub _my_print_text | ||||||
1486 | { | ||||||
1487 | my ( $self, $FILE ) = @_; | ||||||
1488 | |||||||
1489 | if ( UNIVERSAL::isa( $self, "XML::DOM::CDATASection" ) ) | ||||||
1490 | { | ||||||
1491 | $FILE->print( $self->getData() ); | ||||||
1492 | } | ||||||
1493 | else | ||||||
1494 | { | ||||||
1495 | $FILE->print( XML::DOM::encodeText( $self->getData(), "<&" ) ); | ||||||
1496 | } | ||||||
1497 | } | ||||||
1498 | |||||||
1499 | =item toString | ||||||
1500 | |||||||
1501 | Returns the result of transforming the XML with the stylesheet as a | ||||||
1502 | string. | ||||||
1503 | |||||||
1504 | =cut | ||||||
1505 | |||||||
1506 | sub toString | ||||||
1507 | { | ||||||
1508 | my $self = $_[0]; | ||||||
1509 | |||||||
1510 | local $^W; | ||||||
1511 | no warnings 'redefine'; | ||||||
1512 | local *XML::DOM::Text::print = \&_my_print_text; | ||||||
1513 | |||||||
1514 | my $string = ''; | ||||||
1515 | |||||||
1516 | if (defined $self->result_document() ) | ||||||
1517 | { | ||||||
1518 | $string = $self->result_document()->toString(); | ||||||
1519 | } | ||||||
1520 | return $string; | ||||||
1521 | } | ||||||
1522 | |||||||
1523 | =item to_dom | ||||||
1524 | |||||||
1525 | Returns the result of transforming the XML with the stylesheet as an | ||||||
1526 | XML::DOM object. | ||||||
1527 | |||||||
1528 | =cut | ||||||
1529 | |||||||
1530 | sub to_dom | ||||||
1531 | { | ||||||
1532 | my ($self) = @_; | ||||||
1533 | |||||||
1534 | my $document = XML::DOM::Document->new(); | ||||||
1535 | |||||||
1536 | my $dom = $self->result_document()->cloneNode(1); | ||||||
1537 | $dom->setOwnerDocument($document); | ||||||
1538 | $document->appendChild($dom); | ||||||
1539 | return $document; | ||||||
1540 | } | ||||||
1541 | |||||||
1542 | =item media_type | ||||||
1543 | |||||||
1544 | Returns the media type (aka mime type) of the object. | ||||||
1545 | |||||||
1546 | =cut | ||||||
1547 | |||||||
1548 | sub media_type | ||||||
1549 | { | ||||||
1550 | my ( $self, $media_type ) = @_; | ||||||
1551 | |||||||
1552 | if ( defined $media_type ) | ||||||
1553 | { | ||||||
1554 | $self->{MEDIA_TYPE} = $media_type; | ||||||
1555 | } | ||||||
1556 | |||||||
1557 | return $self->{MEDIA_TYPE}; | ||||||
1558 | } | ||||||
1559 | |||||||
1560 | sub print_output | ||||||
1561 | { | ||||||
1562 | my ( $self, $file, $mime ) = @_; | ||||||
1563 | $file ||= ''; # print to STDOUT by default | ||||||
1564 | $mime = 1 unless defined $mime; | ||||||
1565 | |||||||
1566 | # print mime-type header etc by default | ||||||
1567 | |||||||
1568 | # $self->{RESULT_DOCUMENT}->printToFileHandle (\*STDOUT); | ||||||
1569 | # or $self->{RESULT_DOCUMENT}->print (\*STDOUT); ??? | ||||||
1570 | # exit; | ||||||
1571 | |||||||
1572 | carp "print_output is deprecated. Use serve." | ||||||
1573 | unless $self->use_deprecated() | ||||||
1574 | or exists $deprecation_used{print_output}; | ||||||
1575 | $deprecation_used{print_output} = 1; | ||||||
1576 | |||||||
1577 | if ($mime) | ||||||
1578 | { | ||||||
1579 | print "Content-type: " . $self->media_type() . "\n\n"; | ||||||
1580 | |||||||
1581 | if ( $self->xsl_output_method =~ /(?:xml|html)/ ) | ||||||
1582 | { | ||||||
1583 | unless ( $self->omit_xml_declaration() ) | ||||||
1584 | { | ||||||
1585 | print $self->xml_declaration(), "\n"; | ||||||
1586 | } | ||||||
1587 | } | ||||||
1588 | |||||||
1589 | if ( my $doctype = $self->doctype() ) | ||||||
1590 | { | ||||||
1591 | print "$doctype\n"; | ||||||
1592 | } | ||||||
1593 | } | ||||||
1594 | |||||||
1595 | if ($file) | ||||||
1596 | { | ||||||
1597 | if ( ref( \$file ) eq 'SCALAR' ) | ||||||
1598 | { | ||||||
1599 | print $file $self->output_string, "\n"; | ||||||
1600 | } | ||||||
1601 | else | ||||||
1602 | { | ||||||
1603 | if ( open( FILE, ">$file" ) ) | ||||||
1604 | { | ||||||
1605 | print FILE $self->output_string, "\n"; | ||||||
1606 | if ( !close(FILE) ) | ||||||
1607 | { | ||||||
1608 | die("Error writing $file: $!. Nothing written...\n"); | ||||||
1609 | } | ||||||
1610 | } | ||||||
1611 | else | ||||||
1612 | { | ||||||
1613 | die("Error opening $file: $!. Nothing done...\n"); | ||||||
1614 | } | ||||||
1615 | } | ||||||
1616 | } | ||||||
1617 | else | ||||||
1618 | { | ||||||
1619 | print $self->output_string, "\n"; | ||||||
1620 | } | ||||||
1621 | } | ||||||
1622 | |||||||
1623 | =item print_result | ||||||
1624 | |||||||
1625 | An alias for print_output | ||||||
1626 | |||||||
1627 | =cut | ||||||
1628 | |||||||
1629 | *print_result = *print_output; | ||||||
1630 | |||||||
1631 | sub doctype | ||||||
1632 | { | ||||||
1633 | my ($self) = @_; | ||||||
1634 | |||||||
1635 | my $doctype = ""; | ||||||
1636 | |||||||
1637 | if ( $self->doctype_public() || $self->doctype_system() ) | ||||||
1638 | { | ||||||
1639 | my $root_name = | ||||||
1640 | $self->result_document()->getElementsByTagName( '*', 0 )->item(0) | ||||||
1641 | ->getTagName; | ||||||
1642 | |||||||
1643 | if ( $self->doctype_public() ) | ||||||
1644 | { | ||||||
1645 | $doctype = | ||||||
1646 | qq{ | ||||||
1647 | . $self->doctype_public() . qq{" "} | ||||||
1648 | . $self->doctype_system() . qq{">}; | ||||||
1649 | } | ||||||
1650 | else | ||||||
1651 | { | ||||||
1652 | $doctype = | ||||||
1653 | qq{ | ||||||
1654 | . $self->doctype_system() . qq{">}; | ||||||
1655 | } | ||||||
1656 | } | ||||||
1657 | |||||||
1658 | $self->debug("returning doctype of $doctype"); | ||||||
1659 | return $doctype; | ||||||
1660 | } | ||||||
1661 | |||||||
1662 | =item dispose | ||||||
1663 | |||||||
1664 | Executes the C |
||||||
1665 | |||||||
1666 | =cut | ||||||
1667 | |||||||
1668 | sub dispose | ||||||
1669 | { | ||||||
1670 | |||||||
1671 | $_[0]->result_document()->dispose if ( defined $_[0]->result_document() ); | ||||||
1672 | |||||||
1673 | if ( (not defined $_[0]->{XML_PASSED_AS_DOM} ) | ||||||
1674 | and defined $_[0]->xml_document() ) | ||||||
1675 | { | ||||||
1676 | $_[0]->xml_document()->dispose; | ||||||
1677 | } | ||||||
1678 | |||||||
1679 | if ( (not defined $_[0]->{XSL_PASSED_AS_DOM} ) | ||||||
1680 | and defined $_[0]->xsl_document() ) | ||||||
1681 | { | ||||||
1682 | $_[0]->xsl_document()->dispose; | ||||||
1683 | } | ||||||
1684 | |||||||
1685 | $_[0]->_top_xsl_node()->dispose() if defined $_[0]->_top_xsl_node(); | ||||||
1686 | |||||||
1687 | |||||||
1688 | |||||||
1689 | foreach my $topkey ( %{$_[0]} ) | ||||||
1690 | { | ||||||
1691 | $_[0]->{$topkey} = undef if defined $topkey; | ||||||
1692 | } | ||||||
1693 | |||||||
1694 | $_[0] = undef; | ||||||
1695 | } | ||||||
1696 | |||||||
1697 | ###################################################################### | ||||||
1698 | # PRIVATE DEFINITIONS | ||||||
1699 | |||||||
1700 | sub __open_document | ||||||
1701 | { | ||||||
1702 | my $self = shift; | ||||||
1703 | my %args = @_; | ||||||
1704 | %args = ( %{ $self->{PARSER_ARGS} }, %args ); | ||||||
1705 | my $doc; | ||||||
1706 | |||||||
1707 | $self->debug("opening document"); | ||||||
1708 | |||||||
1709 | eval { | ||||||
1710 | my $ref = ref( $args{Source} ); | ||||||
1711 | if ( !$ref ) | ||||||
1712 | { | ||||||
1713 | if ( | ||||||
1714 | length $args{Source} < 255 | ||||||
1715 | && $args{Source} !~ /\n/ | ||||||
1716 | && ( -f $args{Source} | ||||||
1717 | || $args{Source} =~ /^(https?|ftp|file):/i ) | ||||||
1718 | ) | ||||||
1719 | { | ||||||
1720 | |||||||
1721 | # Filename | ||||||
1722 | $self->debug("Opening URL"); | ||||||
1723 | $doc = $self->__open_by_filename( $args{Source}, $args{base} ); | ||||||
1724 | } | ||||||
1725 | else | ||||||
1726 | { | ||||||
1727 | |||||||
1728 | # String | ||||||
1729 | $self->debug("Opening String"); | ||||||
1730 | $doc = $self->{PARSER}->parse( $args{Source} ); | ||||||
1731 | } | ||||||
1732 | } | ||||||
1733 | elsif ( $ref eq "SCALAR" ) | ||||||
1734 | { | ||||||
1735 | |||||||
1736 | # Stringref | ||||||
1737 | $self->debug("Opening Stringref"); | ||||||
1738 | $doc = $self->{PARSER}->parse( ${ $args{Source} } ); | ||||||
1739 | } | ||||||
1740 | elsif ( $args{Source}->isa('XML::DOM::Document') ) | ||||||
1741 | { | ||||||
1742 | |||||||
1743 | # DOM object | ||||||
1744 | $self->debug("Opening XML::DOM"); | ||||||
1745 | $doc = $args{Source}; | ||||||
1746 | } | ||||||
1747 | elsif ( $ref eq "GLOB" ) | ||||||
1748 | { # This is a file glob | ||||||
1749 | $self->debug("Opening GLOB"); | ||||||
1750 | my $ioref = *{ $args{Source} }{IO}; | ||||||
1751 | $doc = $self->{PARSER}->parse($ioref); | ||||||
1752 | } | ||||||
1753 | elsif ( UNIVERSAL::isa( $args{Source}, 'IO::Handle' ) ) | ||||||
1754 | { # IO::Handle | ||||||
1755 | $self->debug("Opening IO::Handle"); | ||||||
1756 | $doc = $self->{PARSER}->parse( $args{Source} ); | ||||||
1757 | } | ||||||
1758 | else | ||||||
1759 | { | ||||||
1760 | $doc = undef; | ||||||
1761 | } | ||||||
1762 | }; | ||||||
1763 | die "Error while parsing: $@\n" . $args{Source} if $@; | ||||||
1764 | return $doc; | ||||||
1765 | } | ||||||
1766 | |||||||
1767 | # private auxiliary function # | ||||||
1768 | sub __open_by_filename | ||||||
1769 | { | ||||||
1770 | my ( $self, $filename, $base ) = @_; | ||||||
1771 | my $doc; | ||||||
1772 | |||||||
1773 | # ** FIXME: currently reads the whole document into memory | ||||||
1774 | # might not be avoidable | ||||||
1775 | |||||||
1776 | # LWP should be able to deal with files as well as links | ||||||
1777 | $ENV{DOMAIN} ||= "example.com"; # hide complaints from Net::Domain | ||||||
1778 | |||||||
1779 | my $file = get( URI->new_abs( $filename, $base ) ); | ||||||
1780 | |||||||
1781 | return $self->{PARSER}->parse( $file, defined $self->{PARSER_ARGS} ? %{ $self->{PARSER_ARGS} } : undef ); | ||||||
1782 | } | ||||||
1783 | |||||||
1784 | sub _match_template | ||||||
1785 | { | ||||||
1786 | my ( $self, $attribute_name, $select_value, $xml_count, $xml_selection_path, | ||||||
1787 | $mode ) | ||||||
1788 | = @_; | ||||||
1789 | $mode ||= ""; | ||||||
1790 | |||||||
1791 | my $template = ""; | ||||||
1792 | my @template_matches = (); | ||||||
1793 | |||||||
1794 | $self->debug( | ||||||
1795 | qq{matching template for "$select_value" with count $xml_count\n\t} | ||||||
1796 | . qq{and path "$xml_selection_path":} ); | ||||||
1797 | |||||||
1798 | if ( $attribute_name eq "match" && ref $self->{TEMPLATE_MATCH} ) | ||||||
1799 | { | ||||||
1800 | push @template_matches, @{ $self->{TEMPLATE_MATCH} }; | ||||||
1801 | } | ||||||
1802 | elsif ( $attribute_name eq "name" && ref $self->{TEMPLATE_NAME} ) | ||||||
1803 | { | ||||||
1804 | push @template_matches, @{ $self->{TEMPLATE_NAME} }; | ||||||
1805 | } | ||||||
1806 | |||||||
1807 | # note that the order of @template_matches is the reverse of $self->{TEMPLATE} | ||||||
1808 | my $count = @template_matches; | ||||||
1809 | |||||||
1810 | $self->debug("matches: @template_matches"); | ||||||
1811 | |||||||
1812 | foreach my $original_match (@template_matches) | ||||||
1813 | { | ||||||
1814 | |||||||
1815 | # templates with no match or name or with both simultaniuously | ||||||
1816 | # have no $template_match value | ||||||
1817 | if ($original_match) | ||||||
1818 | { | ||||||
1819 | my $full_match = $original_match; | ||||||
1820 | |||||||
1821 | # multipe match? (for example: match="*|/") | ||||||
1822 | while ( $full_match =~ s/^(.+?)\|// ) | ||||||
1823 | { | ||||||
1824 | my $match = $1; | ||||||
1825 | if ( | ||||||
1826 | &__template_matches__( | ||||||
1827 | $match, $select_value, | ||||||
1828 | $xml_count, $xml_selection_path | ||||||
1829 | ) | ||||||
1830 | ) | ||||||
1831 | { | ||||||
1832 | $self->debug( | ||||||
1833 | qq{ found #$count with "$match" in "$original_match"}); | ||||||
1834 | |||||||
1835 | $template = ( $self->templates() )[ $count - 1 ]; | ||||||
1836 | return $template; | ||||||
1837 | |||||||
1838 | # last; | ||||||
1839 | } | ||||||
1840 | } | ||||||
1841 | |||||||
1842 | # last match? | ||||||
1843 | if ( !$template ) | ||||||
1844 | { | ||||||
1845 | if ( | ||||||
1846 | &__template_matches__( | ||||||
1847 | $full_match, $select_value, | ||||||
1848 | $xml_count, $xml_selection_path | ||||||
1849 | ) | ||||||
1850 | ) | ||||||
1851 | { | ||||||
1852 | $self->debug( | ||||||
1853 | qq{ found #$count with "$full_match" in "$original_match"} | ||||||
1854 | ); | ||||||
1855 | $template = ( $self->templates() )[ $count - 1 ]; | ||||||
1856 | return $template; | ||||||
1857 | |||||||
1858 | # last; | ||||||
1859 | } | ||||||
1860 | else | ||||||
1861 | { | ||||||
1862 | $self->debug(qq{ #$count "$original_match" did not match}); | ||||||
1863 | } | ||||||
1864 | } | ||||||
1865 | } | ||||||
1866 | $count--; | ||||||
1867 | } | ||||||
1868 | |||||||
1869 | if ( !$template ) | ||||||
1870 | { | ||||||
1871 | $self->warn(qq{No template matching `$xml_selection_path' found !!}); | ||||||
1872 | } | ||||||
1873 | |||||||
1874 | return $template; | ||||||
1875 | } | ||||||
1876 | |||||||
1877 | # auxiliary function # | ||||||
1878 | sub __template_matches__ | ||||||
1879 | { | ||||||
1880 | my ( $template, $select, $count, $path ) = @_; | ||||||
1881 | |||||||
1882 | my $nocount_path = $path; | ||||||
1883 | $nocount_path =~ s/\[.*?\]//g; | ||||||
1884 | |||||||
1885 | if ( ( $template eq $select ) | ||||||
1886 | || ( $template eq $path ) | ||||||
1887 | || ( $template eq "$select\[$count\]" ) | ||||||
1888 | || ( $template eq "$path\[$count\]" ) ) | ||||||
1889 | { | ||||||
1890 | |||||||
1891 | # perfect match or path ends with templates match | ||||||
1892 | #print "perfect match","\n"; | ||||||
1893 | return "True"; | ||||||
1894 | } | ||||||
1895 | elsif ( | ||||||
1896 | ( $template eq substr( $path, -length($template) ) ) | ||||||
1897 | || ( $template eq substr( $nocount_path, -length($template) ) ) | ||||||
1898 | || ( "$template\[$count\]" eq substr( $path, -length($template) ) ) | ||||||
1899 | || ( | ||||||
1900 | "$template\[$count\]" eq substr( $nocount_path, -length($template) ) | ||||||
1901 | ) | ||||||
1902 | ) | ||||||
1903 | { | ||||||
1904 | |||||||
1905 | # template matches tail of path matches perfectly | ||||||
1906 | #print "perfect tail match","\n"; | ||||||
1907 | return "True"; | ||||||
1908 | } | ||||||
1909 | elsif ( $select =~ /\[\s*(\@.*?)\s*=\s*(.*?)\s*\]$/ ) | ||||||
1910 | { | ||||||
1911 | |||||||
1912 | # match attribute test | ||||||
1913 | my $attribute = $1; | ||||||
1914 | my $value = $2; | ||||||
1915 | return ""; # False, no test evaluation yet # | ||||||
1916 | } | ||||||
1917 | elsif ( $select =~ /\[\s*(.*?)\s*=\s*(.*?)\s*\]$/ ) | ||||||
1918 | { | ||||||
1919 | |||||||
1920 | # match test | ||||||
1921 | my $element = $1; | ||||||
1922 | my $value = $2; | ||||||
1923 | return ""; # False, no test evaluation yet # | ||||||
1924 | } | ||||||
1925 | elsif ( $select =~ /(\@\*|\@[\w\.\-\:]+)$/ ) | ||||||
1926 | { | ||||||
1927 | |||||||
1928 | # match attribute | ||||||
1929 | my $attribute = $1; | ||||||
1930 | |||||||
1931 | #print "attribute match?\n"; | ||||||
1932 | return ( ( $template eq '@*' ) | ||||||
1933 | || ( $template eq $attribute ) | ||||||
1934 | || ( $template eq "\@*\[$count\]" ) | ||||||
1935 | || ( $template eq "$attribute\[$count\]" ) ); | ||||||
1936 | } | ||||||
1937 | elsif ( $select =~ /(\*|[\w\.\-\:]+)$/ ) | ||||||
1938 | { | ||||||
1939 | |||||||
1940 | # match element | ||||||
1941 | my $element = $1; | ||||||
1942 | |||||||
1943 | #print "element match?\n"; | ||||||
1944 | return ( ( $template eq "*" ) | ||||||
1945 | || ( $template eq $element ) | ||||||
1946 | || ( $template eq "*\[$count\]" ) | ||||||
1947 | || ( $template eq "$element\[$count\]" ) ); | ||||||
1948 | } | ||||||
1949 | else | ||||||
1950 | { | ||||||
1951 | return ""; # False # | ||||||
1952 | } | ||||||
1953 | } | ||||||
1954 | |||||||
1955 | sub _evaluate_test | ||||||
1956 | { | ||||||
1957 | my ( $self, $test, $current_xml_node, $current_xml_selection_path, | ||||||
1958 | $variables ) = @_; | ||||||
1959 | |||||||
1960 | $self->_indent(); | ||||||
1961 | my $rc = 0; | ||||||
1962 | |||||||
1963 | my $cond; | ||||||
1964 | |||||||
1965 | $self->debug("evaluating $test"); | ||||||
1966 | foreach my $test_part ( split /\s+\b(or|and)\b\s+/, $test ) | ||||||
1967 | { | ||||||
1968 | $self->debug("evaluating part $test_part"); | ||||||
1969 | |||||||
1970 | if ( $test_part =~ /^(or|and)$/i ) | ||||||
1971 | { | ||||||
1972 | $cond = $1; | ||||||
1973 | $self->debug("got '$cond'"); | ||||||
1974 | } | ||||||
1975 | else | ||||||
1976 | { | ||||||
1977 | my $one_rc = $self->_evaluate_test_one($test_part,$current_xml_node, $current_xml_selection_path, $variables ); | ||||||
1978 | |||||||
1979 | if (!$cond) | ||||||
1980 | { | ||||||
1981 | $self->debug("using response"); | ||||||
1982 | $rc = $one_rc; | ||||||
1983 | } | ||||||
1984 | elsif( $cond eq 'or' ) | ||||||
1985 | { | ||||||
1986 | $self->debug("or'ing response"); | ||||||
1987 | $rc |= $one_rc; | ||||||
1988 | } | ||||||
1989 | elsif( $cond eq 'and' ) | ||||||
1990 | { | ||||||
1991 | $self->debug("and'ing response"); | ||||||
1992 | $rc &= $one_rc; | ||||||
1993 | } | ||||||
1994 | } | ||||||
1995 | } | ||||||
1996 | |||||||
1997 | $self->_outdent(); | ||||||
1998 | return $rc; | ||||||
1999 | } | ||||||
2000 | |||||||
2001 | sub _evaluate_test_one | ||||||
2002 | { | ||||||
2003 | my ( $self, $test, $current_xml_node, $current_xml_selection_path, $variables ) | ||||||
2004 | = @_; | ||||||
2005 | |||||||
2006 | $self->_indent(); | ||||||
2007 | $self->debug("processing test $test"); | ||||||
2008 | |||||||
2009 | my $rc = 0; | ||||||
2010 | |||||||
2011 | if ( $test =~ /^(.+)\/\[(.+)\]$/ ) | ||||||
2012 | { | ||||||
2013 | my $path = $1; | ||||||
2014 | my $test = $2; | ||||||
2015 | |||||||
2016 | $self->debug("evaluating test $test at path $path:"); | ||||||
2017 | |||||||
2018 | my $node = | ||||||
2019 | $self->_get_node_set( $path, $self->xml_document(), | ||||||
2020 | $current_xml_selection_path, $current_xml_node, $variables ); | ||||||
2021 | if (@$node) | ||||||
2022 | { | ||||||
2023 | $rc = $self->_evaluate_test_one($test,$node->[0], $current_xml_selection_path, $variables); | ||||||
2024 | } | ||||||
2025 | } | ||||||
2026 | else | ||||||
2027 | { | ||||||
2028 | $self->debug("evaluating path or test $test:"); | ||||||
2029 | my $node = | ||||||
2030 | $self->_get_node_set( $test, $self->xml_document(), | ||||||
2031 | $current_xml_selection_path, $current_xml_node, $variables, | ||||||
2032 | "silent" ); | ||||||
2033 | if (@$node) | ||||||
2034 | { | ||||||
2035 | $self->debug("path exists!"); | ||||||
2036 | $rc = 1; | ||||||
2037 | } | ||||||
2038 | else | ||||||
2039 | { | ||||||
2040 | $self->debug("not a valid path, evaluating as test"); | ||||||
2041 | $rc = $self->__evaluate_test__( $test, $current_xml_selection_path, | ||||||
2042 | $current_xml_node, $variables ); | ||||||
2043 | } | ||||||
2044 | } | ||||||
2045 | |||||||
2046 | |||||||
2047 | $self->debug("test evaluates @{[ $rc ? 'true': 'false']}"); | ||||||
2048 | |||||||
2049 | $self->_outdent(); | ||||||
2050 | return $rc; | ||||||
2051 | } | ||||||
2052 | |||||||
2053 | sub _evaluate_template | ||||||
2054 | { | ||||||
2055 | my ( $self, $template, $current_xml_node, $current_xml_selection_path, | ||||||
2056 | $current_result_node, $variables, $oldvariables ) | ||||||
2057 | = @_; | ||||||
2058 | |||||||
2059 | $self->debug( qq{evaluating template content with current path } | ||||||
2060 | . qq{"$current_xml_selection_path": } ); | ||||||
2061 | $self->_indent(); | ||||||
2062 | |||||||
2063 | die "No Template" | ||||||
2064 | unless defined $template && ref $template; | ||||||
2065 | $template->normalize; | ||||||
2066 | |||||||
2067 | foreach my $child ( $template->getChildNodes ) | ||||||
2068 | { | ||||||
2069 | my $ref = ref $child; | ||||||
2070 | |||||||
2071 | $self->debug("$ref"); | ||||||
2072 | $self->_indent(); | ||||||
2073 | my $node_type = $child->getNodeType; | ||||||
2074 | if ( $node_type == ELEMENT_NODE ) | ||||||
2075 | { | ||||||
2076 | $self->_evaluate_element( $child, $current_xml_node, | ||||||
2077 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2078 | $oldvariables ); | ||||||
2079 | } | ||||||
2080 | elsif ( $node_type == TEXT_NODE ) | ||||||
2081 | { | ||||||
2082 | my $value = $child->getNodeValue; | ||||||
2083 | if ( length($value) and $value !~ /^[\x20\x09\x0D\x0A]+$/s ) | ||||||
2084 | { | ||||||
2085 | $self->_add_node( $child, $current_result_node ); | ||||||
2086 | } | ||||||
2087 | } | ||||||
2088 | elsif ( $node_type == CDATA_SECTION_NODE ) | ||||||
2089 | { | ||||||
2090 | my $text = $self->_create_text_node( $child->getData ); | ||||||
2091 | $self->_add_node( $text, $current_result_node ); | ||||||
2092 | } | ||||||
2093 | elsif ( $node_type == ENTITY_REFERENCE_NODE ) | ||||||
2094 | { | ||||||
2095 | $self->_add_node( $child, $current_result_node ); | ||||||
2096 | } | ||||||
2097 | elsif ( $node_type == DOCUMENT_TYPE_NODE ) | ||||||
2098 | { | ||||||
2099 | |||||||
2100 | # skip # | ||||||
2101 | $self->debug("Skipping Document Type node..."); | ||||||
2102 | } | ||||||
2103 | elsif ( $node_type == COMMENT_NODE ) | ||||||
2104 | { | ||||||
2105 | |||||||
2106 | # skip # | ||||||
2107 | $self->debug("Skipping Comment node..."); | ||||||
2108 | } | ||||||
2109 | else | ||||||
2110 | { | ||||||
2111 | $self->warn( | ||||||
2112 | "evaluate-template: Dunno what to do with node of type $ref !!!\n\t" | ||||||
2113 | . "($current_xml_selection_path)" ); | ||||||
2114 | } | ||||||
2115 | |||||||
2116 | $self->_outdent(); | ||||||
2117 | } | ||||||
2118 | |||||||
2119 | $self->debug("done!"); | ||||||
2120 | $self->_outdent(); | ||||||
2121 | } | ||||||
2122 | |||||||
2123 | sub _add_node | ||||||
2124 | { | ||||||
2125 | my ( $self, $node, $parent, $deep, $owner ) = @_; | ||||||
2126 | $owner ||= $self->xml_document(); | ||||||
2127 | |||||||
2128 | my $what = defined $deep ? 'deep' : 'non-deep'; | ||||||
2129 | |||||||
2130 | $self->debug("adding node ($what).."); | ||||||
2131 | |||||||
2132 | $node = $node->cloneNode($deep); | ||||||
2133 | $node->setOwnerDocument($owner); | ||||||
2134 | if ( $node->getNodeType == ATTRIBUTE_NODE ) | ||||||
2135 | { | ||||||
2136 | $parent->setAttributeNode($node); | ||||||
2137 | } | ||||||
2138 | else | ||||||
2139 | { | ||||||
2140 | $parent->appendChild($node); | ||||||
2141 | } | ||||||
2142 | } | ||||||
2143 | |||||||
2144 | sub _apply_templates | ||||||
2145 | { | ||||||
2146 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
2147 | $current_result_node, $variables, $oldvariables ) | ||||||
2148 | = @_; | ||||||
2149 | my $children; | ||||||
2150 | my $params = {}; | ||||||
2151 | my $newvariables = defined $variables ? {%$variables} : {}; | ||||||
2152 | |||||||
2153 | my $select = $xsl_node->getAttribute('select'); | ||||||
2154 | |||||||
2155 | if ( $select =~ /\$/ and defined $variables ) | ||||||
2156 | { | ||||||
2157 | |||||||
2158 | # replacing occurences of variables: | ||||||
2159 | foreach my $varname ( keys(%$variables) ) | ||||||
2160 | { | ||||||
2161 | $self->debug("Applying variable $varname"); | ||||||
2162 | $select =~ s/[^\\]\$$varname/$$variables{$varname}/g; | ||||||
2163 | } | ||||||
2164 | } | ||||||
2165 | |||||||
2166 | if ($select) | ||||||
2167 | { | ||||||
2168 | $self->debug( | ||||||
2169 | qq{applying templates on children select of "$current_xml_selection_path":} | ||||||
2170 | ); | ||||||
2171 | $children = | ||||||
2172 | $self->_get_node_set( $select, $self->xml_document(), | ||||||
2173 | $current_xml_selection_path, $current_xml_node, $variables ); | ||||||
2174 | } | ||||||
2175 | else | ||||||
2176 | { | ||||||
2177 | $self->debug( | ||||||
2178 | qq{applying templates on all children of "$current_xml_selection_path":} | ||||||
2179 | ); | ||||||
2180 | $children = [ $current_xml_node->getChildNodes ]; | ||||||
2181 | } | ||||||
2182 | |||||||
2183 | $self->_process_with_params( $xsl_node, | ||||||
2184 | $current_xml_node, | ||||||
2185 | $current_xml_selection_path, | ||||||
2186 | $variables, | ||||||
2187 | $params ); | ||||||
2188 | |||||||
2189 | # process xsl:sort here | ||||||
2190 | |||||||
2191 | $self->_indent(); | ||||||
2192 | |||||||
2193 | my $count = 1; | ||||||
2194 | foreach my $child (@$children) | ||||||
2195 | { | ||||||
2196 | my $node_type = $child->getNodeType; | ||||||
2197 | |||||||
2198 | if ( $node_type == DOCUMENT_TYPE_NODE ) | ||||||
2199 | { | ||||||
2200 | |||||||
2201 | # skip # | ||||||
2202 | $self->debug("Skipping Document Type node..."); | ||||||
2203 | } | ||||||
2204 | elsif ( $node_type == DOCUMENT_FRAGMENT_NODE ) | ||||||
2205 | { | ||||||
2206 | |||||||
2207 | # skip # | ||||||
2208 | $self->debug("Skipping Document Fragment node..."); | ||||||
2209 | } | ||||||
2210 | elsif ( $node_type == NOTATION_NODE ) | ||||||
2211 | { | ||||||
2212 | |||||||
2213 | # skip # | ||||||
2214 | $self->debug("Skipping Notation node..."); | ||||||
2215 | } | ||||||
2216 | else | ||||||
2217 | { | ||||||
2218 | |||||||
2219 | my $newselect = ""; | ||||||
2220 | my $newcount = $count; | ||||||
2221 | if ( !$select || ( $select eq '.' ) ) | ||||||
2222 | { | ||||||
2223 | if ( $node_type == ELEMENT_NODE ) | ||||||
2224 | { | ||||||
2225 | $newselect = $child->getTagName; | ||||||
2226 | } | ||||||
2227 | elsif ( $node_type == ATTRIBUTE_NODE ) | ||||||
2228 | { | ||||||
2229 | $newselect = "@$child->getName"; | ||||||
2230 | } | ||||||
2231 | elsif (( $node_type == TEXT_NODE ) | ||||||
2232 | || ( $node_type == ENTITY_REFERENCE_NODE ) ) | ||||||
2233 | { | ||||||
2234 | $newselect = "text()"; | ||||||
2235 | } | ||||||
2236 | elsif ( $node_type == PROCESSING_INSTRUCTION_NODE ) | ||||||
2237 | { | ||||||
2238 | $newselect = "processing-instruction()"; | ||||||
2239 | } | ||||||
2240 | elsif ( $node_type == COMMENT_NODE ) | ||||||
2241 | { | ||||||
2242 | $newselect = "comment()"; | ||||||
2243 | } | ||||||
2244 | else | ||||||
2245 | { | ||||||
2246 | my $ref = ref $child; | ||||||
2247 | $self->debug("Unknown node encountered: `$ref'"); | ||||||
2248 | } | ||||||
2249 | } | ||||||
2250 | else | ||||||
2251 | { | ||||||
2252 | $newselect = $select; | ||||||
2253 | if ( $newselect =~ s/\[(\d+)\]$// ) | ||||||
2254 | { | ||||||
2255 | $newcount = $1; | ||||||
2256 | } | ||||||
2257 | } | ||||||
2258 | |||||||
2259 | $self->_select_template( | ||||||
2260 | $child, $newselect, | ||||||
2261 | $newcount, $current_xml_node, | ||||||
2262 | $current_xml_selection_path, $current_result_node, | ||||||
2263 | $newvariables, $params | ||||||
2264 | ); | ||||||
2265 | } | ||||||
2266 | $count++; | ||||||
2267 | } | ||||||
2268 | |||||||
2269 | $self->_outdent(); | ||||||
2270 | } | ||||||
2271 | |||||||
2272 | sub _for_each | ||||||
2273 | { | ||||||
2274 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
2275 | $current_result_node, $variables, $oldvariables ) | ||||||
2276 | = @_; | ||||||
2277 | |||||||
2278 | my $ns = $self->xsl_ns(); | ||||||
2279 | my $select = $xsl_node->getAttribute('select') | ||||||
2280 | || die "No `select' attribute in for-each element"; | ||||||
2281 | |||||||
2282 | if ( $select =~ /\$/ ) | ||||||
2283 | { | ||||||
2284 | |||||||
2285 | # replacing occurences of variables: | ||||||
2286 | foreach my $varname ( keys(%$variables) ) | ||||||
2287 | { | ||||||
2288 | $select =~ s/[^\\]\$$varname/$$variables{$varname}/g; | ||||||
2289 | } | ||||||
2290 | } | ||||||
2291 | |||||||
2292 | if ( defined $select ) | ||||||
2293 | { | ||||||
2294 | $self->debug( | ||||||
2295 | qq{applying template for each child $select of "$current_xml_selection_path":} | ||||||
2296 | ); | ||||||
2297 | |||||||
2298 | |||||||
2299 | my $children = $self->_get_node_set( $select, | ||||||
2300 | $self->xml_document(), | ||||||
2301 | $current_xml_selection_path, | ||||||
2302 | $current_xml_node, $variables ); | ||||||
2303 | |||||||
2304 | my $sort = $xsl_node->getElementsByTagName("$ns:sort",0); | ||||||
2305 | |||||||
2306 | if ( my $nokeys = $sort->getLength() ) | ||||||
2307 | { | ||||||
2308 | $self->debug("going to sort with $nokeys"); | ||||||
2309 | } | ||||||
2310 | |||||||
2311 | $self->_indent(); | ||||||
2312 | my $count = 1; | ||||||
2313 | foreach my $child (@$children) | ||||||
2314 | { | ||||||
2315 | my $node_type = $child->getNodeType; | ||||||
2316 | |||||||
2317 | if ( $node_type == DOCUMENT_TYPE_NODE ) | ||||||
2318 | { | ||||||
2319 | |||||||
2320 | # skip # | ||||||
2321 | $self->debug("Skipping Document Type node..."); | ||||||
2322 | } | ||||||
2323 | elsif ( $node_type == DOCUMENT_FRAGMENT_NODE ) | ||||||
2324 | { | ||||||
2325 | |||||||
2326 | # skip # | ||||||
2327 | $self->debug("Skipping Document Fragment node..."); | ||||||
2328 | } | ||||||
2329 | elsif ( $node_type == NOTATION_NODE ) | ||||||
2330 | { | ||||||
2331 | |||||||
2332 | # skip # | ||||||
2333 | $self->debug("Skipping Notation node..."); | ||||||
2334 | } | ||||||
2335 | else | ||||||
2336 | { | ||||||
2337 | |||||||
2338 | $self->_evaluate_template( | ||||||
2339 | $xsl_node, | ||||||
2340 | $child, | ||||||
2341 | "$current_xml_selection_path/$select\[$count\]", | ||||||
2342 | $current_result_node, | ||||||
2343 | $variables, | ||||||
2344 | $oldvariables | ||||||
2345 | ); | ||||||
2346 | } | ||||||
2347 | $count++; | ||||||
2348 | } | ||||||
2349 | |||||||
2350 | $self->_outdent(); | ||||||
2351 | } | ||||||
2352 | else | ||||||
2353 | { | ||||||
2354 | $self->warn(qq%expected attribute "select" in <${ns}for-each>%); | ||||||
2355 | } | ||||||
2356 | |||||||
2357 | } | ||||||
2358 | |||||||
2359 | sub _select_template | ||||||
2360 | { | ||||||
2361 | my ( $self, $child, $select, $count, $current_xml_node, | ||||||
2362 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2363 | $oldvariables ) | ||||||
2364 | = @_; | ||||||
2365 | |||||||
2366 | my $ref = ref $child; | ||||||
2367 | $self->debug( | ||||||
2368 | qq{selecting template $select for child type $ref of "$current_xml_selection_path":} | ||||||
2369 | ); | ||||||
2370 | |||||||
2371 | $self->_indent(); | ||||||
2372 | |||||||
2373 | foreach my $select_part ( split /\|/, $select ) | ||||||
2374 | { | ||||||
2375 | my $child_xml_selection_path = "$current_xml_selection_path/$select_part"; | ||||||
2376 | my $template = | ||||||
2377 | $self->_match_template( "match", $select_part, $count, | ||||||
2378 | $child_xml_selection_path ); | ||||||
2379 | |||||||
2380 | if ($template) | ||||||
2381 | { | ||||||
2382 | |||||||
2383 | $self->_evaluate_template( $template, $child, | ||||||
2384 | "$child_xml_selection_path\[$count\]", | ||||||
2385 | $current_result_node, $variables, $oldvariables ); | ||||||
2386 | } | ||||||
2387 | else | ||||||
2388 | { | ||||||
2389 | $self->debug("skipping template selection..."); | ||||||
2390 | } | ||||||
2391 | |||||||
2392 | } | ||||||
2393 | |||||||
2394 | $self->_outdent(); | ||||||
2395 | } | ||||||
2396 | |||||||
2397 | sub _evaluate_element | ||||||
2398 | { | ||||||
2399 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
2400 | $current_result_node, $variables, $oldvariables ) | ||||||
2401 | = @_; | ||||||
2402 | my ( $ns, $xsl_tag ) = split( ':', $xsl_node->getTagName ); | ||||||
2403 | |||||||
2404 | if ( not defined $xsl_tag ) | ||||||
2405 | { | ||||||
2406 | $xsl_tag = $ns; | ||||||
2407 | $ns = $self->default_ns(); | ||||||
2408 | } | ||||||
2409 | else | ||||||
2410 | { | ||||||
2411 | $ns .= ':'; | ||||||
2412 | } | ||||||
2413 | $self->debug( | ||||||
2414 | qq{evaluating element `$xsl_tag' from `$current_xml_selection_path': }); | ||||||
2415 | $self->_indent(); | ||||||
2416 | |||||||
2417 | if ( $ns eq $self->xsl_ns() ) | ||||||
2418 | { | ||||||
2419 | my @attributes = $xsl_node->getAttributes->getValues; | ||||||
2420 | $self->debug(qq{This is an xsl tag}); | ||||||
2421 | if ( $xsl_tag eq 'apply-templates' ) | ||||||
2422 | { | ||||||
2423 | $self->_apply_templates( $xsl_node, $current_xml_node, | ||||||
2424 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2425 | $oldvariables ); | ||||||
2426 | |||||||
2427 | } | ||||||
2428 | elsif ( $xsl_tag eq 'attribute' ) | ||||||
2429 | { | ||||||
2430 | $self->_attribute( $xsl_node, $current_xml_node, | ||||||
2431 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2432 | $oldvariables ); | ||||||
2433 | |||||||
2434 | } | ||||||
2435 | elsif ( $xsl_tag eq 'call-template' ) | ||||||
2436 | { | ||||||
2437 | $self->_call_template( $xsl_node, $current_xml_node, | ||||||
2438 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2439 | $oldvariables ); | ||||||
2440 | |||||||
2441 | } | ||||||
2442 | elsif ( $xsl_tag eq 'choose' ) | ||||||
2443 | { | ||||||
2444 | $self->_choose( $xsl_node, $current_xml_node, | ||||||
2445 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2446 | $oldvariables ); | ||||||
2447 | |||||||
2448 | } | ||||||
2449 | elsif ( $xsl_tag eq 'comment' ) | ||||||
2450 | { | ||||||
2451 | $self->_comment( $xsl_node, $current_xml_node, | ||||||
2452 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2453 | $oldvariables ); | ||||||
2454 | |||||||
2455 | } | ||||||
2456 | elsif ( $xsl_tag eq 'copy' ) | ||||||
2457 | { | ||||||
2458 | $self->_copy( $xsl_node, $current_xml_node, | ||||||
2459 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2460 | $oldvariables ); | ||||||
2461 | |||||||
2462 | } | ||||||
2463 | elsif ( $xsl_tag eq 'copy-of' ) | ||||||
2464 | { | ||||||
2465 | $self->_copy_of( $xsl_node, $current_xml_node, | ||||||
2466 | $current_xml_selection_path, $current_result_node, $variables ); | ||||||
2467 | } | ||||||
2468 | elsif ( $xsl_tag eq 'element' ) | ||||||
2469 | { | ||||||
2470 | $self->_element( $xsl_node, $current_xml_node, | ||||||
2471 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2472 | $oldvariables ); | ||||||
2473 | } | ||||||
2474 | elsif ( $xsl_tag eq 'for-each' ) | ||||||
2475 | { | ||||||
2476 | $self->_for_each( $xsl_node, $current_xml_node, | ||||||
2477 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2478 | $oldvariables ); | ||||||
2479 | |||||||
2480 | } | ||||||
2481 | elsif ( $xsl_tag eq 'if' ) | ||||||
2482 | { | ||||||
2483 | $self->_if( $xsl_node, $current_xml_node, | ||||||
2484 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2485 | $oldvariables ); | ||||||
2486 | |||||||
2487 | # } elsif ($xsl_tag eq 'output') { | ||||||
2488 | |||||||
2489 | } | ||||||
2490 | elsif ( $xsl_tag eq 'param' ) | ||||||
2491 | { | ||||||
2492 | $self->_variable( $xsl_node, $current_xml_node, | ||||||
2493 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2494 | $oldvariables, 1 ); | ||||||
2495 | |||||||
2496 | } | ||||||
2497 | elsif ( $xsl_tag eq 'processing-instruction' ) | ||||||
2498 | { | ||||||
2499 | $self->_processing_instruction( $xsl_node, $current_result_node ); | ||||||
2500 | |||||||
2501 | } | ||||||
2502 | elsif ( $xsl_tag eq 'text' ) | ||||||
2503 | { | ||||||
2504 | $self->_text( $xsl_node, $current_result_node ); | ||||||
2505 | |||||||
2506 | } | ||||||
2507 | elsif ( $xsl_tag eq 'value-of' ) | ||||||
2508 | { | ||||||
2509 | $self->_value_of( $xsl_node, $current_xml_node, | ||||||
2510 | $current_xml_selection_path, $current_result_node, $variables ); | ||||||
2511 | |||||||
2512 | } | ||||||
2513 | elsif ( $xsl_tag eq 'variable' ) | ||||||
2514 | { | ||||||
2515 | $self->_variable( $xsl_node, $current_xml_node, | ||||||
2516 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2517 | $oldvariables, 0 ); | ||||||
2518 | |||||||
2519 | } | ||||||
2520 | elsif ( $xsl_tag eq 'sort' ) | ||||||
2521 | { | ||||||
2522 | $self->_sort( $xsl_node, $current_xml_node, | ||||||
2523 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2524 | $oldvariables, 0 ); | ||||||
2525 | } | ||||||
2526 | elsif ( $xsl_tag eq 'fallback' ) | ||||||
2527 | { | ||||||
2528 | $self->_fallback( $xsl_node, $current_xml_node, | ||||||
2529 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2530 | $oldvariables, 0 ); | ||||||
2531 | } | ||||||
2532 | elsif ( $xsl_tag eq 'attribute-set' ) | ||||||
2533 | { | ||||||
2534 | $self->_attribute_set( $xsl_node, $current_xml_node, | ||||||
2535 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2536 | $oldvariables, 0 ); | ||||||
2537 | } | ||||||
2538 | else | ||||||
2539 | { | ||||||
2540 | $self->_add_and_recurse( $xsl_node, $current_xml_node, | ||||||
2541 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2542 | $oldvariables ); | ||||||
2543 | } | ||||||
2544 | } | ||||||
2545 | else | ||||||
2546 | { | ||||||
2547 | $self->debug( $ns . " does not match " . $self->xsl_ns() ); | ||||||
2548 | |||||||
2549 | # not entirely sure if this right but the spec is a bit vague | ||||||
2550 | |||||||
2551 | if ( $self->is_cdata_section($xsl_tag) ) | ||||||
2552 | { | ||||||
2553 | $self->debug("This is a CDATA section element"); | ||||||
2554 | $self->_add_cdata_section( $xsl_node, $current_xml_node, | ||||||
2555 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2556 | $oldvariables ); | ||||||
2557 | } | ||||||
2558 | else | ||||||
2559 | { | ||||||
2560 | $self->debug("This is a literal element"); | ||||||
2561 | $self->_check_attributes_and_recurse( $xsl_node, $current_xml_node, | ||||||
2562 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2563 | $oldvariables ); | ||||||
2564 | } | ||||||
2565 | } | ||||||
2566 | |||||||
2567 | $self->_outdent(); | ||||||
2568 | } | ||||||
2569 | |||||||
2570 | sub _add_cdata_section | ||||||
2571 | { | ||||||
2572 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
2573 | $current_result_node, $variables, $oldvariables ) | ||||||
2574 | = @_; | ||||||
2575 | |||||||
2576 | my $node = $self->xml_document()->createElement( $xsl_node->getTagName ); | ||||||
2577 | |||||||
2578 | my $cdata = ''; | ||||||
2579 | |||||||
2580 | foreach my $child_node ( $xsl_node->getChildNodes() ) | ||||||
2581 | { | ||||||
2582 | if ( $child_node->can('asString') ) | ||||||
2583 | { | ||||||
2584 | $cdata .= $child_node->asString(); | ||||||
2585 | } | ||||||
2586 | else | ||||||
2587 | { | ||||||
2588 | $cdata .= $child_node->getNodeValue(); | ||||||
2589 | } | ||||||
2590 | } | ||||||
2591 | |||||||
2592 | $node->addCDATA($cdata); | ||||||
2593 | |||||||
2594 | $current_result_node->appendChild($node); | ||||||
2595 | |||||||
2596 | } | ||||||
2597 | |||||||
2598 | sub _add_and_recurse | ||||||
2599 | { | ||||||
2600 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
2601 | $current_result_node, $variables, $oldvariables ) | ||||||
2602 | = @_; | ||||||
2603 | |||||||
2604 | # the addition is commented out to prevent unknown xsl: commands to be printed in the result | ||||||
2605 | $self->_add_node( $xsl_node, $current_result_node ); | ||||||
2606 | $self->_evaluate_template( $xsl_node, $current_xml_node, | ||||||
2607 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
2608 | $oldvariables ); #->getLastChild); | ||||||
2609 | } | ||||||
2610 | |||||||
2611 | sub _check_attributes_and_recurse | ||||||
2612 | { | ||||||
2613 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
2614 | $current_result_node, $variables, $oldvariables ) | ||||||
2615 | = @_; | ||||||
2616 | |||||||
2617 | $self->_add_node( $xsl_node, $current_result_node ); | ||||||
2618 | $self->_attribute_value_of( | ||||||
2619 | $current_result_node->getLastChild, $current_xml_node, | ||||||
2620 | $current_xml_selection_path, $variables | ||||||
2621 | ); | ||||||
2622 | $self->_evaluate_template( $xsl_node, $current_xml_node, | ||||||
2623 | $current_xml_selection_path, $current_result_node->getLastChild, | ||||||
2624 | $variables, $oldvariables ); | ||||||
2625 | } | ||||||
2626 | |||||||
2627 | sub _element | ||||||
2628 | { | ||||||
2629 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
2630 | $current_result_node, $variables, $oldvariables ) | ||||||
2631 | = @_; | ||||||
2632 | |||||||
2633 | my $name = $xsl_node->getAttribute('name'); | ||||||
2634 | $self->debug(qq{inserting Element named "$name":}); | ||||||
2635 | $self->_indent(); | ||||||
2636 | |||||||
2637 | if ( defined $name ) | ||||||
2638 | { | ||||||
2639 | my $result = $self->xml_document()->createElement($name); | ||||||
2640 | |||||||
2641 | $self->_evaluate_template( $xsl_node, $current_xml_node, | ||||||
2642 | $current_xml_selection_path, $result, $variables, $oldvariables ); | ||||||
2643 | |||||||
2644 | $self->_apply_attribute_set($xsl_node,$result); | ||||||
2645 | $current_result_node->appendChild($result); | ||||||
2646 | } | ||||||
2647 | else | ||||||
2648 | { | ||||||
2649 | $self->warn( | ||||||
2650 | q{expected attribute "name" in <} . $self->xsl_ns() . q{element>} ); | ||||||
2651 | } | ||||||
2652 | $self->_outdent(); | ||||||
2653 | } | ||||||
2654 | |||||||
2655 | sub _apply_attribute_set | ||||||
2656 | { | ||||||
2657 | my ( $self,$xsl_node, $output_node) = @_; | ||||||
2658 | |||||||
2659 | my $attr_set = $xsl_node->getAttribute('use-attribute-sets'); | ||||||
2660 | |||||||
2661 | if ($attr_set) | ||||||
2662 | { | ||||||
2663 | $self->_indent(); | ||||||
2664 | my $set_name = $attr_set; | ||||||
2665 | |||||||
2666 | if ( my $set = $self->__attribute_set_($set_name) ) | ||||||
2667 | { | ||||||
2668 | $self->debug("Adding attribute-set '$set_name'"); | ||||||
2669 | |||||||
2670 | foreach my $attr_name ( keys %{$set} ) | ||||||
2671 | { | ||||||
2672 | $self->debug( | ||||||
2673 | "Adding attribute $attr_name ->" . $set->{$attr_name} ); | ||||||
2674 | $output_node->setAttribute( $attr_name, $set->{$attr_name} ); | ||||||
2675 | } | ||||||
2676 | } | ||||||
2677 | $self->_outdent(); | ||||||
2678 | } | ||||||
2679 | } | ||||||
2680 | |||||||
2681 | { | ||||||
2682 | ###################################################################### | ||||||
2683 | # Auxiliary package for disable-output-escaping | ||||||
2684 | ###################################################################### | ||||||
2685 | |||||||
2686 | package XML::XSLT::DOM::TextDOE; | ||||||
2687 | use vars qw( @ISA ); | ||||||
2688 | @ISA = qw( XML::DOM::Text ); | ||||||
2689 | |||||||
2690 | sub print | ||||||
2691 | { | ||||||
2692 | my ( $self, $FILE ) = @_; | ||||||
2693 | $FILE->print( $self->getData ); | ||||||
2694 | } | ||||||
2695 | } | ||||||
2696 | |||||||
2697 | sub _value_of | ||||||
2698 | { | ||||||
2699 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
2700 | $current_result_node, $variables ) | ||||||
2701 | = @_; | ||||||
2702 | |||||||
2703 | my $select = $xsl_node->getAttribute('select'); | ||||||
2704 | |||||||
2705 | # Need to determine here whether the value is an XPath expression | ||||||
2706 | # and act accordingly | ||||||
2707 | |||||||
2708 | my $xml_node; | ||||||
2709 | |||||||
2710 | if ( defined $select ) | ||||||
2711 | { | ||||||
2712 | $xml_node = $self->_get_node_set( $select, | ||||||
2713 | $self->xml_document(), | ||||||
2714 | $current_xml_selection_path, | ||||||
2715 | $current_xml_node, | ||||||
2716 | $variables ); | ||||||
2717 | |||||||
2718 | $self->debug("stripping node to text:"); | ||||||
2719 | |||||||
2720 | $self->_indent(); | ||||||
2721 | my $text = ''; | ||||||
2722 | $text = $self->__string__( $xml_node->[0] ) if @{$xml_node}; | ||||||
2723 | $self->_outdent(); | ||||||
2724 | |||||||
2725 | if ( $text ne '' ) | ||||||
2726 | { | ||||||
2727 | my $node = $self->_create_text_node($text); | ||||||
2728 | if ( $xsl_node->getAttribute('disable-output-escaping') eq 'yes' ) | ||||||
2729 | { | ||||||
2730 | $self->debug("disabling output escaping"); | ||||||
2731 | bless $node, 'XML::XSLT::DOM::TextDOE'; | ||||||
2732 | } | ||||||
2733 | $self->_move_node( $node, $current_result_node ); | ||||||
2734 | } | ||||||
2735 | else | ||||||
2736 | { | ||||||
2737 | $self->debug("nothing left.."); | ||||||
2738 | } | ||||||
2739 | } | ||||||
2740 | else | ||||||
2741 | { | ||||||
2742 | $self->warn( qq{expected attribute "select" in <} | ||||||
2743 | . $self->xsl_ns() | ||||||
2744 | . q{value-of>} ); | ||||||
2745 | } | ||||||
2746 | } | ||||||
2747 | |||||||
2748 | # Convenience as we do this a lot. | ||||||
2749 | sub _create_text_node | ||||||
2750 | { | ||||||
2751 | my ( $self, $text ) = @_; | ||||||
2752 | |||||||
2753 | return $self->xml_document()->createTextNode($text); | ||||||
2754 | } | ||||||
2755 | |||||||
2756 | sub __strip_node_to_text__ | ||||||
2757 | { | ||||||
2758 | my ( $self, $node ) = @_; | ||||||
2759 | |||||||
2760 | my $result = ""; | ||||||
2761 | |||||||
2762 | my $node_type = $node->getNodeType; | ||||||
2763 | if ( $node_type == TEXT_NODE ) | ||||||
2764 | { | ||||||
2765 | $result = $node->getData; | ||||||
2766 | } | ||||||
2767 | elsif (( $node_type == ELEMENT_NODE ) | ||||||
2768 | || ( $node_type == DOCUMENT_FRAGMENT_NODE ) ) | ||||||
2769 | { | ||||||
2770 | $self->_indent(); | ||||||
2771 | foreach my $child ( $node->getChildNodes ) | ||||||
2772 | { | ||||||
2773 | $result .= &__strip_node_to_text__( $self, $child ); | ||||||
2774 | } | ||||||
2775 | $self->_outdent(); | ||||||
2776 | } | ||||||
2777 | return $result; | ||||||
2778 | } | ||||||
2779 | |||||||
2780 | sub __string__ | ||||||
2781 | { | ||||||
2782 | my ( $self, $node, $depth ) = @_; | ||||||
2783 | |||||||
2784 | my $result = ""; | ||||||
2785 | |||||||
2786 | if ( defined $node ) | ||||||
2787 | { | ||||||
2788 | my $ref = ( ref($node) || "not a reference" ); | ||||||
2789 | $self->debug("stripping child nodes ($ref):"); | ||||||
2790 | |||||||
2791 | $self->_indent(); | ||||||
2792 | |||||||
2793 | if ( $ref eq "ARRAY" ) | ||||||
2794 | { | ||||||
2795 | my $str = $self->__string__( $$node[0], $depth ); | ||||||
2796 | $self->_outdent(); | ||||||
2797 | return $str; | ||||||
2798 | } | ||||||
2799 | else | ||||||
2800 | { | ||||||
2801 | my $node_type = $node->getNodeType; | ||||||
2802 | |||||||
2803 | if ( ( $node_type == ELEMENT_NODE ) | ||||||
2804 | || ( $node_type == DOCUMENT_FRAGMENT_NODE ) | ||||||
2805 | || ( $node_type == DOCUMENT_NODE ) ) | ||||||
2806 | { | ||||||
2807 | foreach my $child ( $node->getChildNodes ) | ||||||
2808 | { | ||||||
2809 | $result .= &__string__( $self, $child, 1 ); | ||||||
2810 | } | ||||||
2811 | } | ||||||
2812 | elsif ( $node_type == ATTRIBUTE_NODE ) | ||||||
2813 | { | ||||||
2814 | $result .= $node->getValue; | ||||||
2815 | } | ||||||
2816 | elsif (( $node_type == TEXT_NODE ) | ||||||
2817 | || ( $node_type == CDATA_SECTION_NODE ) | ||||||
2818 | || ( $node_type == ENTITY_REFERENCE_NODE ) ) | ||||||
2819 | { | ||||||
2820 | $result .= $node->getData; | ||||||
2821 | } | ||||||
2822 | elsif ( | ||||||
2823 | !$depth | ||||||
2824 | && ( ( $node_type == PROCESSING_INSTRUCTION_NODE ) | ||||||
2825 | || ( $node_type == COMMENT_NODE ) ) | ||||||
2826 | ) | ||||||
2827 | { | ||||||
2828 | $result .= $node->getData; # COM,PI - only in 'top-level' call | ||||||
2829 | } | ||||||
2830 | else | ||||||
2831 | { | ||||||
2832 | |||||||
2833 | # just to be consistent | ||||||
2834 | $self->warn("Can't get string-value for node of type $ref !"); | ||||||
2835 | } | ||||||
2836 | } | ||||||
2837 | |||||||
2838 | $self->debug(qq{ "$result"}); | ||||||
2839 | $self->_outdent(); | ||||||
2840 | } | ||||||
2841 | else | ||||||
2842 | { | ||||||
2843 | $self->debug(" no result"); | ||||||
2844 | } | ||||||
2845 | |||||||
2846 | return $result; | ||||||
2847 | } | ||||||
2848 | |||||||
2849 | sub _move_node | ||||||
2850 | { | ||||||
2851 | my ( $self, $node, $parent ) = @_; | ||||||
2852 | |||||||
2853 | $self->debug("moving node.."); | ||||||
2854 | |||||||
2855 | $parent->appendChild($node); | ||||||
2856 | } | ||||||
2857 | |||||||
2858 | # returns an array ref of nodes | ||||||
2859 | sub _get_node_set | ||||||
2860 | { | ||||||
2861 | my ( $self, $path, $root_node, $current_path, $current_node, $variables, | ||||||
2862 | $silent ) | ||||||
2863 | = @_; | ||||||
2864 | $current_path ||= "/"; | ||||||
2865 | $current_node ||= $root_node; | ||||||
2866 | $silent ||= 0; | ||||||
2867 | |||||||
2868 | $self->{VARIABLES} ||= {}; | ||||||
2869 | $variables ||= {}; | ||||||
2870 | |||||||
2871 | %{$variables} = ( %{ $self->{VARIABLES} }, %{$variables} ); | ||||||
2872 | $self->debug(qq{getting node-set "$path" from "$current_path"}); | ||||||
2873 | |||||||
2874 | $self->_indent(); | ||||||
2875 | |||||||
2876 | $path = $self->_expand_abbreviations($path); | ||||||
2877 | |||||||
2878 | my $return_nodes = []; | ||||||
2879 | |||||||
2880 | if ( my $varname = $self->_variable_name($path) ) | ||||||
2881 | { | ||||||
2882 | $self->debug('got a variable'); | ||||||
2883 | $return_nodes = $self->_expand_variable($varname, $variables); | ||||||
2884 | } | ||||||
2885 | elsif ( $path =~ /^'([^']*)'$/ ) | ||||||
2886 | { | ||||||
2887 | # this is for the convenience of _process arguments | ||||||
2888 | $self->debug("got a literal '$1'"); | ||||||
2889 | $return_nodes = [ $self->_create_text_node($1) ]; | ||||||
2890 | } | ||||||
2891 | elsif ( $path eq $current_path || $path eq 'self::node()' ) | ||||||
2892 | { | ||||||
2893 | $self->debug("direct hit!"); | ||||||
2894 | $return_nodes = [$current_node]; | ||||||
2895 | } | ||||||
2896 | else | ||||||
2897 | { | ||||||
2898 | |||||||
2899 | # open external documents first # | ||||||
2900 | if ($path =~ /^\s*document\s*\(["'](.*?)["']\s*(,\s*(.*)\s*){0,1}\)\s*(.*)$/) | ||||||
2901 | { | ||||||
2902 | my $filename = $1; | ||||||
2903 | my $sec_arg = $3; | ||||||
2904 | $path = ( $4 || "" ); | ||||||
2905 | |||||||
2906 | $self->debug(qq{external selection ("$filename")!}); | ||||||
2907 | |||||||
2908 | if ($sec_arg) | ||||||
2909 | { | ||||||
2910 | $self->warn("Ignoring second argument of $path"); | ||||||
2911 | } | ||||||
2912 | |||||||
2913 | ($root_node) = $self->__open_by_filename( $filename, $self->{XSL_BASE} ); | ||||||
2914 | } | ||||||
2915 | |||||||
2916 | foreach my $path_part ( split( /\|/, $path ) ) | ||||||
2917 | { | ||||||
2918 | $self->debug("path_part: $path_part"); | ||||||
2919 | |||||||
2920 | if ( my @func_nodes = $self->_process_function( $path_part, $root_node, $current_path, $current_node, $variables, $silent ) ) | ||||||
2921 | { | ||||||
2922 | push @{$return_nodes}, @func_nodes; | ||||||
2923 | } | ||||||
2924 | else | ||||||
2925 | { | ||||||
2926 | if ( $path_part =~ /^\// ) | ||||||
2927 | { | ||||||
2928 | |||||||
2929 | # start from the root # | ||||||
2930 | $current_node = $root_node; | ||||||
2931 | } | ||||||
2932 | elsif ( $path_part =~ /^self\:\:node\(\)\// ) | ||||||
2933 | { #'#"#'#" | ||||||
2934 | # remove preceding dot from './etc', which is expanded to 'self::node()' | ||||||
2935 | # at the top of this subroutine # | ||||||
2936 | $path_part =~ s/^self\:\:node\(\)//; | ||||||
2937 | } | ||||||
2938 | else | ||||||
2939 | { | ||||||
2940 | |||||||
2941 | # to facilitate parsing, precede path with a '/' # | ||||||
2942 | $path_part = "/$path_part"; | ||||||
2943 | } | ||||||
2944 | |||||||
2945 | $self->debug(qq{using "$path_part":}); | ||||||
2946 | |||||||
2947 | if ( $path_part eq '/' ) | ||||||
2948 | { | ||||||
2949 | push @{$return_nodes}, @{$current_node}; | ||||||
2950 | } | ||||||
2951 | else | ||||||
2952 | { | ||||||
2953 | push @{$return_nodes}, | ||||||
2954 | @{$self->__get_node_set__($path_part,[$current_node],$silent)}; | ||||||
2955 | } | ||||||
2956 | } | ||||||
2957 | |||||||
2958 | } | ||||||
2959 | } | ||||||
2960 | $self->_outdent(); | ||||||
2961 | return $return_nodes; | ||||||
2962 | } | ||||||
2963 | |||||||
2964 | # given a path_part and the remaining arguments of _get_node_set | ||||||
2965 | # will return a set of nodes if it is indeed a function call, otherwise | ||||||
2966 | # an empty list | ||||||
2967 | |||||||
2968 | # The builtin functions are implemented as _XSLT_FUNC_ |
||||||
2969 | # passed $root_node, $current_path, $current_node, $variables and a list of | ||||||
2970 | # arguments which are themselves array refs of nodes that will have been expanded | ||||||
2971 | # from _get_node_list. | ||||||
2972 | # | ||||||
2973 | sub _process_function | ||||||
2974 | { | ||||||
2975 | my ( $self, $path_part, $root_node, $current_path, $current_node, $variables, $silent ) = @_; | ||||||
2976 | my @nodes; | ||||||
2977 | |||||||
2978 | $self->_indent(); | ||||||
2979 | $self->debug("check to see if we have a function"); | ||||||
2980 | if ( $path_part =~ /^([a-z-]+)\(\s*(.*)\s*\)/ ) | ||||||
2981 | { | ||||||
2982 | $self->debug("'$path_part' likes like a function call"); | ||||||
2983 | |||||||
2984 | my $func = $1; | ||||||
2985 | my $args = $2; | ||||||
2986 | |||||||
2987 | my $meth_name = "_XSLT_FUNC_$func"; | ||||||
2988 | $meth_name =~ s/-/_/g; | ||||||
2989 | |||||||
2990 | if ( $self->can($meth_name ) ) | ||||||
2991 | { | ||||||
2992 | $self->debug("got implementation for $func()"); | ||||||
2993 | my @args = $self->_process_function_arguments($args,$root_node, $current_path, $current_node, $variables); | ||||||
2994 | @nodes = $self->$meth_name($root_node, $current_path, $current_node, $variables, @args); | ||||||
2995 | } | ||||||
2996 | else | ||||||
2997 | { | ||||||
2998 | $self->debug("$func() either invalid or not implemented"); | ||||||
2999 | } | ||||||
3000 | |||||||
3001 | } | ||||||
3002 | |||||||
3003 | $self->_outdent(); | ||||||
3004 | |||||||
3005 | return @nodes; | ||||||
3006 | } | ||||||
3007 | |||||||
3008 | # _process_function_arguments takes a string representing the arguments | ||||||
3009 | # and $root_node, $current_path, $current_node, $variables, splits the | ||||||
3010 | # arguments on comma and whitespace and processes each resulting "path" | ||||||
3011 | # with _get_node_list to derive a list of array refs of nodes that are to be | ||||||
3012 | # passed to the function implementation | ||||||
3013 | # | ||||||
3014 | |||||||
3015 | sub _process_function_arguments | ||||||
3016 | { | ||||||
3017 | my ( $self, $args, $root_node, $current_path, $current_node, $variables ) = @_; | ||||||
3018 | |||||||
3019 | my @res; | ||||||
3020 | |||||||
3021 | $self->_indent(); | ||||||
3022 | $self->debug("processing args"); | ||||||
3023 | foreach my $arg ( split /\s*,\s*/, $args ) | ||||||
3024 | { | ||||||
3025 | $self->debug("Going to get node set for $arg"); | ||||||
3026 | push @res, $self->_get_node_set($arg, $root_node, $current_path, $current_node, $variables); | ||||||
3027 | } | ||||||
3028 | |||||||
3029 | $self->_outdent(); | ||||||
3030 | |||||||
3031 | return @res; | ||||||
3032 | } | ||||||
3033 | |||||||
3034 | # move these elsewhere when we are done | ||||||
3035 | |||||||
3036 | sub _XSLT_FUNC_concat | ||||||
3037 | { | ||||||
3038 | my ( $self, $root_node, $current_path, $current_node, $variables, @args ) = @_; | ||||||
3039 | |||||||
3040 | $self->_indent(); | ||||||
3041 | $self->debug("processing concat() with " . @args . " arguments"); | ||||||
3042 | |||||||
3043 | my @nodes; # will only be one | ||||||
3044 | |||||||
3045 | my $string; | ||||||
3046 | foreach my $arg ( $self->_arguments_to_strings(@args )) | ||||||
3047 | { | ||||||
3048 | $string = "" unless defined $string; | ||||||
3049 | $string .= $arg if defined $arg; | ||||||
3050 | } | ||||||
3051 | |||||||
3052 | if ( defined $string ) | ||||||
3053 | { | ||||||
3054 | $self->debug("Returning '$string'"); | ||||||
3055 | push @nodes, $self->_create_text_node($string); | ||||||
3056 | } | ||||||
3057 | |||||||
3058 | $self->_outdent(); | ||||||
3059 | return @nodes; | ||||||
3060 | } | ||||||
3061 | |||||||
3062 | sub _XSLT_FUNC_translate | ||||||
3063 | { | ||||||
3064 | my ( $self, $root_node, $current_path, $current_node, $variables, @args ) = @_; | ||||||
3065 | |||||||
3066 | $self->_indent(); | ||||||
3067 | $self->debug("processing translate() with " . @args . " arguments"); | ||||||
3068 | |||||||
3069 | my @nodes; # will only be one | ||||||
3070 | |||||||
3071 | if ( @args == 3 ) | ||||||
3072 | { | ||||||
3073 | my ( $string, $from, $to ) = $self->_arguments_to_strings(@args); | ||||||
3074 | |||||||
3075 | if ( defined $string && defined $from && defined $to ) | ||||||
3076 | { | ||||||
3077 | $self->debug("substituting '$to' for '$from' in '$string'"); | ||||||
3078 | |||||||
3079 | $string =~ s/$from/$to/g; | ||||||
3080 | $self->debug("Returning '$string'"); | ||||||
3081 | push @nodes, $self->_create_text_node($string); | ||||||
3082 | } | ||||||
3083 | } | ||||||
3084 | |||||||
3085 | $self->_outdent(); | ||||||
3086 | return @nodes; | ||||||
3087 | } | ||||||
3088 | |||||||
3089 | # handy for string processing | ||||||
3090 | |||||||
3091 | sub _arguments_to_strings | ||||||
3092 | { | ||||||
3093 | my ( $self, @args ) = @_; | ||||||
3094 | |||||||
3095 | my @ret; | ||||||
3096 | foreach my $arg (@args) | ||||||
3097 | { | ||||||
3098 | if ( ref $arg ) | ||||||
3099 | { | ||||||
3100 | if ( @{$arg} ) | ||||||
3101 | { | ||||||
3102 | my $string = $self->__strip_node_to_text__( $arg->[0] ); | ||||||
3103 | push @ret, $string; | ||||||
3104 | } | ||||||
3105 | } | ||||||
3106 | } | ||||||
3107 | return @ret; | ||||||
3108 | } | ||||||
3109 | |||||||
3110 | # given a path it returns a version with common expansions. | ||||||
3111 | sub _expand_abbreviations | ||||||
3112 | { | ||||||
3113 | my ( $self, $path ) = @_; | ||||||
3114 | |||||||
3115 | # expand abbriviated syntax | ||||||
3116 | $path =~ s/current\(\s*\)/./g; | ||||||
3117 | $path =~ s/\@/attribute\:\:/g; | ||||||
3118 | $path =~ s/\.\./parent\:\:node\(\)/g; | ||||||
3119 | $path =~ s/\./self\:\:node\(\)/g; | ||||||
3120 | $path =~ s/\/\//\/descendant\-or\-self\:\:node\(\)\//g; | ||||||
3121 | |||||||
3122 | return $path; | ||||||
3123 | } | ||||||
3124 | |||||||
3125 | # This returns an array reference of nodes | ||||||
3126 | # if it is a simple text variable then this will be created as text node first | ||||||
3127 | sub _expand_variable | ||||||
3128 | { | ||||||
3129 | my ( $self, $varname, $variables ) = @_; | ||||||
3130 | |||||||
3131 | $self->_indent(); | ||||||
3132 | my $ret = []; | ||||||
3133 | $self->debug("looking for variable $varname"); | ||||||
3134 | $self->debug( join ' ', keys %{$variables} ); | ||||||
3135 | my $var = $variables->{$varname}; | ||||||
3136 | if ( defined $var ) | ||||||
3137 | { | ||||||
3138 | if ( ref( $var ) eq 'ARRAY' ) | ||||||
3139 | { | ||||||
3140 | |||||||
3141 | # node-set array-ref | ||||||
3142 | $ret = $var; | ||||||
3143 | } | ||||||
3144 | elsif ( ref( $var ) eq 'XML::DOM::NodeList' ) | ||||||
3145 | { | ||||||
3146 | |||||||
3147 | # node-set nodelist | ||||||
3148 | $ret = [ @{ $var } ]; | ||||||
3149 | } | ||||||
3150 | elsif ( ref( $var ) eq 'XML::DOM::DocumentFragment' ) | ||||||
3151 | { | ||||||
3152 | |||||||
3153 | # node-set documentfragment | ||||||
3154 | $ret = [ $var->getChildNodes() ]; | ||||||
3155 | } | ||||||
3156 | else | ||||||
3157 | { | ||||||
3158 | $self->debug("$varname is literal '$var'"); | ||||||
3159 | # string or number? | ||||||
3160 | $ret = [$self->_create_text_node($var)]; | ||||||
3161 | } | ||||||
3162 | } | ||||||
3163 | |||||||
3164 | return $ret; | ||||||
3165 | } | ||||||
3166 | |||||||
3167 | # given a candidate expression will return an extracted | ||||||
3168 | # variable name if it looks like a variable. | ||||||
3169 | sub _variable_name | ||||||
3170 | { | ||||||
3171 | my ( $self, $part ) = @_; | ||||||
3172 | |||||||
3173 | my $rc; | ||||||
3174 | |||||||
3175 | if ($part && $part =~ /^\$([\w\.\-]+)$/ ) | ||||||
3176 | { | ||||||
3177 | $rc = $1 | ||||||
3178 | } | ||||||
3179 | return $rc; | ||||||
3180 | } | ||||||
3181 | |||||||
3182 | # auxiliary function # | ||||||
3183 | sub __get_node_set__ | ||||||
3184 | { | ||||||
3185 | my ( $self, $path, $node, $silent ) = @_; | ||||||
3186 | |||||||
3187 | # a Qname (?) should actually be: [a-Z_][\w\.\-]*\:[a-Z_][\w\.\-]* | ||||||
3188 | |||||||
3189 | my $list = []; | ||||||
3190 | |||||||
3191 | if ( $path eq "" ) | ||||||
3192 | { | ||||||
3193 | |||||||
3194 | $self->debug("node found!"); | ||||||
3195 | push @{$list}, @{$node}; | ||||||
3196 | |||||||
3197 | } | ||||||
3198 | else | ||||||
3199 | { | ||||||
3200 | foreach my $item (@$node) | ||||||
3201 | { | ||||||
3202 | my $sublist = $self->__try_a_step__( $path, $item, $silent ); | ||||||
3203 | push @{$list}, @{$sublist} ; | ||||||
3204 | } | ||||||
3205 | } | ||||||
3206 | |||||||
3207 | return $list; | ||||||
3208 | } | ||||||
3209 | |||||||
3210 | sub __try_a_step__ | ||||||
3211 | { | ||||||
3212 | my ( $self, $path, $node, $silent ) = @_; | ||||||
3213 | |||||||
3214 | |||||||
3215 | $self->_indent(); | ||||||
3216 | $self->debug("Trying $path >"); | ||||||
3217 | if ( $path =~ s/^\/parent\:\:node\(\)// ) | ||||||
3218 | { | ||||||
3219 | |||||||
3220 | # /.. # | ||||||
3221 | $self->debug(qq{getting parent ("$path")}); | ||||||
3222 | return $self->__parent__( $path, $node, $silent ); | ||||||
3223 | |||||||
3224 | } | ||||||
3225 | elsif ( $path =~ s/^\/attribute\:\:(\*|[\w\.\:\-]+)// ) | ||||||
3226 | { | ||||||
3227 | |||||||
3228 | # /@attr # | ||||||
3229 | $self->debug(qq{getting attribute `$1' ("$path")}); | ||||||
3230 | return $self->__attribute__( $1, $path, $node, $silent ); | ||||||
3231 | |||||||
3232 | } | ||||||
3233 | elsif ( $path =~ | ||||||
3234 | s/^\/descendant\-or\-self\:\:node\(\)\/(child\:\:|)(\*|[\w\.\:\-]+)\[(.+?)\]// | ||||||
3235 | ) | ||||||
3236 | { | ||||||
3237 | |||||||
3238 | # //elem[n] # | ||||||
3239 | $self->debug(qq{getting deep indexed element `$1' `$2' ("$path")}); | ||||||
3240 | $self->_outdent(); | ||||||
3241 | return &__indexed_element__( $self, $1, $2, $path, $node, $silent, | ||||||
3242 | "deep" ); | ||||||
3243 | |||||||
3244 | } | ||||||
3245 | elsif ( $path =~ s/^\/descendant\-or\-self\:\:node\(\)\/(\*|[\w\.\:\-]+)// ) | ||||||
3246 | { | ||||||
3247 | |||||||
3248 | # //elem # | ||||||
3249 | $self->debug(qq{getting deep element `$1' ("$path")}); | ||||||
3250 | $self->_outdent(); | ||||||
3251 | return &__element__( $self, $1, $path, $node, $silent, "deep" ); | ||||||
3252 | |||||||
3253 | } | ||||||
3254 | elsif ( $path =~ s/^\/(child\:\:|)(\*|[\w\.\:\-]+)\[(.+?)\]// ) | ||||||
3255 | { | ||||||
3256 | |||||||
3257 | # /elem[n] # | ||||||
3258 | $self->debug(qq{getting indexed element `$2' `$3' ("$path")}); | ||||||
3259 | $self->_outdent(); | ||||||
3260 | return &__indexed_element__( $self, $2, $3, $path, $node, $silent ); | ||||||
3261 | |||||||
3262 | } | ||||||
3263 | elsif ( $path =~ s/^\/(child\:\:|)text\(\)// ) | ||||||
3264 | { | ||||||
3265 | |||||||
3266 | # /text() # | ||||||
3267 | $self->debug(qq{getting text ("$path")}); | ||||||
3268 | $self->_outdent(); | ||||||
3269 | return &__get_nodes__( $self, TEXT_NODE, $path, $node, $silent ); | ||||||
3270 | |||||||
3271 | } | ||||||
3272 | elsif ( $path =~ s/^\/(child\:\:|)processing-instruction\(\)// ) | ||||||
3273 | { | ||||||
3274 | |||||||
3275 | # /processing-instruction() # | ||||||
3276 | $self->debug(qq{getting processing instruction ("$path")}); | ||||||
3277 | $self->_outdent(); | ||||||
3278 | return $self->__get_nodes__(PROCESSING_INSTRUCTION_NODE, | ||||||
3279 | $path, | ||||||
3280 | $node, | ||||||
3281 | $silent ); | ||||||
3282 | |||||||
3283 | } | ||||||
3284 | elsif ( $path =~ s/^\/(child\:\:|)comment\(\)// ) | ||||||
3285 | { | ||||||
3286 | |||||||
3287 | # /comment() # | ||||||
3288 | $self->debug(qq{getting comment ("$path")}); | ||||||
3289 | $self->_outdent(); | ||||||
3290 | return &__get_nodes__( $self, COMMENT_NODE, $path, $node, $silent ); | ||||||
3291 | |||||||
3292 | } | ||||||
3293 | elsif ( $path =~ s/^\/(child\:\:|)(\*|[\w\.\:\-]+)// ) | ||||||
3294 | { | ||||||
3295 | |||||||
3296 | # /elem # | ||||||
3297 | $self->debug(qq{getting element `$2' ("$path")}); | ||||||
3298 | $self->_outdent(); | ||||||
3299 | return &__element__( $self, $2, $path, $node, $silent ); | ||||||
3300 | |||||||
3301 | } | ||||||
3302 | else | ||||||
3303 | { | ||||||
3304 | $self->warn( | ||||||
3305 | "get-node-from-path: Don't know what to do with path $path !!!"); | ||||||
3306 | $self->_outdent(); | ||||||
3307 | return []; | ||||||
3308 | } | ||||||
3309 | } | ||||||
3310 | |||||||
3311 | sub __parent__ | ||||||
3312 | { | ||||||
3313 | my ( $self, $path, $node, $silent ) = @_; | ||||||
3314 | |||||||
3315 | $self->_indent(); | ||||||
3316 | if ( ( $node->getNodeType == DOCUMENT_NODE ) | ||||||
3317 | || ( $node->getNodeType == DOCUMENT_FRAGMENT_NODE ) ) | ||||||
3318 | { | ||||||
3319 | $self->debug("no parent!"); | ||||||
3320 | $node = []; | ||||||
3321 | } | ||||||
3322 | else | ||||||
3323 | { | ||||||
3324 | $node = $node->getParentNode; | ||||||
3325 | |||||||
3326 | $node = &__get_node_set__( $self, $path, [$node], $silent ); | ||||||
3327 | } | ||||||
3328 | $self->_outdent(); | ||||||
3329 | |||||||
3330 | return $node; | ||||||
3331 | } | ||||||
3332 | |||||||
3333 | sub __indexed_element__ | ||||||
3334 | { | ||||||
3335 | my ( $self, $element, $index, $path, $node, $silent, $deep ) = @_; | ||||||
3336 | $index ||= 0; | ||||||
3337 | $deep ||= ""; # False # | ||||||
3338 | |||||||
3339 | my $xpath; | ||||||
3340 | |||||||
3341 | $self->debug("got element $element and index $index at $path"); | ||||||
3342 | if ( $index =~ /^\d+$/ ) | ||||||
3343 | { | ||||||
3344 | $self->debug("got a numeric index"); | ||||||
3345 | $index--; | ||||||
3346 | } | ||||||
3347 | else | ||||||
3348 | { | ||||||
3349 | $self->debug("index is an expression"); | ||||||
3350 | if ( $index =~ /^first\s*\(\)/ ) | ||||||
3351 | { | ||||||
3352 | $index = 0; | ||||||
3353 | } | ||||||
3354 | elsif ( $index =~ /^last\s*\(\)/ ) | ||||||
3355 | { | ||||||
3356 | $index = -1; | ||||||
3357 | } | ||||||
3358 | elsif ( $index =~ /attribute::(\S+)/ ) | ||||||
3359 | { | ||||||
3360 | $xpath = "$element\[\@$1\]"; | ||||||
3361 | $index = 0; | ||||||
3362 | } | ||||||
3363 | } | ||||||
3364 | |||||||
3365 | my @list; | ||||||
3366 | if ( $xpath ) | ||||||
3367 | { | ||||||
3368 | $self->debug("tring with expression $xpath"); | ||||||
3369 | @list = $node->findnodes($xpath); | ||||||
3370 | } | ||||||
3371 | else | ||||||
3372 | { | ||||||
3373 | @list = $node->getElementsByTagName( $element, $deep ); | ||||||
3374 | } | ||||||
3375 | |||||||
3376 | $self->debug( "got " . @list . " candidate elements" ); | ||||||
3377 | if (@list) | ||||||
3378 | { | ||||||
3379 | $self->debug("Getting index item $index"); | ||||||
3380 | $node = $list[$index]; | ||||||
3381 | } | ||||||
3382 | else | ||||||
3383 | { | ||||||
3384 | $node = ""; | ||||||
3385 | } | ||||||
3386 | |||||||
3387 | $self->_indent(); | ||||||
3388 | if ($node) | ||||||
3389 | { | ||||||
3390 | $node = &__get_node_set__( $self, $path, [$node], $silent ); | ||||||
3391 | } | ||||||
3392 | else | ||||||
3393 | { | ||||||
3394 | $self->debug("failed!"); | ||||||
3395 | $node = []; | ||||||
3396 | } | ||||||
3397 | $self->_outdent(); | ||||||
3398 | |||||||
3399 | return $node; | ||||||
3400 | } | ||||||
3401 | |||||||
3402 | sub __element__ | ||||||
3403 | { | ||||||
3404 | my ( $self, $element, $path, $node, $silent, $deep ) = @_; | ||||||
3405 | $deep ||= ""; # False # | ||||||
3406 | |||||||
3407 | $node = [ $node->getElementsByTagName( $element, $deep ) ]; | ||||||
3408 | |||||||
3409 | $self->_indent(); | ||||||
3410 | if (@$node) | ||||||
3411 | { | ||||||
3412 | $node = $self->__get_node_set__( $path, $node, $silent ); | ||||||
3413 | } | ||||||
3414 | else | ||||||
3415 | { | ||||||
3416 | $self->debug("failed!"); | ||||||
3417 | } | ||||||
3418 | $self->_outdent(); | ||||||
3419 | |||||||
3420 | return $node; | ||||||
3421 | } | ||||||
3422 | |||||||
3423 | sub __attribute__ | ||||||
3424 | { | ||||||
3425 | my ( $self, $attribute, $path, $node, $silent ) = @_; | ||||||
3426 | |||||||
3427 | $self->_indent(); | ||||||
3428 | |||||||
3429 | if ( $attribute eq '*' ) | ||||||
3430 | { | ||||||
3431 | $node = [ $node->getAttributes->getValues ]; | ||||||
3432 | |||||||
3433 | if ($node) | ||||||
3434 | { | ||||||
3435 | $node = &__get_node_set__( $self, $path, $node, $silent ); | ||||||
3436 | } | ||||||
3437 | else | ||||||
3438 | { | ||||||
3439 | $self->debug("failed!"); | ||||||
3440 | } | ||||||
3441 | } | ||||||
3442 | else | ||||||
3443 | { | ||||||
3444 | $node = $node->getAttributeNode($attribute); | ||||||
3445 | |||||||
3446 | if ($node) | ||||||
3447 | { | ||||||
3448 | $node = &__get_node_set__( $self, $path, [$node], $silent ); | ||||||
3449 | } | ||||||
3450 | else | ||||||
3451 | { | ||||||
3452 | $self->debug("failed!"); | ||||||
3453 | $node = []; | ||||||
3454 | } | ||||||
3455 | } | ||||||
3456 | |||||||
3457 | $self->_outdent(); | ||||||
3458 | |||||||
3459 | return $node; | ||||||
3460 | } | ||||||
3461 | |||||||
3462 | sub __get_nodes__ | ||||||
3463 | { | ||||||
3464 | my ( $self, $node_type, $path, $node, $silent ) = @_; | ||||||
3465 | |||||||
3466 | my $result = []; | ||||||
3467 | |||||||
3468 | $self->_indent(); | ||||||
3469 | foreach my $child ( $node->getChildNodes ) | ||||||
3470 | { | ||||||
3471 | if ( $child->getNodeType == $node_type ) | ||||||
3472 | { | ||||||
3473 | push @{$result}, @{$self->__get_node_set__($path, | ||||||
3474 | [$child], $silent )}; | ||||||
3475 | } | ||||||
3476 | } | ||||||
3477 | $self->_outdent(); | ||||||
3478 | |||||||
3479 | if ( !@$result ) | ||||||
3480 | { | ||||||
3481 | $self->debug("failed!"); | ||||||
3482 | } | ||||||
3483 | |||||||
3484 | return $result; | ||||||
3485 | } | ||||||
3486 | |||||||
3487 | sub _attribute_value_of | ||||||
3488 | { | ||||||
3489 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
3490 | $variables ) | ||||||
3491 | = @_; | ||||||
3492 | |||||||
3493 | foreach my $attribute ( $xsl_node->getAttributes->getValues ) | ||||||
3494 | { | ||||||
3495 | my $value = $attribute->getValue; | ||||||
3496 | study($value); | ||||||
3497 | |||||||
3498 | #$value =~ s/(\*|\$|\@|\&|\?|\+|\\)/\\$1/g; | ||||||
3499 | $value =~ s/(\*|\?|\+)/\\$1/g; | ||||||
3500 | study($value); | ||||||
3501 | while ( $value =~ /\G[^\\]*\{(.*?[^\\]*)\}/ ) | ||||||
3502 | { | ||||||
3503 | my $node = | ||||||
3504 | $self->_get_node_set( $1, $self->xml_document(), | ||||||
3505 | $current_xml_selection_path, $current_xml_node, $variables ); | ||||||
3506 | if (@$node) | ||||||
3507 | { | ||||||
3508 | $self->_indent(); | ||||||
3509 | my $text = $self->__string__( $$node[0] ); | ||||||
3510 | $self->_outdent(); | ||||||
3511 | $value =~ s/(\G[^\\]*)\{(.*?)[^\\]*\}/$1$text/; | ||||||
3512 | } | ||||||
3513 | else | ||||||
3514 | { | ||||||
3515 | $value =~ s/(\G[^\\]*)\{(.*?)[^\\]*\}/$1/; | ||||||
3516 | } | ||||||
3517 | } | ||||||
3518 | |||||||
3519 | #$value =~ s/\\(\*|\$|\@|\&|\?|\+|\\)/$1/g; | ||||||
3520 | $value =~ s/\\(\*|\?|\+)/$1/g; | ||||||
3521 | $value =~ s/\\(\{|\})/$1/g; | ||||||
3522 | $attribute->setValue($value); | ||||||
3523 | } | ||||||
3524 | } | ||||||
3525 | |||||||
3526 | sub _processing_instruction | ||||||
3527 | { | ||||||
3528 | my ( $self, $xsl_node, $current_result_node, $variables, $oldvariables ) = | ||||||
3529 | @_; | ||||||
3530 | |||||||
3531 | my $new_PI_name = $xsl_node->getAttribute('name'); | ||||||
3532 | |||||||
3533 | if ( $new_PI_name eq "xml" ) | ||||||
3534 | { | ||||||
3535 | $self->warn( "<" | ||||||
3536 | . $self->xsl_ns() | ||||||
3537 | . "processing-instruction> may not be used to create XML" ); | ||||||
3538 | $self->warn( | ||||||
3539 | "declaration. Use <" . $self->xsl_ns() . "output> instead..." ); | ||||||
3540 | } | ||||||
3541 | elsif ($new_PI_name) | ||||||
3542 | { | ||||||
3543 | my $text = $self->__string__($xsl_node); | ||||||
3544 | my $new_PI = | ||||||
3545 | $self->xml_document() | ||||||
3546 | ->createProcessingInstruction( $new_PI_name, $text ); | ||||||
3547 | |||||||
3548 | if ($new_PI) | ||||||
3549 | { | ||||||
3550 | $self->_move_node( $new_PI, $current_result_node ); | ||||||
3551 | } | ||||||
3552 | } | ||||||
3553 | else | ||||||
3554 | { | ||||||
3555 | $self->warn( q{Expected attribute "name" in <} | ||||||
3556 | . $self->xsl_ns() | ||||||
3557 | . "processing-instruction> !" ); | ||||||
3558 | } | ||||||
3559 | } | ||||||
3560 | |||||||
3561 | sub _process_with_params | ||||||
3562 | { | ||||||
3563 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
3564 | $variables, $params ) | ||||||
3565 | = @_; | ||||||
3566 | |||||||
3567 | my @params = | ||||||
3568 | $xsl_node->getElementsByTagName( $self->xsl_ns() . "with-param" ); | ||||||
3569 | foreach my $param (@params) | ||||||
3570 | { | ||||||
3571 | my $varname = $param->getAttribute('name'); | ||||||
3572 | |||||||
3573 | if ($varname) | ||||||
3574 | { | ||||||
3575 | my $value = $param->getAttribute('select'); | ||||||
3576 | |||||||
3577 | if ( !$value ) | ||||||
3578 | { | ||||||
3579 | |||||||
3580 | # process content as template | ||||||
3581 | $value = $self->xml_document()->createDocumentFragment; | ||||||
3582 | |||||||
3583 | $self->_evaluate_template( $param, $current_xml_node, | ||||||
3584 | $current_xml_selection_path, $value, $variables, {} ); | ||||||
3585 | $$params{$varname} = $value; | ||||||
3586 | |||||||
3587 | } | ||||||
3588 | else | ||||||
3589 | { | ||||||
3590 | |||||||
3591 | # *** FIXME - should evaluate this as an expression! | ||||||
3592 | $$params{$varname} = $value; | ||||||
3593 | } | ||||||
3594 | } | ||||||
3595 | else | ||||||
3596 | { | ||||||
3597 | $self->warn( q{Expected attribute "name" in <} | ||||||
3598 | . $self->xsl_ns() | ||||||
3599 | . q{with-param> !} ); | ||||||
3600 | } | ||||||
3601 | } | ||||||
3602 | |||||||
3603 | } | ||||||
3604 | |||||||
3605 | sub _call_template | ||||||
3606 | { | ||||||
3607 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
3608 | $current_result_node, $variables, $oldvariables ) | ||||||
3609 | = @_; | ||||||
3610 | |||||||
3611 | my $params = {}; | ||||||
3612 | my $newvariables = defined $variables ? {%$variables} : {}; | ||||||
3613 | my $name = $xsl_node->getAttribute('name'); | ||||||
3614 | |||||||
3615 | if ($name) | ||||||
3616 | { | ||||||
3617 | $self->debug(qq{calling template named "$name"}); | ||||||
3618 | |||||||
3619 | $self->_process_with_params( $xsl_node, $current_xml_node, | ||||||
3620 | $current_xml_selection_path, $variables, $params ); | ||||||
3621 | |||||||
3622 | $self->_indent(); | ||||||
3623 | my $template = $self->_match_template( "name", $name, 0, '' ); | ||||||
3624 | |||||||
3625 | if ($template) | ||||||
3626 | { | ||||||
3627 | $self->_evaluate_template( $template, $current_xml_node, | ||||||
3628 | $current_xml_selection_path, $current_result_node, | ||||||
3629 | $newvariables, $params ); | ||||||
3630 | } | ||||||
3631 | else | ||||||
3632 | { | ||||||
3633 | $self->warn("no template named $name found!"); | ||||||
3634 | } | ||||||
3635 | $self->_outdent(); | ||||||
3636 | } | ||||||
3637 | else | ||||||
3638 | { | ||||||
3639 | $self->warn( q{Expected attribute "name" in <} | ||||||
3640 | . $self->xsl_ns() | ||||||
3641 | . q{call-template/>} ); | ||||||
3642 | } | ||||||
3643 | } | ||||||
3644 | |||||||
3645 | sub _choose | ||||||
3646 | { | ||||||
3647 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
3648 | $current_result_node, $variables, $oldvariables ) | ||||||
3649 | = @_; | ||||||
3650 | |||||||
3651 | $self->debug("evaluating choose:"); | ||||||
3652 | |||||||
3653 | $self->_indent(); | ||||||
3654 | |||||||
3655 | my $notdone = "true"; | ||||||
3656 | my $testwhen = "active"; | ||||||
3657 | foreach my $child ( $xsl_node->getElementsByTagName( '*', 0 ) ) | ||||||
3658 | { | ||||||
3659 | if ( $notdone | ||||||
3660 | && $testwhen | ||||||
3661 | && ( $child->getTagName eq $self->xsl_ns() . "when" ) ) | ||||||
3662 | { | ||||||
3663 | my $test = $child->getAttribute('test'); | ||||||
3664 | |||||||
3665 | if ($test) | ||||||
3666 | { | ||||||
3667 | my $test_succeeds = | ||||||
3668 | $self->_evaluate_test( $test, $current_xml_node, | ||||||
3669 | $current_xml_selection_path, $variables ); | ||||||
3670 | if ($test_succeeds) | ||||||
3671 | { | ||||||
3672 | $self->_evaluate_template( $child, $current_xml_node, | ||||||
3673 | $current_xml_selection_path, $current_result_node, | ||||||
3674 | $variables, $oldvariables ); | ||||||
3675 | $testwhen = ""; | ||||||
3676 | $notdone = ""; | ||||||
3677 | } | ||||||
3678 | } | ||||||
3679 | else | ||||||
3680 | { | ||||||
3681 | $self->warn( q{expected attribute "test" in <} | ||||||
3682 | . $self->xsl_ns() | ||||||
3683 | . q{when>} ); | ||||||
3684 | } | ||||||
3685 | } | ||||||
3686 | elsif ( $notdone | ||||||
3687 | && ( $child->getTagName eq $self->xsl_ns() . "otherwise" ) ) | ||||||
3688 | { | ||||||
3689 | $self->_evaluate_template( $child, $current_xml_node, | ||||||
3690 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
3691 | $oldvariables ); | ||||||
3692 | $notdone = ""; | ||||||
3693 | } | ||||||
3694 | } | ||||||
3695 | |||||||
3696 | if ($notdone) | ||||||
3697 | { | ||||||
3698 | $self->debug("nothing done!"); | ||||||
3699 | } | ||||||
3700 | |||||||
3701 | $self->_outdent(); | ||||||
3702 | } | ||||||
3703 | |||||||
3704 | sub _if | ||||||
3705 | { | ||||||
3706 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
3707 | $current_result_node, $variables, $oldvariables ) | ||||||
3708 | = @_; | ||||||
3709 | |||||||
3710 | $self->debug("evaluating if:"); | ||||||
3711 | |||||||
3712 | $self->_indent(); | ||||||
3713 | |||||||
3714 | my $test = $xsl_node->getAttribute('test'); | ||||||
3715 | |||||||
3716 | if ($test) | ||||||
3717 | { | ||||||
3718 | my $test_succeeds = | ||||||
3719 | $self->_evaluate_test( $test, $current_xml_node, | ||||||
3720 | $current_xml_selection_path, $variables ); | ||||||
3721 | if ($test_succeeds) | ||||||
3722 | { | ||||||
3723 | $self->_evaluate_template( $xsl_node, $current_xml_node, | ||||||
3724 | $current_xml_selection_path, $current_result_node, $variables, | ||||||
3725 | $oldvariables ); | ||||||
3726 | } | ||||||
3727 | } | ||||||
3728 | else | ||||||
3729 | { | ||||||
3730 | $self->warn( | ||||||
3731 | q{expected attribute "test" in <} . $self->xsl_ns() . q{if>} ); | ||||||
3732 | } | ||||||
3733 | |||||||
3734 | $self->_outdent(); | ||||||
3735 | } | ||||||
3736 | |||||||
3737 | sub __evaluate_test__ | ||||||
3738 | { | ||||||
3739 | my ( $self, $test, $path, $node, $variables ) = @_; | ||||||
3740 | |||||||
3741 | my $rc = 0; | ||||||
3742 | my $tagname = eval { $node->getTagName() } || ''; | ||||||
3743 | |||||||
3744 | $self->debug(qq{testing with "$test" and $tagname}); | ||||||
3745 | |||||||
3746 | if ($test =~ /^\s*(\S+?)\s*(<=|>=|!=|<|>|=)\s*(\S+?)\s*$/) | ||||||
3747 | { | ||||||
3748 | my $lhs = $1; | ||||||
3749 | my $test_cond = $2; | ||||||
3750 | my $rhs = $3; | ||||||
3751 | $self->debug("Test LHS: $lhs COND: $test_cond RHS: $rhs"); | ||||||
3752 | |||||||
3753 | my $content = $self->_get_first_value($lhs, $path, $node, $variables); | ||||||
3754 | my $expval = $self->_get_first_value($rhs, $path, $node, $variables); | ||||||
3755 | |||||||
3756 | $rc = $self->_evaluate_test_expression($content, $test_cond, $expval); | ||||||
3757 | } | ||||||
3758 | else | ||||||
3759 | { | ||||||
3760 | $self->debug("no match for test [$test]"); | ||||||
3761 | } | ||||||
3762 | |||||||
3763 | return $rc; | ||||||
3764 | } | ||||||
3765 | |||||||
3766 | # convenience for above | ||||||
3767 | |||||||
3768 | sub _get_first_value | ||||||
3769 | { | ||||||
3770 | my ( $self, $test_path, $path, $node, $variables ) = @_; | ||||||
3771 | |||||||
3772 | if ( $test_path =~ /^\d+$/ ) | ||||||
3773 | { | ||||||
3774 | $test_path = "'$test_path'"; | ||||||
3775 | } | ||||||
3776 | |||||||
3777 | my $content; | ||||||
3778 | |||||||
3779 | my $nodeset = $self->_get_node_set( $test_path, | ||||||
3780 | $self->xml_document(), | ||||||
3781 | $path, | ||||||
3782 | $node, | ||||||
3783 | $variables ); | ||||||
3784 | |||||||
3785 | if ( @{$nodeset} ) | ||||||
3786 | { | ||||||
3787 | $content = $self->__string__( $nodeset->[0] ); | ||||||
3788 | } | ||||||
3789 | else | ||||||
3790 | { | ||||||
3791 | $self->debug("didn't get a result for $test_path"); | ||||||
3792 | } | ||||||
3793 | |||||||
3794 | return $content; | ||||||
3795 | |||||||
3796 | } | ||||||
3797 | |||||||
3798 | =item _evaluate_test_expression | ||||||
3799 | |||||||
3800 | Given two values and a condition return a boolean. | ||||||
3801 | |||||||
3802 | =cut | ||||||
3803 | |||||||
3804 | sub _evaluate_test_expression | ||||||
3805 | { | ||||||
3806 | my ( $self, $content, $test_cond, $expval ) = @_; | ||||||
3807 | |||||||
3808 | my $rc = 0; | ||||||
3809 | |||||||
3810 | if ( defined $content && defined $test_cond && defined $expval ) | ||||||
3811 | { | ||||||
3812 | my $numeric = ( $content =~ /^\d+$/ && $expval =~ /^\d+$/ ? 1 : 0 ); | ||||||
3813 | |||||||
3814 | $self->debug("evaluating $content $test_cond $expval"); | ||||||
3815 | |||||||
3816 | $test_cond =~ s/\s+//g; | ||||||
3817 | |||||||
3818 | if ( $test_cond eq '!=' ) | ||||||
3819 | { | ||||||
3820 | $rc = $numeric ? $content != $expval : $content ne $expval; | ||||||
3821 | } | ||||||
3822 | elsif ( $test_cond eq '=' ) | ||||||
3823 | { | ||||||
3824 | $rc = $numeric ? $content == $expval : $content eq $expval; | ||||||
3825 | } | ||||||
3826 | elsif ( $test_cond eq '<' ) | ||||||
3827 | { | ||||||
3828 | $rc = $numeric ? $content < $expval : $content lt $expval; | ||||||
3829 | } | ||||||
3830 | elsif ( $test_cond eq '>' ) | ||||||
3831 | { | ||||||
3832 | $rc = $numeric ? $content > $expval : $content gt $expval; | ||||||
3833 | } | ||||||
3834 | elsif ( $test_cond eq '>=' ) | ||||||
3835 | { | ||||||
3836 | $rc = $numeric ? $content >= $expval : $content ge $expval; | ||||||
3837 | } | ||||||
3838 | elsif ( $test_cond eq '<=' ) | ||||||
3839 | { | ||||||
3840 | $rc = $numeric ? $content <= $expval : $content le $expval; | ||||||
3841 | } | ||||||
3842 | else | ||||||
3843 | { | ||||||
3844 | $self->debug("no test matches"); | ||||||
3845 | } | ||||||
3846 | } | ||||||
3847 | else | ||||||
3848 | { | ||||||
3849 | $self->debug("not all test parts defined"); | ||||||
3850 | } | ||||||
3851 | |||||||
3852 | return $rc; | ||||||
3853 | |||||||
3854 | } | ||||||
3855 | |||||||
3856 | sub _copy_of | ||||||
3857 | { | ||||||
3858 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
3859 | $current_result_node, $variables ) | ||||||
3860 | = @_; | ||||||
3861 | |||||||
3862 | my $nodelist; | ||||||
3863 | my $select = $xsl_node->getAttribute('select'); | ||||||
3864 | $self->debug(qq{evaluating copy-of with select "$select":}); | ||||||
3865 | |||||||
3866 | $self->_indent(); | ||||||
3867 | if ($select) | ||||||
3868 | { | ||||||
3869 | $nodelist = | ||||||
3870 | $self->_get_node_set( $select, $self->xml_document(), | ||||||
3871 | $current_xml_selection_path, $current_xml_node, $variables ); | ||||||
3872 | } | ||||||
3873 | else | ||||||
3874 | { | ||||||
3875 | $self->warn( q{expected attribute "select" in <} | ||||||
3876 | . $self->xsl_ns() | ||||||
3877 | . q{copy-of>} ); | ||||||
3878 | } | ||||||
3879 | foreach my $node (@$nodelist) | ||||||
3880 | { | ||||||
3881 | $self->_add_node( $node, $current_result_node, "deep" ); | ||||||
3882 | } | ||||||
3883 | |||||||
3884 | $self->_outdent(); | ||||||
3885 | } | ||||||
3886 | |||||||
3887 | sub _copy | ||||||
3888 | { | ||||||
3889 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
3890 | $current_result_node, $variables, $oldvariables ) | ||||||
3891 | = @_; | ||||||
3892 | |||||||
3893 | $self->debug("evaluating copy:"); | ||||||
3894 | |||||||
3895 | $self->_indent(); | ||||||
3896 | if ( $current_xml_node->getNodeType == ATTRIBUTE_NODE ) | ||||||
3897 | { | ||||||
3898 | my $attribute = $current_xml_node->cloneNode(0); | ||||||
3899 | $current_result_node->setAttributeNode($attribute); | ||||||
3900 | } | ||||||
3901 | elsif (( $current_xml_node->getNodeType == COMMENT_NODE ) | ||||||
3902 | || ( $current_xml_node->getNodeType == PROCESSING_INSTRUCTION_NODE ) ) | ||||||
3903 | { | ||||||
3904 | $self->_add_node( $current_xml_node, $current_result_node ); | ||||||
3905 | } | ||||||
3906 | else | ||||||
3907 | { | ||||||
3908 | $self->_add_node( $current_xml_node, $current_result_node ); | ||||||
3909 | $self->_apply_attribute_set($xsl_node,$current_result_node->getLastChild()); | ||||||
3910 | $self->_evaluate_template( $xsl_node, $current_xml_node, | ||||||
3911 | $current_xml_selection_path, $current_result_node->getLastChild, | ||||||
3912 | $variables, $oldvariables ); | ||||||
3913 | } | ||||||
3914 | $self->_outdent(); | ||||||
3915 | } | ||||||
3916 | |||||||
3917 | sub _text | ||||||
3918 | { | ||||||
3919 | |||||||
3920 | #=item addText (text) | ||||||
3921 | # | ||||||
3922 | #Appends the specified string to the last child if it is a Text node, or else | ||||||
3923 | #appends a new Text node (with the specified text.) | ||||||
3924 | # | ||||||
3925 | #Return Value: the last child if it was a Text node or else the new Text node. | ||||||
3926 | my ( $self, $xsl_node, $current_result_node ) = @_; | ||||||
3927 | |||||||
3928 | $self->debug("inserting text:"); | ||||||
3929 | |||||||
3930 | $self->_indent(); | ||||||
3931 | |||||||
3932 | $self->debug("stripping node to text:"); | ||||||
3933 | |||||||
3934 | $self->_indent(); | ||||||
3935 | my $text = $self->__string__($xsl_node); | ||||||
3936 | $self->_outdent(); | ||||||
3937 | |||||||
3938 | if ( $text ne '' ) | ||||||
3939 | { | ||||||
3940 | my $node = $self->_create_text_node($text); | ||||||
3941 | if ( $xsl_node->getAttribute('disable-output-escaping') eq 'yes' ) | ||||||
3942 | { | ||||||
3943 | $self->debug("disabling output escaping"); | ||||||
3944 | bless $node, 'XML::XSLT::DOM::TextDOE'; | ||||||
3945 | } | ||||||
3946 | $self->_move_node( $node, $current_result_node ); | ||||||
3947 | } | ||||||
3948 | else | ||||||
3949 | { | ||||||
3950 | $self->debug("nothing left.."); | ||||||
3951 | } | ||||||
3952 | |||||||
3953 | $current_result_node->normalize(); | ||||||
3954 | |||||||
3955 | $self->_outdent(); | ||||||
3956 | } | ||||||
3957 | |||||||
3958 | sub _attribute | ||||||
3959 | { | ||||||
3960 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
3961 | $current_result_node, $variables, $oldvariables ) | ||||||
3962 | = @_; | ||||||
3963 | |||||||
3964 | my $name = $xsl_node->getAttribute('name'); | ||||||
3965 | $self->debug(qq{inserting attribute named "$name":}); | ||||||
3966 | $self->_indent(); | ||||||
3967 | |||||||
3968 | if ($name) | ||||||
3969 | { | ||||||
3970 | if ( $name =~ /^xmlns:/ ) | ||||||
3971 | { | ||||||
3972 | $self->debug("Won't create namespace declaration"); | ||||||
3973 | } | ||||||
3974 | else | ||||||
3975 | { | ||||||
3976 | my $result = $self->xml_document()->createDocumentFragment; | ||||||
3977 | |||||||
3978 | $self->_evaluate_template( $xsl_node, $current_xml_node, | ||||||
3979 | $current_xml_selection_path, $result, $variables, | ||||||
3980 | $oldvariables ); | ||||||
3981 | |||||||
3982 | $self->_indent(); | ||||||
3983 | my $text = $self->fix_attribute_value( $self->__string__($result) ); | ||||||
3984 | |||||||
3985 | $self->_outdent(); | ||||||
3986 | |||||||
3987 | $current_result_node->setAttribute( $name, $text ); | ||||||
3988 | $result->dispose(); | ||||||
3989 | } | ||||||
3990 | } | ||||||
3991 | else | ||||||
3992 | { | ||||||
3993 | $self->warn( q{expected attribute "name" in <} | ||||||
3994 | . $self->xsl_ns() | ||||||
3995 | . q{attribute>} ); | ||||||
3996 | } | ||||||
3997 | $self->_outdent(); | ||||||
3998 | } | ||||||
3999 | |||||||
4000 | sub _comment | ||||||
4001 | { | ||||||
4002 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
4003 | $current_result_node, $variables, $oldvariables ) | ||||||
4004 | = @_; | ||||||
4005 | |||||||
4006 | $self->debug("inserting comment:"); | ||||||
4007 | |||||||
4008 | $self->_indent(); | ||||||
4009 | |||||||
4010 | my $result = $self->xml_document()->createDocumentFragment; | ||||||
4011 | |||||||
4012 | $self->_evaluate_template( $xsl_node, $current_xml_node, | ||||||
4013 | $current_xml_selection_path, $result, $variables, $oldvariables ); | ||||||
4014 | |||||||
4015 | $self->_indent(); | ||||||
4016 | my $text = $self->__string__($result); | ||||||
4017 | $self->_outdent(); | ||||||
4018 | |||||||
4019 | $self->_move_node( $self->xml_document()->createComment($text), | ||||||
4020 | $current_result_node ); | ||||||
4021 | $result->dispose(); | ||||||
4022 | |||||||
4023 | $self->_outdent(); | ||||||
4024 | } | ||||||
4025 | |||||||
4026 | sub _variable | ||||||
4027 | { | ||||||
4028 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
4029 | $current_result_node, $variables, $params, $is_param ) | ||||||
4030 | = @_; | ||||||
4031 | |||||||
4032 | my $varname = $xsl_node->getAttribute('name'); | ||||||
4033 | |||||||
4034 | if ($varname) | ||||||
4035 | { | ||||||
4036 | $self->debug("definition of variable \$$varname:"); | ||||||
4037 | |||||||
4038 | $self->_indent(); | ||||||
4039 | |||||||
4040 | if ( $is_param and exists $$params{$varname} ) | ||||||
4041 | { | ||||||
4042 | |||||||
4043 | $self->debug("copying from parent-template"); | ||||||
4044 | # copy from parent-template | ||||||
4045 | |||||||
4046 | $$variables{$varname} = $$params{$varname}; | ||||||
4047 | |||||||
4048 | } | ||||||
4049 | else | ||||||
4050 | { | ||||||
4051 | $self->debug("new variable"); | ||||||
4052 | |||||||
4053 | # new variable definition | ||||||
4054 | |||||||
4055 | my $value = $xsl_node->getAttribute('select'); | ||||||
4056 | |||||||
4057 | if ( !$value ) | ||||||
4058 | { | ||||||
4059 | $self->debug("no select - evaluate as template"); | ||||||
4060 | |||||||
4061 | #tough case, evaluate content as template | ||||||
4062 | |||||||
4063 | $value = $self->xml_document()->createDocumentFragment; | ||||||
4064 | |||||||
4065 | $self->_evaluate_template( $xsl_node, $current_xml_node, | ||||||
4066 | $current_xml_selection_path, $value, $variables, $params ); | ||||||
4067 | } | ||||||
4068 | else # either a literal or path | ||||||
4069 | { | ||||||
4070 | if ( $value =~ /^'(.*)'$/ ) | ||||||
4071 | { | ||||||
4072 | $self->debug('literal value $1'); | ||||||
4073 | $value = $1; | ||||||
4074 | } | ||||||
4075 | else | ||||||
4076 | { | ||||||
4077 | $self->debug("processing as a path"); | ||||||
4078 | |||||||
4079 | my $node = | ||||||
4080 | $self->_get_node_set( $value, $self->xml_document(), | ||||||
4081 | $current_xml_selection_path, $current_xml_node, | ||||||
4082 | $variables ); | ||||||
4083 | $value = $self->__string__($node); | ||||||
4084 | |||||||
4085 | } | ||||||
4086 | |||||||
4087 | } | ||||||
4088 | $variables->{$varname} = $value; | ||||||
4089 | } | ||||||
4090 | |||||||
4091 | $self->_outdent(); | ||||||
4092 | } | ||||||
4093 | else | ||||||
4094 | { | ||||||
4095 | $self->warn( q{expected attribute "name" in <} | ||||||
4096 | . $self->xsl_ns() | ||||||
4097 | . q{param> or <} | ||||||
4098 | . $self->xsl_ns() | ||||||
4099 | . q{variable>} ); | ||||||
4100 | } | ||||||
4101 | } | ||||||
4102 | |||||||
4103 | # not implemented - but log it and make it go away | ||||||
4104 | |||||||
4105 | sub _sort | ||||||
4106 | { | ||||||
4107 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
4108 | $current_result_node, $variables, $params, $is_param ) | ||||||
4109 | = @_; | ||||||
4110 | |||||||
4111 | $self->debug("dummy process for sort"); | ||||||
4112 | } | ||||||
4113 | |||||||
4114 | # Not quite sure how fallback should be implemented as the spec seems a | ||||||
4115 | # little vague to me | ||||||
4116 | |||||||
4117 | sub _fallback | ||||||
4118 | { | ||||||
4119 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
4120 | $current_result_node, $variables, $params, $is_param ) | ||||||
4121 | = @_; | ||||||
4122 | |||||||
4123 | $self->debug("dummy process for fallback"); | ||||||
4124 | } | ||||||
4125 | |||||||
4126 | # This is a no-op - attribute-sets should not appear within templates and | ||||||
4127 | # we have already processed the stylesheet wide ones. | ||||||
4128 | |||||||
4129 | sub _attribute_set | ||||||
4130 | { | ||||||
4131 | my ( $self, $xsl_node, $current_xml_node, $current_xml_selection_path, | ||||||
4132 | $current_result_node, $variables, $params, $is_param ) | ||||||
4133 | = @_; | ||||||
4134 | |||||||
4135 | $self->debug("in _attribute_set"); | ||||||
4136 | } | ||||||
4137 | |||||||
4138 | sub _indent | ||||||
4139 | { | ||||||
4140 | my ($self) = @_; | ||||||
4141 | $self->{INDENT} += $self->{INDENT_INCR}; | ||||||
4142 | |||||||
4143 | } | ||||||
4144 | |||||||
4145 | sub _outdent | ||||||
4146 | { | ||||||
4147 | my ($self) = @_; | ||||||
4148 | $self->{INDENT} -= $self->{INDENT_INCR}; | ||||||
4149 | } | ||||||
4150 | |||||||
4151 | sub fix_attribute_value | ||||||
4152 | { | ||||||
4153 | my ( $self, $text ) = @_; | ||||||
4154 | |||||||
4155 | # The spec say's that there can't be a literal line break in the | ||||||
4156 | # attributes value - white space at the beginning or the end is | ||||||
4157 | # almost certainly an mistake. | ||||||
4158 | |||||||
4159 | $text =~ s/^\s+//g; | ||||||
4160 | $text =~ s/\s+$//g; | ||||||
4161 | |||||||
4162 | if ($text) | ||||||
4163 | { | ||||||
4164 | $text =~ s/([\x0A\x0D])/sprintf("\%02X;",ord $1)/eg; | ||||||
4165 | } | ||||||
4166 | |||||||
4167 | return $text; | ||||||
4168 | } | ||||||
4169 | |||||||
4170 | 1; | ||||||
4171 | |||||||
4172 | __DATA__ |