001: /*
002: * Modified by Nabh Information Systems, Inc.
003: * Modifications (c) 2006 Nabh Information Systems, Inc.
004: *
005: * Copyright 2001-2004 The Apache Software Foundation.
006: *
007: * Licensed under the Apache License, Version 2.0 (the "License");
008: * you may not use this file except in compliance with the License.
009: * You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: */
019:
020: package com.nabhinc.util;
021:
022: import org.w3c.dom.Attr;
023: import org.w3c.dom.Element;
024: import org.w3c.dom.NamedNodeMap;
025: import org.w3c.dom.Node;
026: import org.w3c.dom.NodeList;
027:
028: import java.io.PrintWriter;
029: import java.io.StringWriter;
030: import java.io.Writer;
031:
032: /**
033: * This class is a utility to serialize a DOM node as XML. This class
034: * uses the <code>DOM Level 2</code> APIs.
035: * The main difference between this class and DOMWriter is that this class
036: * generates and prints out namespace declarations.
037: *
038: * @author Matthew J. Duftler (duftler@us.ibm.com)
039: * @author Joseph Kesselman
040: */
041: public class DOM2Writer {
042: public static final char NL = '\n';
043:
044: public static final char CR = '\r';
045: public static final String LS = System.getProperty(
046: "line.separator", (new Character(NL)).toString());
047: public static final String NS_URI_XMLNS = "http://www.w3.org/2000/xmlns/";
048:
049: public static final String NS_URI_XML = "http://www.w3.org/XML/1998/namespace";
050:
051: /**
052: * Return a string containing this node serialized as XML.
053: */
054: public static String nodeToString(Node node, boolean omitXMLDecl,
055: String encoding) {
056: StringWriter sw = new StringWriter();
057:
058: serializeAsXML(node, sw, omitXMLDecl, encoding, 0, 4);
059:
060: return sw.toString();
061: }
062:
063: /**
064: * Serialize this node into the writer as XML.
065: */
066: public static void serializeAsXML(Node node, Writer writer,
067: boolean omitXMLDecl, String encoding, int indent, int delta) {
068: serializeAsXML(node, writer, omitXMLDecl, false, encoding,
069: indent, delta);
070: }
071:
072: /**
073: * Serialize this node into the writer as XML.
074: */
075: public static void serializeAsXML(Node node, Writer writer,
076: boolean omitXMLDecl, boolean pretty, String encoding,
077: int indent, int delta) {
078: PrintWriter out = new PrintWriter(writer);
079: if (!omitXMLDecl) {
080: out.print("<?xml version=\"1.0\" encoding=\"");
081: out.print(encoding);
082: out.println("\"?>");
083: }
084: NSStack namespaceStack = new NSStack();
085: print(node, namespaceStack, node, out, pretty, indent, delta);
086: out.flush();
087: }
088:
089: private static void print(Node node, NSStack namespaceStack,
090: Node startnode, PrintWriter out, boolean pretty,
091: int indent, int delta) {
092: if (node == null) {
093: return;
094: }
095:
096: boolean hasChildren = false;
097: int type = node.getNodeType();
098:
099: switch (type) {
100: case Node.DOCUMENT_NODE: {
101: NodeList children = node.getChildNodes();
102:
103: if (children != null) {
104: int numChildren = children.getLength();
105:
106: for (int i = 0; i < numChildren; i++) {
107: print(children.item(i), namespaceStack, startnode,
108: out, pretty, indent, 4);
109: }
110: }
111: break;
112: }
113:
114: case Node.ELEMENT_NODE: {
115: namespaceStack.push();
116:
117: if (pretty) {
118: for (int i = 0; i < indent; i++)
119: out.print(' ');
120: }
121:
122: out.print('<' + node.getNodeName());
123:
124: String elPrefix = node.getPrefix();
125: String elNamespaceURI = node.getNamespaceURI();
126:
127: if (elPrefix != null && elNamespaceURI != null
128: && elPrefix.length() > 0) {
129: boolean prefixIsDeclared = false;
130:
131: try {
132: String namespaceURI = namespaceStack
133: .getNamespaceURI(elPrefix);
134:
135: if (elNamespaceURI.equals(namespaceURI)) {
136: prefixIsDeclared = true;
137: }
138: } catch (IllegalArgumentException e) {
139: }
140:
141: if (!prefixIsDeclared) {
142: printNamespaceDecl(node, namespaceStack, startnode,
143: out);
144: }
145: }
146:
147: NamedNodeMap attrs = node.getAttributes();
148: int len = (attrs != null) ? attrs.getLength() : 0;
149:
150: for (int i = 0; i < len; i++) {
151: Attr attr = (Attr) attrs.item(i);
152:
153: out.print(' ' + attr.getNodeName() + "=\""
154: + normalize(attr.getValue()) + '\"');
155:
156: String attrPrefix = attr.getPrefix();
157: String attrNamespaceURI = attr.getNamespaceURI();
158:
159: if (attrPrefix != null && attrNamespaceURI != null
160: && attrPrefix.length() > 0) {
161: boolean prefixIsDeclared = false;
162:
163: try {
164: String namespaceURI = namespaceStack
165: .getNamespaceURI(attrPrefix);
166:
167: if (attrNamespaceURI.equals(namespaceURI)) {
168: prefixIsDeclared = true;
169: }
170: } catch (IllegalArgumentException e) {
171: }
172:
173: if (!prefixIsDeclared) {
174: printNamespaceDecl(attr, namespaceStack,
175: startnode, out);
176: }
177: }
178: }
179:
180: NodeList children = node.getChildNodes();
181:
182: if (children != null) {
183: int numChildren = children.getLength();
184:
185: hasChildren = (numChildren > 0);
186:
187: if (hasChildren) {
188: out.print('>');
189: if (pretty)
190: out.print(DOM2Writer.LS);
191: }
192:
193: for (int i = 0; i < numChildren; i++) {
194: print(children.item(i), namespaceStack, startnode,
195: out, pretty, indent + delta, delta);
196: }
197: } else {
198: hasChildren = false;
199: }
200:
201: if (!hasChildren) {
202: out.print("/>");
203: if (pretty)
204: out.print(DOM2Writer.LS);
205: }
206:
207: namespaceStack.pop();
208: break;
209: }
210:
211: case Node.ENTITY_REFERENCE_NODE: {
212: out.print('&');
213: out.print(node.getNodeName());
214: out.print(';');
215: break;
216: }
217:
218: case Node.CDATA_SECTION_NODE: {
219: out.print("<![CDATA[");
220: out.print(node.getNodeValue());
221: out.print("]]>");
222: break;
223: }
224:
225: case Node.TEXT_NODE: {
226: out.print(normalize(node.getNodeValue()));
227: break;
228: }
229:
230: case Node.COMMENT_NODE: {
231: out.print("<!--");
232: out.print(node.getNodeValue());
233: out.print("-->");
234: if (pretty)
235: out.print(DOM2Writer.LS);
236: break;
237: }
238:
239: case Node.PROCESSING_INSTRUCTION_NODE: {
240: out.print("<?");
241: out.print(node.getNodeName());
242:
243: String data = node.getNodeValue();
244:
245: if (data != null && data.length() > 0) {
246: out.print(' ');
247: out.print(data);
248: }
249:
250: out.println("?>");
251: if (pretty)
252: out.print(DOM2Writer.LS);
253: break;
254: }
255: }
256:
257: if (type == Node.ELEMENT_NODE && hasChildren == true) {
258: if (pretty) {
259: for (int i = 0; i < indent; i++)
260: out.print(' ');
261: }
262: out.print("</");
263: out.print(node.getNodeName());
264: out.print('>');
265: if (pretty)
266: out.print(DOM2Writer.LS);
267: hasChildren = false;
268: }
269: }
270:
271: private static void printNamespaceDecl(Node node,
272: NSStack namespaceStack, Node startnode, PrintWriter out) {
273: switch (node.getNodeType()) {
274: case Node.ATTRIBUTE_NODE: {
275: printNamespaceDecl(((Attr) node).getOwnerElement(), node,
276: namespaceStack, startnode, out);
277: break;
278: }
279:
280: case Node.ELEMENT_NODE: {
281: printNamespaceDecl((Element) node, node, namespaceStack,
282: startnode, out);
283: break;
284: }
285: }
286: }
287:
288: private static void printNamespaceDecl(Element owner, Node node,
289: NSStack namespaceStack, Node startnode, PrintWriter out) {
290: String namespaceURI = node.getNamespaceURI();
291: String prefix = node.getPrefix();
292:
293: if (!(namespaceURI.equals(DOM2Writer.NS_URI_XMLNS) && prefix
294: .equals("xmlns"))
295: && !(namespaceURI.equals(DOM2Writer.NS_URI_XML) && prefix
296: .equals("xml"))) {
297: if (DOM2Writer.getNamespace(prefix, owner, startnode) == null) {
298: out.print(" xmlns:" + prefix + "=\"" + namespaceURI
299: + '\"');
300: }
301: } else {
302: prefix = node.getLocalName();
303: namespaceURI = node.getNodeValue();
304: }
305:
306: namespaceStack.add(namespaceURI, prefix);
307: }
308:
309: /**
310: * Searches for the namespace URI of the given prefix in the given DOM range.
311: *
312: * The namespace is not searched in parent of the "stopNode". This is
313: * usefull to get all the needed namespaces when you need to ouput only a
314: * subtree of a DOM document.
315: *
316: * @param prefix the prefix to find
317: * @param e the starting node
318: * @param stopNode null to search in all the document or a parent node where the search must stop.
319: * @return null if no namespace is found, or the namespace URI.
320: */
321: public static String getNamespace(String prefix, Node e,
322: Node stopNode) {
323: while (e != null && (e.getNodeType() == Node.ELEMENT_NODE)) {
324: Attr attr = null;
325: if (prefix == null) {
326: attr = ((Element) e).getAttributeNode("xmlns");
327: } else {
328: attr = ((Element) e).getAttributeNodeNS(
329: DOM2Writer.NS_URI_XMLNS, prefix);
330: }
331: if (attr != null)
332: return attr.getValue();
333: if (e == stopNode)
334: return null;
335: e = e.getParentNode();
336: }
337: return null;
338: }
339:
340: public static String getNamespace(String prefix, Node e) {
341: return getNamespace(prefix, e, null);
342: }
343:
344: public static String normalize(String s) {
345: return StringUtil.encodeXML(s);
346: }
347: }
|