1 package Koha
::XSLT_Handler
;
3 # Copyright 2014, 2019 Rijksmuseum, Prosentient Systems
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 code
32 $xslt_engine->refresh($xsltfilename);
36 A XSLT handler object on top of LibXML and LibXSLT, allowing you to
37 run XSLT stylesheets repeatedly without loading them again.
38 Errors occurring during loading, parsing or transforming are reported
39 via the err attribute.
40 Reloading XSLT files can be done with the refresh method.
50 Run transformation for specific string and stylesheet
54 Allow to reload stylesheets when transforming again
60 Error code (see list of ERROR CODES)
62 =head2 do_not_return_source
64 If true, transform returns undef on failure. By default, it returns the
65 original string passed. Errors are reported as described.
69 If set, print error messages to STDERR. False by default. Looks at the
70 DEBUG environment variable too.
74 =head2 Error XSLTH_ERR_NO_FILE
78 =head2 Error XSLTH_ERR_FILE_NOT_FOUND
82 =head2 Error XSLTH_ERR_LOADING
84 Error while loading stylesheet xml: [optional warnings]
86 =head2 Error XSLTH_ERR_PARSING_CODE
88 Error while parsing stylesheet: [optional warnings]
90 =head2 Error XSLTH_ERR_PARSING_DATA
92 Error while parsing input: [optional warnings]
94 =head2 Error XSLTH_ERR_TRANSFORMING
96 Error while transforming input: [optional warnings]
98 =head2 Error XSLTH_NO_STRING_PASSED
100 No string to transform
104 For documentation purposes. You are not encouraged to access them.
108 Contains the last successfully executed XSLT filename
112 Hash reference to loaded stylesheets
114 =head1 ADDITIONAL COMMENTS
121 use Koha
::XSLT
::Security
;
123 use base
qw(Class::Accessor);
125 __PACKAGE__
->mk_ro_accessors(qw( err ));
126 __PACKAGE__
->mk_accessors(qw( do_not_return_source print_warns ));
128 use constant XSLTH_ERR_1
=> 'XSLTH_ERR_NO_FILE';
129 use constant XSLTH_ERR_2
=> 'XSLTH_ERR_FILE_NOT_FOUND';
130 use constant XSLTH_ERR_3
=> 'XSLTH_ERR_LOADING';
131 use constant XSLTH_ERR_4
=> 'XSLTH_ERR_PARSING_CODE';
132 use constant XSLTH_ERR_5
=> 'XSLTH_ERR_PARSING_DATA';
133 use constant XSLTH_ERR_6
=> 'XSLTH_ERR_TRANSFORMING';
134 use constant XSLTH_ERR_7
=> 'XSLTH_NO_STRING_PASSED';
138 my $xslt_engine = Koha::XSLT_Handler->new;
143 my ($class, $params) = @_;
144 my $self = $class->SUPER::new
($params);
145 $self->{_security
} = Koha
::XSLT
::Security
->new;
146 $self->{_security
}->register_callbacks;
152 my $output= $xslt_engine->transform( $xml, $xsltfilename, [$format] );
154 #$output = $xslt_engine->transform({ xml => $xml, file => $file, [parameters => $parameters], [format => ['chars'|'bytes'|'xmldoc']] });
155 #$output = $xslt_engine->transform({ xml => $xml, code => $code, [parameters => $parameters], [format => ['chars'|'bytes'|'xmldoc']] });
156 if( $xslt_engine->err ) {
157 #decide what to do on failure..
159 my $output2= $xslt_engine->transform( $xml2 );
161 Pass a xml string and a fully qualified path of a XSLT file.
162 Instead of a filename, you may also pass a URL.
163 You may also pass the contents of a xsl file as a string like $code above.
164 If you do not pass a filename, the last file used is assumed.
165 Normally returns the transformed string; if you pass format => 'xmldoc' in
166 the hash format, it returns a xml document object.
167 Check the error number in err to know if something went wrong.
168 In that case do_not_return_source did determine the return value.
176 # old style: $xml, $filename, $format
177 # new style: $hashref
178 my ( $xml, $filename, $xsltcode, $format );
180 if( ref $_[0] eq 'HASH' ) {
182 $xsltcode = $_[0]->{code
};
183 $filename = $_[0]->{file
} if !$xsltcode; #xsltcode gets priority
184 $parameters = $_[0]->{parameters
} if ref $_[0]->{parameters
} eq 'HASH';
185 $format = $_[0]->{format
} || 'chars';
187 ( $xml, $filename, $format ) = @_;
192 if ( !$self->{xslt_hash
} ) {
196 $self->_set_error; #clear last error
198 my $retval = $self->{do_not_return_source
} ?
undef : $xml;
200 #check if no string passed
201 if ( !defined $xml ) {
202 $self->_set_error( XSLTH_ERR_7
);
203 return; #always undef
207 my $key = $self->_load( $filename, $xsltcode );
208 my $stsh = $key?
$self->{xslt_hash
}->{$key}: undef;
209 return $retval if $self->{err
};
211 #parse input and transform
212 my $parser = XML
::LibXML
->new();
213 $self->{_security
}->set_parser_options($parser);
214 my $source = eval { $parser->parse_string($xml) };
216 $self->_set_error( XSLTH_ERR_5
, $@
);
220 #$parameters is an optional hashref that contains
221 #key-value pairs to be sent to the XSLT.
222 #Numbers may be bare but strings must be double quoted
223 #(e.g. "'string'" or '"string"'). See XML::LibXSLT for
226 #NOTE: Parameters are not cached. They are provided for
227 #each different transform.
228 my $transformed = $stsh->transform($source, %$parameters);
230 ?
$stsh->output_as_bytes( $transformed )
231 : $format eq 'xmldoc'
233 : $stsh->output_as_chars( $transformed ); # default: chars
236 $self->_set_error( XSLTH_ERR_6
, $@
);
239 $self->{last_xsltfile
} = $key;
245 $xslt_engine->refresh;
246 $xslt_engine->refresh( $xsltfilename );
248 Pass a file for an individual refresh or no file to refresh all.
249 Refresh returns the number of items affected.
250 What we actually do, is just clear the internal cache for reloading next
251 time when transform is called.
252 The return value is mainly theoretical. Since this is supposed to work
253 always(...), there is no actual need to test it.
254 Note that refresh does also clear the error information.
259 my ( $self, $file ) = @_;
261 return if !$self->{xslt_hash
};
264 $rv = delete $self->{xslt_hash
}->{$file} ?
1 : 0;
267 $rv = scalar keys %{ $self->{xslt_hash
} };
268 $self->{xslt_hash
} = {};
273 # ************** INTERNAL ROUTINES ********************************************
276 # Internal routine for initialization.
282 $self->{xslt_hash
} = {};
283 $self->{print_warns
} = 1 unless exists $self->{print_warns
};
284 $self->{do_not_return_source
} = 0
285 unless exists $self->{do_not_return_source
};
287 #by default we return source on a failing transformation
288 #but it could be passed at construction time already
293 # Internal routine for loading a new stylesheet.
296 my ( $self, $filename, $code ) = @_;
297 my ( $digest, $codelen, $salt, $rv );
298 $salt = 'AZ'; #just a constant actually
300 #If no file or code passed, use the last file again
301 if ( !$filename && !$code ) {
302 my $last = $self->{last_xsltfile
};
303 if ( !$last || !exists $self->{xslt_hash
}->{$last} ) {
304 $self->_set_error( XSLTH_ERR_1
);
310 #check if it is loaded already
312 $codelen = length( $code );
313 $digest = eval { crypt($code, $salt) };
314 if( $digest && exists $self->{xslt_hash
}->{$digest.$codelen} ) {
315 return $digest.$codelen;
317 } elsif( $filename && exists $self->{xslt_hash
}->{$filename} ) {
321 #Check file existence (skipping URLs)
322 if( $filename && $filename !~ /^https?:\/\
// && !-e
$filename ) {
323 $self->_set_error( XSLTH_ERR_2
);
328 my $parser = XML
::LibXML
->new;
329 $self->{_security
}->set_parser_options($parser);
330 my $style_doc = eval {
331 $parser->load_xml( $self->_load_xml_args($filename, $code) )
334 $self->_set_error( XSLTH_ERR_3
, $@
);
339 my $xslt = XML
::LibXSLT
->new;
340 $self->{_security
}->set_callbacks($xslt);
342 $rv = $code?
$digest.$codelen: $filename;
343 $self->{xslt_hash
}->{$rv} = eval { $xslt->parse_stylesheet($style_doc) };
345 $self->_set_error( XSLTH_ERR_4
, $@
);
346 delete $self->{xslt_hash
}->{$rv};
354 return $_[1]?
{ 'string' => $_[1]//'' }: { 'location' => $_[0]//'' };
358 # Internal routine for handling error information.
361 my ( $self, $errcode, $warn ) = @_;
363 $self->{err
} = $errcode; #set or clear error
364 warn 'XSLT_Handler: '. $warn if $warn && $self->{print_warns
};
369 Marcel de Rooy, Rijksmuseum Netherlands
370 David Cook, Prosentient Systems