1 package Koha
::XSLT_Handler
;
3 # Copyright 2014 Rijksmuseum
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 3 of the License, or (at your option) any later
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 Koha::XSLT_Handler - Facilitate use of XSLT transformations
26 use Koha::XSLT_Handler;
27 my $xslt_engine = Koha::XSLT_Handler->new;
28 my $output = $xslt_engine->transform($xml, $xsltfilename);
29 $output = $xslt_engine->transform({ xml => $xml, file => $file });
30 $output = $xslt_engine->transform({ xml => $xml, code => $code });
31 my $err= $xslt_engine->err; # error number
32 my $errstr= $xslt_engine->errstr; # error message
33 $xslt_engine->refresh($xsltfilename);
37 A XSLT handler object on top of LibXML and LibXSLT, allowing you to
38 run XSLT stylesheets repeatedly without loading them again.
39 Errors occurring during loading, parsing or transforming are reported
40 via the err and errstr attributes.
41 Reloading XSLT files can be done with the refresh method.
47 Create handler object (via Class::Accessor)
51 Run transformation for specific string and stylesheet
55 Allow to reload stylesheets when transforming again
61 Error number (see list of ERROR CODES)
67 =head2 do_not_return_source
69 If true, transform returns undef on failure. By default, it returns the
70 original string passed. Errors are reported as described.
74 If set, print error messages to STDERR. True by default.
88 Error while loading stylesheet xml: [furter information]
92 Error while parsing stylesheet: [furter information]
96 Error while parsing input: [furter information]
100 Error while transforming input: [furter information]
104 No string to transform
108 For documentation purposes. You are not encouraged to access them.
112 Contains the last successfully executed XSLT filename
116 Hash reference to loaded stylesheets
118 =head1 ADDITIONAL COMMENTS
126 use base
qw(Class::Accessor);
128 __PACKAGE__
->mk_ro_accessors(qw( err errstr ));
129 __PACKAGE__
->mk_accessors(qw( do_not_return_source print_warns ));
133 my $output= $xslt_engine->transform( $xml, $xsltfilename, [$format] );
135 #$output = $xslt_engine->transform({ xml => $xml, file => $file, [parameters => $parameters], [format => ['chars'|'bytes'|'xmldoc']] });
136 #$output = $xslt_engine->transform({ xml => $xml, code => $code, [parameters => $parameters], [format => ['chars'|'bytes'|'xmldoc']] });
137 if( $xslt_engine->err ) {
138 #decide what to do on failure..
140 my $output2= $xslt_engine->transform( $xml2 );
142 Pass a xml string and a fully qualified path of a XSLT file.
143 Instead of a filename, you may also pass a URL.
144 You may also pass the contents of a xsl file as a string like $code above.
145 If you do not pass a filename, the last file used is assumed.
146 Normally returns the transformed string; if you pass format => 'xmldoc' in
147 the hash format, it returns a xml document object.
148 Check the error number in err to know if something went wrong.
149 In that case do_not_return_source did determine the return value.
157 # old style: $xml, $filename, $format
158 # new style: $hashref
159 my ( $xml, $filename, $xsltcode, $format );
161 if( ref $_[0] eq 'HASH' ) {
163 $xsltcode = $_[0]->{code
};
164 $filename = $_[0]->{file
} if !$xsltcode; #xsltcode gets priority
165 $parameters = $_[0]->{parameters
} if ref $_[0]->{parameters
} eq 'HASH';
166 $format = $_[0]->{format
} || 'chars';
168 ( $xml, $filename, $format ) = @_;
173 if ( !$self->{xslt_hash
} ) {
177 $self->_set_error; #clear last error
179 my $retval = $self->{do_not_return_source
} ?
undef : $xml;
181 #check if no string passed
182 if ( !defined $xml ) {
183 $self->_set_error(7);
184 return; #always undef
188 my $key = $self->_load( $filename, $xsltcode );
189 my $stsh = $key?
$self->{xslt_hash
}->{$key}: undef;
190 return $retval if $self->{err
};
192 #parse input and transform
193 my $parser = XML
::LibXML
->new();
194 my $source = eval { $parser->parse_string($xml) };
196 $self->_set_error( 5, $@
);
200 #$parameters is an optional hashref that contains
201 #key-value pairs to be sent to the XSLT.
202 #Numbers may be bare but strings must be double quoted
203 #(e.g. "'string'" or '"string"'). See XML::LibXSLT for
206 #NOTE: Parameters are not cached. They are provided for
207 #each different transform.
208 my $transformed = $stsh->transform($source, %$parameters);
210 ?
$stsh->output_as_bytes( $transformed )
211 : $format eq 'xmldoc'
213 : $stsh->output_as_chars( $transformed ); # default: chars
216 $self->_set_error( 6, $@
);
219 $self->{last_xsltfile
} = $key;
225 $xslt_engine->refresh;
226 $xslt_engine->refresh( $xsltfilename );
228 Pass a file for an individual refresh or no file to refresh all.
229 Refresh returns the number of items affected.
230 What we actually do, is just clear the internal cache for reloading next
231 time when transform is called.
232 The return value is mainly theoretical. Since this is supposed to work
233 always(...), there is no actual need to test it.
234 Note that refresh does also clear the error information.
239 my ( $self, $file ) = @_;
241 return if !$self->{xslt_hash
};
244 $rv = delete $self->{xslt_hash
}->{$file} ?
1 : 0;
247 $rv = scalar keys %{ $self->{xslt_hash
} };
248 $self->{xslt_hash
} = {};
253 # ************** INTERNAL ROUTINES ********************************************
256 # Internal routine for initialization.
262 $self->{xslt_hash
} = {};
263 $self->{print_warns
} = 1 unless exists $self->{print_warns
};
264 $self->{do_not_return_source
} = 0
265 unless exists $self->{do_not_return_source
};
267 #by default we return source on a failing transformation
268 #but it could be passed at construction time already
273 # Internal routine for loading a new stylesheet.
276 my ( $self, $filename, $code ) = @_;
277 my ( $digest, $codelen, $salt, $rv );
278 $salt = 'AZ'; #just a constant actually
280 #If no file or code passed, use the last file again
281 if ( !$filename && !$code ) {
282 my $last = $self->{last_xsltfile
};
283 if ( !$last || !exists $self->{xslt_hash
}->{$last} ) {
284 $self->_set_error(1);
290 #check if it is loaded already
292 $codelen = length( $code );
293 $digest = eval { crypt($code, $salt) };
294 if( $digest && exists $self->{xslt_hash
}->{$digest.$codelen} ) {
295 return $digest.$codelen;
297 } elsif( $filename && exists $self->{xslt_hash
}->{$filename} ) {
301 #Check file existence (skipping URLs)
302 if( $filename && $filename !~ /^https?:\/\
// && !-e
$filename ) {
303 $self->_set_error(2);
308 my $parser = XML
::LibXML
->new;
309 my $style_doc = eval {
310 $parser->load_xml( $self->_load_xml_args($filename, $code) )
313 $self->_set_error( 3, $@
);
318 my $xslt = XML
::LibXSLT
->new;
319 $rv = $code?
$digest.$codelen: $filename;
320 $self->{xslt_hash
}->{$rv} = eval { $xslt->parse_stylesheet($style_doc) };
322 $self->_set_error( 4, $@
);
323 delete $self->{xslt_hash
}->{$rv};
331 return $_[1]?
{ 'string' => $_[1]//'' }: { 'location' => $_[0]//'' };
335 # Internal routine for handling error information.
338 my ( $self, $errno, $addmsg ) = @_;
340 if ( !$errno ) { #clear the error
341 $self->{err
} = undef;
342 $self->{errstr
} = undef;
346 $self->{err
} = $errno;
348 $self->{errstr
} = "No XSLT file passed.";
350 elsif ( $errno == 2 ) {
351 $self->{errstr
} = "XSLT file not found.";
353 elsif ( $errno == 3 ) {
354 $self->{errstr
} = "Error while loading stylesheet xml:";
356 elsif ( $errno == 4 ) {
357 $self->{errstr
} = "Error while parsing stylesheet:";
359 elsif ( $errno == 5 ) {
360 $self->{errstr
} = "Error while parsing input:";
362 elsif ( $errno == 6 ) {
363 $self->{errstr
} = "Error while transforming input:";
365 elsif ( $errno == 7 ) {
366 $self->{errstr
} = "No string to transform.";
370 $self->{errstr
} .= " $addmsg";
373 warn $self->{errstr
} if $self->{print_warns
};
379 Marcel de Rooy, Rijksmuseum Netherlands