1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.pdf.impl

File PdfExportImpl.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart0.png
83% of files have more coverage

Code metrics

44
157
17
1
616
357
56
0.36
9.24
17
3.29

Classes

Class Line # Actions
PdfExportImpl 104 157 0% 56 218
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.pdf.impl;
21   
22    import java.io.File;
23    import java.io.IOException;
24    import java.io.InputStream;
25    import java.io.OutputStream;
26    import java.io.Reader;
27    import java.io.StringReader;
28    import java.io.StringWriter;
29    import java.lang.reflect.Type;
30    import java.net.URL;
31    import java.util.ArrayList;
32    import java.util.HashMap;
33    import java.util.List;
34   
35    import javax.xml.transform.Result;
36    import javax.xml.transform.Source;
37    import javax.xml.transform.Transformer;
38    import javax.xml.transform.TransformerFactory;
39    import javax.xml.transform.sax.SAXResult;
40    import javax.xml.transform.sax.SAXSource;
41    import javax.xml.transform.stream.StreamSource;
42   
43    import org.apache.avalon.framework.configuration.Configuration;
44    import org.apache.avalon.framework.configuration.ConfigurationException;
45    import org.apache.avalon.framework.configuration.DefaultConfiguration;
46    import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
47    import org.apache.commons.io.FileUtils;
48    import org.apache.commons.io.IOUtils;
49    import org.apache.commons.lang3.RandomStringUtils;
50    import org.apache.commons.lang3.StringUtils;
51    import org.apache.commons.lang3.exception.ExceptionUtils;
52    import org.apache.fop.apps.EnvironmentProfile;
53    import org.apache.fop.apps.EnvironmentalProfileFactory;
54    import org.apache.fop.apps.FOUserAgent;
55    import org.apache.fop.apps.Fop;
56    import org.apache.fop.apps.FopFactory;
57    import org.apache.fop.apps.FopFactoryBuilder;
58    import org.apache.fop.apps.FormattingResults;
59    import org.apache.fop.apps.PageSequenceResults;
60    import org.apache.velocity.VelocityContext;
61    import org.dom4j.Element;
62    import org.dom4j.io.OutputFormat;
63    import org.dom4j.io.SAXReader;
64    import org.dom4j.io.XMLWriter;
65    import org.slf4j.Logger;
66    import org.slf4j.LoggerFactory;
67    import org.w3c.dom.css.CSSStyleDeclaration;
68    import org.xml.sax.InputSource;
69    import org.xml.sax.XMLReader;
70    import org.xwiki.bridge.DocumentAccessBridge;
71    import org.xwiki.context.Execution;
72    import org.xwiki.environment.Environment;
73    import org.xwiki.model.reference.DocumentReference;
74    import org.xwiki.model.reference.DocumentReferenceResolver;
75    import org.xwiki.model.reference.EntityReferenceSerializer;
76    import org.xwiki.velocity.VelocityManager;
77    import org.xwiki.velocity.XWikiVelocityException;
78    import org.xwiki.xml.EntityResolver;
79    import org.xwiki.xml.XMLReaderFactory;
80    import org.xwiki.xml.XMLUtils;
81    import org.xwiki.xml.html.HTMLCleaner;
82    import org.xwiki.xml.html.HTMLCleanerConfiguration;
83    import org.xwiki.xml.html.HTMLUtils;
84    import org.xwiki.xml.html.filter.HTMLFilter;
85   
86    import com.xpn.xwiki.XWikiContext;
87    import com.xpn.xwiki.XWikiException;
88    import com.xpn.xwiki.doc.XWikiDocument;
89    import com.xpn.xwiki.pdf.api.PdfExport;
90    import com.xpn.xwiki.web.Utils;
91    import com.xpn.xwiki.web.XWikiRequest;
92   
93    import info.informatica.doc.dom4j.CSSStylableElement;
94    import info.informatica.doc.dom4j.XHTMLDocument;
95    import info.informatica.doc.dom4j.XHTMLDocumentFactory;
96    import info.informatica.doc.xml.dtd.DefaultEntityResolver;
97   
98    /**
99    * Default implementation for the PDF Export process, which uses XSLT transformations and Apache FOP to convert a
100    * Document into PDF, passing through HTML, valid XHTML, styled XHTML, and XSL-FO.
101    *
102    * @version $Id: b84857945f1eaeb907a4559e85f1c5d71d06aaf4 $
103    */
 
104    public class PdfExportImpl implements PdfExport
105    {
106    /** The location where fonts to be used during PDF export should be placed. */
107    private static final String FONTS_PATH = "/WEB-INF/fonts/";
108   
109    /** The name of the default XHTML2FOP transformation file. */
110    private static final String DEFAULT_XHTML2FOP_XSLT = "xhtml2fo.xsl";
111   
112    /** The name of the default FOP post-processing transformation file. */
113    private static final String DEFAULT_CLEANUP_XSLT = "fop.xsl";
114   
115    /** Logging helper object. */
116    private static final Logger LOGGER = LoggerFactory.getLogger(PdfExportImpl.class);
117   
118    /** Document name resolver. */
119    private static DocumentReferenceResolver<String> referenceResolver =
120    Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "currentmixed");
121   
122    /** Document name serializer. */
123    private static EntityReferenceSerializer<String> referenceSerializer =
124    Utils.getComponent(EntityReferenceSerializer.TYPE_STRING);
125   
126    /** Provides access to document properties. */
127    private static DocumentAccessBridge dab = Utils.getComponent(DocumentAccessBridge.class);
128   
129    /** Velocity engine manager, used for interpreting velocity. */
130    private static VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
131   
132    /** XSLT transformer factory. */
133    private static TransformerFactory transformerFactory = TransformerFactory.newInstance();
134   
135    /** The Apache FOP instance used for XSL-FO processing. */
136    private static FopFactory fopFactory;
137   
138    /**
139    * Used to get the temporary directory.
140    */
141    private Environment environment = Utils.getComponent((Type) Environment.class);
142   
143    // Fields initialization
 
144  0 toggle static {
145    // ----------------------------------------------------------------------
146    // FOP configuration
147    // ----------------------------------------------------------------------
148   
149  0 EnvironmentProfile environmentProfile = EnvironmentalProfileFactory.createDefault(new File(".").toURI(),
150    Utils.getComponent(PDFResourceResolver.class));
151  0 FopFactoryBuilder builder = new FopFactoryBuilder(environmentProfile);
152   
153    // Load configuration
154  0 Configuration configuration = null;
155  0 try (InputStream fopConfigurationFile = PdfExportImpl.class.getResourceAsStream("/fop-config.xml")) {
156  0 if (fopConfigurationFile != null) {
157  0 configuration = new DefaultConfigurationBuilder().build(fopConfigurationFile);
158    }
159    } catch (Exception e) {
160  0 LOGGER.warn("Wrong FOP configuration: " + ExceptionUtils.getRootCauseMessage(e));
161    }
162   
163  0 if (configuration != null) {
164    // Get a writable configuration instance
165  0 DefaultConfiguration writableConfiguration = null;
166  0 if (configuration instanceof DefaultConfiguration) {
167  0 writableConfiguration = (DefaultConfiguration) configuration;
168    } else {
169  0 try {
170  0 writableConfiguration = new DefaultConfiguration(configuration, true);
171    } catch (ConfigurationException e) {
172    // Should never happen
173  0 LOGGER.error("Failed to copy configuration", e);
174    }
175    }
176   
177  0 if (writableConfiguration != null) {
178    // Add XWiki fonts folder to the configuration
179  0 try {
180  0 Environment environment = Utils.getComponent(Environment.class);
181  0 String fontsPath = environment.getResource(FONTS_PATH).getPath();
182  0 Execution execution = Utils.getComponent(Execution.class);
183  0 XWikiContext xcontext = (XWikiContext) execution.getContext().getProperty("xwikicontext");
184  0 if (xcontext != null) {
185  0 XWikiRequest request = xcontext.getRequest();
186  0 if (request != null && request.getSession() != null) {
187  0 fontsPath = request.getSession().getServletContext().getRealPath(FONTS_PATH);
188    }
189    }
190   
191  0 DefaultConfiguration rendererConfiguration =
192    (DefaultConfiguration) writableConfiguration.getChild("renderer");
193  0 if (rendererConfiguration != null) {
194  0 DefaultConfiguration fontsConfiguration =
195    (DefaultConfiguration) rendererConfiguration.getChild("fonts");
196  0 DefaultConfiguration directoryConfiguration = new DefaultConfiguration("directory");
197  0 directoryConfiguration.setValue(fontsPath);
198  0 fontsConfiguration.addChild(directoryConfiguration);
199   
200    }
201    } catch (Throwable ex) {
202  0 LOGGER.warn("Starting with 1.5, XWiki uses the WEB-INF/fonts/ directory as the font directory, "
203    + "and it should contain the FreeFont (http://savannah.gnu.org/projects/freefont/) fonts. "
204    + "FOP cannot access this directory. If this is an upgrade from a previous version, "
205    + "make sure you also copy the WEB-INF/fonts directory from the new distribution package.");
206    }
207   
208  0 builder.setConfiguration(writableConfiguration);
209    }
210    }
211   
212  0 fopFactory = builder.build();
213    }
214   
 
215  0 toggle @Override
216    public void exportToPDF(XWikiDocument doc, OutputStream out, XWikiContext context) throws XWikiException
217    {
218  0 export(doc, out, ExportType.PDF, context);
219    }
220   
 
221  0 toggle @Override
222    public void export(XWikiDocument doc, OutputStream out, ExportType type, XWikiContext context) throws XWikiException
223    {
224    // Note: The passed document is not used currently since we're calling pdf.vm and that
225    // velocity template uses the XWiki Context to get the current doc or its translations.
226    // This could be improved by setting a specific context using the passed document but we
227    // would also need to get the translations and set them too.
228   
229  0 File dir = this.environment.getTemporaryDirectory();
230  0 File tempdir = new File(dir, RandomStringUtils.randomAlphanumeric(8));
231  0 try {
232  0 tempdir.mkdirs();
233  0 context.put("pdfexportdir", tempdir);
234  0 context.put("pdfexport-file-mapping", new HashMap<String, File>());
235  0 boolean useLocalPlaceholders = !Utils.arePlaceholdersEnabled(context);
236  0 if (useLocalPlaceholders) {
237  0 Utils.enablePlaceholders(context);
238    }
239  0 String content = context.getWiki().parseTemplate("pdf.vm", context).trim();
240  0 if (useLocalPlaceholders) {
241  0 content = Utils.replacePlaceholders(content, context);
242  0 Utils.disablePlaceholders(context);
243    }
244  0 exportHtml(content, out, type, context);
245    } finally {
246  0 try {
247  0 FileUtils.deleteDirectory(tempdir);
248    } catch (IOException ex) {
249    // Should not happen, but it's nothing serious, just that temporary files are left on the disk.
250  0 LOGGER.warn("Failed to cleanup temporary files after a PDF export", ex);
251    }
252    }
253    }
254   
 
255  0 toggle @Override
256    public void exportHtml(String html, OutputStream out, ExportType type, XWikiContext context) throws XWikiException
257    {
258  0 exportXHTML(applyCSS(convertToStrictXHtml(html), context), out, type, context);
259    }
260   
261    /**
262    * Cleans up an HTML document, turning it into valid XHTML.
263    *
264    * @param input the source HTML to process
265    * @return the cleaned up source
266    */
 
267  0 toggle private String convertToStrictXHtml(String input)
268    {
269  0 LOGGER.debug("Cleaning HTML:\n{}", input);
270   
271  0 HTMLCleaner cleaner = Utils.getComponent(HTMLCleaner.class);
272  0 HTMLCleanerConfiguration config = cleaner.getDefaultConfiguration();
273  0 List<HTMLFilter> filters = new ArrayList<HTMLFilter>(config.getFilters());
274  0 filters.add(Utils.getComponent(HTMLFilter.class, "uniqueId"));
275  0 config.setFilters(filters);
276  0 String result = HTMLUtils.toString(cleaner.clean(new StringReader(input), config));
277  0 LOGGER.debug("Cleaned XHTML:\n{}", result);
278  0 return result;
279    }
280   
281    /**
282    * Convert a valid XHTML document into PDF. No further processing of the XHTML occurs.
283    * <p>
284    * Note: This method is protected just allow other exporters to hook their code and use the PDF export
285    * infrastructure. This is just a temporary solution. The PDF export code needs to be redesigned because it has
286    * parts than can be reused for other export formats.
287    *
288    * @param xhtml the source document to transform
289    * @param out where to write the resulting document
290    * @param type the type of the output: PDF or RTF
291    * @param context the current request context
292    * @throws XWikiException if the conversion fails for any reason
293    */
 
294  0 toggle protected void exportXHTML(String xhtml, OutputStream out, ExportType type, XWikiContext context)
295    throws XWikiException
296    {
297  0 LOGGER.debug("Final XHTML for export:\n{}", xhtml);
298   
299    // XSL Transformation to XML-FO
300  0 String xmlfo = convertXHtmlToXMLFO(xhtml, context);
301   
302    // Debug output
303  0 LOGGER.debug("Final XSL-FO source:\n{}", xmlfo);
304   
305  0 renderXSLFO(xmlfo, out, type, context);
306    }
307   
308    /**
309    * Convert a valid XHTML document into an XSL-FO document through XSLT transformations. Two transformations are
310    * involved:
311    * <ol>
312    * <li>A base transformation which converts the XHTML into a temporary XSL-FO; it uses the <tt>xhtml2fo.xsl</tt>
313    * file, or the <tt>xhtmlxsl</tt> property of the applied PDFTemplate.</li>
314    * <li>An eventual post-processing transformation which cleans up the temporary XSL-FO in order to avoid FOP bugs;
315    * it uses the <tt>fop.xsl</tt> file, or the <tt>fopxsl</tt> property of the applied PDFTemplate.</li>
316    * </ol>
317    *
318    * @param xhtml the XHTML document to convert
319    * @param context the current request context
320    * @return the resulting XML-FO document
321    * @throws XWikiException if the conversion fails for any reason
322    */
 
323  0 toggle private String convertXHtmlToXMLFO(String xhtml, XWikiContext context) throws XWikiException
324    {
325  0 String xmlfo = applyXSLT(xhtml, getXhtml2FopXslt(context));
326  0 LOGGER.debug("Intermediary XSL-FO:\n{}", xmlfo);
327  0 return applyXSLT(xmlfo, getFopCleanupXslt(context));
328    }
329   
330    /**
331    * Convert an XSL-FO document into PDF.
332    *
333    * @param xmlfo the source FO to render
334    * @param out where to write the resulting document
335    * @param type the type of the output: PDF or RTF
336    * @param context the XWiki Context used by the custom URI Resolver we use to locate image attachment data
337    * @throws XWikiException if the conversion fails for any reason
338    */
 
339  0 toggle private void renderXSLFO(String xmlfo, OutputStream out, ExportType type, final XWikiContext context)
340    throws XWikiException
341    {
342  0 try {
343  0 FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
344   
345    // Construct fop with desired output format
346  0 Fop fop = fopFactory.newFop(type.getMimeType(), foUserAgent, out);
347   
348    // Identity transformer
349  0 Transformer transformer = transformerFactory.newTransformer();
350   
351    // Setup input stream
352  0 Source source = new StreamSource(new StringReader(xmlfo));
353   
354    // Resulting SAX events (the generated FO) must be piped through to FOP
355  0 Result res = new SAXResult(fop.getDefaultHandler());
356   
357    // Start XSLT transformation and FOP processing
358  0 transformer.transform(source, res);
359   
360    // Result processing
361  0 FormattingResults foResults = fop.getResults();
362  0 if (foResults != null && LOGGER.isDebugEnabled()) {
363  0 @SuppressWarnings("unchecked")
364    java.util.List<PageSequenceResults> pageSequences = foResults.getPageSequences();
365  0 for (PageSequenceResults pageSequenceResults : pageSequences) {
366  0 LOGGER.debug("PageSequence " + StringUtils.defaultIfEmpty(pageSequenceResults.getID(), "<no id>")
367    + " generated " + pageSequenceResults.getPageCount() + " pages.");
368    }
369  0 LOGGER.debug("Generated " + foResults.getPageCount() + " pages in total.");
370    }
371    } catch (IllegalStateException e) {
372  0 throw createException(e, type, XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION);
373    } catch (Exception e) {
374  0 throw createException(e, type, XWikiException.ERROR_XWIKI_EXPORT_PDF_FOP_FAILED);
375    }
376    }
377   
378    /**
379    * Applies an XSLT transformation to an XML document.
380    *
381    * @param xml the XML document to convert
382    * @param xslt the XSLT to apply
383    * @return the converted document
384    * @throws XWikiException if the transformation fails for any reason
385    */
 
386  0 toggle protected String applyXSLT(String xml, InputStream xslt) throws XWikiException
387    {
388  0 try {
389  0 XMLReader xmlReader = Utils.getComponent(XMLReaderFactory.class).createXMLReader();
390  0 xmlReader.setEntityResolver(Utils.getComponent(EntityResolver.class));
391  0 SAXSource xmlSource = new SAXSource(xmlReader, new InputSource(new StringReader(xml)));
392  0 SAXSource xsltSource = new SAXSource(xmlReader, new InputSource(xslt));
393  0 return XMLUtils.transform(xmlSource, xsltSource);
394    } catch (Exception e) {
395  0 throw new XWikiException(XWikiException.MODULE_XWIKI_EXPORT, XWikiException.ERROR_XWIKI_EXPORT_XSL_FAILED,
396    "XSL Transformation Failed", e);
397    }
398    }
399   
400    /**
401    * Apply CSS styling to an XHTML document. The style to apply is taken from:
402    * <ol>
403    * <li>the <tt>pdf.css</tt> skin file</li>
404    * <li>and the <tt>style</tt> property of the applied PDFTemplate</li>
405    * </ol>
406    * The content found in these locations is concatenated. The CSS rules are applied on the document, and the
407    * resulting style properties are embedded in the document, inside <tt>style</tt> attributes. The resulting XHTML
408    * document with the inlined style is then serialized and returned.
409    *
410    * @param html the valid XHTML document to style
411    * @param context the current request context
412    * @return the document with inlined style
413    */
 
414  0 toggle private String applyCSS(String html, XWikiContext context)
415    {
416  0 String css =
417  0 (context == null || context.getWiki() == null) ? "" : context.getWiki().parseTemplate("pdf.css", context);
418  0 String style = getPDFTemplateProperty("style", context);
419  0 if (style != null) {
420  0 css += style;
421    }
422    // Don't apply CSS if there's no CSS to apply!
423  0 return StringUtils.isBlank(css) ? html : applyCSS(html, css, context);
424    }
425   
426    /**
427    * Apply a CSS style sheet to an XHTML document and return the document with the resulting style properties inlined
428    * in <tt>style</tt> attributes.
429    *
430    * @param html the valid XHTML document to style
431    * @param css the style sheet to apply
432    * @param context the current request context
433    * @return the document with inlined style
434    */
 
435  0 toggle private String applyCSS(String html, String css, XWikiContext context)
436    {
437  0 LOGGER.debug("Applying the following CSS:\n{}", css);
438  0 try {
439    //System.setProperty("org.w3c.css.sac.parser", "org.apache.batik.css.parser.Parser");
440   
441    // Prepare the input
442  0 Reader re = new StringReader(html);
443  0 InputSource source = new InputSource(re);
444  0 SAXReader reader = new SAXReader(XHTMLDocumentFactory.getInstance());
445  0 reader.setEntityResolver(new DefaultEntityResolver());
446  0 XHTMLDocument document = (XHTMLDocument) reader.read(source);
447   
448    // Set the base URL so that CSS4J can resolve URLs in CSS. Use the current document in the XWiki Context
449  0 document.setBaseURL(new URL(context.getDoc().getExternalURL("view", context)));
450   
451    // Apply the style sheet
452  0 document.addStyleSheet(new org.w3c.css.sac.InputSource(new StringReader(css)));
453  0 applyInlineStyle(document.getRootElement());
454  0 OutputFormat outputFormat = new OutputFormat("", false);
455  0 if ((context == null) || (context.getWiki() == null)) {
456  0 outputFormat.setEncoding("UTF-8");
457    } else {
458  0 outputFormat.setEncoding(context.getWiki().getEncoding());
459    }
460  0 StringWriter out = new StringWriter();
461  0 XMLWriter writer = new XMLWriter(out, outputFormat);
462  0 writer.write(document);
463  0 String result = out.toString();
464    // Debug output
465  0 if (LOGGER.isDebugEnabled()) {
466  0 LOGGER.debug("HTML with CSS applied: " + result);
467    }
468  0 return result;
469    } catch (Exception ex) {
470  0 LOGGER.warn("Failed to apply CSS: " + ex.getMessage(), ex);
471  0 return html;
472    }
473    }
474   
475    /**
476    * Recursively inline the computed style that applies to a DOM Element into the {@code style} attribute of that
477    * Element.
478    *
479    * @param element the Element whose style should be inlined
480    */
 
481  0 toggle private void applyInlineStyle(Element element)
482    {
483  0 for (int i = 0; i < element.nodeCount(); i++) {
484  0 org.dom4j.Node node = element.node(i);
485  0 if (node instanceof CSSStylableElement) {
486  0 CSSStylableElement styleElement = (CSSStylableElement) node;
487  0 CSSStyleDeclaration style = styleElement.getComputedStyle();
488  0 if (style != null && StringUtils.isNotEmpty(style.getCssText())) {
489  0 styleElement.addAttribute("style", styleElement.getComputedStyle().getCssText());
490    }
491    }
492  0 if (node instanceof Element) {
493  0 applyInlineStyle((Element) node);
494    }
495    }
496    }
497   
498    /**
499    * Get the XSLT for converting (valid) XHTML to XSL-FO. The content is searched in:
500    * <ol>
501    * <li>the <tt>xhtmlxsl</tt> property of the current PDFTemplate</li>
502    * <li>the <tt>xhtml2fo.xsl</tt> resource (usually a file inside xwiki-core-*.jar)</li>
503    * </ol>
504    *
505    * @param context the current request context
506    * @return the content of the XSLT as a byte stream
507    */
 
508  0 toggle private InputStream getXhtml2FopXslt(XWikiContext context)
509    {
510  0 return getXslt("xhtmlxsl", DEFAULT_XHTML2FOP_XSLT, context);
511    }
512   
513    /**
514    * Get the XSLT for post-processing the XSL-FO file. The content is searched in:
515    * <ol>
516    * <li>the <tt>fopxsl</tt> property of the current PDFTemplate</li>
517    * <li>the <tt>fop.xsl</tt> resource (usually a file inside xwiki-core-*.jar)</li>
518    * </ol>
519    *
520    * @param context the current request context
521    * @return the content of the XSLT as a byte stream
522    */
 
523  0 toggle private InputStream getFopCleanupXslt(XWikiContext context)
524    {
525  0 return getXslt("fopxsl", DEFAULT_CLEANUP_XSLT, context);
526    }
527   
528    /**
529    * Get an XSLT file.
530    *
531    * @param propertyName the name of the xproperty from which to read the XSLT file.
532    * See {@link #getPDFTemplateProperty(String, XWikiContext)} for details on how this property
533    * is resolved. If the property doesn't point to any XSLT file then the fallback file parameter
534    * is used instead
535    * @param fallbackFile the name of a resource file to use when no XSLT content was found using the passed
536    * {@code propertyName}
537    * @param context the current request context
538    * @return the content of the XSLT as a byte stream
539    */
 
540  0 toggle protected InputStream getXslt(String propertyName, String fallbackFile, XWikiContext context)
541    {
542  0 String xsl = getPDFTemplateProperty(propertyName, context);
543  0 if (!StringUtils.isBlank(xsl)) {
544  0 try {
545  0 return IOUtils.toInputStream(xsl, context.getWiki().getEncoding());
546    } catch (IOException e) {
547    // This really shouldn't happen since it would mean that the encoding is either invalid or doesn't
548    // exist in the JVM.
549  0 LOGGER.error("Couldn't get XSLT for PDF exporting. Invalid or not existing encoding [{}]",
550    context.getWiki().getEncoding(), e);
551    }
552    }
553  0 return getClass().getClassLoader().getResourceAsStream(fallbackFile);
554    }
555   
556    /**
557    * Extract XSLT file content using the following algorithm:
558    * <ul>
559    * <li>Check if a query string named {@code pdftemplate} exists and if so use its value as the reference to
560    * a document containing a XWiki.PDFClass xobject from which to extract the XSLT data. If not defined
561    * (or if empty) then use the current document as the document having the XWiki.PDFClass xobject.</li>
562    * <li>Read the value of the xproperty named after the passed {@code propertyName} parameter. If the document
563    * or the property don't exist, then return an empty String. Otherwise execute Velocity on the xproperty's
564    * value and return this.</li>
565    * </ul>
566    *
567    * @param propertyName the xproperty containing the XSLT to return
568    * @param context the current request context
569    * @return the content of the xproperty, velocity-parsed, or an empty string if there's no such property
570    */
 
571  0 toggle private String getPDFTemplateProperty(String propertyName, XWikiContext context)
572    {
573  0 String pdftemplate = context.getRequest().getParameter("pdftemplate");
574   
575  0 DocumentReference templateReference;
576  0 DocumentReference classReference;
577  0 if (StringUtils.isNotEmpty(pdftemplate)) {
578  0 templateReference = referenceResolver.resolve(pdftemplate);
579  0 classReference = new DocumentReference(templateReference.getWikiReference().getName(), "XWiki", "PDFClass");
580    } else {
581  0 templateReference = dab.getCurrentDocumentReference();
582  0 String currentWiki = dab.getCurrentDocumentReference().getRoot().getName();
583  0 classReference = new DocumentReference(currentWiki, "XWiki", "PDFClass");
584    }
585   
586  0 String result = (String) dab.getProperty(templateReference, classReference, propertyName);
587  0 if (StringUtils.isBlank(result)) {
588  0 return "";
589    }
590  0 String templateName = referenceSerializer.serialize(templateReference);
591  0 try {
592  0 StringWriter writer = new StringWriter();
593  0 VelocityContext vcontext = velocityManager.getVelocityContext();
594  0 velocityManager.getVelocityEngine().evaluate(vcontext, writer, templateName, result);
595  0 result = writer.toString();
596    } catch (XWikiVelocityException e) {
597  0 LOGGER.warn("Error applying Velocity to the [{}] property of the [{}] document. Using the property's value "
598    + "without applying Velocity.", propertyName, templateName, ExceptionUtils.getRootCauseMessage(e));
599    }
600  0 return result;
601    }
602   
603    /**
604    * Create an XWikiException object with the given source, export type and error type.
605    *
606    * @param source the source exception that is forwarded
607    * @param exportType the type of the export performed while the exception occurred, PDF or RTF
608    * @param errorType the type of error that occurred, one of the constants in {@link XWikiException}
609    * @return a new XWikiException object
610    */
 
611  0 toggle private XWikiException createException(Throwable source, ExportType exportType, int errorType)
612    {
613  0 return new XWikiException(XWikiException.MODULE_XWIKI_EXPORT, errorType,
614    "Exception while exporting " + exportType.getExtension(), source);
615    }
616    }