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.xml;
054:
055: import java.io.StringWriter;
056: import java.util.List;
057:
058: import org.jaxen.Context;
059: import org.jaxen.NamespaceContext;
060: import org.jaxen.dom.DOMXPath;
061: import org.w3c.dom.Attr;
062: import org.w3c.dom.Document;
063: import org.w3c.dom.DocumentType;
064: import org.w3c.dom.Element;
065: import org.w3c.dom.NamedNodeMap;
066: import org.w3c.dom.Node;
067: import org.w3c.dom.NodeList;
068: import org.w3c.dom.ProcessingInstruction;
069: import org.w3c.dom.Text;
070: import freemarker.template.TemplateModelException;
071: import freemarker.template.utility.StringUtil;
072:
073: /**
074: * @version $Id: DomNavigator.java,v 1.8 2003/09/02 09:54:59 szegedia Exp $
075: * @author Attila Szegedi
076: */
077: class DomNavigator extends Navigator {
078: DomNavigator() {
079: }
080:
081: void getAsString(Object node, StringWriter sw) {
082: outputContent((Node) node, sw.getBuffer());
083: }
084:
085: private void outputContent(Node n, StringBuffer buf) {
086: switch (n.getNodeType()) {
087: case Node.ATTRIBUTE_NODE: {
088: buf.append(' ').append(getQualifiedName(n)).append("=\"")
089: .append(StringUtil.XMLEncNA(n.getNodeValue())) // XmlEncNA for HTML compatibility
090: .append('"');
091: break;
092: }
093: case Node.CDATA_SECTION_NODE: {
094: buf.append("<![CDATA[").append(n.getNodeValue()).append(
095: "]]>");
096: break;
097: }
098: case Node.COMMENT_NODE: {
099: buf.append("<!--").append(n.getNodeValue()).append("-->");
100: break;
101: }
102: case Node.DOCUMENT_NODE: {
103: outputContent(n.getChildNodes(), buf);
104: break;
105: }
106: case Node.DOCUMENT_TYPE_NODE: {
107: buf.append("<!DOCTYPE ").append(n.getNodeName());
108: DocumentType dt = (DocumentType) n;
109: if (dt.getPublicId() != null) {
110: buf.append(" PUBLIC \"").append(dt.getPublicId())
111: .append('"');
112: }
113: if (dt.getSystemId() != null) {
114: buf.append('"').append(dt.getSystemId()).append('"');
115: }
116: if (dt.getInternalSubset() != null) {
117: buf.append(" [").append(dt.getInternalSubset()).append(
118: ']');
119: }
120: buf.append('>');
121: break;
122: }
123: case Node.ELEMENT_NODE: {
124: buf.append('<').append(getQualifiedName(n));
125: outputContent(n.getAttributes(), buf);
126: buf.append('>');
127: outputContent(n.getChildNodes(), buf);
128: buf.append("</").append(getQualifiedName(n)).append('>');
129: break;
130: }
131: case Node.ENTITY_NODE: {
132: outputContent(n.getChildNodes(), buf);
133: break;
134: }
135: case Node.ENTITY_REFERENCE_NODE: {
136: buf.append('&').append(n.getNodeName()).append(';');
137: break;
138: }
139: case Node.PROCESSING_INSTRUCTION_NODE: {
140: buf.append("<?").append(n.getNodeName()).append(' ')
141: .append(n.getNodeValue()).append("?>");
142: break;
143: }
144: case Node.TEXT_NODE: {
145: buf.append(StringUtil.XMLEncNQG(n.getNodeValue()));
146: break;
147: }
148: }
149: }
150:
151: private void outputContent(NodeList nodes, StringBuffer buf) {
152: for (int i = 0; i < nodes.getLength(); ++i) {
153: outputContent(nodes.item(i), buf);
154: }
155: }
156:
157: private void outputContent(NamedNodeMap nodes, StringBuffer buf) {
158: for (int i = 0; i < nodes.getLength(); ++i) {
159: outputContent(nodes.item(i), buf);
160: }
161: }
162:
163: void getChildren(Object node, String localName,
164: String namespaceUri, List result) {
165: if ("".equals(namespaceUri)) {
166: namespaceUri = null;
167: }
168: NodeList children = ((Node) node).getChildNodes();
169: for (int i = 0; i < children.getLength(); ++i) {
170: Node subnode = children.item(i);
171: // IMO, we should get the text nodes as well -- will discuss.
172: if (subnode.getNodeType() == Node.ELEMENT_NODE
173: || subnode.getNodeType() == Node.TEXT_NODE) {
174: if (localName == null
175: || (equal(subnode.getNodeName(), localName) && equal(
176: subnode.getNamespaceURI(), namespaceUri))) {
177: result.add(subnode);
178: }
179: }
180: }
181: }
182:
183: void getAttributes(Object node, String localName,
184: String namespaceUri, List result) {
185: if (node instanceof Element) {
186: Element e = (Element) node;
187: if (localName == null) {
188: NamedNodeMap atts = e.getAttributes();
189: for (int i = 0; i < atts.getLength(); ++i) {
190: result.add(atts.item(i));
191: }
192: } else {
193: if ("".equals(namespaceUri)) {
194: namespaceUri = null;
195: }
196: Attr attr = e.getAttributeNodeNS(namespaceUri,
197: localName);
198: if (attr != null) {
199: result.add(attr);
200: }
201: }
202: } else if (node instanceof ProcessingInstruction) {
203: ProcessingInstruction pi = (ProcessingInstruction) node;
204: if ("target".equals(localName)) {
205: result
206: .add(createAttribute(pi, "target", pi
207: .getTarget()));
208: } else if ("data".equals(localName)) {
209: result.add(createAttribute(pi, "data", pi.getData()));
210: } else {
211: // TODO: DOM has no facility for parsing data into
212: // name-value pairs...
213: ;
214: }
215: } else if (node instanceof DocumentType) {
216: DocumentType doctype = (DocumentType) node;
217: if ("publicId".equals(localName)) {
218: result.add(createAttribute(doctype, "publicId", doctype
219: .getPublicId()));
220: } else if ("systemId".equals(localName)) {
221: result.add(createAttribute(doctype, "systemId", doctype
222: .getSystemId()));
223: } else if ("elementName".equals(localName)) {
224: result.add(createAttribute(doctype, "elementName",
225: doctype.getNodeName()));
226: }
227: }
228: }
229:
230: private Attr createAttribute(Node node, String name, String value) {
231: Attr attr = node.getOwnerDocument().createAttribute(name);
232: attr.setNodeValue(value);
233: return attr;
234: }
235:
236: void getDescendants(Object node, List result) {
237: NodeList children = ((Node) node).getChildNodes();
238: for (int i = 0; i < children.getLength(); ++i) {
239: Node subnode = children.item(i);
240: if (subnode.getNodeType() == Node.ELEMENT_NODE) {
241: result.add(subnode);
242: getDescendants(subnode, result);
243: }
244: }
245: }
246:
247: Object getParent(Object node) {
248: return ((Node) node).getParentNode();
249: }
250:
251: Object getDocument(Object node) {
252: return ((Node) node).getOwnerDocument();
253: }
254:
255: Object getDocumentType(Object node) {
256: return node instanceof Document ? ((Document) node)
257: .getDoctype() : null;
258: }
259:
260: void getContent(Object node, List result) {
261: NodeList children = ((Node) node).getChildNodes();
262: for (int i = 0; i < children.getLength(); ++i) {
263: result.add(children.item(i));
264: }
265: }
266:
267: String getText(Object node) {
268: StringBuffer buf = new StringBuffer();
269: if (node instanceof Element) {
270: NodeList children = ((Node) node).getChildNodes();
271: for (int i = 0; i < children.getLength(); ++i) {
272: Node child = children.item(i);
273: if (child instanceof Text) {
274: buf.append(child.getNodeValue());
275: }
276: }
277: return buf.toString();
278: } else {
279: return ((Node) node).getNodeValue();
280: }
281: }
282:
283: String getLocalName(Object node) {
284: return ((Node) node).getNodeName();
285: }
286:
287: String getNamespacePrefix(Object node) {
288: return ((Node) node).getPrefix();
289: }
290:
291: String getNamespaceUri(Object node) {
292: return ((Node) node).getNamespaceURI();
293: }
294:
295: String getType(Object node) {
296: switch (((Node) node).getNodeType()) {
297: case Node.ATTRIBUTE_NODE: {
298: return "attribute";
299: }
300: case Node.CDATA_SECTION_NODE: {
301: return "cdata";
302: }
303: case Node.COMMENT_NODE: {
304: return "comment";
305: }
306: case Node.DOCUMENT_NODE: {
307: return "document";
308: }
309: case Node.DOCUMENT_TYPE_NODE: {
310: return "documentType";
311: }
312: case Node.ELEMENT_NODE: {
313: return "element";
314: }
315: case Node.ENTITY_NODE: {
316: return "entity";
317: }
318: case Node.ENTITY_REFERENCE_NODE: {
319: return "entityReference";
320: }
321: case Node.PROCESSING_INSTRUCTION_NODE: {
322: return "processingInstruction";
323: }
324: case Node.TEXT_NODE: {
325: return "text";
326: }
327: }
328: return "unknown";
329: }
330:
331: XPathEx createXPathEx(String xpathString)
332: throws TemplateModelException {
333: try {
334: return new DomXPathEx(xpathString);
335: } catch (Exception e) {
336: throw new TemplateModelException(e);
337: }
338: }
339:
340: private static final class DomXPathEx extends DOMXPath implements
341: XPathEx {
342: DomXPathEx(String path) throws Exception {
343: super (path);
344: }
345:
346: public List selectNodes(Object object,
347: NamespaceContext namespaces)
348: throws TemplateModelException {
349: Context context = getContext(object);
350: context.getContextSupport().setNamespaceContext(namespaces);
351: try {
352: return selectNodesForContext(context);
353: } catch (Exception e) {
354: throw new TemplateModelException(e);
355: }
356: }
357: }
358: }
|