001: // THIS SOFTWARE IS PROVIDED BY SOFTARIS PTY.LTD. AND OTHER METABOSS
002: // CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
003: // BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
004: // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTARIS PTY.LTD.
005: // OR OTHER METABOSS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
006: // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
007: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
008: // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
009: // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
010: // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
011: // EVEN IF SOFTARIS PTY.LTD. OR OTHER METABOSS CONTRIBUTORS ARE ADVISED OF THE
012: // POSSIBILITY OF SUCH DAMAGE.
013: //
014: // Copyright 2000-2005 © Softaris Pty.Ltd. All Rights Reserved.
015: package com.metaboss.util;
016:
017: import java.util.ArrayList;
018: import java.util.HashMap;
019: import java.util.Iterator;
020: import java.util.List;
021: import java.util.Map;
022: import java.util.TreeMap;
023:
024: import org.w3c.dom.Attr;
025: import org.w3c.dom.CDATASection;
026: import org.w3c.dom.Document;
027: import org.w3c.dom.Element;
028: import org.w3c.dom.Node;
029: import org.w3c.dom.NodeList;
030: import org.w3c.dom.Text;
031:
032: /** Utilities used to ease some org.w3c.dom related programming tasks */
033: public class DOMUtils {
034: // Private implementation of the NodeList
035: private static class NodeListImpl implements NodeList {
036: private Node[] mNodes;
037:
038: public NodeListImpl(List pSourceList) {
039: mNodes = (Node[]) pSourceList.toArray(new Node[pSourceList
040: .size()]);
041: }
042:
043: public int getLength() {
044: return mNodes == null ? 0 : mNodes.length;
045: }
046:
047: public Node item(int index) {
048: if (mNodes == null || index < 0 || index >= mNodes.length)
049: return null;
050: return mNodes[index];
051: }
052: }
053:
054: /** Removes all child elements found under the given node. */
055: public static void removeAllChildNodes(Node pParentNode) {
056: NodeList lAllChildren = pParentNode.getChildNodes();
057: // Copy nodes aside in the array, so we are not intefeering with the list during deletions
058: Node[] lAllChildrenArray = new Node[lAllChildren.getLength()];
059: for (int i = 0; i < lAllChildrenArray.length; i++)
060: lAllChildrenArray[i] = lAllChildren.item(i);
061: // Now do the delete
062: for (int i = 0; i < lAllChildrenArray.length; i++)
063: pParentNode.removeChild(lAllChildrenArray[i]);
064: }
065:
066: /** Returns only direct child elements with the specified TagName
067: * @return NodeList with found children. */
068: public static NodeList getChildElementsByTagName(
069: Element pParentElement, String pTagName) {
070: ArrayList lResultNodes = new ArrayList();
071: for (Element lCandidateElement = getFirstChildElement(pParentElement); lCandidateElement != null; lCandidateElement = getNextSiblingElement(lCandidateElement)) {
072: // Got to be of the right tag name from the default namespace
073: String lNamespaceURI = lCandidateElement.getNamespaceURI();
074: if ((lNamespaceURI == null || lNamespaceURI.length() == 0)
075: && lCandidateElement.getTagName().equals(pTagName))
076: lResultNodes.add(lCandidateElement);
077: }
078: return new NodeListImpl(lResultNodes);
079: }
080:
081: /** Retrieves next sibling element of the given node, skips all
082: * nodes with type other than org.w3c.dom.Node.ELEMENT_NODE in the process.
083: * @return next sibling element to the given node or null if no sibling found. */
084: public static Element getNextSiblingElement(Node pStartNode) {
085: // Return null if what we are given is null
086: if (pStartNode == null)
087: return null;
088: // Skip all not elelement siblings
089: Node lNextSiblingNode = pStartNode.getNextSibling();
090: while (lNextSiblingNode != null
091: && lNextSiblingNode.getNodeType() != Node.ELEMENT_NODE)
092: lNextSiblingNode = lNextSiblingNode.getNextSibling();
093: return (Element) lNextSiblingNode;
094: }
095:
096: /** Retrieves previous sibling element of the given node, skips all
097: * nodes with type other than org.w3c.dom.Node.ELEMENT_NODE in the process.
098: * @return previous sibling element to the given node or null if no sibling found. */
099: public static Element getPrevSiblingElement(Node pStartNode) {
100: // Return null if what we are given is null
101: if (pStartNode == null)
102: return null;
103: // Skip all not elelement siblings
104: Node lPreviousSiblingNode = pStartNode.getPreviousSibling();
105: while (lPreviousSiblingNode != null
106: && lPreviousSiblingNode.getNodeType() != Node.ELEMENT_NODE)
107: lPreviousSiblingNode = lPreviousSiblingNode
108: .getPreviousSibling();
109: return (Element) lPreviousSiblingNode;
110: }
111:
112: /** Retrieves the first child element of the given node, skips all
113: * nodes with type other than org.w3c.dom.Node.ELEMENT_NODE in the process.
114: * @return the first child node, which is an Element or null if no child Element found. */
115: public static Element getFirstChildElement(Node pParentNode) {
116: // Return null if what we are given is null
117: if (pParentNode == null)
118: return null;
119: Node lFirstChildNode = pParentNode.getFirstChild();
120: while (lFirstChildNode != null
121: && lFirstChildNode.getNodeType() != Node.ELEMENT_NODE)
122: lFirstChildNode = lFirstChildNode.getNextSibling();
123: return (Element) lFirstChildNode;
124: }
125:
126: /** Retrieves the last child element of the given node, skips all
127: * nodes with type other than org.w3c.dom.Node.ELEMENT_NODE in the process.
128: * @return the last child node, which is an Element or null if no child Element found. */
129: public static Element getLastChildElement(Node pParentNode) {
130: // Return null if what we are given is null
131: if (pParentNode == null)
132: return null;
133: Node lLastChildNode = pParentNode.getLastChild();
134: while (lLastChildNode != null
135: && lLastChildNode.getNodeType() != Node.ELEMENT_NODE)
136: lLastChildNode = lLastChildNode.getPreviousSibling();
137: return (Element) lLastChildNode;
138: }
139:
140: /** This utility returns a human readable name of the org.w3c.dom.Node type
141: * @throws IllegalArgumentException in case if invalid value (i.e. does not correspond to any known org.w3c.dom.Node type) is supplied */
142: public static String getNoteTypeName(short pNodeType) {
143: switch (pNodeType) {
144: case Node.ATTRIBUTE_NODE:
145: return "ATTRIBUTE_NODE";
146: case Node.CDATA_SECTION_NODE:
147: return "CDATA_SECTION_NODE";
148: case Node.COMMENT_NODE:
149: return "COMMENT_NODE";
150: case Node.DOCUMENT_FRAGMENT_NODE:
151: return "DOCUMENT_FRAGMENT_NODE";
152: case Node.DOCUMENT_NODE:
153: return "DOCUMENT_NODE";
154: case Node.DOCUMENT_TYPE_NODE:
155: return "DOCUMENT_TYPE_NODE";
156: case Node.ELEMENT_NODE:
157: return "ELEMENT_NODE";
158: case Node.ENTITY_NODE:
159: return "ENTITY_NODE";
160: case Node.ENTITY_REFERENCE_NODE:
161: return "ENTITY_REFERENCE_NODE";
162: case Node.NOTATION_NODE:
163: return "NOTATION_NODE";
164: case Node.PROCESSING_INSTRUCTION_NODE:
165: return "PROCESSING_INSTRUCTION_NODE";
166: case Node.TEXT_NODE:
167: return "TEXT_NODE";
168: }
169: throw new IllegalArgumentException(
170: "Value '"
171: + pNodeType
172: + "' is not a valid value for the org.w3c.dom.Node type.");
173: }
174:
175: /** Converts contents of the supplied DOM Element tree (identified by the top Element)
176: * to the tree made of Map elements. At each level the name of the Element is the key and the
177: * Map representing the children of the element is the value. For the Text Elements - the value is
178: * the String with text. For the sibling Elements with the same name - the array index expression '[n]' is added to the
179: * back of the name. This routine allows to convert between Properties representation and XML representation of the parameter settings.
180: * @param pElement source element to use as the root of the tree.
181: * @return the property tree made of Maps */
182: public static Map convertElementToTree(Element pElement) {
183: Map lResultMap = new TreeMap();
184: Map lElementIndexesMap = new HashMap(); // Keeps the index of the elements
185: for (Element lChildElement = getFirstChildElement(pElement); lChildElement != null; lChildElement = getNextSiblingElement(lChildElement)) {
186: // Work on the name
187: String lElementName = lChildElement.getTagName();
188: // First work on the key
189: String lMapKey = lElementName;
190: if (lElementIndexesMap.containsKey(lElementName)) {
191: // This element is known to be an array
192: int lElementIndex = ((Integer) lElementIndexesMap
193: .get(lElementName)).intValue() + 1;
194: lElementIndexesMap.put(lElementName, new Integer(
195: lElementIndex));
196: lMapKey += "[" + lElementIndex + "]";
197: } else {
198: // This element may or may not be an array (we do not know yet)
199: // Go and find out.
200: if (getChildElementsByTagName(pElement, lElementName)
201: .getLength() > 1) {
202: // This Element is an array. Append frirst index
203: int lElementIndex = 1;
204: lElementIndexesMap.put(lElementName, new Integer(
205: lElementIndex));
206: lMapKey += "[" + lElementIndex + "]";
207: }
208: }
209: // Now work on the value
210: NodeList lGrandchildNodes = lChildElement.getChildNodes();
211: if (lGrandchildNodes.getLength() == 1
212: && lGrandchildNodes.item(0).getNodeType() == Node.TEXT_NODE) {
213: // Text element - insert string at this key
214: lResultMap.put(lMapKey, lGrandchildNodes.item(0)
215: .getNodeValue());
216: } else {
217: // Call this method recursively and set result map as the value
218: lResultMap.put(lMapKey,
219: convertElementToTree(lChildElement));
220: }
221: }
222: return lResultMap;
223: }
224:
225: /**
226: * @return absolute path to the node in xpath - like notation. Can only deal with attributes, elements text and CDATA nodes
227: * @throws IllegalArgumentException if node type is not an Attribute, Element, Text or CDATA */
228: public static String getAbsoluteNodePath(Node pNode) {
229: short lNodeType = pNode.getNodeType();
230: if (lNodeType != Node.ATTRIBUTE_NODE
231: && lNodeType != Node.ELEMENT_NODE
232: && lNodeType != Node.TEXT_NODE
233: && lNodeType != Node.CDATA_SECTION_NODE)
234: throw new IllegalArgumentException(
235: "Unsupported type of Node for the operation. Only Element, Attribute, Text and CDATA are supported. Got '"
236: + lNodeType + "'");
237: // Build a path consisting of string elements
238: List lPathElementsList = new ArrayList();
239: for (Node lNextNode = pNode; lNextNode != null;) {
240: short lNextNodeType = lNextNode.getNodeType();
241: if (lNextNodeType == Node.ATTRIBUTE_NODE) {
242: lPathElementsList.add(0, lNextNode);
243: // Get the parent node
244: lNextNode = ((Attr) lNextNode).getOwnerElement();
245: } else if (lNextNodeType == Node.TEXT_NODE) {
246: lPathElementsList.add(0, lNextNode);
247: // Get the parent node
248: lNextNode = ((Text) lNextNode).getParentNode();
249: } else if (lNextNodeType == Node.CDATA_SECTION_NODE) {
250: lPathElementsList.add(0, lNextNode);
251: // Get the parent node
252: lNextNode = ((CDATASection) lNextNode).getParentNode();
253: } else if (lNextNodeType == Node.ELEMENT_NODE) {
254: lPathElementsList.add(0, lNextNode);
255: // Get the parent node
256: lNextNode = ((Element) lNextNode).getParentNode();
257: } else if (lNextNodeType == Node.DOCUMENT_NODE) {
258: break; // Got to the top
259: } else
260: throw new IllegalArgumentException(
261: "Unexpected type of Node in the Node hierarchy. Only Document, Element, Attribute, Text and CDATA are supported. Got '"
262: + lNextNodeType + "'");
263: }
264: // Now build the xpath like expression
265: StringBuffer lAbsoluteNodePath = new StringBuffer();
266: String lCurrentNamespaceURI = null;
267: for (Iterator lNodePathIterator = lPathElementsList.iterator(); lNodePathIterator
268: .hasNext();) {
269: Node lNextNode = (Node) lNodePathIterator.next();
270: short lNextNodeType = lNextNode.getNodeType();
271: if (lNextNodeType == Node.ATTRIBUTE_NODE) {
272: lAbsoluteNodePath.append("[@");
273: String lAttributeNamespaceURI = ((Attr) lNextNode)
274: .getNamespaceURI();
275: if (lAttributeNamespaceURI != null
276: && (lCurrentNamespaceURI == null || lCurrentNamespaceURI
277: .equals(lAttributeNamespaceURI) == false)) {
278: lAbsoluteNodePath.append("{");
279: lAbsoluteNodePath.append(lAttributeNamespaceURI);
280: lAbsoluteNodePath.append("}:");
281: }
282: lAbsoluteNodePath.append(((Attr) lNextNode).getName());
283: lAbsoluteNodePath.append("]");
284: } else if (lNextNodeType == Node.TEXT_NODE) {
285: lAbsoluteNodePath.append("/{TEXT}");
286: } else if (lNextNodeType == Node.CDATA_SECTION_NODE) {
287: lAbsoluteNodePath.append("/{CDATA}");
288: } else if (lNextNodeType == Node.ELEMENT_NODE) {
289: lAbsoluteNodePath.append("/");
290: String lElementNamespaceURI = ((Element) lNextNode)
291: .getNamespaceURI();
292: String lElementTagName = ((Element) lNextNode)
293: .getTagName();
294: if (lElementNamespaceURI != null
295: && (lCurrentNamespaceURI == null || lCurrentNamespaceURI
296: .equals(lElementNamespaceURI) == false)) {
297: lAbsoluteNodePath.append("{");
298: lAbsoluteNodePath.append(lElementNamespaceURI);
299: lAbsoluteNodePath.append("}:");
300: lCurrentNamespaceURI = lElementNamespaceURI;
301: }
302: lAbsoluteNodePath.append(lElementTagName);
303: // Check if there are siblings with exact same name and in the same namespace
304: int lThisElementIndex = 1;
305: int lMatchingSiblingsCount = 0;
306: for (Node lPreviousSibling = lNextNode
307: .getPreviousSibling(); lPreviousSibling != null; lPreviousSibling = lPreviousSibling
308: .getPreviousSibling()) {
309: // Match type
310: if (lPreviousSibling.getNodeType() == Node.ELEMENT_NODE) {
311: // Match namespace
312: String lChildNamespaceURI = ((Element) lPreviousSibling)
313: .getNamespaceURI();
314: if ((lElementNamespaceURI == null && lChildNamespaceURI == null)
315: || (lElementNamespaceURI != null
316: && lChildNamespaceURI != null && lElementNamespaceURI
317: .equals(lChildNamespaceURI))) {
318: // Match tag name
319: if (lElementTagName
320: .equals(((Element) lPreviousSibling)
321: .getTagName())) {
322: lThisElementIndex++;
323: lMatchingSiblingsCount++;
324: }
325: }
326: }
327: }
328: // If we could not find any siblings before this element, we need to check
329: // next siblings. This is because if this element is not alone - index will have to be used
330: if (lMatchingSiblingsCount == 0) {
331: for (Node lNextSibling = lNextNode.getNextSibling(); lNextSibling != null; lNextSibling = lNextSibling
332: .getNextSibling()) {
333: // Match type
334: if (lNextSibling.getNodeType() == Node.ELEMENT_NODE) {
335: // Match namespace
336: String lChildNamespaceURI = ((Element) lNextSibling)
337: .getNamespaceURI();
338: if ((lElementNamespaceURI == null && lChildNamespaceURI == null)
339: || (lElementNamespaceURI != null
340: && lChildNamespaceURI != null && lElementNamespaceURI
341: .equals(lChildNamespaceURI))) {
342: // Match tag name
343: if (lElementTagName
344: .equals(((Element) lNextSibling)
345: .getTagName())) {
346: lMatchingSiblingsCount++;
347: break; // Enough - we have got an indication that there are other siblings - so we have to use an index
348: }
349: }
350: }
351: }
352: }
353: // Append eleenmnt index if there are siblings
354: if (lMatchingSiblingsCount > 0) {
355: lAbsoluteNodePath.append("[");
356: lAbsoluteNodePath.append(lThisElementIndex);
357: lAbsoluteNodePath.append("]");
358: }
359: }
360: }
361: return lAbsoluteNodePath.toString();
362: }
363:
364: /** Simple utility used to get XML string from XML Document without transformer.
365: * It is needed some times because we have discovered problems while trying to run Transformer
366: * inside the applet running inside Internet Explorer */
367: public static String convertDocumentToString(Document pDocument) {
368: StringBuffer lStringBuffer = new StringBuffer(
369: "<?xml version=\"1.0\"?>");
370: convertElementToString(lStringBuffer, pDocument
371: .getDocumentElement());
372: return lStringBuffer.toString();
373: }
374:
375: /** Simple utility used to get XML string from XML Element without transformer.
376: * It is needed some times because we have discovered problems while trying to run Transformer
377: * inside the applet running inside Internet Explorer. This method
378: * produces XML string with just given Element and without xml header */
379: public static String convertElementToString(Element pElement) {
380: StringBuffer lStringBuffer = new StringBuffer();
381: convertElementToString(lStringBuffer, pElement);
382: return lStringBuffer.toString();
383: }
384:
385: // Recursive helper used from within convertDocumentToString method
386: private static void convertElementToString(
387: StringBuffer pTargetBuffer, Element pSourceElement) {
388: pTargetBuffer.append("<");
389: pTargetBuffer.append(pSourceElement.getTagName());
390: org.w3c.dom.NamedNodeMap lAttributes = pSourceElement
391: .getAttributes();
392: for (int i = 0; i < lAttributes.getLength(); i++) {
393: org.w3c.dom.Node lAttribute = lAttributes.item(i);
394: pTargetBuffer.append(" ");
395: pTargetBuffer.append(lAttribute.getNodeName());
396: pTargetBuffer.append("=\"");
397: pTargetBuffer.append(lAttribute.getNodeValue());
398: pTargetBuffer.append("\"");
399: }
400: pTargetBuffer.append(">");
401: NodeList lChildNodes = pSourceElement.getChildNodes();
402: for (int i = 0; i < lChildNodes.getLength(); i++) {
403: org.w3c.dom.Node lChildNode = (org.w3c.dom.Node) lChildNodes
404: .item(i);
405: if (org.w3c.dom.Node.ELEMENT_NODE == lChildNode
406: .getNodeType())
407: convertElementToString(pTargetBuffer,
408: (Element) lChildNode);
409: else if (org.w3c.dom.Node.TEXT_NODE == lChildNode
410: .getNodeType())
411: pTargetBuffer.append(lChildNode.getNodeValue());
412:
413: }
414: pTargetBuffer.append("</");
415: pTargetBuffer.append(pSourceElement.getTagName());
416: pTargetBuffer.append(">");
417: }
418:
419: }
|