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.forms.util;
018:
019: import java.io.IOException;
020: import java.util.ArrayList;
021: import java.util.HashMap;
022: import java.util.LinkedList;
023: import java.util.List;
024: import java.util.ListIterator;
025: import java.util.Map;
026: import javax.xml.XMLConstants;
027:
028: import org.apache.avalon.framework.service.ServiceException;
029: import org.apache.avalon.framework.service.ServiceManager;
030: import org.apache.excalibur.xml.sax.SAXParser;
031: import org.apache.excalibur.xml.sax.XMLizable;
032:
033: import org.apache.cocoon.forms.FormsException;
034: import org.apache.cocoon.util.location.Location;
035: import org.apache.cocoon.util.location.LocationAttributes;
036: import org.apache.cocoon.xml.SaxBuffer;
037: import org.apache.cocoon.xml.dom.DOMBuilder;
038: import org.apache.cocoon.xml.dom.DOMStreamer;
039:
040: import org.apache.commons.lang.BooleanUtils;
041: import org.w3c.dom.Attr;
042: import org.w3c.dom.CDATASection;
043: import org.w3c.dom.Document;
044: import org.w3c.dom.Element;
045: import org.w3c.dom.NamedNodeMap;
046: import org.w3c.dom.Node;
047: import org.w3c.dom.NodeList;
048: import org.w3c.dom.Text;
049: import org.xml.sax.ContentHandler;
050: import org.xml.sax.InputSource;
051: import org.xml.sax.SAXException;
052: import org.xml.sax.SAXNotSupportedException;
053:
054: /**
055: * Helper class to create and retrieve information from DOM-trees. It provides
056: * some functionality comparable to what's found in Avalon's Configuration
057: * objects. These lasts one could however not be used by Cocoon Forms because they
058: * don't provide an accurate model of an XML file (no mixed content,
059: * no namespaced attributes, no namespace declarations, ...).
060: *
061: * <p>This class depends specifically on the Xerces DOM implementation to be
062: * able to provide information about the location of elements in their source
063: * XML file. See the {@link #getLocation(Element)} method.
064: *
065: * @version $Id: DomHelper.java 449149 2006-09-23 03:58:05Z crossley $
066: */
067: public class DomHelper {
068:
069: public static final String XMLNS_URI = XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
070:
071: public static Location getLocationObject(Element element) {
072: return LocationAttributes.getLocation(element);
073: }
074:
075: /**
076: * Retrieves the location of an element node in the source file from which
077: * the Document was created. This will only work for Document's created
078: * with the method {@link #parse(InputSource, ServiceManager)} of this class.
079: */
080: public static String getLocation(Element element) {
081: return LocationAttributes.getLocationString(element);
082: }
083:
084: public static String getSystemIdLocation(Element element) {
085: return LocationAttributes.getURI(element);
086: }
087:
088: public static int getLineLocation(Element element) {
089: return LocationAttributes.getLine(element);
090: }
091:
092: public static int getColumnLocation(Element element) {
093: return LocationAttributes.getColumn(element);
094: }
095:
096: /**
097: * Returns all Element children of an Element that belong to the given
098: * namespace.
099: */
100: public static Element[] getChildElements(Element element,
101: String namespace) {
102: ArrayList elements = new ArrayList();
103: NodeList nodeList = element.getChildNodes();
104: for (int i = 0; i < nodeList.getLength(); i++) {
105: Node node = nodeList.item(i);
106: if (node instanceof Element
107: && namespace.equals(node.getNamespaceURI()))
108: elements.add(node);
109: }
110: return (Element[]) elements
111: .toArray(new Element[elements.size()]);
112: }
113:
114: /**
115: * Returns all Element children of an Element that belong to the given
116: * namespace and have the given local name.
117: */
118: public static Element[] getChildElements(Element element,
119: String namespace, String localName) {
120: ArrayList elements = new ArrayList();
121: NodeList nodeList = element.getChildNodes();
122: for (int i = 0; i < nodeList.getLength(); i++) {
123: Node node = nodeList.item(i);
124: if (node instanceof Element
125: && namespace.equals(node.getNamespaceURI())
126: && localName.equals(node.getLocalName())) {
127: elements.add(node);
128: }
129: }
130: return (Element[]) elements
131: .toArray(new Element[elements.size()]);
132: }
133:
134: /**
135: * Returns the first child element with the given namespace and localName,
136: * or null if there is no such element.
137: */
138: public static Element getChildElement(Element element,
139: String namespace, String localName) {
140: Element node;
141: try {
142: node = getChildElement(element, namespace, localName, false);
143: } catch (Exception e) {
144: node = null;
145: }
146: return node;
147: }
148:
149: /**
150: * Returns the first child element with the given namespace and localName,
151: * or null if there is no such element and required flag is unset or
152: * throws an Exception if the "required" flag is set.
153: */
154: public static Element getChildElement(Element element,
155: String namespace, String localName, boolean required)
156: throws FormsException {
157: NodeList nodeList = element.getChildNodes();
158: for (int i = 0; i < nodeList.getLength(); i++) {
159: Node node = nodeList.item(i);
160: if (node instanceof Element
161: && namespace.equals(node.getNamespaceURI())
162: && localName.equals(node.getLocalName())) {
163: return (Element) node;
164: }
165: }
166:
167: if (required) {
168: throw new FormsException("Required element '" + localName
169: + "' is missing.", DomHelper
170: .getLocationObject(element));
171: }
172:
173: return null;
174: }
175:
176: /**
177: * Returns the value of an element's attribute, but throws an exception
178: * if the element has no such attribute.
179: */
180: public static String getAttribute(Element element,
181: String attributeName) throws FormsException {
182: String attrValue = element.getAttribute(attributeName);
183: if (attrValue.length() == 0) {
184: throw new FormsException("Required attribute '"
185: + attributeName + "' is missing.", DomHelper
186: .getLocationObject(element));
187: }
188:
189: return attrValue;
190: }
191:
192: /**
193: * Returns the value of an element's attribute, or a default value if the
194: * element has no such attribute.
195: */
196: public static String getAttribute(Element element,
197: String attributeName, String defaultValue) {
198: String attrValue = element.getAttribute(attributeName);
199: if (attrValue.length() == 0) {
200: return defaultValue;
201: }
202: return attrValue;
203: }
204:
205: public static int getAttributeAsInteger(Element element,
206: String attributeName) throws FormsException {
207: String attrValue = getAttribute(element, attributeName);
208: try {
209: return Integer.parseInt(attrValue);
210: } catch (NumberFormatException e) {
211: throw new FormsException("Cannot parse the value '"
212: + attrValue + "' "
213: + "as an integer in the attribute '"
214: + attributeName + "',"
215: + DomHelper.getLocationObject(element));
216: }
217: }
218:
219: public static int getAttributeAsInteger(Element element,
220: String attributeName, int defaultValue)
221: throws FormsException {
222: String attrValue = element.getAttribute(attributeName);
223: if (attrValue.length() == 0) {
224: return defaultValue;
225: }
226:
227: try {
228: return Integer.parseInt(attrValue);
229: } catch (NumberFormatException e) {
230: throw new FormsException("Cannot parse the value '"
231: + attrValue + "' "
232: + "as an integer in the attribute '"
233: + attributeName + "',"
234: + DomHelper.getLocationObject(element));
235: }
236: }
237:
238: public static boolean getAttributeAsBoolean(Element element,
239: String attributeName, boolean defaultValue) {
240: String attrValue = element.getAttribute(attributeName);
241: if (attrValue.length() == 0) {
242: return defaultValue;
243: }
244:
245: Boolean result;
246: try {
247: result = BooleanUtils.toBooleanObject(attrValue, "true",
248: "false", null);
249: } catch (IllegalArgumentException e1) {
250: try {
251: result = BooleanUtils.toBooleanObject(attrValue, "yes",
252: "no", null);
253: } catch (IllegalArgumentException e2) {
254: result = null;
255: }
256: }
257: if (result == null) {
258: return defaultValue;
259: }
260:
261: return result.booleanValue();
262: }
263:
264: public static String getElementText(Element element) {
265: StringBuffer value = new StringBuffer();
266: NodeList nodeList = element.getChildNodes();
267: for (int i = 0; i < nodeList.getLength(); i++) {
268: Node node = nodeList.item(i);
269: if (node instanceof Text || node instanceof CDATASection) {
270: value.append(node.getNodeValue());
271: }
272: }
273: return value.toString();
274: }
275:
276: /**
277: * Returns the content of the given Element as an object implementing the
278: * XMLizable interface. Practically speaking, the implementation uses the
279: * {@link SaxBuffer} class. The XMLizable object will be a standalone blurb
280: * of SAX events, not producing start/endDocument calls and containing all
281: * necessary namespace declarations.
282: */
283: public static XMLizable compileElementContent(Element element) {
284: // Remove location information
285: LocationAttributes.remove(element, true);
286:
287: SaxBuffer saxBuffer = new SaxBuffer();
288: DOMStreamer domStreamer = new DOMStreamer();
289: domStreamer.setContentHandler(saxBuffer);
290:
291: NodeList childNodes = element.getChildNodes();
292: for (int i = 0; i < childNodes.getLength(); i++) {
293: try {
294: domStreamer.stream(childNodes.item(i));
295: } catch (SAXException e) {
296: // It's unlikely that an exception will occur here,
297: // so use a runtime exception
298: throw new RuntimeException(
299: "Error in DomHelper.compileElementContent: "
300: + e.toString());
301: }
302: }
303: return saxBuffer;
304: }
305:
306: /**
307: * Creates a W3C Document that remembers the location of each element in
308: * the source file. The location of element nodes can then be retrieved
309: * using the {@link #getLocation(Element)} method.
310: *
311: * @param inputSource the inputSource to read the document from
312: * @param manager the service manager where to lookup the entity resolver
313: */
314: public static Document parse(InputSource inputSource,
315: ServiceManager manager) throws SAXException,
316: SAXNotSupportedException, IOException, ServiceException {
317:
318: SAXParser parser = (SAXParser) manager.lookup(SAXParser.ROLE);
319: DOMBuilder builder = new DOMBuilder();
320:
321: // Enhance the sax stream with location information
322: ContentHandler locationHandler = new LocationAttributes.Pipe(
323: builder);
324:
325: try {
326: parser.parse(inputSource, locationHandler);
327: } finally {
328: manager.release(parser);
329: }
330:
331: return builder.getDocument();
332: }
333:
334: public static Map getLocalNSDeclarations(Element elm) {
335: return addLocalNSDeclarations(elm, null);
336: }
337:
338: private static Map addLocalNSDeclarations(Element elm,
339: Map nsDeclarations) {
340: NamedNodeMap atts = elm.getAttributes();
341: int attsSize = atts.getLength();
342:
343: for (int i = 0; i < attsSize; i++) {
344: Attr attr = (Attr) atts.item(i);
345: if (XMLNS_URI.equals(attr.getNamespaceURI())) {
346: String nsUri = attr.getValue();
347: String pfx = attr.getLocalName();
348: if (nsDeclarations == null)
349: nsDeclarations = new HashMap();
350: nsDeclarations.put(nsUri, pfx);
351: }
352: }
353: return nsDeclarations;
354: }
355:
356: public static Map getInheritedNSDeclarations(Element elm) {
357: List ancestorsAndSelf = new LinkedList();
358: Element current = elm;
359: while (current != null) {
360: ancestorsAndSelf.add(current);
361: Node parent = current.getParentNode();
362: if (parent.getNodeType() == Node.ELEMENT_NODE)
363: current = (Element) parent;
364: else
365: current = null;
366: }
367:
368: Map nsDeclarations = null;
369: ListIterator i = ancestorsAndSelf.listIterator(ancestorsAndSelf
370: .size());
371: while (i.hasPrevious()) {
372: Element element = (Element) i.previous();
373: nsDeclarations = addLocalNSDeclarations(element,
374: nsDeclarations);
375: }
376:
377: return nsDeclarations;
378: }
379: }
|