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

cdc.io.tools.XmlToHtml Maven / Gradle / Ivy

package cdc.io.tools;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import cdc.util.cli.AbstractMainSupport;
import cdc.util.files.Resources;
import cdc.util.lang.Checks;

/**
 * Utility to convert an XML file to HTML using an XSLT.
 *
 * @author Damien Carbonne
 *
 */
public final class XmlToHtml {
    protected static final Logger LOGGER = LogManager.getLogger(XmlToHtml.class);
    private final MainArgs margs;

    public static class MainArgs {
        /** XML input file (mandatory). */
        public URL xml;
        /** XSLT transformation file (mandatory). */
        public URL xslt;
        /** HTML output file or directory (mandatory). */
        public File output;
        /** Map of parameters (name, value) pairs to be passed to the style sheet. */
        public final Map params = new HashMap<>();
    }

    private XmlToHtml(MainArgs margs) {
        Checks.isNotNull(margs, "margs");
        Checks.isNotNull(margs.xml, "margs.xml");
        Checks.isNotNull(margs.xslt, "margs.xslt");
        this.margs = margs;
    }

    private static class ClasspathURIResolver implements URIResolver {
        private static final Logger LOGGER = LogManager.getLogger(ClasspathURIResolver.class);

        public ClasspathURIResolver() {
            super();
        }

        private static String decode(String s) {
            try {
                return URLDecoder.decode(s, "UTF-8");
            } catch (final UnsupportedEncodingException e) {
                LOGGER.error("decode({}) Failed, {}", s, e.getMessage());
                return s;
            }
        }

        @Override
        public Source resolve(String href,
                              String base) throws TransformerException {
            LOGGER.trace("resolve({}, {})", href, base);

            // If href is absolute, don't need to use base
            final URL url = Resources.getResource(decode(href), false);
            if (url != null) {
                try {
                    return new StreamSource(url.openStream(), url.toExternalForm());
                } catch (final IOException e) {
                    throw new TransformerException(e);
                }
            } else if (base != null) {
                try {
                    final URI baseUri = new URI(base);
                    final URL baseUrl = baseUri.toURL();
                    if ("file".equals(baseUrl.getProtocol())) {
                        final File basefile = new File(baseUri);
                        LOGGER.trace("base file: {}", basefile);
                        final File basedir = basefile.getParentFile();
                        final File target = new File(basedir, href);
                        final URL url2 = Resources.getResource(target.getPath());
                        Checks.assertTrue(url2 != null, "Failed to find {}", target);
                        LOGGER.trace("url: {}", url2);
                        return new StreamSource(url2.openStream(), url2.toExternalForm());
                    } else if ("jar".equals(baseUrl.getProtocol())) {
                        final String filename = baseUrl.getPath().substring(baseUrl.getPath().indexOf('!') + 1);
                        final File basedir = new File(filename).getParentFile();
                        LOGGER.trace("file: {} {}", filename, basedir);
                        final File target = new File(basedir, href);
                        final URL url2 = Resources.getResource(target.getPath());
                        LOGGER.trace("url: {}", url2);
                        return new StreamSource(url2.openStream(), url2.toExternalForm());
                    } else {
                        throw new TransformerException("Unsupported protocol");
                    }
                } catch (final URISyntaxException | IOException e) {
                    LOGGER.catching(e);
                }

            }
            return null;
        }
    }

    private void execute() throws Exception {
        LOGGER.info("Convert '{}'", margs.xml);
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("xslt: {} {}", margs.xslt, margs.xslt.toExternalForm());
        }
        try (final InputStream xmlInput = margs.xml.openStream();
                final InputStream xsltInput = margs.xslt.openStream()) {
            // TODO Java 9 use default JDK factory.
            // TODO Java 9 final TransformerFactory transformerFactory = TransformerFactory.newDefaultInstance();
            final TransformerFactory transformerFactory = TransformerFactory.newInstance();
            // Do not enable XMLConstants.FEATURE_SECURE_PROCESSING
            // This disables handling of xsl:result-document with saxon,
            // unless FeatureKeys.ALLOW_EXTERNAL_FUNCTIONS is enabled
            transformerFactory.setURIResolver(new ClasspathURIResolver());
            final StreamSource xmlSource = new StreamSource(xmlInput, margs.xml.toExternalForm());
            final StreamSource xsltSource = new StreamSource(xsltInput, margs.xslt.toExternalForm());

            final Transformer transformer = transformerFactory.newTransformer(xsltSource);
            transformer.setOutputProperty(OutputKeys.METHOD, "html");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            for (final Map.Entry entry : margs.params.entrySet()) {
                transformer.setParameter(entry.getKey(), entry.getValue());
            }

            // There is an issue on Window when using StreamResult(File) and file name contains special characters.
            final File file = margs.output == null
                    ? File.createTempFile("xmltohtml-", "")
                    : margs.output;
            final Result result;
            if (file.isDirectory()) {
                result = new StreamResult(file);
            } else {
                result = new StreamResult(new BufferedOutputStream(new FileOutputStream(file)));
            }
            result.setSystemId(URLDecoder.decode(file.toURI().toURL().toString(), "UTF-8"));
            transformer.transform(xmlSource, result);
            LOGGER.info("Conversion done into: {}", result.getSystemId());
        } catch (final IOException | TransformerException e) {
            LOGGER.catching(e);
            throw e;
        }
    }

    public static void execute(MainArgs margs) throws Exception {
        final XmlToHtml instance = new XmlToHtml(margs);
        instance.execute();
    }

    public static void main(String[] args) {
        final MainSupport support = new MainSupport();
        support.main(args);
    }

    private static class MainSupport extends AbstractMainSupport {
        private static final String XML = "xml";
        private static final String XSLT = "xslt";
        private static final String PARAM = "param";

        public MainSupport() {
            super(XmlToHtml.class, LOGGER);
        }

        @Override
        protected String getVersion() {
            return Config.VERSION;
        }

        @Override
        protected void addSpecificOptions(Options options) {
            options.addOption(Option.builder()
                                    .longOpt(XML)
                                    .desc("Name of the XML input .")
                                    .hasArg()
                                    .required()
                                    .build());
            options.addOption(Option.builder()
                                    .longOpt(XSLT)
                                    .desc("Name of the  containing the XSLT to apply.")
                                    .hasArg()
                                    .required()
                                    .build());
            options.addOption(Option.builder()
                                    .longOpt(OUTPUT)
                                    .desc("Name of the HTML  to generate.")
                                    .hasArg()
                                    .build());
            options.addOption(Option.builder()
                                    .longOpt(PARAM)
                                    .desc("Parameter(s) (name=value) to be passed to the transformation.")
                                    .hasArgs()
                                    .build());
        }

        private static void addParam(MainArgs margs,
                                     String s) throws ParseException {
            final int pos = s.indexOf('=');
            if (pos >= 0) {
                final String name = s.substring(0, pos);
                final String value = pos + 1 < s.length() ? s.substring(pos + 1) : "";
                margs.params.put(name, value);
            } else {
                throw new ParseException("Invalid param '" + s + "'");
            }
        }

        @Override
        protected MainArgs analyze(CommandLine cl) throws ParseException {
            final MainArgs margs = new MainArgs();
            margs.xml = Resources.getResource(cl.getOptionValue(XML));
            if (margs.xml == null) {
                throw new ParseException("Invalid xml: " + margs.xml);
            }
            margs.xslt = Resources.getResource(cl.getOptionValue(XSLT));
            if (margs.xml == null) {
                throw new ParseException("Invalid xslt: " + margs.xslt);
            }
            if (cl.hasOption(OUTPUT)) {
                margs.output = new File(cl.getOptionValue(OUTPUT));
            }
            if (cl.hasOption(PARAM)) {
                for (final String s : cl.getOptionValues(PARAM)) {
                    addParam(margs, s);
                }
            }
            return margs;
        }

        @Override
        protected Void execute(MainArgs margs) throws Exception {
            XmlToHtml.execute(margs);
            return null;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy