File Coverage

blib/lib/XML/ApacheFOP.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             package XML::ApacheFOP;
2 1     1   23775 use strict;
  1         2  
  1         64  
3             our $VERSION = '0.03';
4              
5             =head1 NAME
6              
7             XML::ApacheFOP - Access Apache FOP from Perl to create PDF files using XSL-FO.
8              
9             =head1 SYNOPSIS
10              
11             use XML::ApacheFOP;
12            
13             my $Fop = XML::ApacheFOP->new();
14            
15             # create a PDF using a xml/xsl tranformation
16             $Fop->fop(xml=>"foo.xml", xsl=>"bar.xsl", outfile=>"temp1.pdf") || die "cannot create pdf: " . $Fop->errstr;
17            
18             # create a PDF using an xsl-fo file
19             $Fop->fop(fo=>"foo.fo", outfile=>"temp2.pdf") || die "cannot create pdf: " . $Fop->errstr;
20            
21             # create a PostScript file using an xsl-fo file
22             $Fop->fop(fo=>"foo.fo", outfile=>"temp3.ps", rendertype=>"ps") || die "cannot create ps file: " . $Fop->errstr;
23            
24             # reset FOP's image cache (available starting with FOP version 0.20.5)
25             $Fop->reset_image_cache() || die "could not reset FOP's image cache: " . $Fop->errstr;
26              
27             =head1 DESCRIPTION
28              
29             XML::ApacheFOP allows you to create PDFs (or other output types, explained below) using Apache FOP.
30              
31             Since FOP is written in Java, this module relies on Java.pm.
32             You will need to have FOP and Java.pm installed before installing this module.
33              
34             =head1 SETUP
35              
36             The biggest hurdle in getting this module to work will be installing and setting up FOP and Java.pm.
37             I recommend you thoroughly read the FOP and Java.pm documentation.
38              
39             You will also need Java2 1.2.x or later installed.
40             See the L<"SEE ALSO"> section below for a download link.
41              
42             Once you have them installed, you will need to make a change to the JavaServer startup so that FOP will be accessible.
43             The -classpath will need to be tailored to suit your system.
44             Hopefully the following example will help you get it right though. Here is the command I use:
45              
46             /path/to/java -classpath \
47             /path/to/JavaServer.jar\
48             :/usr/local/xml-fop/build/fop.jar\
49             :/usr/local/xml-fop/lib/avalon-framework-cvs-20020806.jar\
50             :/usr/local/xml-fop/lib/batik.jar\
51             :/usr/local/xml-fop/lib/xalan-2.4.1.jar\
52             :/usr/local/xml-fop/lib/xercesImpl-2.2.1.jar \
53             com.zzo.javaserver.JavaServer
54              
55             Once your JavaServer is running you'll be ready to start using this module.
56              
57             The README file included with this distribution contains more help
58             for getting this module setup.
59              
60             =head1 METHODS
61              
62             =cut
63              
64 1     1   6 use Carp;
  1         2  
  1         92  
65 1     1   455 use Java;
  0            
  0            
66              
67             =head2 new
68              
69             This will connect to the JavaServer and return a Fop object.
70             It will die if it cannot connect to the JavaServer.
71              
72             The new call accepts a hash with the following keys:
73             (note that many of these options are the same as those in Java.pm)
74              
75             host => hostname of remote machine to connect to
76             default is 'localhost'
77            
78             port => port the JVM is listening on (JavaServer)
79             default is 2000
80            
81             event_port => port that the remote JVM will send events to
82             default is -1 (off)
83             Since this module doesn't do any GUI work, leaving this
84             off is a good idea as the second event port will NOT
85             get used/opened saving some system resources.
86            
87             authfile => The path to a file whose first line is used as a
88             shared 'secret' which will be passed to
89             JavaServer. To use this feature you must start
90             JavaServer with the '--authfile='
91             command-line option.
92             If the secret words match access will be granted
93             to this client. By default there is no shared
94             secret. See the 'Authorization' section in Java.pm docs for more info.
95            
96             debug => when set to true it will print various warn messages stating what
97             the module is doing. Default is false.
98            
99             allowed_paths => this is an array ref containing the allowed paths for any filename
100             passed to this module (such as xml, xsl, fo, or pdf filenames).
101             For example, if set to ['/home/foo'], then only files within
102             /home/foo or its children directories will be allowed. If any files
103             outside of this path are passed, the fop call will fail.
104             Default is undef, meaning files from anywhere are allowed.
105              
106             =cut
107              
108             sub new
109             {
110             my $Class = shift;
111             my $Self = {};
112             bless $Self, $Class;
113             $Self->_init(@_);
114             return $Self;
115             }
116              
117             sub _init
118             {
119             my $Self = shift;
120             my %Args = @_;
121            
122             $Self->{host} = $Args{host} ? $Args{host} : 'localhost';
123             $Self->{port} = $Args{port} ? $Args{port} : 2000;
124             $Self->{event_port} = $Args{event_port} ? $Args{event_port} : -1;
125             $Self->{authfile} = $Args{authfile} ? $Args{authfile} : undef; # see Authentication section in Java.pm documentation
126             $Self->debug($Args{debug});
127             # only allow input/output files to be from directories in these paths
128             # this should be an array ref (if used)
129             $Self->allowed_paths($Args{allowed_paths});
130            
131             # create the java object
132             warn "Debug mode On. Connecting to JavaServer at $Self->{host} port $Self->{port}." if $Self->{debug};
133             warn "Using authfile: $Self->{authfile}" if $Self->{debug} and $Self->{authfile};
134             eval { $Self->{_java} = new Java(host=>$Self->{host}, port=>$Self->{port}, event_port=>$Self->{event_port}, authfile=>$Self->{authfile},) };
135             croak "could not connect to JavaServer" if $@;
136             }
137              
138             sub allowed_paths
139             {
140             my $Self = shift;
141             if ($_[0] && ref($_[0]) eq 'ARRAY')
142             {
143             $Self->{allowed_paths} = $_[0];
144             }
145             return $Self->{allowed_paths};
146             }
147              
148             sub debug
149             {
150             my $Self = shift;
151             if (defined $_[0])
152             {
153             $Self->{debug} = $_[0] ? 1 : 0;
154             }
155             return $Self->{debug};
156             }
157              
158             =head2 fop
159              
160             This makes the actual call to FOP.
161              
162             The fop call accepts a hash with the following keys:
163              
164             fo => path to the xsl-fo file, must I be used with xml and xsl
165            
166             xml => path to the xml file, must be used together with xsl
167             xsl => path to xsl stylesheet, must be used together with xml
168            
169             outfile => filename to save the generated file as
170            
171             rendertype => the type of file that should be generated.
172             Default is pdf. Also supports the following formats:
173            
174             mif - will be rendered as mif file
175             pcl - will be rendered as pcl file
176             ps - will be rendered as PostScript file
177             txt - will be rendered as text file
178             svg - will be rendered as a svg slides file
179             at - representation of area tree as XML
180            
181             txt_encoding => if the 'txt' rendertype is used, this is the
182             output encoding used for the outfile.
183             The encoding must be a valid java encoding.
184              
185             s => if the 'at' rendertype is used, setting this to true
186             will omit the tree below block areas.
187            
188             c => the path to an xml configuration file of options
189             such as baseDir, fontBaseDir, and strokeSVGText.
190             See http://xmlgraphics.apache.org/fop/configuration.html
191              
192             Will return 1 if the call is successfull.
193              
194             Will return undef if there was a problem.
195             In this case, $Fop->errstr will contain a string explaining what went wrong.
196              
197             =cut
198              
199             sub fop
200             {
201             my $Self = shift;
202             my %Args = @_;
203            
204             warn "starting fop call" if $Self->{debug};
205            
206             croak "java object doesn't seem to exist" unless $Self->{_java};
207            
208             # will be used for error messages
209             $Self->{'errstr'} = "";
210            
211             my @Options;
212            
213             # let fop run quietly unless debug mode is on
214             push @Options, ('-q') unless $Self->{debug};
215            
216             #
217             # Set the rendering files
218             #
219            
220             # outfile will be created using an fo file
221             if ($Args{fo})
222             {
223             # Although I like the idea of making sure a file exists,
224             # doing so would prevent running the JavaServer on a remote host.
225             # So I'm commenting out the -e check for now.
226             #return $Self->_error("$Args{fo} doesn't exist") unless -e $Args{fo};
227             push @Options, ('-fo', $Args{fo});
228             }
229             # outfile will be created using an xml/xsl transforamtion
230             elsif ($Args{xml} and $Args{xsl})
231             {
232             #return $Self->_error("$Args{xml} doesn't exist") unless -e $Args{xml};
233             #return $Self->_error("$Args{xsl} doesn't exist") unless -e $Args{xsl};
234             push @Options, ('-xml', $Args{xml});
235             push @Options, ('-xsl', $Args{xsl});
236             }
237             else
238             {
239             return $Self->_error('Not enough formatting information to run fop. (need fo=>$fofile or (xml=>$xmlfile and xsl=>$xslfile))');
240             }
241            
242             #
243             # Set the rendering type and outfile
244             #
245            
246             my $RenderType = $Args{rendertype};
247             $RenderType = 'pdf' unless $RenderType;
248             $RenderType = lc($RenderType);
249             return $Self->_error("Invalid option for 'rendertype'. (valid values: pdf mif pcl ps txt svg at)") unless $RenderType =~ /^(pdf|mif|pcl|ps|txt|svg|at)$/;
250            
251             my $Outfile = $Args{outfile};
252             return $Self->_error("'outfile' is not set") unless $Outfile;
253             push @Options, ("-$RenderType", $Outfile);
254            
255             # 'txt' render type has unique option
256             if ($RenderType eq 'txt' and $Args{'txt_encoding'})
257             {
258             # -txt output encoding use the encoding for the output file.
259             # The encoding must be a valid java encoding.
260             push @Options, ('-txt.encoding', $Args{'txt_encoding'});
261             }
262             # 'at' render type has unique option
263             if ($RenderType eq 'at' and $Args{'s'})
264             {
265             # omit tree below block areas
266             push @Options, ('-s');
267             }
268            
269             # read in configuration file
270             if ($Args{'c'})
271             {
272             push @Options, ('-c', $Args{'c'});
273             }
274            
275             # if allowed_paths is set, verify that all files are in the given paths
276             if ($Self->{allowed_paths})
277             {
278             my $OutfileIsOk = 0;
279             my $FoIsOk = 0;
280             my $XmlIsOk = 0;
281             my $XslIsOk = 0;
282             if ($Args{fo})
283             {
284             return $Self->_error('fo file cannot contain ".."') if $Args{fo} =~ /\.\./;
285             }
286             else
287             {
288             return $Self->_error('xml file cannot contain ".."') if $Args{xml} =~ /\.\./;
289             return $Self->_error('xsl file cannot contain ".."') if $Args{xsl} =~ /\.\./;
290             }
291             foreach my $Path (@{$Self->{allowed_paths}})
292             {
293             $OutfileIsOk = 1 if $Outfile =~ /^$Path/;
294             if ($Args{fo})
295             {
296             $FoIsOk = 1 if $Args{fo} =~ /^$Path/;
297             }
298             else
299             {
300             $XmlIsOk = 1 if $Args{xml} =~ /^$Path/;
301             $XslIsOk = 1 if $Args{xsl} =~ /^$Path/;
302             }
303             }
304             if ( !$OutfileIsOk or ($Args{fo} and !$FoIsOk) or ($Args{xml} and $Args{xsl} and (!$XmlIsOk or !$XslIsOk)) )
305             {
306             return $Self->_error("Some files are from forbidden paths! Allowed paths are: @{$Self->{allowed_paths}}");
307             }
308             }
309            
310             # create a java array of the FOP options
311             my $OptionsLength = @Options; # java array lengths must be declared
312             my $Options = $Self->{_java}->create_array("java.lang.String", $OptionsLength);
313             for (my $Element = 0; $Element < $OptionsLength; $Element++)
314             {
315             $Options->[$Element] = $Options[$Element];
316             }
317            
318             warn "creating fop object with options: @Options" if $Self->{debug};
319             # this is where fop is first called
320             my $Fop;
321             eval { $Fop = $Self->{_java}->create_object('org.apache.fop.apps.CommandLineOptions', $Options) };
322             return $Self->_eval_error("could not create java fop object") if $@;
323            
324             warn "creating fop starter object" if $Self->{debug};
325             my $Starter;
326             eval { $Starter = $Fop->getStarter() };
327             return $Self->_eval_error("could not create Starter object") if $@;
328            
329             # create the pdf file (or whatever rendering filetype was selected)
330             warn "generating $RenderType file" if $Self->{debug};
331             eval { $Starter->run() };
332             return $Self->_eval_error("$RenderType file generation failed") if $@;
333            
334             warn "$RenderType file generated successfully" if $Self->{debug};
335            
336             return 1;
337             }
338              
339             =head2 reset_image_cache
340              
341             Instruct FOP to clear its image cache. This method is available
342             starting with FOP version 0.20.5. For more information, see
343             L
344              
345             Will return 1 on success. Will return undef on failure, in which case
346             the error message will be accessible via $Fop->errstr.
347              
348             =cut
349              
350             sub reset_image_cache
351             {
352             my $Self = shift;
353            
354             $Self->{'errstr'} = "";
355            
356             warn "resetting FOP image cache" if $Self->{debug};
357             eval { $Self->{_java}->org_apache_fop_image_FopImageFactory('resetCache') };
358             return $Self->_eval_error("could not reset FOP image cache") if $@;
359            
360             return 1;
361             }
362              
363             =head2 errstr
364              
365             Will return an error message if the previous $Fop method call failed.
366              
367             =cut
368              
369             sub errstr
370             {
371             my $Self = shift;
372             return $Self->{errstr};
373             }
374              
375             sub _error
376             {
377             my $Self = shift;
378             $Self->{'errstr'} = $_[0];
379             return undef;
380             }
381              
382             sub _eval_error
383             {
384             my $Self = shift;
385              
386             my $Error = $@;
387             chomp($Error);
388              
389             # Gets rid of 'ERROR: '
390             $Error =~ s/^ERROR: //;
391              
392             # Gets rid of the fop exception class in the message
393             $Error =~ s/org.apache.fop.apps.FOPException: //;
394              
395             # Gets rid of 'croak' generated stuff
396             # I'm reversing the error string because the non-greedy *? only works from left-to-right
397             # If you have a better way to do this, let me know :)
398             $Error = reverse $Error;
399             $Error =~ s/^\d+ enil .*?(\/|[\/\\]:[a-zA-Z]) ta //;
400             $Error = reverse $Error;
401              
402             return $Self->_error("$_[0]: $Error");
403             }
404              
405             =head1 AUTHOR
406              
407             Ken Prows (perl@xev.net)
408              
409             =head1 SEE ALSO
410              
411             Please let me know if any of the below links are broken.
412              
413             Java2:
414             L
415              
416             Java.pm:
417             L
418              
419             SourceForge page for Java.pm/JavaServer:
420             L
421              
422             FOP:
423             L
424              
425             Ken Neighbors has created Debian packages for Java.pm/JavaServer and XML::ApacheFOP.
426             This greatly eases the installation for the Debian platform:
427             L
428              
429             =head1 COPYRIGHT and LICENSE
430              
431             Copyright (C) 2005 Online-Rewards. All rights reserved.
432              
433             This module is free software; you can redistribute it and/or modify
434             it under the same terms as Perl itself.
435              
436             =cut
437              
438             1;