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

org.conqat.lib.commons.xml.XMLReader Maven / Gradle / Ivy

There is a newer version: 2024.7.2
Show newest version
/*
 * Copyright (c) CQSE GmbH
 *
 * 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.conqat.lib.commons.xml;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.enums.EnumUtils;
import org.conqat.lib.commons.filesystem.FileSystemUtils;
import org.conqat.lib.commons.string.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Utility class for reading XML documents. The XML document can be validated by an optional schema
 * that is passed via {@link #setSchema(URL)}. Please consult test case XMLReaderTest to see how
 * this class is intended to be used.
 */
public abstract class XMLReader, A extends Enum, X extends Exception> {

	/** The current DOM element. */
	private Element currentDOMElement;

	/** The schema URL to use or null if no schema is used. */
	private URL schemaURL;

	/** Resolver used by the writer. */
	private final IXMLResolver xmlResolver;

	/** Reader that accesses content. */
	private final Reader reader;

	/**
	 * Create new reader.
	 * 
	 * @param file
	 *            the file to be read
	 * @param xmlResolver
	 *            resolvers used by this reader
	 */
	public XMLReader(File file, IXMLResolver xmlResolver) throws IOException {
		this(file, null, xmlResolver);
	}

	/**
	 * Create reader.
	 * 
	 * @param file
	 *            the file to be read
	 * @param encoding
	 *            XML encoding of the file. No encoding is set if null.
	 * @param xmlResolver
	 *            resolvers used by this reader
	 */
	public XMLReader(File file, Charset encoding, IXMLResolver xmlResolver) throws IOException {
		this(FileSystemUtils.streamReader(new FileInputStream(file), ensureNotNullEncoding(encoding)), xmlResolver);
	}

	/**
	 * Create reader.
	 * 
	 * @param content
	 *            XML string that gets parsed validation will be performed if null.
	 * @param xmlResolver
	 *            resolvers used by this reader
	 */
	public XMLReader(String content, IXMLResolver xmlResolver) {
		this(new StringReader(content), xmlResolver);
	}

	/**
	 * Create reader.
	 * 
	 * @param reader
	 *            the reader used to access the XML document.
	 * @param xmlResolver
	 *            resolvers used by this reader
	 */
	public XMLReader(Reader reader, IXMLResolver xmlResolver) {
		CCSMAssert.isFalse(reader == null, "Reader may not be null.");
		CCSMAssert.isFalse(xmlResolver == null, "XML resolver may not be null.");
		this.reader = reader;
		this.xmlResolver = xmlResolver;
	}

	/** Replaces a null value with the name of the default encoding. */
	private static Charset ensureNotNullEncoding(Charset encoding) {
		if (encoding == null) {
			return Charset.defaultCharset();
		}
		return encoding;
	}

	/** Sets the URL pointing to the schema that is used for validation. */
	protected void setSchema(URL schemaURL) {
		this.schemaURL = schemaURL;
	}

	/**
	 * Get boolean value of an attribute.
	 * 
	 * @return the boolean value, semantics for non-translatable or empty values is defined by
	 *         {@link Boolean#parseBoolean(String)}.
	 */
	protected boolean getBooleanAttribute(A attribute) {
		String value = getStringAttribute(attribute);
		return Boolean.parseBoolean(value);
	}

	/**
	 * Get boolean value of an attribute or the given default if not set.
	 *
	 * @return the boolean value, semantics for non-translatable or empty values is defined by
	 *         {@link Boolean#parseBoolean(String)}.
	 */
	protected boolean getBooleanAttributeOrDefault(A attribute, boolean defaultValue) {
		if (!hasAttribute(attribute)) {
			return defaultValue;
		}
		String value = getStringAttribute(attribute);
		return Boolean.parseBoolean(value);
	}

	/**
	 * Get the text content of a child element of the current element.
	 * 
	 * @param childElement
	 *            the child element
	 * @return the text or null if the current element doesn't have the requested child
	 *         element
	 */
	protected String getChildText(E childElement) {
		String elementName = xmlResolver.resolveElementName(childElement);
		Element domElement = XMLUtils.getNamedChild(currentDOMElement, elementName);
		if (domElement == null) {
			return null;
		}

		return domElement.getTextContent();
	}

	/**
	 * Translate attribute value to an enumeration element.
	 * 
	 * @param attribute
	 *            the attribute
	 * @param enumClass
	 *            the enumeration class
	 * 
	 * @return the enum value, semantics for non-translatable or empty values is defined by
	 *         {@link Enum#valueOf(Class, String)}.
	 */
	protected > T getEnumAttribute(A attribute, Class enumClass) {
		String value = getStringAttribute(attribute);
		return Enum.valueOf(enumClass, value);
	}

	/**
	 * Translate attribute value to an enumeration element.
	 * 
	 * @param attribute
	 *            the attribute
	 * @param enumClass
	 *            the enumeration class
	 * @param defaultValue
	 *            the default value to return in case the attribute is not specified or the enumeration
	 *            does not contain the specified value.
	 * 
	 * @return The enum value, semantics for non-translatable or empty values is defined by
	 *         {@link EnumUtils#valueOfIgnoreCase(Class, String)}.
	 */
	protected > T getEnumAttributeIgnoreCase(A attribute, Class enumClass, T defaultValue) {
		String value = getStringAttribute(attribute);
		if (StringUtils.isEmpty(value)) {
			return defaultValue;
		}
		T result = EnumUtils.valueOfIgnoreCase(enumClass, value);
		if (result == null) {
			return defaultValue;
		}
		return result;
	}

	/**
	 * Get int value of an attribute.
	 * 
	 * @return the int value, semantics for non-translatable or empty values is defined by
	 *         {@link Integer#parseInt(String)}.
	 */
	protected int getIntAttribute(A attribute) {
		String value = getStringAttribute(attribute);
		return Integer.parseInt(value);
	}

	/**
	 * Get long value of an attribute.
	 * 
	 * @return the long value, semantics for non-translatable or empty values is defined by
	 *         {@link Long#parseLong(String)}.
	 */
	protected long getLongAttribute(A attribute) {
		String value = getStringAttribute(attribute);
		return Long.parseLong(value);
	}

	/**
	 * Get attribute value.
	 * 
	 * 
	 * @return the attribute value or the empty string if attribute is undefined.
	 */
	protected String getStringAttribute(A attribute) {
		return currentDOMElement.getAttribute(xmlResolver.resolveAttributeName(attribute));
	}

	/**
	 * Get attribute value or a given default if not set.
	 *
	 *
	 * @return the attribute value or the given default if attribute is undefined.
	 */
	protected String getStringAttributeOrDefault(A attribute, String defaultValue) {
		if (!hasAttribute(attribute)) {
			return defaultValue;
		}
		return currentDOMElement.getAttribute(xmlResolver.resolveAttributeName(attribute));
	}

	/**
	 * Get non-empty attribute value. If the value is null or the empty String, an {@link IOException}
	 * will be thrown.
	 */
	protected String getNonEmptyStringAttribute(A attribute) throws IOException {
		String value = getStringAttribute(attribute);
		if (StringUtils.isEmpty(value)) {
			throw new IOException("Expected non-empty value for " + attribute.name());
		}
		return value;
	}

	/** Returns true if the current element has a given attribute. */
	protected boolean hasAttribute(A attribute) {
		return currentDOMElement.hasAttribute(xmlResolver.resolveAttributeName(attribute));
	}

	/**
	 * Get text content of current node.
	 */
	protected String getText() {
		return currentDOMElement.getTextContent();
	}

	/**
	 * Parse file. This sets the current element focus to the document root element. If schema URL was
	 * set the document is validated against the schema.
	 * 

* Sub classes should typically wrap this method with a proper error handling mechanism. * * @throws SAXException * if a parsing exceptions occurs * @throws IOException * if an IO exception occurs. */ protected void parseFile() throws SAXException, IOException { try { InputSource input = new InputSource(reader); Document document; if (schemaURL == null) { document = XMLUtils.parse(input); } else { document = XMLUtils.parse(input, schemaURL); } currentDOMElement = document.getDocumentElement(); } finally { reader.close(); } } /** * Process the child elements of the current element with a given processor. Target elements are * specified by {@link IXMLElementProcessor#getTargetElement()}. * * @param processor * the processor used to process the elements * @throws X * if the processor throws an exception */ protected void processChildElements(IXMLElementProcessor processor) throws X { String targetElementName = xmlResolver.resolveElementName(processor.getTargetElement()); processElementList(processor, XMLUtils.getNamedChildren(currentDOMElement, targetElementName)); } /** * Process all descendant elements of the current element with a given processor. In contrast to * {@link #processChildElements(IXMLElementProcessor)}, not only direct child elements are * processed. Descendant elements are processed in the sequence they are found during a top-down, * left-right traversal of the XML document. *

* Target elements are specified by {@link IXMLElementProcessor#getTargetElement()}. * * @param processor * the processor used to process the elements * @throws X * if the processor throws an exception */ protected void processDecendantElements(IXMLElementProcessor processor) throws X { String targetElementName = xmlResolver.resolveElementName(processor.getTargetElement()); NodeList descendantNodes = currentDOMElement.getElementsByTagName(targetElementName); processElementList(processor, XMLUtils.elementNodes(descendantNodes)); } /** * Processes the elements in the list with the given processor * * @param processor * the processor used to process the elements * @param elements * list of elements that get processed * @throws X * if the processor throws an exception */ private void processElementList(IXMLElementProcessor processor, List elements) throws X { Element oldElement = currentDOMElement; for (Element child : elements) { currentDOMElement = child; processor.process(); } currentDOMElement = oldElement; } /** * This works similar to the template mechanism known from XSLT. It traverses the DOM tree starting * from the current DOM element in depth-first fashion. For each element it checks if one of the * provided processors has the current element as target element. If a matching processor is found, * it is executed. * * @throws AssertionError * if multiple processors apply to the same target element */ @SuppressWarnings("unchecked") protected void apply(IXMLElementProcessor... processors) throws X { Map> processorMap = new HashMap<>(); for (IXMLElementProcessor processor : processors) { String targetElementName = xmlResolver.resolveElementName(processor.getTargetElement()); CCSMAssert.isFalse(processorMap.containsKey(targetElementName), "Multiple processors found for element: " + targetElementName); processorMap.put(targetElementName, processor); } Element oldElement = currentDOMElement; traverse(processorMap); currentDOMElement = oldElement; } /** * Traverse element tree in depth-first fashion and execute the processors provided by the processor * map. */ private void traverse(Map> processorMap) throws X { IXMLElementProcessor processor = processorMap.get(currentDOMElement.getTagName()); if (processor != null) { processor.process(); } NodeList nodeList = currentDOMElement.getChildNodes(); for (Element element : XMLUtils.elementNodes(nodeList)) { currentDOMElement = element; traverse(processorMap); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy