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

org.apache.tools.ant.taskdefs.XmlProperty Maven / Gradle / Ivy

There is a newer version: 1.0-rc5
Show newest version
/*
 * Copyright  2002-2004 The Apache Software Foundation
 *
 *  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.apache.tools.ant.taskdefs;

import java.io.File;
import java.io.IOException;
import java.util.Hashtable;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.XMLCatalog;
import org.apache.tools.ant.util.FileUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.xml.sax.EntityResolver;

/**
 * Loads property values from a valid XML file, generating the
 * property names from the file's element and attribute names.
 *
 * 

Example:

*
 *   <root-tag myattr="true">
 *     <inner-tag someattr="val">Text</inner-tag>
 *     <a2><a3><a4>false</a4></a3></a2>
 *     <x>x1</x>
 *     <x>x2</x>
 *   </root-tag>
 *
* *

this generates the following properties:

* *
 *  root-tag(myattr)=true
 *  root-tag.inner-tag=Text
 *  root-tag.inner-tag(someattr)=val
 *  root-tag.a2.a3.a4=false
 *  root-tag.x=x1,x2
 * 
* *

The collapseAttributes property of this task can be set * to true (the default is false) which will instead result in the * following properties (note the difference in names of properties * corresponding to XML attributes):

* *
 *  root-tag.myattr=true
 *  root-tag.inner-tag=Text
 *  root-tag.inner-tag.someattr=val
 *  root-tag.a2.a3.a4=false
 *  root-tag.x=x1,x2
 * 
* *

Optionally, to more closely mirror the abilities of the Property * task, a selected set of attributes can be treated specially. To * enable this behavior, the "semanticAttributes" property of this task * must be set to true (it defaults to false). If this attribute is * specified, the following attributes take on special meaning * (setting this to true implicitly sets collapseAttributes to true as * well):

* *
    *
  • value: Identifies a text value for a property.
  • *
  • location: Identifies a file location for a property.
  • *
  • id: Sets an id for a property
  • *
  • refid: Sets a property to the value of another property * based upon the provided id
  • *
  • pathid: Defines a path rather than a property with * the given id.
  • *
* *

For example, with keepRoot = false, the following properties file:

* *
 * <root-tag>
 *   <build>
 *   <build folder="build">
 *     <classes id="build.classes" location="${build.folder}/classes"/>
 *     <reference refid="build.classes"/>
 *   </build>
 *   <compile>
 *     <classpath pathid="compile.classpath">
 *       <pathelement location="${build.classes}"/>
 *     </classpath>
 *   </compile>
 *   <run-time>
 *     <jars>*.jar</jars>
 *     <classpath pathid="run-time.classpath">
 *       <path refid="compile.classpath"/>
 *       <pathelement path="${run-time.jars}"/>
 *     </classpath>
 *   </run-time>
 * </root-tag>
 * 
* *

is equivalent to the following entries in a build file:

* *
 * <property name="build" location="build"/>
 * <property name="build.classes" location="${build.location}/classes"/>
 * <property name="build.reference" refid="build.classes"/>
 *
 * <property name="run-time.jars" value="*.jar/>
 *
 * <classpath id="compile.classpath">
 *   <pathelement location="${build.classes}"/>
 * </classpath>
 *
 * <classpath id="run-time.classpath">
 *   <path refid="compile.classpath"/>
 *   <pathelement path="${run-time.jars}"/>
 * </classpath>
 * 
* *

This task requires the following attributes:

* *
    *
  • file: The name of the file to load.
  • *
* *

This task supports the following attributes:

* *
    *
  • prefix: Optionally specify a prefix applied to * all properties loaded. Defaults to an empty string.
  • *
  • keepRoot: Indicate whether the root xml element * is kept as part of property name. Defaults to true.
  • *
  • validate: Indicate whether the xml file is validated. * Defaults to false.
  • *
  • collapseAttributes: Indicate whether attributes are * stored in property names with parens or with period * delimiters. Defaults to false, meaning properties * are stored with parens (i.e., foo(attr)).
  • *
  • semanticAttributes: Indicate whether attributes * named "location", "value", "refid" and "path" * are interpreted as ant properties. Defaults * to false.
  • *
  • rootDirectory: Indicate the directory to use * as the root directory for resolving location * properties. Defaults to the directory * of the project using the task.
  • *
  • includeSemanticAttribute: Indicate whether to include * the semantic attribute ("location" or "value") as * part of the property name. Defaults to false.
  • *
* * @ant.task name="xmlproperty" category="xml" */ public class XmlProperty extends org.apache.tools.ant.Task { private File src; private String prefix = ""; private boolean keepRoot = true; private boolean validate = false; private boolean collapseAttributes = false; private boolean semanticAttributes = false; private boolean includeSemanticAttribute = false; private File rootDirectory = null; private FileUtils fileUtils = FileUtils.newFileUtils(); private Hashtable addedAttributes = new Hashtable(); private XMLCatalog xmlCatalog = new XMLCatalog(); private static final String ID = "id"; private static final String REF_ID = "refid"; private static final String LOCATION = "location"; private static final String VALUE = "value"; private static final String PATH = "path"; private static final String PATHID = "pathid"; private static final String[] ATTRIBUTES = new String[] { ID, REF_ID, LOCATION, VALUE, PATH, PATHID }; /** * Constructor. */ public XmlProperty() { super(); } /** * Initializes the task. */ public void init() { super.init(); xmlCatalog.setProject(getProject()); } /** * @return the xmlCatalog as the entityresolver. */ protected EntityResolver getEntityResolver() { return xmlCatalog; } /** * Run the task. * @throws BuildException The exception raised during task execution. * @todo validate the source file is valid before opening, print a better error message * @todo add a verbose level log message listing the name of the file being loaded */ public void execute() throws BuildException { if (getFile() == null) { String msg = "XmlProperty task requires a file attribute"; throw new BuildException(msg); } try { log("Loading " + src.getAbsolutePath(), Project.MSG_VERBOSE); if (src.exists()) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(validate); factory.setNamespaceAware(false); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(getEntityResolver()); Document document = builder.parse(src); Element topElement = document.getDocumentElement(); // Keep a hashtable of attributes added by this task. // This task is allow to override its own properties // but not other properties. So we need to keep track // of which properties we've added. addedAttributes = new Hashtable(); if (keepRoot) { addNodeRecursively(topElement, prefix, null); } else { NodeList topChildren = topElement.getChildNodes(); int numChildren = topChildren.getLength(); for (int i = 0; i < numChildren; i++) { addNodeRecursively(topChildren.item(i), prefix, null); } } } else { log("Unable to find property file: " + src.getAbsolutePath(), Project.MSG_VERBOSE); } } catch (SAXException sxe) { // Error generated during parsing Exception x = sxe; if (sxe.getException() != null) { x = sxe.getException(); } throw new BuildException(x); } catch (ParserConfigurationException pce) { // Parser with specified options can't be built throw new BuildException(pce); } catch (IOException ioe) { // I/O error throw new BuildException(ioe); } } /** Iterate through all nodes in the tree. */ private void addNodeRecursively(Node node, String prefix, Object container) { // Set the prefix for this node to include its tag name. String nodePrefix = prefix; if (node.getNodeType() != Node.TEXT_NODE) { if (prefix.trim().length() > 0) { nodePrefix += "."; } nodePrefix += node.getNodeName(); } // Pass the container to the processing of this node, Object nodeObject = processNode(node, nodePrefix, container); // now, iterate through children. if (node.hasChildNodes()) { NodeList nodeChildren = node.getChildNodes(); int numChildren = nodeChildren.getLength(); for (int i = 0; i < numChildren; i++) { // For each child, pass the object added by // processNode to its children -- in other word, each // object can pass information along to its children. addNodeRecursively(nodeChildren.item(i), nodePrefix, nodeObject); } } } void addNodeRecursively(org.w3c.dom.Node node, String prefix) { addNodeRecursively(node, prefix, null); } /** * Process the given node, adding any required attributes from * this child node alone -- but not processing any * children. * * @param node the XML Node to parse * @param prefix A string to prepend to any properties that get * added by this node. * @param container Optionally, an object that a parent node * generated that this node might belong to. For example, this * node could be within a node that generated a Path. * @return the Object created by this node. Generally, this is * either a String if this node resulted in setting an attribute, * or a Path. */ public Object processNode (Node node, String prefix, Object container) { // Parse the attribute(s) and text of this node, adding // properties for each. // if the "path" attribute is specified, then return the created path // which will be passed to the children of this node. Object addedPath = null; // The value of an id attribute of this node. String id = null; if (node.hasAttributes()) { NamedNodeMap nodeAttributes = node.getAttributes(); // Is there an id attribute? Node idNode = nodeAttributes.getNamedItem(ID); id = (semanticAttributes && idNode != null ? idNode.getNodeValue() : null); // Now, iterate through the attributes adding them. for (int i = 0; i < nodeAttributes.getLength(); i++) { Node attributeNode = nodeAttributes.item(i); if (!semanticAttributes) { String attributeName = getAttributeName(attributeNode); String attributeValue = getAttributeValue(attributeNode); addProperty(prefix + attributeName, attributeValue, null); } else { String nodeName = attributeNode.getNodeName(); String attributeValue = getAttributeValue(attributeNode); Path containingPath = (container != null && container instanceof Path ? (Path) container : null); /* * The main conditional logic -- if the attribute * is somehow "special" (i.e., it has known * semantic meaning) then deal with it * appropriately. */ if (nodeName.equals(ID)) { // ID has already been found above. continue; } else if (containingPath != null && nodeName.equals(PATH)) { // A "path" attribute for a node within a Path object. containingPath.setPath(attributeValue); } else if (container instanceof Path && nodeName.equals(REF_ID)) { // A "refid" attribute for a node within a Path object. containingPath.setPath(attributeValue); } else if (container instanceof Path && nodeName.equals(LOCATION)) { // A "location" attribute for a node within a // Path object. containingPath.setLocation(resolveFile(attributeValue)); } else if (nodeName.equals(PATHID)) { // A node identifying a new path if (container != null) { throw new BuildException("XmlProperty does not " + "support nested paths"); } addedPath = new Path(getProject()); getProject().addReference(attributeValue, addedPath); } else { // An arbitrary attribute. String attributeName = getAttributeName(attributeNode); addProperty(prefix + attributeName, attributeValue, id); } } } } String nodeText = null; if (node.getNodeType() == Node.TEXT_NODE) { // For the text node, add a property. nodeText = getAttributeValue(node); } else if ((node.getNodeType() == Node.ELEMENT_NODE) && (node.getChildNodes().getLength() == 1) && (node.getFirstChild().getNodeType() == Node.CDATA_SECTION_NODE)) { nodeText = node.getFirstChild().getNodeValue(); } if (nodeText != null) { // If the containing object was a String, then use it as the ID. if (semanticAttributes && id == null && container instanceof String) { id = (String) container; } if (nodeText.trim().length() != 0) { addProperty(prefix, nodeText, id); } } // Return the Path we added or the ID of this node for // children to reference if needed. Path objects are // definitely used by child path elements, and ID may be used // for a child text node. return (addedPath != null ? addedPath : id); } /** * Actually add the given property/value to the project * after writing a log message. */ private void addProperty (String name, String value, String id) { String msg = name + ":" + value; if (id != null) { msg += ("(id=" + id + ")"); } log(msg, Project.MSG_DEBUG); if (addedAttributes.containsKey(name)) { // If this attribute was added by this task, then // we append this value to the existing value. // We use the setProperty method which will // forcibly override the property if it already exists. // We need to put these properties into the project // when we read them, though (instead of keeping them // outside of the project and batch adding them at the end) // to allow other properties to reference them. value = (String) addedAttributes.get(name) + "," + value; getProject().setProperty(name, value); } else { getProject().setNewProperty(name, value); } addedAttributes.put(name, value); if (id != null) { getProject().addReference(id, value); } } /** * Return a reasonable attribute name for the given node. * If we are using semantic attributes or collapsing * attributes, the returned name is ".nodename". * Otherwise, we return "(nodename)". This is long-standing * (and default) <xmlproperty> behavior. */ private String getAttributeName (Node attributeNode) { String attributeName = attributeNode.getNodeName(); if (semanticAttributes) { // Never include the "refid" attribute as part of the // attribute name. if (attributeName.equals(REF_ID)) { return ""; // Otherwise, return it appended unless property to hide it is set. } else if (!isSemanticAttribute(attributeName) || includeSemanticAttribute) { return "." + attributeName; } else { return ""; } } else if (collapseAttributes) { return "." + attributeName; } else { return "(" + attributeName + ")"; } } /** * Return whether the provided attribute name is recognized or not. */ private static boolean isSemanticAttribute (String attributeName) { for (int i = 0; i < ATTRIBUTES.length; i++) { if (attributeName.equals(ATTRIBUTES[i])) { return true; } } return false; } /** * Return the value for the given attribute. * If we are not using semantic attributes, its just the * literal string value of the attribute. * *

If we are using semantic attributes, then first * dependent properties are resolved (i.e., ${foo} is resolved * based on the foo property value), and then an appropriate data * type is used. In particular, location-based properties are * resolved to absolute file names. Also for refid values, look * up the referenced object from the project.

*/ private String getAttributeValue (Node attributeNode) { String nodeValue = attributeNode.getNodeValue().trim(); if (semanticAttributes) { String attributeName = attributeNode.getNodeName(); nodeValue = getProject().replaceProperties(nodeValue); if (attributeName.equals(LOCATION)) { File f = resolveFile(nodeValue); return f.getPath(); } else if (attributeName.equals(REF_ID)) { Object ref = getProject().getReference(nodeValue); if (ref != null) { return ref.toString(); } } } return nodeValue; } /** * The XML file to parse; required. * @param src the file to parse */ public void setFile(File src) { this.src = src; } /** * the prefix to prepend to each property * @param prefix the prefix to prepend to each property */ public void setPrefix(String prefix) { this.prefix = prefix.trim(); } /** * flag to include the xml root tag as a * first value in the property name; optional, * default is true * @param keepRoot if true (default), include the xml root tag */ public void setKeeproot(boolean keepRoot) { this.keepRoot = keepRoot; } /** * flag to validate the XML file; optional, default false * @param validate if true validate the XML file, default false */ public void setValidate(boolean validate) { this.validate = validate; } /** * flag to treat attributes as nested elements; * optional, default false * @param collapseAttributes if true treat attributes as nested elements */ public void setCollapseAttributes(boolean collapseAttributes) { this.collapseAttributes = collapseAttributes; } /** * Attribute to enable special handling of attributes - see ant manual. * @param semanticAttributes if true enable the special handling. */ public void setSemanticAttributes(boolean semanticAttributes) { this.semanticAttributes = semanticAttributes; } /** * The directory to use for resolving file references. * Ignored if semanticAttributes is not set to true. * @param rootDirectory the directory. */ public void setRootDirectory(File rootDirectory) { this.rootDirectory = rootDirectory; } /** * Include the semantic attribute name as part of the property name. * Ignored if semanticAttributes is not set to true. * @param includeSemanticAttribute if true include the sematic attribute * name. */ public void setIncludeSemanticAttribute(boolean includeSemanticAttribute) { this.includeSemanticAttribute = includeSemanticAttribute; } /** * add an XMLCatalog as a nested element; optional. * @param catalog the XMLCatalog to use */ public void addConfiguredXMLCatalog(XMLCatalog catalog) { xmlCatalog.addConfiguredXMLCatalog(catalog); } /* Expose members for extensibility */ /** * @return the file attribute. */ protected File getFile () { return this.src; } /** * @return the prefix attribute. */ protected String getPrefix () { return this.prefix; } /** * @return the keeproot attribute. */ protected boolean getKeeproot () { return this.keepRoot; } /** * @return the validate attribute. */ protected boolean getValidate () { return this.validate; } /** * @return the collapse attributes attribute. */ protected boolean getCollapseAttributes () { return this.collapseAttributes; } /** * @return the semantic attributes attribute. */ protected boolean getSemanticAttributes () { return this.semanticAttributes; } /** * @return the root directory attribute. */ protected File getRootDirectory () { return this.rootDirectory; } /** * @return the include semantic attribute. */ protected boolean getIncludeSementicAttribute () { return this.includeSemanticAttribute; } /** * Let project resolve the file - or do it ourselves if * rootDirectory has been set. */ private File resolveFile(String fileName) { if (rootDirectory == null) { return getProject().resolveFile(fileName); } return fileUtils.resolveFile(rootDirectory, fileName); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy