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

org.xmlbeam.config.DefaultXMLFactoriesConfig Maven / Gradle / Ivy

/**
 *  Copyright 2012 Sven Ewald
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.xmlbeam.config;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xmlbeam.XBProjector;
import org.xmlbeam.exceptions.XBException;
import org.xmlbeam.util.UnionIterator;
import org.xmlbeam.util.intern.DOMHelper;
import org.xmlbeam.util.intern.ReflectionHelper;

/**
 * Default configuration for {@link XBProjector} which uses Java default factories to create
 * {@link Transformer} {@link DocumentBuilder} and {@link XPath}. You may want to inherit from this
 * class to change this behavior.
 *
 * @author Sven Ewald
 */
@SuppressWarnings("serial")
public class DefaultXMLFactoriesConfig implements XMLFactoriesConfig {

    /**
     * This configuration can use one of three different ways to configure namespace handling.
     * Namespaces may be ignored (NIHILISTIC), handled user defined prefix mappings (AGNOSTIC) or
     * mapped automatically to the document prefixes (HEDONISTIC).
     */
    public static enum NamespacePhilosophy {

        /**
         * Maybe there are namespaces. Maybe not. You have to decide for yourself. Neither the xml
         * parser, nor the XPath instances bill be modified by this confiruration. Using this option
         * will require you to subclass this configuration and specify namespace handling yourself.
         * This way allowes you to control prefix to namespace mapping for the XPath expressions.
         * The namespace awareness flag of created DocumentBuilders won't be touched.
         */
        AGNOSTIC,

        /**
         * Fun without pain. This is the default option in this configuration. If namespaces are
         * defined in the document, the definition will be applied to your XPath expressions. Thus
         * you may just use existing namespaces without bothering about prefix mapping.
         * DocumentBuilders are created with namespace awareness set to false.
         */
        HEDONISTIC,

        /**
         * There is no such thing as a namespace. Only elements and attributes without a namespace
         * will be visible to projections. Using this option prevents getting exceptions when an
         * XPath expression tries to select a non defined namespace. (You can't get errors if you
         * deny the existence of errors.) DocumentBuilders are created with namespace awareness set
         * to false.
         */
        NIHILISTIC
    }

    /**
     * A facade to provide user defined namespace mappings. This way a document with namespaces can
     * be created from scratch.
     *
     * @author sven
     */
    public interface NSMapping {
        /**
         * @param prefix
         * @param uri
         * @return the current mapping for convenience.
         */
        NSMapping add(String prefix, String uri);

        /**
         * @param uri
         * @return the current mapping for convenience.
         */
        NSMapping addDefaultNamespace(String uri);
    }

    private static final String NON_EXISTING_URL = "http://xmlbeam.org/nonexisting_namespace";

    private boolean isExpandEntityReferences = false;
    private boolean isNoEntityResolving = true;
    private boolean isOmitXMLDeclaration = true;
    private boolean isPrettyPrinting = true;
    private boolean isXIncludeAware = false;
    private NamespacePhilosophy namespacePhilosophy = NamespacePhilosophy.HEDONISTIC;
    private static final EntityResolver NONRESOLVING_RESOLVER = new EntityResolver() {

        @Override
        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
            return new InputSource(new StringReader(""));
        }
    };
//    private static final String[] FEATURE_DEFAULTS = new String[] { "http://apache.org/xml/features/disallow-doctype-decl#false", //
//            "http://xml.org/sax/features/external-general-entities#false", //
//            "http://xml.org/sax/features/external-parameter-entities#false", //
//            "http://apache.org/xml/features/nonvalidating/load-external-dtd#false" };

    private final Map USER_DEFINED_MAPPING = new TreeMap();

    /**
     * Empty default constructor, a Configuration has no state.
     */
    public DefaultXMLFactoriesConfig() {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DocumentBuilder createDocumentBuilder() {
        try {
            DocumentBuilder documentBuilder = createDocumentBuilderFactory().newDocumentBuilder();
            if (isNoEntityResolving) {
                documentBuilder.setEntityResolver(NONRESOLVING_RESOLVER);
            }
            return documentBuilder;
        } catch (ParserConfigurationException e) {
            throw new XBException("Error on creating document builder", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DocumentBuilderFactory createDocumentBuilderFactory() {
        DocumentBuilderFactory instance = DocumentBuilderFactory.newInstance();
        instance.setXIncludeAware(this.isXIncludeAware);
        instance.setExpandEntityReferences(this.isExpandEntityReferences);
//        for (String featureDefault : FEATURE_DEFAULTS) {
//            String[] featureValue = featureDefault.split("#");
//            try {
//                instance.setFeature(featureValue[0], Boolean.valueOf(featureValue[1]));
//            } catch (ParserConfigurationException e) {
//                // No worries if one feature is not supported.
//            }
//        }
        if (!NamespacePhilosophy.AGNOSTIC.equals(namespacePhilosophy)) {
            instance.setNamespaceAware(NamespacePhilosophy.HEDONISTIC.equals(namespacePhilosophy));
        }
        return instance;
    }

    /**
     * @return A NSMapping that can be used to create documents with namespaces from scratch. Just
     *         add your prefixes and ns uris.
     */
    public NSMapping createNameSpaceMapping() {
        if (!NamespacePhilosophy.HEDONISTIC.equals(namespacePhilosophy)) {
            throw new IllegalStateException("To use a namespace mapping, you need to use the HEDONISTIC NamespacePhilosophy.");
        }
        return new NSMapping() {

            @Override
            public NSMapping add(final String prefix, final String uri) {
                if ((prefix == null) || (prefix.isEmpty())) {
                    throw new IllegalArgumentException("prefix must not be empty");
                }
                if ((uri == null) || (uri.isEmpty())) {
                    throw new IllegalArgumentException("uri must not be empty");
                }
                if (USER_DEFINED_MAPPING.containsKey(prefix) && (!uri.equals(USER_DEFINED_MAPPING.get(prefix)))) {
                    throw new IllegalArgumentException("The prefix '" + prefix + "' is bound to namespace '" + USER_DEFINED_MAPPING.get(prefix) + " already.");
                }
                USER_DEFINED_MAPPING.put(prefix, uri);
                return this;
            }

            @Override
            public NSMapping addDefaultNamespace(String uri) {
                return add("xbdefaultns", uri);
            }

        };
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("resource")
    @Override
    public Transformer createTransformer(final Document... document) {
        try {
            TransformerFactory transformerFactory = createTransformerFactory();
            if (isPrettyPrinting && (ReflectionHelper.JAVA_VERSION > 8)) {
                transformerFactory.setAttribute("indent-number", new Integer(2));
            }
            Transformer transformer = isPrettyPrinting && (ReflectionHelper.JAVA_VERSION > 8) ? transformerFactory.newTransformer(new StreamSource(getClass().getResourceAsStream("prettyprint.xslt"))) : transformerFactory.newTransformer();

            if (isPrettyPrinting()) {
                // Enable some pretty printing of the resulting xml.
                if (ReflectionHelper.JAVA_VERSION > 8) {
                    transformer.setOutputProperty(OutputKeys.STANDALONE, "no");
                }
                transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
                transformer.setOutputProperty(OutputKeys.INDENT, "yes");

            }
            if (isOmitXMLDeclaration()) {
                transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            }
            return transformer;
        } catch (TransformerConfigurationException e) {
            throw new XBException("Error on creating transformer", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public TransformerFactory createTransformerFactory() {
        return TransformerFactory.newInstance();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public XPath createXPath(final Document... document) {
        final XPath xPath = createXPathFactory().newXPath();
        if ((document == null) || (document.length == 0) || (!NamespacePhilosophy.HEDONISTIC.equals(namespacePhilosophy))) {
            return xPath;
        }
        // For hedonistic name space philosophy we aspire a reasonable name space mapping.
        final Map nameSpaceMapping = DOMHelper.getNamespaceMapping(document[0]);
        final NamespaceContext ctx = new NamespaceContext() {
            @Override
            public String getNamespaceURI(final String prefix) {
                if (prefix == null) {
                    throw new IllegalArgumentException("null not allowed as prefix");
                }
                if (nameSpaceMapping.containsKey(prefix)) {
                    return nameSpaceMapping.get(prefix);
                }
                if (USER_DEFINED_MAPPING.containsKey(prefix)) {
                    return USER_DEFINED_MAPPING.get(prefix);
                }
                // Default is a global unique string uri to prevent xpath expression exeptions on
                // nonexisting ns.
                return NON_EXISTING_URL;
            }

            @Override
            public String getPrefix(final String uri) {
                for (Entry e : nameSpaceMapping.entrySet()) {
                    if (e.getValue().equals(uri)) {
                        return e.getKey();
                    }
                }
                for (Entry e : USER_DEFINED_MAPPING.entrySet()) {
                    if (e.getValue().equals(uri)) {
                        return e.getKey();
                    }
                }
                return null;
            }

            @Override
            public Iterator getPrefixes(final String val) {
                return new UnionIterator(nameSpaceMapping.keySet().iterator(), USER_DEFINED_MAPPING.keySet().iterator());
            }
        };
        xPath.setNamespaceContext(ctx);
        return xPath;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public XPathFactory createXPathFactory() {
        return XPathFactory.newInstance();
    }

    /**
     * @return the namespacePhilosophy
     */
    public NamespacePhilosophy getNamespacePhilosophy() {
        return namespacePhilosophy;
    }

    @Override
    public Map getUserDefinedNamespaceMapping() {
        return Collections.unmodifiableMap(USER_DEFINED_MAPPING);
    }

    /**
     * @return the isExpandEntityReferences
     */
    public boolean isExpandEntityReferences() {
        return isExpandEntityReferences;
    }

    /**
     * @return the isNoEntityResolving
     */
    public boolean isNoEntityResolving() {
        return isNoEntityResolving;
    }

    /**
     * @return the isOmitXMLDeclaration
     */
    public boolean isOmitXMLDeclaration() {
        return isOmitXMLDeclaration;
    }

    /**
     * Getter for pretty printing option.
     *
     * @return true if output will be formatted
     */
    public boolean isPrettyPrinting() {
        return isPrettyPrinting;
    }

    /**
     * @return the isXIncludeAware
     */
    public boolean isXIncludeAware() {
        return isXIncludeAware;
    }

    /**
     * @param isExpandEntityReferences
     *            the isExpandEntityReferences to set
     */
    public void setExpandEntityReferences(boolean isExpandEntityReferences) {
        this.isExpandEntityReferences = isExpandEntityReferences;
    }

    /**
     * @param namespacePhilosophy
     * @return this for convenience
     */
    public XMLFactoriesConfig setNamespacePhilosophy(final NamespacePhilosophy namespacePhilosophy) {
        this.namespacePhilosophy = namespacePhilosophy;
        return this;
    }

    /**
     * @param isNoEntityResolving
     *            the isNoEntityResolving to set
     */
    public void setNoEntityResolving(boolean isNoEntityResolving) {
        this.isNoEntityResolving = isNoEntityResolving;
    }

    /**
     * @param isOmitXMLDeclaration
     *            the isOmitXMLDeclaration to set
     * @return this for convenience
     */
    public DefaultXMLFactoriesConfig setOmitXMLDeclaration(final boolean isOmitXMLDeclaration) {
        this.isOmitXMLDeclaration = isOmitXMLDeclaration;
        return this;
    }

    /**
     * Setter for pretty printing option
     *
     * @param on
     *            (true == output will be formatted)
     * @return this for convenience
     */
    public DefaultXMLFactoriesConfig setPrettyPrinting(final boolean on) {
        this.isPrettyPrinting = on;
        return this;
    }

    /**
     * @param isXIncludeAware
     *            the isXIncludeAware to set
     */
    public void setXIncludeAware(boolean isXIncludeAware) {
        this.isXIncludeAware = isXIncludeAware;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy