File Coverage

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 for
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 method on each XML::DOM object.
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_ and are
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__