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

com.jcabi.xml.XSLDocument Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2012-2024, jcabi.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 1) Redistributions of source code must retain the above
 * copyright notice, this list of conditions and the following
 * disclaimer. 2) Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution. 3) Neither the name of the jcabi.com nor
 * the names of its contributors may be used to endorse or promote
 * products derived from this software without specific prior written
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jcabi.xml;

import com.jcabi.log.Logger;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import lombok.EqualsAndHashCode;
import net.sf.saxon.Version;
import net.sf.saxon.jaxp.TransformerImpl;
import net.sf.saxon.serialize.MessageWarner;
import org.cactoos.map.MapEntry;
import org.cactoos.map.MapOf;
import org.w3c.dom.Document;

/**
 * Implementation of {@link XSL}.
 *
 * 

Objects of this class are immutable and thread-safe. * * @since 0.4 * @checkstyle ClassDataAbstractionCouplingCheck (500 lines) * @checkstyle AbbreviationAsWordInNameCheck (5 lines) * @checkstyle ClassFanOutComplexityCheck (500 lines) */ @EqualsAndHashCode(of = "xsl") @SuppressWarnings({"PMD.TooManyMethods", "PMD.ExcessiveImports"}) public final class XSLDocument implements XSL { /** * Strips spaces of whitespace-only text nodes. * *

This will NOT remove * existing indentation between Element nodes currently introduced by the * constructor of {@link XMLDocument}. For example: * *

     * {@code
     * <a>
     *           <b> TXT </b>
     *    </a>}
     * 
* * becomes * *
     * {@code
     * <a>
     *     <b> TXT </b>
     * </a>}
     * 
* * @since 0.14 */ public static final XSL STRIP = XSLDocument.make( XSL.class.getResourceAsStream("strip.xsl") ); /** * XSL document. */ private final transient String xsl; /** * Sources. */ private final transient Sources sources; /** * Parameters. */ private final transient Map params; /** * System ID (base). * @since 0.20 */ private final transient String sid; /** * Public ctor, from XML as a source. * @param src XSL document body */ public XSLDocument(final XML src) { this(src, "/"); } /** * Public ctor, from XML as a source. * @param src XSL document body * @param base SystemId/Base * @since 0.20 */ public XSLDocument(final XML src, final String base) { this(src.toString(), base); } /** * Public ctor, from URL. * @param url Location of document * @throws IOException If fails to read * @since 0.7.4 */ public XSLDocument(final URL url) throws IOException { this(url, url.toString()); } /** * Public ctor, from URL with alternative SystemId. * @param url Location of document * @param base SystemId/Base * @throws IOException If fails to read * @since 0.26.0 */ public XSLDocument(final URL url, final String base) throws IOException { this(new TextResource(url).toString(), base); } /** * Public ctor, from file. * @param file Location of document * @throws FileNotFoundException If fails to read * @since 0.21 */ public XSLDocument(final File file) throws FileNotFoundException { this(file, file.getAbsolutePath()); } /** * Public ctor, from file with alternative SystemId. * @param file Location of document * @param base SystemId/Base * @throws FileNotFoundException If fails to read * @since 0.26.0 */ public XSLDocument(final File file, final String base) throws FileNotFoundException { this(new TextResource(file).toString(), base); } /** * Public ctor, from file. * @param file Location of document * @throws FileNotFoundException If fails to read * @since 0.21 */ public XSLDocument(final Path file) throws FileNotFoundException { this(file.toFile()); } /** * Public ctor, from file with custom SystemId. * @param file Location of document * @param base SystemId/Base * @throws FileNotFoundException If fails to read * @since 0.26.0 */ public XSLDocument(final Path file, final String base) throws FileNotFoundException { this(file.toFile(), base); } /** * Public ctor, from URI. * @param uri Location of document * @throws IOException If fails to read * @since 0.15 */ public XSLDocument(final URI uri) throws IOException { this(uri, uri.toString()); } /** * Public ctor, from URI. * @param uri Location of document * @param base SystemId/Base * @throws IOException If fails to read * @since 0.26.0 */ public XSLDocument(final URI uri, final String base) throws IOException { this(new TextResource(uri).toString(), base); } /** * Public ctor, from XSL as an input stream. * @param stream XSL input stream */ public XSLDocument(final InputStream stream) { this(new TextResource(stream).toString()); } /** * Public ctor, from XSL as an input stream. * @param stream XSL input stream * @param base SystemId/Base * @since 0.20 */ public XSLDocument(final InputStream stream, final String base) { this(new TextResource(stream).toString(), base); } /** * Public ctor, from XSL as a string. * @param src XML document body */ public XSLDocument(final String src) { this(src, Sources.DUMMY); } /** * Public ctor, from XSL as a string. * @param src XML document body * @param base SystemId/Base * @since 0.20 */ public XSLDocument(final String src, final String base) { this(src, Sources.DUMMY, base); } /** * Public ctor, from XSL as a string. * @param src XML document body * @param srcs Sources * @since 0.9 */ public XSLDocument(final String src, final Sources srcs) { this(src, srcs, new HashMap<>(0)); } /** * Public ctor, from XSL as a string. * @param src XML document body * @param srcs Sources * @param base SystemId/Base * @since 0.20 */ public XSLDocument(final String src, final Sources srcs, final String base) { this(src, srcs, new HashMap<>(0), base); } /** * Public ctor, from XSL as a string. * @param src XML document body * @param srcs Sources * @param map Map of XSL params * @since 0.16 */ public XSLDocument(final String src, final Sources srcs, final Map map) { this(src, srcs, map, "/"); } /** * Public ctor, from XSL as a string. * @param src XML document body * @param srcs Sources * @param map Map of XSL params * @param base SystemId/Base * @since 0.20 * @checkstyle ParameterNumberCheck (5 lines) */ public XSLDocument(final String src, final Sources srcs, final Map map, final String base) { this.xsl = src; this.sources = srcs; this.params = new HashMap<>(map); this.sid = base; } @Override public XSL with(final Sources src) { return new XSLDocument(this.xsl, src, this.params, this.sid); } @Override public XSL with(final String name, final Object value) { return new XSLDocument( this.xsl, this.sources, new MapOf(this.params, new MapEntry<>(name, value)), this.sid ); } /** * Make an instance of XSL stylesheet without I/O exceptions. * *

This factory method is useful when you need to create * an instance of XSL stylesheet as a static final variable. In this * case you can't catch an exception but this method can help, for example: * *

 class Foo {
     *   private static final XSL STYLESHEET = XSLDocument.make(
     *     Foo.class.getResourceAsStream("my-stylesheet.xsl")
     *   );
     * }
* * @param stream Input stream * @return XSL stylesheet */ @SuppressWarnings("PMD.ProhibitPublicStaticMethods") public static XSL make(final InputStream stream) { return new XSLDocument(stream); } /** * Make an instance of XSL stylesheet without I/O exceptions. * @param url URL with content * @return XSL stylesheet * @see #make(InputStream) * @since 0.7.4 */ @SuppressWarnings("PMD.ProhibitPublicStaticMethods") public static XSL make(final URL url) { try { return new XSLDocument(url, url.toString()); } catch (final IOException ex) { throw new IllegalStateException( String.format( "Failed to read from URL '%s'", url ), ex ); } } @Override public String toString() { return new XMLDocument(this.xsl).toString(); } @Override public XML transform(final XML xml) { final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); final DocumentBuilder builder; try { builder = factory.newDocumentBuilder(); } catch (final ParserConfigurationException ex) { throw new IllegalArgumentException( String.format( "Failed to create new XML document by %s", factory.getClass().getName() ), ex ); } final Document target = builder.newDocument(); this.transformInto(xml, new DOMResult(target)); return new XMLDocument(target); } @Override public String applyTo(final XML xml) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); this.transformInto(xml, new StreamResult(baos)); try { return baos.toString(StandardCharsets.UTF_8.name()); } catch (final UnsupportedEncodingException ex) { throw new IllegalArgumentException( "Failed to convert bytes into UTF-8 string", ex ); } } /** * Transform XML into result. * * We create {@link TransformerFactory} here on every transformation * because {@link javax.xml.transform.URIResolver} must be set into * it before making an instance of a transformer. Otherwise, it won't * understand "xsl:import" statements. * * @param xml XML * @param result Result * @since 0.11 * @link Relevant SO question */ private void transformInto(final XML xml, final Result result) { final Transformer trans = this.transformer(); final ConsoleErrorListener errors = new ConsoleErrorListener(); trans.setErrorListener(errors); final long start = System.nanoTime(); try { trans.transform(new DOMSource(xml.node()), result); } catch (final TransformerException ex) { final StringBuilder summary = new StringBuilder( String.join("; ", errors.summary()) ); if (!summary.toString().equals(ex.getMessageAndLocation())) { summary.append("; ").append(ex.getMessageAndLocation()); } throw new IllegalArgumentException( String.format( "Failed to transform by %s: %s", trans.getClass().getName(), summary ), ex ); } if (Logger.isTraceEnabled(this)) { Logger.trace( this, "%s transformed XML in %[nano]s", trans.getClass().getName(), System.nanoTime() - start ); } } /** * Make a transformer. * @return The transformer */ private Transformer transformer() { final TransformerFactory factory = TransformerFactory.newInstance(); factory.setURIResolver(this.sources); final Transformer trans; try { trans = factory.newTransformer( new StreamSource(new StringReader(this.xsl), this.sid) ); } catch (final TransformerConfigurationException ex) { throw new IllegalArgumentException( String.format( "Failed to create transformer by %s", factory.getClass().getName() ), ex ); } for (final Map.Entry ent : this.params.entrySet()) { trans.setParameter(ent.getKey(), ent.getValue()); } return trans; } /** * Prepare it for Saxon. * @param trans The transformer * @return The same * @checkstyle ReturnCountCheck (5 lines) */ @SuppressWarnings("deprecation") private static Transformer forSaxon(final Transformer trans) { final String type = trans.getClass().getCanonicalName(); if (!"net.sf.saxon.jaxp.TransformerImpl".equals(type)) { return trans; } if (Version.getStructuredVersionNumber()[0] < 11) { ((TransformerImpl) trans) .getUnderlyingController() .setMessageEmitter(new MessageWarner()); } if (Version.getStructuredVersionNumber()[0] >= 11) { ((TransformerImpl) trans) .getUnderlyingController() .setMessageHandler( message -> Logger.error( XSLDocument.class, "%s: %s", message.getLocation(), message.toString() ) ); } return trans; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy