001: /*
002: * Copyright (c) 2003 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.ext.dom;
054:
055: import freemarker.template.utility.StringUtil;
056: import freemarker.template.Template;
057: import freemarker.core.Environment;
058: import java.util.*;
059: import org.w3c.dom.*;
060:
061: class NodeOutputter {
062:
063: private Element contextNode;
064: private Environment env;
065: private String defaultNS;
066: private boolean hasDefaultNS;
067: private boolean explicitDefaultNSPrefix;
068: private HashMap namespacesToPrefixLookup = new HashMap();
069: private String namespaceDecl;
070:
071: NodeOutputter(Node node) {
072: if (node instanceof Element) {
073: setContext((Element) node);
074: } else if (node instanceof Attr) {
075: setContext(((Attr) node).getOwnerElement());
076: } else if (node instanceof Document) {
077: setContext(((Document) node).getDocumentElement());
078: }
079: }
080:
081: private void setContext(Element contextNode) {
082: this .contextNode = contextNode;
083: this .env = Environment.getCurrentEnvironment();
084: this .defaultNS = env.getDefaultNS();
085: this .hasDefaultNS = defaultNS != null && defaultNS.length() > 0;
086: namespacesToPrefixLookup.put(null, "");
087: namespacesToPrefixLookup.put("", "");
088: buildPrefixLookup(contextNode);
089: if (!explicitDefaultNSPrefix && hasDefaultNS) {
090: namespacesToPrefixLookup.put(defaultNS, "");
091: }
092: constructNamespaceDecl();
093: }
094:
095: private void buildPrefixLookup(Node n) {
096: String nsURI = n.getNamespaceURI();
097: if (nsURI != null && nsURI.length() > 0) {
098: String prefix = env.getPrefixForNamespace(nsURI);
099: namespacesToPrefixLookup.put(nsURI, prefix);
100: } else if (hasDefaultNS && n.getNodeType() == Node.ELEMENT_NODE) {
101: namespacesToPrefixLookup.put(defaultNS,
102: Template.DEFAULT_NAMESPACE_PREFIX);
103: explicitDefaultNSPrefix = true;
104: } else if (n.getNodeType() == Node.ATTRIBUTE_NODE
105: && hasDefaultNS && defaultNS.equals(nsURI)) {
106: namespacesToPrefixLookup.put(defaultNS,
107: Template.DEFAULT_NAMESPACE_PREFIX);
108: explicitDefaultNSPrefix = true;
109: }
110: NodeList childNodes = n.getChildNodes();
111: for (int i = 0; i < childNodes.getLength(); i++) {
112: buildPrefixLookup(childNodes.item(i));
113: }
114: }
115:
116: private void constructNamespaceDecl() {
117: StringBuffer buf = new StringBuffer();
118: if (explicitDefaultNSPrefix) {
119: buf.append(" xmlns=\"");
120: buf.append(defaultNS);
121: buf.append("\"");
122: }
123: for (Iterator it = namespacesToPrefixLookup.keySet().iterator(); it
124: .hasNext();) {
125: String nsURI = (String) it.next();
126: if (nsURI == null || nsURI.length() == 0) {
127: continue;
128: }
129: String prefix = (String) namespacesToPrefixLookup
130: .get(nsURI);
131: if (prefix == null) {
132: // Okay, let's auto-assign a prefix.
133: // Should we do this??? (REVISIT)
134: for (int i = 0; i < 26; i++) {
135: char[] cc = new char[1];
136: cc[0] = (char) ('a' + i);
137: prefix = new String(cc);
138: if (env.getNamespaceForPrefix(prefix) == null) {
139: break;
140: }
141: prefix = null;
142: }
143: if (prefix == null) {
144: throw new RuntimeException(
145: "This will almost never happen!");
146: }
147: namespacesToPrefixLookup.put(nsURI, prefix);
148: }
149: buf.append(" xmlns");
150: if (prefix.length() > 0) {
151: buf.append(":");
152: buf.append(prefix);
153: }
154: buf.append("=\"");
155: buf.append(nsURI);
156: buf.append("\"");
157: }
158: this .namespaceDecl = buf.toString();
159: }
160:
161: private void outputQualifiedName(Node n, StringBuffer buf) {
162: String nsURI = n.getNamespaceURI();
163: if (nsURI == null || nsURI.length() == 0) {
164: buf.append(n.getNodeName());
165: } else {
166: String prefix = (String) namespacesToPrefixLookup
167: .get(nsURI);
168: if (prefix == null) {
169: //REVISIT!
170: buf.append(n.getNodeName());
171: } else {
172: if (prefix.length() > 0) {
173: buf.append(prefix);
174: buf.append(':');
175: }
176: buf.append(n.getLocalName());
177: }
178: }
179: }
180:
181: void outputContent(Node n, StringBuffer buf) {
182: switch (n.getNodeType()) {
183: case Node.ATTRIBUTE_NODE: {
184: if (((Attr) n).getSpecified()) {
185: buf.append(' ');
186: outputQualifiedName(n, buf);
187: buf.append("=\"").append(
188: StringUtil.XMLEncQAttr(n.getNodeValue()))
189: .append('"');
190: }
191: break;
192: }
193: case Node.COMMENT_NODE: {
194: buf.append("<!--").append(n.getNodeValue()).append("-->");
195: break;
196: }
197: case Node.DOCUMENT_NODE: {
198: outputContent(n.getChildNodes(), buf);
199: break;
200: }
201: case Node.DOCUMENT_TYPE_NODE: {
202: buf.append("<!DOCTYPE ").append(n.getNodeName());
203: DocumentType dt = (DocumentType) n;
204: if (dt.getPublicId() != null) {
205: buf.append(" PUBLIC \"").append(dt.getPublicId())
206: .append('"');
207: }
208: if (dt.getSystemId() != null) {
209: buf.append(" \"").append(dt.getSystemId()).append('"');
210: }
211: if (dt.getInternalSubset() != null) {
212: buf.append(" [").append(dt.getInternalSubset()).append(
213: ']');
214: }
215: buf.append('>');
216: break;
217: }
218: case Node.ELEMENT_NODE: {
219: buf.append('<');
220: outputQualifiedName(n, buf);
221: if (n == contextNode) {
222: buf.append(namespaceDecl);
223: }
224: outputContent(n.getAttributes(), buf);
225: NodeList children = n.getChildNodes();
226: if (children.getLength() == 0) {
227: buf.append(" />");
228: } else {
229: buf.append('>');
230: outputContent(n.getChildNodes(), buf);
231: buf.append("</");
232: outputQualifiedName(n, buf);
233: buf.append('>');
234: }
235: break;
236: }
237: case Node.ENTITY_NODE: {
238: outputContent(n.getChildNodes(), buf);
239: break;
240: }
241: case Node.ENTITY_REFERENCE_NODE: {
242: buf.append('&').append(n.getNodeName()).append(';');
243: break;
244: }
245: case Node.PROCESSING_INSTRUCTION_NODE: {
246: buf.append("<?").append(n.getNodeName()).append(' ')
247: .append(n.getNodeValue()).append("?>");
248: break;
249: }
250: /*
251: case Node.CDATA_SECTION_NODE: {
252: buf.append("<![CDATA[").append(n.getNodeValue()).append("]]>");
253: break;
254: }*/
255: case Node.CDATA_SECTION_NODE:
256: case Node.TEXT_NODE: {
257: buf.append(StringUtil.XMLEncNQG(n.getNodeValue()));
258: break;
259: }
260: }
261: }
262:
263: void outputContent(NodeList nodes, StringBuffer buf) {
264: for (int i = 0; i < nodes.getLength(); ++i) {
265: outputContent(nodes.item(i), buf);
266: }
267: }
268:
269: void outputContent(NamedNodeMap nodes, StringBuffer buf) {
270: for (int i = 0; i < nodes.getLength(); ++i) {
271: Node n = nodes.item(i);
272: if (n.getNodeType() != Node.ATTRIBUTE_NODE
273: || (!n.getNodeName().startsWith("xmlns:") && !n
274: .getNodeName().equals("xmlns"))) {
275: outputContent(n, buf);
276: }
277: }
278: }
279:
280: String getOpeningTag(Element element) {
281: StringBuffer buf = new StringBuffer();
282: buf.append('<');
283: outputQualifiedName(element, buf);
284: buf.append(namespaceDecl);
285: outputContent(element.getAttributes(), buf);
286: buf.append('>');
287: return buf.toString();
288: }
289:
290: String getClosingTag(Element element) {
291: StringBuffer buf = new StringBuffer();
292: buf.append("</");
293: outputQualifiedName(element, buf);
294: buf.append('>');
295: return buf.toString();
296: }
297: }
|