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

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

Go to download

The coolest XML library for Java around. Define typesafe views (projections) to xml. Use XPath to read and write XML. Bind XML to Java collections. Requires at least Java6, supports Java8 features and has no further runtime dependencies.

There is a newer version: 1.4.24
Show newest version
/**
 *  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.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;

/**
 * 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 InputSource EMPTY_INPUT_SOURCE = new InputSource(new StringReader(""));
    private static final EntityResolver NONRESOLVING_RESOLVER = new EntityResolver() {

        @Override
        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
            return EMPTY_INPUT_SOURCE;
        }
    };
//    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}
     */
    @Override
    public Transformer createTransformer(final Document... document) {
        try {
            Transformer transformer = createTransformerFactory().newTransformer();
            if (isPrettyPrinting()) {

                // Enable some pretty printing of the resulting xml.
                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 - 2024 Weber Informatics LLC | Privacy Policy