All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.offerready.xslt.DocumentGenerator Maven / Gradle / Ivy

The newest version!
package com.offerready.xslt;

import com.databasesandlife.util.DomParser;
import com.databasesandlife.util.MD5Hex;
import com.databasesandlife.util.Timer;
import com.databasesandlife.util.gwtsafe.ConfigurationException;
import com.offerready.xslt.WeaklyCachedXsltTransformer.DocumentTemplateInvalidException;
import com.offerready.xslt.WeaklyCachedXsltTransformer.Xslt;
import com.offerready.xslt.WeaklyCachedXsltTransformer.XsltCompilationThreads;
import com.offerready.xslt.destination.DocumentGenerationDestination;
import lombok.SneakyThrows;
import org.apache.fop.apps.FopConfParser;
import org.apache.fop.apps.FopFactoryBuilder;
import org.apache.fop.apps.MimeConstants;
import org.apache.fop.apps.io.ResourceResolverFactory;
import org.apache.xmlgraphics.io.URIResolverAdapter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;

/**
 * An object capable of generating a document for a particular XSLT file.
 *    

* This object executes XSLT, and in addition: *

*

    *
  • Stylevision hacks. It has hacks to change the XSLT produced by StyleVision into that which can be accepted by * the free version of Saxon called Saxon-HE. *

    *

  • Conversion after XSLT. After the XSLT is applied, the resulting XML can be further processed. * XSL-FO to PDF, convert HTML to XML, and convert XML to JSON. *
*

* Objects of this class reference the complied XSLT transfomer. * As long as you need to access the XSLT, retain a link to this object. * Creating a new object might involve the XSLT being re-complied (depending on the cache in {@link WeaklyCachedXsltTransformer}). *

* A DocumentGenerator (or "all documents which can be generated") should not be added to e.g. Wicket sessions as * these cannot be serialized (and would be big even if they could.) */ public class DocumentGenerator { protected final @Nonnull DocumentOutputDefinition defn; protected final @Nonnull WeaklyCachedXsltTransformer transformer; protected @CheckForNull File fopBaseDirOrNull = null, fopConfigOrNull = null, imagesBase = null; public static class StyleVisionXslt implements Xslt { public final @Nonnull File xsltFile; public StyleVisionXslt(@Nonnull File x) { xsltFile = x; } @Override public @Nonnull String calculateCacheKey() { return MD5Hex.md5(xsltFile); } @SneakyThrows(IOException.class) @Override public @Nonnull Document parseDocument() throws ConfigurationException { final Document result; try { result = DomParser.newDocumentBuilder().parse(xsltFile); } // DOM Object catch (SAXException e) { throw new ConfigurationException("XSLT file '" + xsltFile.getAbsolutePath() + "' is not valid XML"); } // XSLT files produced by Stylevision have // The free version of Saxon throws if this tag is present (tells one to buy the commercial version) // We don't need XSD checking, so firstly parse the XSLT file into a DOM, then strip out this tag var allImportSchemaTags = result.getElementsByTagNameNS( "http://www.w3.org/1999/XSL/Transform", "import-schema"); if (allImportSchemaTags.getLength() > 0) { var importSchemaTag = (Element) allImportSchemaTags.item(0); importSchemaTag.getParentNode().removeChild(importSchemaTag); } // XSLT files produced with and this causes file to be written, // meaning that we can't stream the result to the browser. If we remove the attribute, all is good. var allResultDocumentTags = result.getElementsByTagNameNS( "http://www.w3.org/1999/XSL/Transform", "result-document"); for (var i = 0; i < allResultDocumentTags.getLength(); i++) { var a = allResultDocumentTags.item(i).getAttributes(); if (a.getNamedItem("href") != null) a.removeNamedItem("href"); } return result; } } public DocumentGenerator( @Nonnull XsltCompilationThreads threads, @Nonnull DocumentOutputDefinition defn, @Nonnull Function newXslt ) throws ConfigurationException { this.defn = defn; if (defn.xsltFileOrNull == null) this.transformer = WeaklyCachedXsltTransformer.getIdentityTransformer(); else this.transformer = WeaklyCachedXsltTransformer.getTransformerOrScheduleCompilation( threads, defn.xsltFileOrNull.getAbsolutePath(), newXslt.apply(defn.xsltFileOrNull)); } public DocumentGenerator(@Nonnull XsltCompilationThreads threads, @Nonnull DocumentOutputDefinition defn) throws ConfigurationException { this(threads, defn, StyleVisionXslt::new); } public void setFopConfigOrNull(@CheckForNull File fopBaseDirOrNull, @CheckForNull File fopConfigOrNull) { this.fopBaseDirOrNull = fopBaseDirOrNull; this.fopConfigOrNull = fopConfigOrNull; } public void setImagesBase(@Nonnull File imagesBase) { this.imagesBase = imagesBase; } @SneakyThrows({TransformerException.class, IOException.class}) protected void writePlainXml(@Nonnull DocumentGenerationDestination response, @Nonnull Document xml) { var systemProperties = System.getProperties(); systemProperties.remove("javax.xml.transform.TransformerFactory"); System.setProperties(systemProperties); response.setContentType("text/plain; charset=UTF-8"); var transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name()); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); var source = new DOMSource(xml); var result = new StreamResult(response.getOutputStream()); transformer.transform(source, result); response.getOutputStream().close(); } @SneakyThrows({TransformerException.class, IOException.class, SAXException.class}) protected void writePdfFromXslFo(@Nonnull OutputStream pdf, @Nonnull Document fo, @CheckForNull URIResolver uriResolverOrNull) { try (var ignored = new Timer("Create PDF from XSL-FO")) { // Get a FOP instance (can convert XSL-FO into PDF) FopFactoryBuilder builder; if (fopConfigOrNull != null) { FopConfParser parser; if (uriResolverOrNull == null) parser = new FopConfParser(fopConfigOrNull); else parser = new FopConfParser(fopConfigOrNull, new URIResolverAdapter(uriResolverOrNull)); builder = parser.getFopFactoryBuilder(); } else { if (uriResolverOrNull == null) builder = new FopFactoryBuilder(URI.create("https://www.example.com/")); else builder = new FopFactoryBuilder(URI.create("https://www.example.com/"), new URIResolverAdapter(uriResolverOrNull)); } if (imagesBase != null) builder.setBaseURI(imagesBase.toURI()); var fopFactory = builder.build(); if (fopBaseDirOrNull != null) fopFactory.getFontManager().setResourceResolver( ResourceResolverFactory.createDefaultInternalResourceResolver(fopBaseDirOrNull.toURI())); var foUserAgent = fopFactory.newFOUserAgent(); var fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, pdf); // Setup JAXP using identity transformer var factory = TransformerFactory.newInstance(); var transformer = factory.newTransformer(); // identity transformer // Resulting SAX events (the generated FO) must be piped through to FOP var res = new SAXResult(fop.getDefaultHandler()); // Start XSLT transformation and FOP processing transformer.transform(new DOMSource(fo), res); } } public void assertTemplateValid() throws DocumentTemplateInvalidException { transformer.assertValid(); } /** * @param response this is closed by this method * @param transform if false, then don't do transformation, but output XML instead (for debugging) * @param uriResolverOrNull if not null, pass an object which can, for example, fetch or create images via programmatic logic * @param language for example "de" to choose different XSLT params (placeholder values). Or null to just use the default. */ @SneakyThrows(IOException.class) public void transform( @Nonnull DocumentGenerationDestination response, @Nonnull Document xml, boolean transform, @CheckForNull URIResolver uriResolverOrNull, @CheckForNull String language ) throws DocumentTemplateInvalidException, TransformerException { if ( ! transform) { writePlainXml(response, xml); return; } var xslt = transformer.newTransformer(); for (var placeholderValue : defn.xsltParameters.get(language).entrySet()) xslt.setParameter(placeholderValue.getKey(), placeholderValue.getValue()); switch (defn.outputConversion) { case xmlToJson: response.setContentType((defn.contentType == null ? "application/json" : defn.contentType) + "; charset=UTF-8"); var xmlOutput = new StringWriter(); try (var ignored = new Timer("XSLT Transformation")) { xslt.transform(new DOMSource(xml), new StreamResult(xmlOutput)); } var json = new JsonXmlConverter().convertXmlToJson(xmlOutput.toString()); try (var outputStream = response.getOutputStream()) { outputStream.write(json.getBytes(StandardCharsets.UTF_8)); } break; case xslFoToPdf: response.setContentType(defn.contentType == null ? "application/pdf" : defn.contentType); var xslFo = new DOMResult(); try (var ignored = new Timer("XSLT Transformation to XSL-FO")) { xslt.transform(new DOMSource(xml), xslFo); } try (var outputStream = response.getOutputStream()) { writePdfFromXslFo(outputStream, (Document) xslFo.getNode(), uriResolverOrNull); } break; case excelXmlToExcelBinary: response.setContentType(defn.contentType == null ? "application/ms-excel" : defn.contentType); try (var outputStream = response.getOutputStream()) { xslt.transform(new DOMSource(xml), new SAXResult(new ExcelGenerator(defn.inputDecimalSeparator, outputStream))); } break; default: response.setContentType((defn.contentType == null ? "text/plain" : defn.contentType) + "; charset=UTF-8"); try (var outputStream = response.getOutputStream()) { var result = new StreamResult(outputStream); xslt.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name()); try (var ignored = new Timer("XSLT Transformation")) { xslt.transform(new DOMSource(xml), result); } } break; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy