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 );
135 #$output = $xslt_engine->transform({ xml => $xml, file => $file, [parameters => $parameters] });
136 #$output = $xslt_engine->transform({ xml => $xml, code => $code, [parameters => $parameters] });
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 Returns the transformed string.
147 Check the error number in err to know if something went wrong.
148 In that case do_not_return_source did determine the return value.
156 # old style: $xml, $filename
157 # new style: $hashref
158 my ( $xml, $filename, $xsltcode );
160 if( ref $_[0] eq 'HASH' ) {
162 $xsltcode = $_[0]->{code};
163 $filename = $_[0]->{file} if !$xsltcode; #xsltcode gets priority
164 $parameters = $_[0]->{parameters} if ref $_[0]->{parameters} eq 'HASH';
166 ( $xml, $filename ) = @_;
170 if ( !$self->{xslt_hash} ) {
174 $self->_set_error; #clear last error
176 my $retval = $self->{do_not_return_source} ? undef : $xml;
178 #check if no string passed
179 if ( !defined $xml ) {
180 $self->_set_error(7);
181 return; #always undef
185 my $key = $self->_load( $filename, $xsltcode );
186 my $stsh = $key? $self->{xslt_hash}->{$key}: undef;
187 return $retval if $self->{err};
189 #parse input and transform
190 my $parser = XML::LibXML->new();
191 my $source = eval { $parser->parse_string($xml) };
193 $self->_set_error( 5, $@ );
197 #$parameters is an optional hashref that contains
198 #key-value pairs to be sent to the XSLT.
199 #Numbers may be bare but strings must be double quoted
200 #(e.g. "'string'" or '"string"'). See XML::LibXSLT for
203 #NOTE: Parameters are not cached. They are provided for
204 #each different transform.
205 my $result = $stsh->transform($source, %$parameters);
206 $stsh->output_as_chars($result);
209 $self->_set_error( 6, $@ );
212 $self->{last_xsltfile} = $key;
218 $xslt_engine->refresh;
219 $xslt_engine->refresh( $xsltfilename );
221 Pass a file for an individual refresh or no file to refresh all.
222 Refresh returns the number of items affected.
223 What we actually do, is just clear the internal cache for reloading next
224 time when transform is called.
225 The return value is mainly theoretical. Since this is supposed to work
226 always(...), there is no actual need to test it.
227 Note that refresh does also clear the error information.
232 my ( $self, $file ) = @_;
234 return if !$self->{xslt_hash};
237 $rv = delete $self->{xslt_hash}->{$file} ? 1 : 0;
240 $rv = scalar keys %{ $self->{xslt_hash} };
241 $self->{xslt_hash} = {};
246 # ************** INTERNAL ROUTINES ********************************************
249 # Internal routine for initialization.
255 $self->{xslt_hash} = {};
256 $self->{print_warns} = 1 unless exists $self->{print_warns};
257 $self->{do_not_return_source} = 0
258 unless exists $self->{do_not_return_source};
260 #by default we return source on a failing transformation
261 #but it could be passed at construction time already
266 # Internal routine for loading a new stylesheet.
269 my ( $self, $filename, $code ) = @_;
270 my ( $digest, $codelen, $salt, $rv );
271 $salt = 'AZ'; #just a constant actually
273 #If no file or code passed, use the last file again
274 if ( !$filename && !$code ) {
275 my $last = $self->{last_xsltfile};
276 if ( !$last || !exists $self->{xslt_hash}->{$last} ) {
277 $self->_set_error(1);
283 #check if it is loaded already
285 $codelen = length( $code );
286 $digest = eval { crypt($code, $salt) };
287 if( $digest && exists $self->{xslt_hash}->{$digest.$codelen} ) {
288 return $digest.$codelen;
290 } elsif( $filename && exists $self->{xslt_hash}->{$filename} ) {
294 #Check file existence (skipping URLs)
295 if( $filename && $filename !~ /^https?:\/\// && !-e $filename ) {
296 $self->_set_error(2);
301 my $parser = XML::LibXML->new;
302 my $style_doc = eval {
303 $parser->load_xml( $self->_load_xml_args($filename, $code) )
306 $self->_set_error( 3, $@ );
311 my $xslt = XML::LibXSLT->new;
312 $rv = $code? $digest.$codelen: $filename;
313 $self->{xslt_hash}->{$rv} = eval { $xslt->parse_stylesheet($style_doc) };
315 $self->_set_error( 4, $@ );
316 delete $self->{xslt_hash}->{$rv};
324 return $_[1]? { 'string' => $_[1]//'' }: { 'location' => $_[0]//'' };
328 # Internal routine for handling error information.
331 my ( $self, $errno, $addmsg ) = @_;
333 if ( !$errno ) { #clear the error
334 $self->{err} = undef;
335 $self->{errstr} = undef;
339 $self->{err} = $errno;
341 $self->{errstr} = "No XSLT file passed.";
343 elsif ( $errno == 2 ) {
344 $self->{errstr} = "XSLT file not found.";
346 elsif ( $errno == 3 ) {
347 $self->{errstr} = "Error while loading stylesheet xml:";
349 elsif ( $errno == 4 ) {
350 $self->{errstr} = "Error while parsing stylesheet:";
352 elsif ( $errno == 5 ) {
353 $self->{errstr} = "Error while parsing input:";
355 elsif ( $errno == 6 ) {
356 $self->{errstr} = "Error while transforming input:";
358 elsif ( $errno == 7 ) {
359 $self->{errstr} = "No string to transform.";
363 $self->{errstr} .= " $addmsg";
366 warn $self->{errstr} if $self->{print_warns};
372 Marcel de Rooy, Rijksmuseum Netherlands