| /* |
| * Copyright 1999-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.cocoon.woody.util; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| |
| import org.apache.cocoon.xml.SaxBuffer; |
| import org.apache.cocoon.xml.dom.DOMStreamer; |
| import org.apache.excalibur.xml.sax.XMLizable; |
| import org.apache.xerces.dom.NodeImpl; |
| import org.apache.xerces.parsers.DOMParser; |
| import org.apache.xerces.xni.Augmentations; |
| import org.apache.xerces.xni.NamespaceContext; |
| import org.apache.xerces.xni.QName; |
| import org.apache.xerces.xni.XMLAttributes; |
| import org.apache.xerces.xni.XMLLocator; |
| import org.apache.xerces.xni.XNIException; |
| import org.w3c.dom.CDATASection; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.w3c.dom.Text; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXNotSupportedException; |
| |
| /** |
| * Helper class to create and retrieve information from DOM-trees. It provides |
| * some functionality comparable to what's found in Avalon's Configuration |
| * objects. These lasts one could however not be used by Woody because they |
| * don't provide an accurate model of an XML file (no mixed content, |
| * no namespaced attributes, no namespace declarations, ...). |
| * |
| * <p>This class depends specifically on the Xerces DOM implementation to be |
| * able to provide information about the location of elements in their source |
| * XML file. See the {@link #getLocation(Element)} method. |
| * |
| * @version CVS $Id: DomHelper.java,v 1.15 2004/03/05 13:02:34 bdelacretaz Exp $ |
| */ |
| public class DomHelper { |
| |
| /** |
| * Retrieves the location of an element node in the source file from which |
| * the Document was created. This will only work for Document's created |
| * with the method {@link #parse(InputSource)} of this class. |
| */ |
| public static String getLocation(Element element) { |
| String location = null; |
| if (element instanceof NodeImpl) { |
| location = (String)((NodeImpl)element).getUserData("location"); |
| } |
| |
| if (location != null) { |
| return location; |
| } |
| return "(location unknown)"; |
| } |
| |
| public static String getSystemIdLocation(Element element) { |
| String loc = getLocation(element); |
| if (loc.charAt(0) != '(') { |
| int end = loc.lastIndexOf(':'); |
| if (end > 0) { |
| int start = loc.lastIndexOf(':', end - 1); |
| if (start >= 0) { |
| return loc.substring(0, start); |
| } |
| } |
| } |
| return null; |
| } |
| |
| public static int getLineLocation(Element element) { |
| String loc = getLocation(element); |
| if (loc.charAt(0) != '(') { |
| int end = loc.lastIndexOf(':'); |
| if (end > 0) { |
| int start = loc.lastIndexOf(':', end - 1); |
| if (start >= 0) { |
| return Integer.parseInt(loc.substring(start + 1, end)); |
| } |
| } |
| } |
| return -1; |
| } |
| |
| public static int getColumnLocation(Element element) { |
| String loc = getLocation(element); |
| if (loc.charAt(0) != '(') { |
| int end = loc.lastIndexOf(':'); |
| if (end > 0) { |
| return Integer.parseInt(loc.substring(end)); |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Returns all Element children of an Element that belong to the given |
| * namespace. |
| */ |
| public static Element[] getChildElements(Element element, |
| String namespace) { |
| ArrayList elements = new ArrayList(); |
| NodeList nodeList = element.getChildNodes(); |
| for (int i = 0; i < nodeList.getLength(); i++) { |
| Node node = nodeList.item(i); |
| if (node instanceof Element |
| && namespace.equals(node.getNamespaceURI())) |
| elements.add(node); |
| } |
| return (Element[])elements.toArray(new Element[0]); |
| } |
| |
| /** |
| * Returns all Element children of an Element that belong to the given |
| * namespace and have the given local name. |
| */ |
| public static Element[] getChildElements(Element element, |
| String namespace, String localName) { |
| ArrayList elements = new ArrayList(); |
| NodeList nodeList = element.getChildNodes(); |
| for (int i = 0; i < nodeList.getLength(); i++) { |
| Node node = nodeList.item(i); |
| if (node instanceof Element |
| && namespace.equals(node.getNamespaceURI()) |
| && localName.equals(node.getLocalName())) { |
| elements.add(node); |
| } |
| } |
| return (Element[])elements.toArray(new Element[0]); |
| } |
| |
| /** |
| * Returns the first child element with the given namespace and localName, |
| * or null if there is no such element. |
| */ |
| public static Element getChildElement(Element element, String namespace, |
| String localName) { |
| Element node = null; |
| try { |
| node = getChildElement(element, namespace, localName, false); |
| } catch (Exception e) { |
| node = null; |
| } |
| return node; |
| } |
| |
| /** |
| * Returns the first child element with the given namespace and localName, |
| * or null if there is no such element and required flag is unset or |
| * throws an Exception if the "required" flag is set. |
| */ |
| public static Element getChildElement(Element element, String namespace, |
| String localName, boolean required) throws Exception { |
| NodeList nodeList = element.getChildNodes(); |
| for (int i = 0; i < nodeList.getLength(); i++) { |
| Node node = nodeList.item(i); |
| if (node instanceof Element |
| && namespace.equals(node.getNamespaceURI()) |
| && localName.equals(node.getLocalName())) { |
| return (Element)node; |
| } |
| } |
| if (required) { |
| throw new Exception("Missing element \"" + localName + |
| "\" as child of element \"" + element.getTagName() + |
| "\" at " + DomHelper.getLocation(element)); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the value of an element's attribute, but throws an exception |
| * if the element has no such attribute. |
| */ |
| public static String getAttribute(Element element, String attributeName) |
| throws Exception { |
| String attrValue = element.getAttribute(attributeName); |
| if (attrValue.equals("")) { |
| throw new Exception("Missing attribute \"" + attributeName + |
| "\" on element \"" + element.getTagName() + |
| "\" at " + getLocation(element)); |
| } |
| return attrValue; |
| } |
| |
| /** |
| * Returns the value of an element's attribute, or a default value if the |
| * element has no such attribute. |
| */ |
| public static String getAttribute(Element element, String attributeName, |
| String defaultValue) throws Exception { |
| String attrValue = element.getAttribute(attributeName); |
| if (attrValue.equals("")) { |
| return defaultValue; |
| } |
| return attrValue; |
| } |
| |
| public static int getAttributeAsInteger(Element element, |
| String attributeName) throws Exception { |
| String attrValue = getAttribute(element, attributeName); |
| try { |
| return Integer.parseInt(attrValue); |
| } catch (NumberFormatException e) { |
| throw new Exception("Cannot parse the value \"" + attrValue + |
| "\" as an integer in the attribute \"" + attributeName + |
| "\" on the element \"" + element.getTagName() + |
| "\" at " + getLocation(element)); |
| } |
| } |
| |
| public static int getAttributeAsInteger(Element element, |
| String attributeName, int defaultValue) throws Exception { |
| String attrValue = element.getAttribute(attributeName); |
| if (attrValue.equals("")) { |
| return defaultValue; |
| } else { |
| try { |
| return Integer.parseInt(attrValue); |
| } catch (NumberFormatException e) { |
| throw new Exception("Cannot parse the value \"" + attrValue + |
| "\" as an integer in the attribute \"" + |
| attributeName + "\" on the element \"" + |
| element.getTagName() + "\" at " + |
| getLocation(element)); |
| } |
| } |
| } |
| |
| public static boolean getAttributeAsBoolean(Element element, |
| String attributeName, boolean defaultValue) throws Exception { |
| String attrValue = element.getAttribute(attributeName); |
| if (attrValue.equals("")) { |
| return defaultValue; |
| } else if (attrValue.equalsIgnoreCase("true") |
| || attrValue.equalsIgnoreCase("yes")) { |
| return true; |
| } else if (attrValue.equalsIgnoreCase("false") |
| || attrValue.equalsIgnoreCase("no")) { |
| return false; |
| } else { |
| throw new Exception("Cannot parse the value \"" + attrValue + |
| "\" as a boolean in the attribute \"" + attributeName + |
| "\" on the element \"" + element.getTagName() + |
| "\" at " + getLocation(element)); |
| } |
| } |
| |
| public static String getElementText(Element element) { |
| StringBuffer value = new StringBuffer(); |
| NodeList nodeList = element.getChildNodes(); |
| for (int i = 0; i < nodeList.getLength(); i++) { |
| Node node = nodeList.item(i); |
| if (node instanceof Text || node instanceof CDATASection) { |
| value.append(node.getNodeValue()); |
| } |
| } |
| return value.toString(); |
| } |
| |
| /** |
| * Returns the content of the given Element as an object implementing the |
| * XMLizable interface. Practically speaking, the implementation uses the |
| * {@link SaxBuffer} class. The XMLizable object will be a standalone blurb |
| * of SAX events, not producing start/endDocument calls and containing all |
| * necessary namespace declarations. |
| */ |
| public static XMLizable compileElementContent(Element element) { |
| SaxBuffer saxBuffer = new SaxBuffer(); |
| DOMStreamer domStreamer = new DOMStreamer(); |
| domStreamer.setContentHandler(saxBuffer); |
| |
| NodeList childNodes = element.getChildNodes(); |
| for (int i = 0; i < childNodes.getLength(); i++) { |
| try { |
| domStreamer.stream(childNodes.item(i)); |
| } catch (SAXException e) { |
| // It's unlikely that an exception will occur here, |
| // so use a runtime exception |
| throw new RuntimeException( |
| "Error in DomHelper.compileElementContent: " + |
| e.toString()); |
| } |
| } |
| return saxBuffer; |
| } |
| |
| /** |
| * Creates a W3C Document that remembers the location of each element in |
| * the source file. The location of element nodes can then be retrieved |
| * using the {@link #getLocation(Element)} method. |
| */ |
| public static Document parse(InputSource inputSource) |
| throws SAXException, SAXNotSupportedException, IOException { |
| DOMParser domParser = new LocationTrackingDOMParser(); |
| domParser.setFeature( |
| "http://apache.org/xml/features/dom/defer-node-expansion", |
| false); |
| domParser.setFeature( |
| "http://apache.org/xml/features/dom/create-entity-ref-nodes", |
| false); |
| domParser.parse(inputSource); |
| return domParser.getDocument(); |
| } |
| |
| /** |
| * An extension of the Xerces DOM parser that puts the location of each |
| * node in that node's UserData. |
| */ |
| public static class LocationTrackingDOMParser extends DOMParser { |
| XMLLocator locator; |
| |
| public void startDocument(XMLLocator xmlLocator, String s, |
| NamespaceContext namespaceContext, |
| Augmentations augmentations) throws XNIException { |
| super.startDocument(xmlLocator, s, namespaceContext, |
| augmentations); |
| this.locator = xmlLocator; |
| setLocation(); |
| } |
| |
| public void startElement(QName qName, XMLAttributes xmlAttributes, |
| Augmentations augmentations) throws XNIException { |
| super.startElement(qName, xmlAttributes, augmentations); |
| setLocation(); |
| } |
| |
| private final void setLocation() { |
| // Older versions of Xerces had a different signature for the |
| // startDocument method. If such a version is used, the |
| // startDocument method above will not be called and locator will |
| // hence be null. |
| // Tell the users this so that they don't get a stupid NPE. |
| if (this.locator == null) { |
| throw new RuntimeException( |
| "Error: locator is null. Check that you have the" + |
| " correct version of Xerces (such as the one that" + |
| " comes with Cocoon) in your endorsed library path."); |
| } |
| NodeImpl node = null; |
| try { |
| node = (NodeImpl)this.getProperty( |
| "http://apache.org/xml/properties/dom/current-element-node"); |
| } catch (org.xml.sax.SAXException ex) { |
| System.err.println("except" + ex); |
| } |
| if (node != null) { |
| String location = locator.getLiteralSystemId() + ":" + |
| locator.getLineNumber() + ":" + locator.getColumnNumber(); |
| node.setUserData("location", location, null); |
| } |
| } |
| } |
| } |