001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.woody.util;
018:
019: import java.io.IOException;
020: import java.util.ArrayList;
021:
022: import org.apache.cocoon.xml.SaxBuffer;
023: import org.apache.cocoon.xml.dom.DOMStreamer;
024: import org.apache.commons.lang.BooleanUtils;
025: import org.apache.excalibur.xml.sax.XMLizable;
026: import org.apache.xerces.dom.NodeImpl;
027: import org.apache.xerces.parsers.DOMParser;
028: import org.apache.xerces.xni.Augmentations;
029: import org.apache.xerces.xni.NamespaceContext;
030: import org.apache.xerces.xni.QName;
031: import org.apache.xerces.xni.XMLAttributes;
032: import org.apache.xerces.xni.XMLLocator;
033: import org.apache.xerces.xni.XNIException;
034: import org.w3c.dom.CDATASection;
035: import org.w3c.dom.Document;
036: import org.w3c.dom.Element;
037: import org.w3c.dom.Node;
038: import org.w3c.dom.NodeList;
039: import org.w3c.dom.Text;
040: import org.w3c.dom.UserDataHandler;
041: import org.xml.sax.InputSource;
042: import org.xml.sax.SAXException;
043: import org.xml.sax.SAXNotSupportedException;
044:
045: /**
046: * Helper class to create and retrieve information from DOM-trees. It provides
047: * some functionality comparable to what's found in Avalon's Configuration
048: * objects. These lasts one could however not be used by Woody because they
049: * don't provide an accurate model of an XML file (no mixed content,
050: * no namespaced attributes, no namespace declarations, ...).
051: *
052: * <p>This class depends specifically on the Xerces DOM implementation to be
053: * able to provide information about the location of elements in their source
054: * XML file. See the {@link #getLocation(Element)} method.
055: *
056: * @version CVS $Id: DomHelper.java 433543 2006-08-22 06:22:54Z crossley $
057: */
058: public class DomHelper {
059:
060: /**
061: * Retrieves the location of an element node in the source file from which
062: * the Document was created. This will only work for Document's created
063: * with the method {@link #parse(InputSource)} of this class.
064: */
065: public static String getLocation(Element element) {
066: String location = null;
067: if (element instanceof NodeImpl) {
068: location = (String) ((NodeImpl) element)
069: .getUserData("location");
070: }
071:
072: if (location != null) {
073: return location;
074: }
075: return "(location unknown)";
076: }
077:
078: public static String getSystemIdLocation(Element element) {
079: String loc = getLocation(element);
080: if (loc.charAt(0) != '(') {
081: int end = loc.lastIndexOf(':');
082: if (end > 0) {
083: int start = loc.lastIndexOf(':', end - 1);
084: if (start >= 0) {
085: return loc.substring(0, start);
086: }
087: }
088: }
089: return null;
090: }
091:
092: public static int getLineLocation(Element element) {
093: String loc = getLocation(element);
094: if (loc.charAt(0) != '(') {
095: int end = loc.lastIndexOf(':');
096: if (end > 0) {
097: int start = loc.lastIndexOf(':', end - 1);
098: if (start >= 0) {
099: return Integer.parseInt(loc.substring(start + 1,
100: end));
101: }
102: }
103: }
104: return -1;
105: }
106:
107: public static int getColumnLocation(Element element) {
108: String loc = getLocation(element);
109: if (loc.charAt(0) != '(') {
110: int end = loc.lastIndexOf(':');
111: if (end > 0) {
112: return Integer.parseInt(loc.substring(end));
113: }
114: }
115: return -1;
116: }
117:
118: /**
119: * Returns all Element children of an Element that belong to the given
120: * namespace.
121: */
122: public static Element[] getChildElements(Element element,
123: String namespace) {
124: ArrayList elements = new ArrayList();
125: NodeList nodeList = element.getChildNodes();
126: for (int i = 0; i < nodeList.getLength(); i++) {
127: Node node = nodeList.item(i);
128: if (node instanceof Element
129: && namespace.equals(node.getNamespaceURI()))
130: elements.add(node);
131: }
132: return (Element[]) elements.toArray(new Element[0]);
133: }
134:
135: /**
136: * Returns all Element children of an Element that belong to the given
137: * namespace and have the given local name.
138: */
139: public static Element[] getChildElements(Element element,
140: String namespace, String localName) {
141: ArrayList elements = new ArrayList();
142: NodeList nodeList = element.getChildNodes();
143: for (int i = 0; i < nodeList.getLength(); i++) {
144: Node node = nodeList.item(i);
145: if (node instanceof Element
146: && namespace.equals(node.getNamespaceURI())
147: && localName.equals(node.getLocalName())) {
148: elements.add(node);
149: }
150: }
151: return (Element[]) elements.toArray(new Element[0]);
152: }
153:
154: /**
155: * Returns the first child element with the given namespace and localName,
156: * or null if there is no such element.
157: */
158: public static Element getChildElement(Element element,
159: String namespace, String localName) {
160: Element node = null;
161: try {
162: node = getChildElement(element, namespace, localName, false);
163: } catch (Exception e) {
164: node = null;
165: }
166: return node;
167: }
168:
169: /**
170: * Returns the first child element with the given namespace and localName,
171: * or null if there is no such element and required flag is unset or
172: * throws an Exception if the "required" flag is set.
173: */
174: public static Element getChildElement(Element element,
175: String namespace, String localName, boolean required)
176: throws Exception {
177: NodeList nodeList = element.getChildNodes();
178: for (int i = 0; i < nodeList.getLength(); i++) {
179: Node node = nodeList.item(i);
180: if (node instanceof Element
181: && namespace.equals(node.getNamespaceURI())
182: && localName.equals(node.getLocalName())) {
183: return (Element) node;
184: }
185: }
186: if (required) {
187: throw new Exception("Missing element \"" + localName
188: + "\" as child of element \""
189: + element.getTagName() + "\" at "
190: + DomHelper.getLocation(element));
191: } else {
192: return null;
193: }
194: }
195:
196: /**
197: * Returns the value of an element's attribute, but throws an exception
198: * if the element has no such attribute.
199: */
200: public static String getAttribute(Element element,
201: String attributeName) throws Exception {
202: String attrValue = element.getAttribute(attributeName);
203: if (attrValue.length() == 0) {
204: throw new Exception("Missing attribute \"" + attributeName
205: + "\" on element \"" + element.getTagName()
206: + "\" at " + getLocation(element));
207: }
208: return attrValue;
209: }
210:
211: /**
212: * Returns the value of an element's attribute, or a default value if the
213: * element has no such attribute.
214: */
215: public static String getAttribute(Element element,
216: String attributeName, String defaultValue) throws Exception {
217: String attrValue = element.getAttribute(attributeName);
218: if (attrValue.length() == 0) {
219: return defaultValue;
220: }
221: return attrValue;
222: }
223:
224: public static int getAttributeAsInteger(Element element,
225: String attributeName) throws Exception {
226: String attrValue = getAttribute(element, attributeName);
227: try {
228: return Integer.parseInt(attrValue);
229: } catch (NumberFormatException e) {
230: throw new Exception("Cannot parse the value \"" + attrValue
231: + "\" as an integer in the attribute \""
232: + attributeName + "\" on the element \""
233: + element.getTagName() + "\" at "
234: + getLocation(element));
235: }
236: }
237:
238: public static int getAttributeAsInteger(Element element,
239: String attributeName, int defaultValue) throws Exception {
240: String attrValue = element.getAttribute(attributeName);
241: if (attrValue.length() == 0) {
242: return defaultValue;
243: } else {
244: try {
245: return Integer.parseInt(attrValue);
246: } catch (NumberFormatException e) {
247: throw new Exception("Cannot parse the value \""
248: + attrValue
249: + "\" as an integer in the attribute \""
250: + attributeName + "\" on the element \""
251: + element.getTagName() + "\" at "
252: + getLocation(element));
253: }
254: }
255: }
256:
257: public static boolean getAttributeAsBoolean(Element element,
258: String attributeName, boolean defaultValue) {
259: String attrValue = element.getAttribute(attributeName);
260: Boolean result;
261: try {
262: result = BooleanUtils.toBooleanObject(attrValue, "true",
263: "false", null);
264: } catch (IllegalArgumentException iae) {
265: result = null;
266: }
267: if (result != null) {
268: return result.booleanValue();
269: }
270: try {
271: result = BooleanUtils.toBooleanObject(attrValue, "yes",
272: "no", null);
273: } catch (IllegalArgumentException iae) {
274: result = null;
275: }
276: if (result != null) {
277: return result.booleanValue();
278: }
279: return defaultValue;
280: }
281:
282: public static String getElementText(Element element) {
283: StringBuffer value = new StringBuffer();
284: NodeList nodeList = element.getChildNodes();
285: for (int i = 0; i < nodeList.getLength(); i++) {
286: Node node = nodeList.item(i);
287: if (node instanceof Text || node instanceof CDATASection) {
288: value.append(node.getNodeValue());
289: }
290: }
291: return value.toString();
292: }
293:
294: /**
295: * Returns the content of the given Element as an object implementing the
296: * XMLizable interface. Practically speaking, the implementation uses the
297: * {@link SaxBuffer} class. The XMLizable object will be a standalone blurb
298: * of SAX events, not producing start/endDocument calls and containing all
299: * necessary namespace declarations.
300: */
301: public static XMLizable compileElementContent(Element element) {
302: SaxBuffer saxBuffer = new SaxBuffer();
303: DOMStreamer domStreamer = new DOMStreamer();
304: domStreamer.setContentHandler(saxBuffer);
305:
306: NodeList childNodes = element.getChildNodes();
307: for (int i = 0; i < childNodes.getLength(); i++) {
308: try {
309: domStreamer.stream(childNodes.item(i));
310: } catch (SAXException e) {
311: // It's unlikely that an exception will occur here,
312: // so use a runtime exception
313: throw new RuntimeException(
314: "Error in DomHelper.compileElementContent: "
315: + e.toString());
316: }
317: }
318: return saxBuffer;
319: }
320:
321: /**
322: * Creates a W3C Document that remembers the location of each element in
323: * the source file. The location of element nodes can then be retrieved
324: * using the {@link #getLocation(Element)} method.
325: */
326: public static Document parse(InputSource inputSource)
327: throws SAXException, SAXNotSupportedException, IOException {
328: DOMParser domParser = new LocationTrackingDOMParser();
329: domParser
330: .setFeature(
331: "http://apache.org/xml/features/dom/defer-node-expansion",
332: false);
333: domParser
334: .setFeature(
335: "http://apache.org/xml/features/dom/create-entity-ref-nodes",
336: false);
337: domParser.parse(inputSource);
338: return domParser.getDocument();
339: }
340:
341: /**
342: * An extension of the Xerces DOM parser that puts the location of each
343: * node in that node's UserData.
344: */
345: public static class LocationTrackingDOMParser extends DOMParser {
346: XMLLocator locator;
347:
348: public void startDocument(XMLLocator xmlLocator, String s,
349: NamespaceContext namespaceContext,
350: Augmentations augmentations) throws XNIException {
351: super .startDocument(xmlLocator, s, namespaceContext,
352: augmentations);
353: this .locator = xmlLocator;
354: setLocation();
355: }
356:
357: public void startElement(QName qName,
358: XMLAttributes xmlAttributes, Augmentations augmentations)
359: throws XNIException {
360: super .startElement(qName, xmlAttributes, augmentations);
361: setLocation();
362: }
363:
364: private final void setLocation() {
365: // Older versions of Xerces had a different signature for the
366: // startDocument method. If such a version is used, the
367: // startDocument method above will not be called and locator will
368: // hence be null.
369: // Tell the users this so that they don't get a stupid NPE.
370: if (this .locator == null) {
371: throw new RuntimeException(
372: "Error: locator is null. Check that you have the"
373: + " correct version of Xerces (such as the one that"
374: + " comes with Cocoon) in your endorsed library path.");
375: }
376: NodeImpl node = null;
377: try {
378: node = (NodeImpl) this
379: .getProperty("http://apache.org/xml/properties/dom/current-element-node");
380: } catch (org.xml.sax.SAXException ex) {
381: System.err.println("except" + ex);
382: }
383: if (node != null) {
384: String location = locator.getLiteralSystemId() + ":"
385: + locator.getLineNumber() + ":"
386: + locator.getColumnNumber();
387: node.setUserData("location", location,
388: (UserDataHandler) null);
389: }
390: }
391: }
392: }
|