001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.xml;
030:
031: import com.caucho.util.CharBuffer;
032:
033: import org.w3c.dom.Comment;
034: import org.w3c.dom.Document;
035: import org.w3c.dom.DocumentFragment;
036: import org.w3c.dom.Element;
037: import org.w3c.dom.Node;
038: import org.w3c.dom.ProcessingInstruction;
039: import org.xml.sax.ContentHandler;
040: import org.xml.sax.SAXException;
041:
042: import java.io.IOException;
043: import java.util.HashMap;
044:
045: /**
046: * XML utilities for manipulating names and the DOM.
047: */
048: public class XmlUtil {
049: /**
050: * Treats the string as an attribute list, splitting it into a HashMap.
051: *
052: * @param name a string to be interpreted as an attribute list
053: * @return a hash map containing the attribute (key, value) pairs.
054: */
055: public static HashMap<String, String> splitNameList(String name)
056: throws IOException {
057: HashMap<String, String> attrs = new HashMap<String, String>();
058: CharBuffer cb = new CharBuffer();
059:
060: int length = name.length();
061: int i = 0;
062: int ch = 0;
063: while (i < length) {
064: for (; i < length
065: && XmlChar.isWhitespace(ch = name.charAt(i)); i++) {
066: }
067:
068: if (i < length && !XmlChar.isNameStart(ch))
069: throw new IOException("expected name at " + (char) ch);
070:
071: cb.clear();
072: while (i < length && XmlChar.isNameChar(ch)) {
073: cb.append((char) ch);
074:
075: ch = name.charAt(++i);
076: }
077: String key = cb.toString();
078: cb.clear();
079:
080: for (; i < length
081: && XmlChar.isWhitespace(ch = name.charAt(i)); i++) {
082: }
083:
084: if (ch != '=') {
085: attrs.put(key, "");
086: continue;
087: }
088:
089: while (++i < length
090: && XmlChar.isWhitespace(ch = name.charAt(i))) {
091: }
092:
093: if (i >= length)
094: break;
095:
096: cb.clear();
097: if (ch == '\'') {
098: while (++i < length && (ch = name.charAt(i)) != '\'')
099: cb.append((char) ch);
100: i++;
101: } else if (ch == '"') {
102: while (++i < length && (ch = name.charAt(i)) != '\"')
103: cb.append((char) ch);
104: i++;
105: } else if (XmlChar.isNameChar(ch)) {
106: cb.append((char) ch);
107: while (++i < length
108: && XmlChar.isNameChar(ch = name.charAt(i)))
109: cb.append((char) ch);
110: } else
111: throw new IOException("unexpected");
112:
113: attrs.put(key, cb.toString());
114: }
115:
116: return attrs;
117: }
118:
119: /**
120: * Extracts an attribute from a processing instruction. Since
121: * processing instructions are opaque, the standard DOM has no API
122: * for the common case where the PI value is an attribute list.
123: *
124: * <code><pre>
125: * <?xml-stylesheet href="default.xsl"?>
126: * </pre></code>
127: *
128: * <p>In the above example,
129: * <code><pre>getPIAttribute(node.getNodeValue(), "href")</pre></code>
130: * would return "default.xsl".
131: *
132: * @param pi the value of the processing instruction
133: * @param key the attribute key
134: * @return the value corresponding to the attribute key.
135: */
136: public static String getPIAttribute(String pi, String key) {
137: CharBuffer nameBuf = new CharBuffer();
138: CharBuffer valueBuf = new CharBuffer();
139:
140: int i = 0;
141: int length = pi.length();
142: ;
143: while (i < length) {
144: int ch = 0;
145: for (; i < length
146: && XmlChar.isWhitespace(ch = pi.charAt(i)); i++) {
147: }
148:
149: nameBuf.clear();
150: for (; i < length && XmlChar.isNameChar(ch = pi.charAt(i)); i++)
151: nameBuf.append((char) ch);
152:
153: for (; i < length
154: && XmlChar.isWhitespace(ch = pi.charAt(i)); i++) {
155: }
156:
157: if (i < length && ch != '=') {
158: if (nameBuf.length() == 0)
159: return null;
160: else if (nameBuf.toString().equals(key))
161: return nameBuf.toString();
162: else
163: continue;
164: }
165:
166: i++;
167: for (; i < length
168: && XmlChar.isWhitespace(ch = pi.charAt(i)); i++) {
169: }
170:
171: // Parse the attribute value: '.*' or ".*" or \w+
172: valueBuf.clear();
173: if (ch == '\'') {
174: i++;
175: for (; i < length && (ch = pi.charAt(i)) != '\''; i++)
176: valueBuf.append((char) ch);
177: i++;
178: } else if (ch == '\"') {
179: i++;
180: for (; i < length && (ch = pi.charAt(i)) != '\"'; i++)
181: valueBuf.append((char) ch);
182: i++;
183: } else if (XmlChar.isNameChar(ch)) {
184: for (; i < length
185: && XmlChar.isNameChar(ch = pi.charAt(i)); i++)
186: valueBuf.append((char) ch);
187: } else
188: return null; // XXX: should throw an exception?
189:
190: String name = nameBuf.toString();
191: if (name.equals(key))
192: return valueBuf.toString();
193: }
194:
195: return null;
196: }
197:
198: /**
199: * Get the next node in a depth first preorder traversal.
200: *
201: * <ul>
202: * <li>If the node has a child, return the child
203: * <li>Else if the node has a following sibling, return that sibling
204: * <li>Else if the node has a following uncle, return that uncle
205: * </ul>
206: *
207: * @param node the current node
208: * @return the next node in the preorder traversal
209: */
210: public static Node getNext(Node node) {
211: if (node == null)
212: return null;
213:
214: if (node.getFirstChild() != null)
215: return node.getFirstChild();
216:
217: for (; node != null; node = node.getParentNode()) {
218: if (node.getNextSibling() != null)
219: return node.getNextSibling();
220: }
221:
222: return null;
223: }
224:
225: /**
226: * Get the previous node in a DFS preorder traversal
227: *
228: * @param node the current node
229: * @return the previous node in the preorder traversal
230: */
231: public static Node getPrevious(Node node) {
232: Node previous;
233:
234: if (node == null)
235: return null;
236:
237: if ((previous = node.getPreviousSibling()) != null) {
238: for (; previous.getLastChild() != null; previous = previous
239: .getLastChild()) {
240: }
241:
242: return previous;
243: }
244:
245: return node.getParentNode();
246: }
247:
248: /**
249: * Extracts the text value from the node. Text nodes return their
250: * value and elements return the concatenation of the child values.
251: */
252: public static String textValue(Node node) {
253: if (node instanceof Element || node instanceof DocumentFragment) {
254: String s = null;
255: CharBuffer cb = null;
256:
257: for (Node child = node.getFirstChild(); child != null; child = child
258: .getNextSibling()) {
259: String value = null;
260:
261: if (child instanceof Element
262: || child instanceof Document) {
263: if (cb == null)
264: cb = new CharBuffer();
265: if (s != null)
266: cb.append(s);
267: s = null;
268:
269: textValue(cb, child);
270: } else if ((value = child.getNodeValue()) == null
271: || value == "") {
272: } else if (cb != null)
273: cb.append(value);
274: else if (s == null && s != "") {
275: s = value;
276: } else {
277: cb = new CharBuffer();
278:
279: cb.append(s);
280: cb.append(value);
281: s = null;
282: }
283: }
284:
285: if (s != null)
286: return s;
287: else if (cb != null)
288: return cb.toString();
289: else
290: return "";
291: } else {
292: String value = node.getNodeValue();
293:
294: if (value != null)
295: return value;
296: else
297: return "";
298: }
299: }
300:
301: /**
302: * Extracts the text value from the node. Text nodes return their
303: * value and elements return the concatenation of the child values.
304: */
305: public static void textValue(CharBuffer cb, Node node) {
306: if (node instanceof Element || node instanceof DocumentFragment) {
307: for (Node child = node.getFirstChild(); child != null; child = child
308: .getNextSibling()) {
309: textValue(cb, child);
310: }
311: } else if (node instanceof Comment
312: || node instanceof ProcessingInstruction) {
313: } else
314: cb.append(node.getNodeValue());
315: }
316:
317: /**
318: * Extracts the text value from the node. Text nodes return their
319: * value and elements return the concatenation of the child values.
320: */
321: public static boolean isWhitespace(String text) {
322: for (int i = text.length() - 1; i >= 0; i--)
323: if (!XmlChar.isWhitespace(text.charAt(i)))
324: return false;
325:
326: return true;
327: }
328:
329: /**
330: * Sends data to the helper.
331: */
332: public static void toSAX(Node node, ContentHandler handler)
333: throws SAXException {
334: for (; node != null; node = node.getNextSibling()) {
335: if (node instanceof ProcessingInstruction) {
336: ProcessingInstruction pi = (ProcessingInstruction) node;
337:
338: handler.processingInstruction(pi.getNodeName(), pi
339: .getData());
340: } else if (node instanceof DocumentFragment) {
341: toSAX(node.getFirstChild(), handler);
342: }
343: }
344: }
345:
346: /**
347: * Returns the namespace for the given prefix.
348: */
349: public static String getNamespace(Node node, String prefix) {
350: for (; node != null; node = node.getParentNode()) {
351: if (node instanceof CauchoElement)
352: return ((CauchoElement) node).getNamespace(prefix);
353: }
354:
355: return null;
356: }
357: }
|