KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > freemarker > ext > dom > NodeModel


1 /*
2  * Copyright (c) 2003 The Visigoth Software Society. All rights
3  * reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in
14  * the documentation and/or other materials provided with the
15  * distribution.
16  *
17  * 3. The end-user documentation included with the redistribution, if
18  * any, must include the following acknowledgement:
19  * "This product includes software developed by the
20  * Visigoth Software Society (http://www.visigoths.org/)."
21  * Alternately, this acknowledgement may appear in the software itself,
22  * if and wherever such third-party acknowledgements normally appear.
23  *
24  * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
25  * project contributors may be used to endorse or promote products derived
26  * from this software without prior written permission. For written
27  * permission, please contact visigoths@visigoths.org.
28  *
29  * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
30  * nor may "FreeMarker" or "Visigoth" appear in their names
31  * without prior written permission of the Visigoth Software Society.
32  *
33  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
34  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36  * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
37  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
39  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
40  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
41  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
42  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
43  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44  * SUCH DAMAGE.
45  * ====================================================================
46  *
47  * This software consists of voluntary contributions made by many
48  * individuals on behalf of the Visigoth Software Society. For more
49  * information on the Visigoth Software Society, please see
50  * http://www.visigoths.org/
51  */

52  
53 package freemarker.ext.dom;
54
55
56 import java.io.File JavaDoc;
57 import java.io.IOException JavaDoc;
58 import java.lang.ref.WeakReference JavaDoc;
59 import java.util.Collections JavaDoc;
60 import java.util.List JavaDoc;
61 import java.util.Map JavaDoc;
62 import java.util.WeakHashMap JavaDoc;
63
64 import javax.xml.parsers.DocumentBuilder JavaDoc;
65 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
66 import javax.xml.parsers.ParserConfigurationException JavaDoc;
67
68 import org.w3c.dom.Attr JavaDoc;
69 import org.w3c.dom.CDATASection JavaDoc;
70 import org.w3c.dom.CharacterData JavaDoc;
71 import org.w3c.dom.Document JavaDoc;
72 import org.w3c.dom.DocumentType JavaDoc;
73 import org.w3c.dom.Element JavaDoc;
74 import org.w3c.dom.Node JavaDoc;
75 import org.w3c.dom.NodeList JavaDoc;
76 import org.w3c.dom.ProcessingInstruction JavaDoc;
77 import org.w3c.dom.Text JavaDoc;
78 import org.xml.sax.ErrorHandler JavaDoc;
79 import org.xml.sax.InputSource JavaDoc;
80 import org.xml.sax.SAXException JavaDoc;
81
82 import freemarker.ext.util.WrapperTemplateModel;
83 import freemarker.log.Logger;
84 import freemarker.template.AdapterTemplateModel;
85 import freemarker.template.SimpleScalar;
86 import freemarker.template.TemplateHashModel;
87 import freemarker.template.TemplateModel;
88 import freemarker.template.TemplateModelException;
89 import freemarker.template.TemplateNodeModel;
90 import freemarker.template.TemplateSequenceModel;
91
92 /**
93  * A base class for wrapping a W3C DOM Node as a FreeMarker template model.
94  * @author <a HREF="mailto:jon@revusky.com">Jonathan Revusky</a>
95  * @version $Id: NodeModel.java,v 1.80 2005/06/22 11:33:31 ddekany Exp $
96  */

97 abstract public class NodeModel
98 implements TemplateNodeModel, TemplateHashModel, TemplateSequenceModel,
99     AdapterTemplateModel, WrapperTemplateModel
100 {
101
102     static final Logger logger = Logger.getLogger("freemarker.dom");
103     
104     static private DocumentBuilderFactory JavaDoc docBuilderFactory;
105     
106     static private Map JavaDoc xpathSupportMap = Collections.synchronizedMap(new WeakHashMap JavaDoc());
107     
108     static private XPathSupport jaxenXPathSupport;
109     
110     static private ErrorHandler JavaDoc errorHandler;
111     
112     static Class JavaDoc xpathSupportClass;
113     
114     static {
115         try {
116             useDefaultXPathSupport();
117         } catch (Exception JavaDoc e) {
118             // do nothing
119
}
120         if (xpathSupportClass == null && logger.isWarnEnabled()) {
121             logger.warn("No XPath support is available.");
122         }
123     }
124     
125     /**
126      * The W3C DOM Node being wrapped.
127      */

128     final Node JavaDoc node;
129     private TemplateSequenceModel children;
130     private NodeModel parent;
131     
132     /**
133      * Sets the DOM Parser implementation to be used when building NodeModel
134      * objects from XML files.
135      */

136     static public void setDocumentBuilderFactory(DocumentBuilderFactory JavaDoc docBuilderFactory) {
137         NodeModel.docBuilderFactory = docBuilderFactory;
138     }
139     
140     /**
141      * @return the DOM Parser implementation that is used when
142      * building NodeModel objects from XML files.
143      */

144     static public DocumentBuilderFactory JavaDoc getDocumentBuilderFactory() {
145         if (docBuilderFactory == null) {
146             docBuilderFactory = DocumentBuilderFactory.newInstance();
147             docBuilderFactory.setNamespaceAware(true);
148             docBuilderFactory.setIgnoringElementContentWhitespace(true);
149         }
150         return docBuilderFactory;
151     }
152     
153     /**
154      * sets the error handler to use when parsing the document.
155      */

156     static public void setErrorHandler(ErrorHandler JavaDoc errorHandler) {
157         NodeModel.errorHandler = errorHandler;
158     }
159     
160     /**
161      * Create a NodeModel from a SAX input source. Adjacent text nodes will be merged (and CDATA sections
162      * are considered as text nodes).
163      * @param removeComments whether to remove all comment nodes
164      * (recursively) from the tree before processing
165      * @param removePIs whether to remove all processing instruction nodes
166      * (recursively from the tree before processing
167      */

168     static public NodeModel parse(InputSource JavaDoc is, boolean removeComments, boolean removePIs)
169         throws SAXException JavaDoc, IOException JavaDoc, ParserConfigurationException JavaDoc
170     {
171         DocumentBuilder JavaDoc builder = getDocumentBuilderFactory().newDocumentBuilder();
172         if (errorHandler != null) builder.setErrorHandler(errorHandler);
173         Document JavaDoc doc = builder.parse(is);
174         if (removeComments && removePIs) {
175             simplify(doc);
176         } else {
177             if (removeComments) {
178                 removeComments(doc);
179             }
180             if (removePIs) {
181                 removePIs(doc);
182             }
183             mergeAdjacentText(doc);
184         }
185         return wrap(doc);
186     }
187     
188     /**
189      * Create a NodeModel from an XML input source. By default,
190      * all comments and processing instruction nodes are
191      * stripped from the tree.
192      */

193     static public NodeModel parse(InputSource JavaDoc is)
194     throws SAXException JavaDoc, IOException JavaDoc, ParserConfigurationException JavaDoc {
195         return parse(is, true, true);
196     }
197     
198     
199     /**
200      * Create a NodeModel from an XML file.
201      * @param removeComments whether to remove all comment nodes
202      * (recursively) from the tree before processing
203      * @param removePIs whether to remove all processing instruction nodes
204      * (recursively from the tree before processing
205      */

206     static public NodeModel parse(File JavaDoc f, boolean removeComments, boolean removePIs)
207         throws SAXException JavaDoc, IOException JavaDoc, ParserConfigurationException JavaDoc
208     {
209         DocumentBuilder JavaDoc builder = getDocumentBuilderFactory().newDocumentBuilder();
210         if (errorHandler != null) builder.setErrorHandler(errorHandler);
211         Document JavaDoc doc = builder.parse(f);
212         if (removeComments) {
213             removeComments(doc);
214         }
215         if (removePIs) {
216             removePIs(doc);
217         }
218         mergeAdjacentText(doc);
219         return wrap(doc);
220     }
221     
222     /**
223      * Create a NodeModel from an XML file. By default,
224      * all comments and processing instruction nodes are
225      * stripped from the tree.
226      */

227     static public NodeModel parse(File JavaDoc f)
228     throws SAXException JavaDoc, IOException JavaDoc, ParserConfigurationException JavaDoc {
229         return parse(f, true, true);
230     }
231     
232     protected NodeModel(Node JavaDoc node) {
233         this.node = node;
234     }
235     
236     /**
237      * @return the underling W3C DOM Node object that this TemplateNodeModel
238      * is wrapping.
239      */

240     public Node JavaDoc getNode() {
241         return node;
242     }
243     
244     public TemplateModel get(String JavaDoc key) throws TemplateModelException {
245         if (key.startsWith("@@")) {
246             if (key.equals("@@text")) {
247                 return new SimpleScalar(getText(node));
248             }
249             if (key.equals("@@namespace")) {
250                 String JavaDoc nsURI = node.getNamespaceURI();
251                 return nsURI == null ? null : new SimpleScalar(nsURI);
252             }
253             if (key.equals("@@local_name")) {
254                 String JavaDoc localName = node.getLocalName();
255                 if (localName == null) {
256                     localName = getNodeName();
257                 }
258                 return new SimpleScalar(localName);
259             }
260             if (key.equals("@@markup")) {
261                 StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
262                 NodeOutputter nu = new NodeOutputter(node);
263                 nu.outputContent(node, buf);
264                 return new SimpleScalar(buf.toString());
265             }
266             if (key.equals("@@nested_markup")) {
267                 StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
268                 NodeOutputter nu = new NodeOutputter(node);
269                 nu.outputContent(node.getChildNodes(), buf);
270                 return new SimpleScalar(buf.toString());
271             }
272             if (key.equals("@@qname")) {
273                 String JavaDoc qname = getQualifiedName();
274                 return qname == null ? null : new SimpleScalar(qname);
275             }
276         }
277         XPathSupport xps = getXPathSupport();
278         if (xps != null) {
279             return xps.executeQuery(node, key);
280         } else {
281             throw new TemplateModelException(
282                     "Can't try to resolve the XML query key, because no XPath support is available. "
283                     + "It's either malformed or an XPath expression: " + key);
284         }
285     }
286     
287     public TemplateNodeModel getParentNode() {
288         if (parent == null) {
289             Node JavaDoc parentNode = node.getParentNode();
290             if (parentNode == null) {
291                 if (node instanceof Attr JavaDoc) {
292                     parentNode = ((Attr JavaDoc) node).getOwnerElement();
293                 }
294             }
295             parent = wrap(parentNode);
296         }
297         return parent;
298     }
299     
300     public TemplateSequenceModel getChildNodes() {
301         if (children == null) {
302             children = new NodeListModel(node.getChildNodes(), this);
303         }
304         return children;
305     }
306     
307     public final String JavaDoc getNodeType() throws TemplateModelException {
308         short nodeType = node.getNodeType();
309         switch (nodeType) {
310             case Node.ATTRIBUTE_NODE : return "attribute";
311             case Node.CDATA_SECTION_NODE : return "text";
312             case Node.COMMENT_NODE : return "comment";
313             case Node.DOCUMENT_FRAGMENT_NODE : return "document_fragment";
314             case Node.DOCUMENT_NODE : return "document";
315             case Node.DOCUMENT_TYPE_NODE : return "document_type";
316             case Node.ELEMENT_NODE : return "element";
317             case Node.ENTITY_NODE : return "entity";
318             case Node.ENTITY_REFERENCE_NODE : return "entity_reference";
319             case Node.NOTATION_NODE : return "notation";
320             case Node.PROCESSING_INSTRUCTION_NODE : return "pi";
321             case Node.TEXT_NODE : return "text";
322         }
323         throw new TemplateModelException("Unknown node type: " + nodeType + ". This should be impossible!");
324     }
325     
326     public TemplateModel exec(List JavaDoc args) throws TemplateModelException {
327         if (args.size() != 1) {
328             throw new TemplateModelException("Expecting exactly one arguments");
329         }
330         String JavaDoc query = (String JavaDoc) args.get(0);
331         // Now, we try to behave as if this is an XPath expression
332
XPathSupport xps = getXPathSupport();
333         if (xps == null) {
334             throw new TemplateModelException("No XPath support available");
335         }
336         return xps.executeQuery(node, query);
337     }
338     
339     public final int size() {return 1;}
340     
341     public final TemplateModel get(int i) {
342         return i==0 ? this : null;
343     }
344     
345     public String JavaDoc getNodeNamespace() {
346         int nodeType = node.getNodeType();
347         if (nodeType != Node.ATTRIBUTE_NODE && nodeType != Node.ELEMENT_NODE) {
348             return null;
349         }
350         String JavaDoc result = node.getNamespaceURI();
351         if (result == null && nodeType == Node.ELEMENT_NODE) {
352             result = "";
353         } else if ("".equals(result) && nodeType == Node.ATTRIBUTE_NODE) {
354             result = null;
355         }
356         return result;
357     }
358     
359     public final int hashCode() {
360         return node.hashCode();
361     }
362     
363     public boolean equals(Object JavaDoc other) {
364         if (other == null) return false;
365         return other.getClass() == this.getClass()
366                 && ((NodeModel) other).node.equals(this.node);
367     }
368     
369     static public NodeModel wrap(Node JavaDoc node) {
370         if (node == null) {
371             return null;
372         }
373         NodeModel result = null;
374         switch (node.getNodeType()) {
375             case Node.DOCUMENT_NODE : result = new DocumentModel((Document JavaDoc) node); break;
376             case Node.ELEMENT_NODE : result = new ElementModel((Element JavaDoc) node); break;
377             case Node.ATTRIBUTE_NODE : result = new AttributeNodeModel((Attr JavaDoc) node); break;
378             case Node.CDATA_SECTION_NODE :
379             case Node.COMMENT_NODE :
380             case Node.TEXT_NODE : result = new CharacterDataNodeModel((org.w3c.dom.CharacterData JavaDoc) node); break;
381             case Node.PROCESSING_INSTRUCTION_NODE : result = new PINodeModel((ProcessingInstruction JavaDoc) node); break;
382             case Node.DOCUMENT_TYPE_NODE : result = new DocumentTypeModel((DocumentType JavaDoc) node); break;
383         }
384         return result;
385     }
386     
387     /**
388      * Recursively removes all comment nodes
389      * from the subtree.
390      *
391      * @see #simplify
392      */

393     static public void removeComments(Node JavaDoc node) {
394         NodeList JavaDoc children = node.getChildNodes();
395         int i = 0;
396         int len = children.getLength();
397         while (i < len) {
398             Node JavaDoc child = children.item(i);
399             if (child.hasChildNodes()) {
400                 removeComments(child);
401                 i++;
402             } else {
403                 if (child.getNodeType() == Node.COMMENT_NODE) {
404                     node.removeChild(child);
405                     len--;
406                 } else {
407                     i++;
408                 }
409             }
410         }
411     }
412     
413     /**
414      * Recursively removes all processing instruction nodes
415      * from the subtree.
416      *
417      * @see #simplify
418      */

419     static public void removePIs(Node JavaDoc node) {
420         NodeList JavaDoc children = node.getChildNodes();
421         int i = 0;
422         int len = children.getLength();
423         while (i < len) {
424             Node JavaDoc child = children.item(i);
425             if (child.hasChildNodes()) {
426                 removePIs(child);
427                 i++;
428             } else {
429                 if (child.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
430                     node.removeChild(child);
431                     len--;
432                 } else {
433                     i++;
434                 }
435             }
436         }
437     }
438     
439     /**
440      * Merges adjacent text/cdata nodes, so that there are no
441      * adjacent text/cdata nodes. Operates recursively
442      * on the entire subtree. You thus lose information
443      * about any CDATA sections occurring in the doc.
444      *
445      * @see #simplify
446      */

447     static public void mergeAdjacentText(Node JavaDoc node) {
448         Node JavaDoc child = node.getFirstChild();
449         while (child != null) {
450             if (child instanceof Text JavaDoc || child instanceof CDATASection JavaDoc) {
451                 Node JavaDoc next = child.getNextSibling();
452                 if (next instanceof Text JavaDoc || next instanceof CDATASection JavaDoc) {
453                     String JavaDoc fullText = child.getNodeValue() + next.getNodeValue();
454                     ((CharacterData JavaDoc) child).setData(fullText);
455                     node.removeChild(next);
456                 }
457             }
458             else {
459                 mergeAdjacentText(child);
460             }
461             child = child.getNextSibling();
462         }
463     }
464     
465     /**
466      * Removes comments and processing instruction, and then unites adjacent text nodes.
467      * Note that CDATA sections count as text nodes.
468      */

469     static public void simplify(Node JavaDoc node) {
470         NodeList JavaDoc children = node.getChildNodes();
471         int i = 0;
472         int len = children.getLength();
473         Node JavaDoc prevTextChild = null;
474         while (i < len) {
475             Node JavaDoc child = children.item(i);
476             if (child.hasChildNodes()) {
477                 simplify(child);
478                 prevTextChild = null;
479                 i++;
480             } else {
481                 int type = child.getNodeType();
482                 if (type == Node.PROCESSING_INSTRUCTION_NODE) {
483                     node.removeChild(child);
484                     len--;
485                 } else if (type == Node.COMMENT_NODE) {
486                     node.removeChild(child);
487                     len--;
488                 } else if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE ) {
489                     if (prevTextChild != null) {
490                         CharacterData JavaDoc ptc = (CharacterData JavaDoc) prevTextChild;
491                         ptc.setData(ptc.getNodeValue() + child.getNodeValue());
492                         node.removeChild(child);
493                         len--;
494                     } else {
495                         prevTextChild = child;
496                         i++;
497                     }
498                 } else {
499                     prevTextChild = null;
500                     i++;
501                 }
502             }
503         }
504     }
505     
506     NodeModel getDocumentNodeModel() {
507         if (node instanceof Document JavaDoc) {
508             return this;
509         }
510         else {
511             return wrap(node.getOwnerDocument());
512         }
513     }
514
515     /**
516      * Tells the system to use (restore) the default (initial) XPath system used by
517      * this FreeMarker version on this system.
518      */

519     static public void useDefaultXPathSupport() {
520         xpathSupportClass = null;
521         jaxenXPathSupport = null;
522         try {
523             useXalanXPathSupport();
524         } catch (Exception JavaDoc e) {
525             ; // ignore
526
}
527         if (xpathSupportClass == null) try {
528             useJaxenXPathSupport();
529         } catch (Exception JavaDoc e) {
530             ; // ignore
531
}
532     }
533     
534     /**
535      * Convenience method. Tells the system to use Jaxen for XPath queries.
536      * @throws Exception if the Jaxen classes are not present.
537      */

538     static public void useJaxenXPathSupport() throws Exception JavaDoc {
539         Class.forName("org.jaxen.dom.DOMXPath");
540         Class JavaDoc c = Class.forName("freemarker.ext.dom.JaxenXPathSupport");
541         jaxenXPathSupport = (XPathSupport) c.newInstance();
542         if (logger.isDebugEnabled()) {
543             logger.debug("Using Jaxen classes for XPath support");
544         }
545         xpathSupportClass = c;
546     }
547     
548     /**
549      * Convenience method. Tells the system to use Xalan for XPath queries.
550      * @throws Exception if the Xalan XPath classes are not present.
551      */

552     static public void useXalanXPathSupport() throws Exception JavaDoc {
553         Class.forName("org.apache.xpath.XPath");
554         Class JavaDoc c = Class.forName("freemarker.ext.dom.XalanXPathSupport");
555         if (logger.isDebugEnabled()) {
556             logger.debug("Using Xalan classes for XPath support");
557         }
558         xpathSupportClass = c;
559     }
560
561     /**
562      * Set an alternative implementation of freemarker.ext.dom.XPathSupport to use
563      * as the XPath engine.
564      * @param cl the class, or <code>null</code> to disable XPath support.
565      */

566     static public void setXPathSupportClass(Class JavaDoc cl) {
567         if (cl != null && !cl.isAssignableFrom(XPathSupport.class)) {
568             throw new RuntimeException JavaDoc("Class " + cl.getName()
569                     + " does not implement freemarker.ext.dom.XPathSupport");
570         }
571         xpathSupportClass = cl;
572     }
573
574     /**
575      * Get the currently used freemarker.ext.dom.XPathSupport used as the XPath engine.
576      * Returns <code>null</code> if XPath support is disabled.
577      */

578     static public Class JavaDoc getXPathSupportClass() {
579         return xpathSupportClass;
580     }
581
582     static private String JavaDoc getText(Node JavaDoc node) {
583         String JavaDoc result = "";
584         if (node instanceof Text JavaDoc || node instanceof CDATASection JavaDoc) {
585             result = ((org.w3c.dom.CharacterData JavaDoc) node).getData();
586         }
587         else if (node instanceof Element JavaDoc) {
588             NodeList JavaDoc children = node.getChildNodes();
589             for (int i= 0; i<children.getLength(); i++) {
590                 result += getText(children.item(i));
591             }
592         }
593         else if (node instanceof Document JavaDoc) {
594             result = getText(((Document JavaDoc) node).getDocumentElement());
595         }
596         return result;
597     }
598     
599     XPathSupport getXPathSupport() {
600         if (jaxenXPathSupport != null) {
601             return jaxenXPathSupport;
602         }
603         XPathSupport xps = null;
604         Document JavaDoc doc = node.getOwnerDocument();
605         if (doc == null) {
606             doc = (Document JavaDoc) node;
607         }
608         synchronized (doc) {
609             WeakReference JavaDoc ref = (WeakReference JavaDoc) xpathSupportMap.get(doc);
610             if (ref != null) {
611                 xps = (XPathSupport) ref.get();
612             }
613             if (xps == null) {
614                 try {
615                     xps = (XPathSupport) xpathSupportClass.newInstance();
616                     xpathSupportMap.put(doc, new WeakReference JavaDoc(xps));
617                 } catch (Exception JavaDoc e) {
618                     logger.error("Error instantiating xpathSupport class");
619                 }
620             }
621         }
622         return xps;
623     }
624     
625     
626     String JavaDoc getQualifiedName() throws TemplateModelException {
627         return getNodeName();
628     }
629     
630     public Object JavaDoc getAdaptedObject(Class JavaDoc hint) {
631         return node;
632     }
633     
634     public Object JavaDoc getWrappedObject() {
635         return node;
636     }
637 }
638
Popular Tags