View | Details | Raw Unified | Return to bug 11826
Collapse All | Expand All

(-)a/Koha/XSLT_Handler.pm (-1 / +314 lines)
Line 0 Link Here
0
- 
1
package Koha::XSLT_Handler;
2
3
# Copyright 2014 Rijksmuseum
4
#
5
# This file is part of Koha.
6
#
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
10
# version.
11
#
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.
15
#
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.
19
20
=head1 NAME
21
22
Koha::XSLT_Handler - Facilitate use of XSLT transformations
23
24
=head1 SYNOPSIS
25
26
    use Koha::XSLT_Handler;
27
    my $xslt_engine = Koha::XSLT_Handler->new;
28
    my $output = $xslt_engine->transform($xml, $xsltfilename);
29
    my $err= $xslt_engine->err; # error number
30
    my $errstr= $xslt_engine->errstr; # error message
31
    $xslt_engine->refresh($xsltfilename);
32
33
=head1 DESCRIPTION
34
35
    A XSLT handler object on top of LibXML and LibXSLT, allowing you to
36
    run XSLT stylesheets repeatedly without loading them again.
37
    Errors occurring during loading, parsing or transforming are reported
38
    via the err and errstr attributes.
39
    Reloading XSLT files can be done with the refresh method.
40
41
=head1 METHODS
42
43
=head2 new
44
45
    Create handler object (via Class::Accessor)
46
47
=head2 transform
48
49
    Run transformation for specific string and stylesheet
50
51
=head2 refresh
52
53
    Allow to reload stylesheets when transforming again
54
55
=head1 PROPERTIES
56
57
=head2 err
58
59
    Error number (see list of ERROR CODES)
60
61
=head2 errstr
62
63
    Error message
64
65
=head2 do_not_return_source
66
67
    If true, transform returns undef on failure. By default, it returns the
68
    original string passed. Errors are reported as described.
69
70
=head1 ERROR CODES
71
72
=head2 Error 1
73
74
    No XSLT file passed
75
76
=head2 Error 2
77
78
    XSLT file not found
79
80
=head2 Error 3
81
82
    Error while loading stylesheet xml: [furter information]
83
84
=head2 Error 4
85
86
    Error while parsing stylesheet: [furter information]
87
88
=head2 Error 5
89
90
    Error while parsing input: [furter information]
91
92
=head2 Error 6
93
94
    Error while transforming input: [furter information]
95
96
=head2 Error 7
97
98
    No string to transform
99
100
=head1 INTERNALS
101
102
    For documentation purposes. You are not encouraged to access them.
103
104
=head2 last_xsltfile
105
106
    Contains the last successfully executed XSLT filename
107
108
=head2 xslt_hash
109
110
    Hash reference to loaded stylesheets
111
112
=head1 ADDITIONAL COMMENTS
113
114
=cut
115
116
use Modern::Perl;
117
use XML::LibXML;
118
use XML::LibXSLT;
119
120
use base qw(Class::Accessor);
121
122
__PACKAGE__->mk_ro_accessors(qw( err errstr ));
123
__PACKAGE__->mk_accessors(qw( do_not_return_source ));
124
125
=head2 transform
126
127
    my $output= $xslt_engine->transform( $xml, $xsltfilename );
128
    if( $xslt_engine->err ) {
129
        #decide what to do on failure..
130
    }
131
    my $output2= $xslt_engine->transform( $xml2 );
132
133
    Pass a xml string and a fully qualified path of a XSLT file.
134
    If you do not pass a filename, the last file used is assumed.
135
    Returns the transformed string.
136
    Check the error number in err to know if something went wrong.
137
    In that case do_not_return_source did determine the return value.
138
139
=cut
140
141
sub transform {
142
    my ( $self, $orgxml, $file ) = @_;
143
144
    #Initialized yet?
145
    if( !$self->{xslt_hash} ) {
146
        $self->_init;
147
    }
148
    else {
149
        $self->_set_error; #clear error
150
    }
151
    my $retval= $self->{do_not_return_source}? undef: $orgxml;
152
153
    #check if no string passed
154
    if( !defined $orgxml ) {
155
        $self->_set_error(7);
156
        return; #always undef
157
    }
158
159
    #If no file passed, use the last file again
160
    if( !$file ) {
161
        if( !$self->{last_xsltfile} ) {
162
            $self->_set_error(1);
163
            return $retval;
164
        }
165
        $file= $self->{last_xsltfile};
166
    }
167
168
    #load stylesheet
169
    my $stsh= $self->{xslt_hash}->{$file} // $self->_load($file);
170
    return $retval if $self->{err};
171
172
    #parse input and transform
173
    my $parser = XML::LibXML->new();
174
    my $source= eval { $parser->parse_string($orgxml) };
175
    if( $@ ) {
176
        $self->_set_error(5, $@);
177
        return $retval;
178
    }
179
    my $str= eval {
180
        my $result= $stsh->transform($source);
181
        $stsh->output_as_chars($result);
182
    };
183
    if( $@ ) {
184
        $self->_set_error(6, $@);
185
        return $retval;
186
    }
187
    $self->{last_xsltfile}= $file;
188
    return $str;
189
}
190
191
=head2 refresh
192
193
    $xslt_engine->refresh;
194
    $xslt_engine->refresh( $xsltfilename );
195
196
    Pass a file for an individual refresh or no file to refresh all.
197
    Refresh returns the number of items affected.
198
    What we actually do, is just clear the internal cache for reloading next
199
    time when transform is called.
200
    The return value is mainly theoretical. Since this is supposed to work
201
    always(...), there is no actual need to test it.
202
    Note that refresh does also clear the error information.
203
204
=cut
205
206
sub refresh {
207
    my ( $self, $file )= @_;
208
    $self->_set_error;
209
    return if !$self->{xslt_hash};
210
    my $rv;
211
    if( $file ) {
212
        $rv= delete $self->{xslt_hash}->{$file}? 1: 0;
213
    }
214
    else {
215
        $rv= scalar keys %{ $self->{xslt_hash} };
216
        $self->{xslt_hash}= {};
217
    }
218
    return $rv;
219
}
220
221
# **************  INTERNAL ROUTINES ********************************************
222
223
# _init
224
# Internal routine for initialization.
225
226
sub _init {
227
    my $self= shift;
228
229
    $self->_set_error;
230
    $self->{xslt_hash}={};
231
    $self->{do_not_return_source}=0 unless exists $self->{do_not_return_source};
232
        #by default we return source on a failing transformation
233
        #but it could be passed at construction time already
234
    return;
235
}
236
237
# _load
238
# Internal routine for loading a new stylesheet.
239
240
sub _load {
241
    my ($self, $file)= @_;
242
243
    if( !$file || !-e $file ) {
244
        $self->_set_error(2);
245
        return;
246
    }
247
248
    #load sheet
249
    my $parser = XML::LibXML->new;
250
    my $style_doc = eval { $parser->load_xml( location => $file ) };
251
    if( $@ ) {
252
        $self->_set_error(3, $@);
253
        return;
254
    }
255
256
    #parse sheet
257
    my $xslt = XML::LibXSLT->new;
258
    $self->{xslt_hash}->{$file} = eval { $xslt->parse_stylesheet($style_doc) };
259
    if( $@ ) {
260
        $self->_set_error(4, $@);
261
        delete $self->{xslt_hash}->{$file};
262
        return;
263
    }
264
    return $self->{xslt_hash}->{$file};
265
}
266
267
# _set_error
268
# Internal routine for handling error information.
269
270
sub _set_error {
271
    my ($self, $errno, $addmsg)= @_;
272
273
    if(!$errno) { #clear the error
274
        $self->{err}= undef;
275
        $self->{errstr}= undef;
276
        return;
277
    }
278
279
    $self->{err}= $errno;
280
    if($errno==1) {
281
        $self->{errstr}= "No XSLT file passed.";
282
    }
283
    elsif($errno==2) {
284
        $self->{errstr}= "XSLT file not found.";
285
    }
286
    elsif($errno==3) {
287
        $self->{errstr}= "Error while loading stylesheet xml:";
288
    }
289
    elsif($errno==4) {
290
        $self->{errstr}= "Error while parsing stylesheet:";
291
    }
292
    elsif($errno==5) {
293
        $self->{errstr}= "Error while parsing input:";
294
    }
295
    elsif($errno==6) {
296
        $self->{errstr}= "Error while transforming input:";
297
    }
298
    elsif($errno==7) {
299
        $self->{errstr}= "No string to transform.";
300
    }
301
302
    if( $addmsg ) {
303
        $self->{errstr}.= " $addmsg";
304
    }
305
    return;
306
}
307
308
=head1 AUTHOR
309
310
    Marcel de Rooy, Rijksmuseum Netherlands
311
312
=cut
313
314
1;

Return to bug 11826