001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.lib.collab.util;
043:
044: import java.io.*;
045:
046: import org.w3c.dom.*;
047: import org.xml.sax.*;
048: import javax.xml.parsers.*;
049:
050: import org.netbeans.lib.collab.util.StringUtility;
051:
052: /**
053: * XML parsing utilities
054: *
055: *
056: *
057: *
058: */
059: public class XMLUtil extends Object {
060:
061: private XMLUtil() {
062: }
063:
064: private static DocumentBuilderFactory _fac;
065:
066: static {
067: _fac = DocumentBuilderFactory.newInstance();
068: _fac.setValidating(false);
069: _fac.setNamespaceAware(false);
070: _fac.setIgnoringElementContentWhitespace(true);
071: _fac.setIgnoringComments(true);
072: _fac.setCoalescing(true);
073: }
074:
075: public static Document parse(java.io.InputStream in)
076: throws XMLProcessingException, IOException {
077: try {
078: DocumentBuilder builder = _fac.newDocumentBuilder();
079: return builder.parse(in);
080: } catch (SAXException se) {
081: //se.printStackTrace();
082: throw new XMLProcessingException(
083: "Error while parsing the stream : " + se.toString());
084: } catch (ParserConfigurationException pce) {
085: //pce.printStackTrace();
086: throw new XMLProcessingException(
087: "Error while parsing the stream : "
088: + pce.toString());
089: }
090: }
091:
092: public static Document parse(InputSource in)
093: throws XMLProcessingException, IOException {
094: try {
095: DocumentBuilder builder = _fac.newDocumentBuilder();
096: return builder.parse(in);
097: } catch (SAXException se) {
098: //se.printStackTrace();
099: throw new XMLProcessingException(
100: "Error while parsing the stream : " + se.toString());
101: } catch (ParserConfigurationException pce) {
102: //pce.printStackTrace();
103: throw new XMLProcessingException(
104: "Error while parsing the stream : "
105: + pce.toString());
106: }
107: }
108:
109: public static Document parse(String in)
110: throws XMLProcessingException {
111: try {
112: // System.out.println("PARSING XML DOC: " + in);
113:
114: StringReader strReader = new StringReader(in);
115: strReader.ready();
116: InputSource inputSrc = new InputSource(strReader);
117: return parse(inputSrc);
118:
119: // ByteArrayOutputStream baos = new ByteArrayOutputStream();
120: // OutputStreamWriter osw = new OutputStreamWriter(baos, "UTF-8");
121: // char[] c = in.toCharArray();
122: // osw.write(c, 0, c.length);
123: // osw.flush();
124: // byte[] b = baos.toByteArray();
125: // ByteArrayInputStream bais = new ByteArrayInputStream(b);
126: // return parse(bais);
127:
128: } catch (IOException ioe) {
129: throw new XMLProcessingException(
130: "Error while parsing the stream : "
131: + ioe.toString());
132: }
133: }
134:
135: public static void appendElementTag(StringBuffer buf,
136: String namespace, String tagName, boolean endTag,
137: boolean closeTag) {
138: if (endTag) {
139: buf.append("</");
140: } else {
141: buf.append("<");
142: }
143: buf.append(namespace);
144: buf.append(tagName);
145: if (endTag || closeTag) {
146: buf.append(">");
147: }
148: }
149:
150: public static String getElementText(Element e) {
151: NodeList nl = e.getChildNodes();
152: for (int i = 0; i < nl.getLength(); i++) {
153: Node n = (Node) nl.item(i);
154: if (n.getNodeType() == Node.TEXT_NODE) {
155: return n.getNodeValue();
156: }
157: }
158: return null;
159: }
160:
161: public static String getNamespacePrefix(Element elt)
162: throws XMLProcessingException {
163: String s = elt.getTagName();
164: int c = s.indexOf(':');
165: if (c < 0) {
166: throw new XMLProcessingException("unexpected element: " + s);
167: } else {
168: return s.substring(c);
169: }
170: }
171:
172: public static String dumpNodeAsString(Node node)
173: throws XMLProcessingException {
174: try {
175: ByteArrayOutputStream baos = new ByteArrayOutputStream();
176: dumpNode(node, baos);
177: return baos.toString();
178: } catch (IOException e) {
179: throw new XMLProcessingException(e.toString());
180: }
181: }
182:
183: /**
184: * This method allows to dump a Document/Node to an outputStream.
185: * @param node The Node to dump.
186: * @param outputStream Where to dump it.
187: * @throws XMLProcessingException
188: */
189: public static void dumpNode(Node node, OutputStream outputStream)
190: throws XMLProcessingException, IOException {
191: BufferedWriter outWriter = new BufferedWriter(
192: new OutputStreamWriter(outputStream, "UTF-8"));
193: unparse(node, outWriter, 0);
194: outWriter.write("\n");
195: outWriter.flush();
196: }
197:
198: /**
199: * This method allows to dump a Document/Node to a file.
200: * @param node The Node to dump.
201: * @param filename Where to dump it.
202: * @throws XMLProcessingException
203: */
204: public static void dumpNode(Node node, String filename)
205: throws XMLProcessingException, IOException {
206: FileOutputStream stream = new FileOutputStream(filename);
207: BufferedWriter outWriter = new BufferedWriter(
208: new OutputStreamWriter(stream, "UTF-8"));
209: unparse(node, outWriter, 0);
210: outWriter.flush();
211: stream.flush();
212: stream.close();
213: }
214:
215: /**
216: * This method dump all the childrens of a Document/Node to an outputStream but not the Node itself
217: * @param node The Node to dump.
218: * @param outputStream Where to dump it.
219: * @throws XMLProcessingException
220: */
221: public static void dumpChildrenOfNode(Node node,
222: OutputStream outputStream) throws XMLProcessingException,
223: IOException {
224: BufferedWriter outWriter = new BufferedWriter(
225: new OutputStreamWriter(outputStream, "UTF-8"));
226: Node child = node.getFirstChild();
227:
228: while (child != null) {
229: unparse(child, outWriter, 0);
230: outWriter.write("\n");
231: outWriter.flush();
232: child = child.getNextSibling();
233: }
234: }
235:
236: /**
237: * This method dump all the childrens of a Document/Node to an outputStream but not the Node itself
238: * @param node The Node to dump.
239: * @param filename Where to dump it.
240: * @throws XMLProcessingException
241: */
242: public static void dumpChildrenOfNode(Node node, String filename)
243: throws XMLProcessingException, IOException {
244: FileOutputStream stream = new FileOutputStream(filename);
245: dumpChildrenOfNode(node, stream);
246: stream.flush();
247: stream.close();
248: }
249:
250: /**
251: * This method allows to unparse an xml node to a stream
252: * @param node the document/node to unparse
253: * @param out Where to dump the unparsed document
254: * @param processSiblings tell wether or not we have to process siblings of node
255: * @return A boolean indicating whether last printed line contains a carriage return or not, this is for internal use only
256: */
257: private static void unparse(Node node, Writer outWriter, int indent)
258: throws XMLProcessingException {
259: int i;
260: int type = node.getNodeType();
261: try {
262: switch (type) {
263: case Node.ATTRIBUTE_NODE:
264: String attribValue = node.getNodeValue();
265: if (attribValue == null) {
266: outWriter.write(" " + node.getNodeName()
267: + "=\"!!!null!!!\"");
268: } else {
269: /* Escape the double quote character in attribute value */
270: outWriter.write(" " + node.getNodeName() + "=\""
271: + escape(attribValue, true) + "\"");
272: }
273: break;
274: case Node.CDATA_SECTION_NODE:
275: throw new XMLProcessingException(
276: "Tag exception : CDATA");
277: case Node.COMMENT_NODE:
278: throw new XMLProcessingException(
279: "Tag exception : COMMENT");
280: case Node.DOCUMENT_FRAGMENT_NODE:
281: throw new XMLProcessingException(
282: "Tag exception : DOC_FRAG");
283: case Node.DOCUMENT_NODE:
284: outWriter
285: .write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
286: if (node.hasChildNodes()) {
287: Node curChild = node.getFirstChild();
288: while (curChild != null) {
289: outWriter.write("\n");
290: unparse(curChild, outWriter, indent);
291: curChild = curChild.getNextSibling();
292: }
293: }
294: break;
295: case Node.DOCUMENT_TYPE_NODE:
296: outWriter.write("<!DOCTYPE " + node.getNodeName()
297: + ">\n");
298: break;
299: case Node.ELEMENT_NODE:
300: outWriter.write("<" + node.getNodeName());
301: NamedNodeMap attribsnode = node.getAttributes();
302: for (i = 0; i < attribsnode.getLength(); i++) {
303: Node attrNode = attribsnode.item(i);
304: String attrValue = attrNode.getNodeValue();
305: if (attrValue == null) {
306: outWriter.write(" " + attrNode.getNodeName()
307: + "=\"!!!null!!!\"");
308: } else {
309: /* Escape the double quote character in attribute value */
310: outWriter.write(" " + attrNode.getNodeName()
311: + "=\"" + escape(attrValue, true)
312: + "\"");
313: }
314: }
315: if (node.hasChildNodes()) {
316: outWriter.write(">");
317:
318: Node curChild = node.getFirstChild();
319: Node firstChild = node.getFirstChild();
320: Node lastChild = node.getLastChild();
321:
322: while (curChild != null) {
323: if (curChild.getNodeType() != Node.TEXT_NODE) {
324: if (curChild == firstChild) {
325: outWriter.write("\n");
326: outputIndentation(outWriter, indent + 1);
327: } else if ((curChild.getPreviousSibling() != null)
328: && (curChild.getPreviousSibling()
329: .getNodeType() != Node.TEXT_NODE)) {
330: outWriter.write("\n");
331: outputIndentation(outWriter, indent + 1);
332: }
333: }
334: unparse(curChild, outWriter, indent + 1);
335: curChild = curChild.getNextSibling();
336: }
337: if ((firstChild.getNodeType() != Node.TEXT_NODE)
338: && (lastChild.getNodeType() != Node.TEXT_NODE)) {
339: outWriter.write("\n");
340: outputIndentation(outWriter, indent);
341: }
342: outWriter.write("</" + node.getNodeName() + ">");
343: } else {
344: outWriter.write(" />");
345: }
346: break;
347: case Node.ENTITY_NODE:
348: throw new XMLProcessingException(
349: "Tag exception : ENTITY");
350: case Node.ENTITY_REFERENCE_NODE:
351: throw new XMLProcessingException(
352: "Tag exception : ENTTIY_REF");
353: case Node.NOTATION_NODE:
354: throw new XMLProcessingException(
355: "Tag exception : NOTATION");
356: case Node.PROCESSING_INSTRUCTION_NODE:
357: outputIndentation(outWriter, indent);
358: outWriter.write("<?" + node.getNodeName() + " "
359: + node.getNodeValue());
360: if (node.hasChildNodes()) {
361: outWriter.write(">");
362:
363: unparse(node.getFirstChild(), outWriter, indent + 1);
364:
365: outWriter.write("\n");
366: outputIndentation(outWriter, indent);
367:
368: outWriter.write("</" + node.getNodeName() + ">");
369: } else
370: outWriter.write(" ?>\n");
371: break;
372: case Node.TEXT_NODE:
373: String nodeValue = node.getNodeValue();
374: outWriter.write(escape(nodeValue));
375: break;
376: default:
377: throw new XMLProcessingException(
378: "Tag exception : UNSUPPORTED NODE : " + type);
379: }
380: } catch (Exception e) {
381: throw new XMLProcessingException(
382: "An error occured while dumping : " + e.toString());
383: }
384: }
385:
386: private static final String basicIndent = " ";
387:
388: /**
389: * Indent to the current level in multiples of basicIndent
390: */
391: private static void outputIndentation(Writer outWriter, int indent)
392: throws XMLProcessingException, IOException {
393: for (int i = 0; i < indent; i++) {
394: outWriter.write("" + basicIndent);
395: }
396: }
397:
398: private static Node stripWhiteSpaceNodes(Node node) {
399: boolean lastNodeRemoved = false;
400: Node curNode = node.getFirstChild();
401: // System.out.println("Entering stripWhiteSpaceNodes : "+curNode.getNodeType());
402: while (curNode != null) {
403: if (curNode.hasChildNodes())
404: stripWhiteSpaceNodes(curNode);
405: if (curNode.getNodeType() == Node.TEXT_NODE) {
406: // System.out.println(" Text node found, value='"+curNode.getNodeValue()+"'");
407: if (curNode.getNodeValue() != null)
408: if ((curNode.getNodeValue().trim().length() == 0)
409: || (curNode.getNodeValue().trim()
410: .endsWith("\n"))
411: || (curNode.getNodeValue().trim()
412: .startsWith("\n"))) {
413: // System.out.println(" This is a whitespace node");
414: if ((curNode.getPreviousSibling() != null)
415: || (curNode.getNextSibling() != null)) {
416: node.removeChild(curNode);
417: lastNodeRemoved = true;
418: // System.out.println(" Just removed a node");
419: }
420: }
421: }
422: curNode = (lastNodeRemoved) ? node.getFirstChild()
423: : curNode.getNextSibling();
424: lastNodeRemoved = false;
425: }
426: return node;
427: }
428:
429: private static String escape(final String original,
430: boolean escapeQuotes) {
431: String str2Return = StringUtility.substitute(original, "&",
432: "&");
433: str2Return = StringUtility.substitute(str2Return, "<", "<");
434: str2Return = StringUtility.substitute(str2Return, ">", ">");
435: if (escapeQuotes) {
436: //str2Return=StringUtility.substitute(str2Return, "'", "'");
437: str2Return = StringUtility.substitute(str2Return, "\"",
438: """);
439: }
440: return (str2Return);
441: }
442:
443: private static String escape(final String original) {
444: return (escape(original, false));
445: }
446:
447: /**
448: * This method given a myDoc, myNode and string, parse the latter
449: * and attach to the myNode of myDoc the nodes representing the content of the string
450: * @param doc The Document which owe the Node node
451: * @param node The Node in which append nodes represented by the string
452: * @param str String reprensenting one or more nodes to parse
453: * @throws XMLProcessingException
454: */
455: public static void insertNodeFromString(Document doc, Node node,
456: String str) throws XMLProcessingException, IOException {
457: Document dummyDoc;
458: Node curNode;
459: Node parentNode;
460:
461: dummyDoc = createDocFromString(str);
462: if (dummyDoc != null) {
463: parentNode = dummyDoc.getFirstChild();
464: if (parentNode != null) {
465: curNode = parentNode.getFirstChild();
466: while (curNode != null) {
467: node.appendChild(doc.importNode(curNode, true));
468: curNode = curNode.getNextSibling();
469: }
470: }
471: }
472: }
473:
474: /**
475: * This method given a string, parse it and returns a document containing nodes
476: * representing the content of the string
477: * @param str The String to parse.
478: * @return A dummy Document containing the nodes representing the string
479: * @throws XMLProcessingException
480: */
481: public static Document createDocFromString(String str)
482: throws XMLProcessingException {
483: Document dummyDoc = null;
484: StringBuffer strBuff = new StringBuffer();
485:
486: strBuff.append("<?xml version=\"1.0\"?>");
487: strBuff.append("<dummyDocument>");
488: strBuff.append(str);
489: strBuff.append("</dummyDocument>");
490:
491: return parse(strBuff.toString());
492: }
493:
494: public static String getPrefixFromXMLString(String sElement)
495: throws XMLProcessingException {
496: int ix1 = sElement.indexOf('<');
497: if (ix1 >= 0) {
498: int ix2 = sElement.indexOf(':');
499: if (ix2 > ix1 + 1) {
500: // todo analyze all characters
501: return sElement.substring(ix1 + 1, ix2);
502: } else {
503: throw new XMLProcessingException("invalid XML: "
504: + sElement);
505: }
506: } else {
507: throw new XMLProcessingException("invalid XML: " + sElement);
508: }
509: }
510:
511: public static boolean isNamespaceAttribute(Node n) {
512: String prefix = n.getPrefix();
513: if (prefix != null) {
514: return prefix.equals("xmlns");
515: } else {
516: // if (n.getLocalName() != null) {
517: // return n.getLocalName().equals("xmlns"); // This implies a null Name part!
518: // }
519: return n.getNodeName().startsWith("xmlns:");
520: }
521: }
522:
523: public static String replacePrefix(String in, String oldPrefix,
524: String newPrefix) {
525: String out = in;
526: out = StringUtility.substitute(out, "<" + oldPrefix + ":", "<"
527: + newPrefix + ":");
528: out = StringUtility.substitute(out, "</" + oldPrefix + ":",
529: "</" + newPrefix + ":");
530: return out;
531: }
532:
533: }
|