PageRenderTime 57ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/tags/2.3.18-gae/src/freemarker/ext/dom/NodeModel.java

#
Java | 652 lines | 452 code | 54 blank | 146 comment | 124 complexity | c174037acf6407f5bf5e3679f105bd38 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
  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. package freemarker.ext.dom;
  53. import java.io.File;
  54. import java.io.IOException;
  55. import java.lang.ref.WeakReference;
  56. import java.util.Collections;
  57. import java.util.List;
  58. import java.util.Map;
  59. import java.util.WeakHashMap;
  60. import javax.xml.parsers.DocumentBuilder;
  61. import javax.xml.parsers.DocumentBuilderFactory;
  62. import javax.xml.parsers.ParserConfigurationException;
  63. import org.w3c.dom.Attr;
  64. import org.w3c.dom.CDATASection;
  65. import org.w3c.dom.CharacterData;
  66. import org.w3c.dom.Document;
  67. import org.w3c.dom.DocumentType;
  68. import org.w3c.dom.Element;
  69. import org.w3c.dom.Node;
  70. import org.w3c.dom.NodeList;
  71. import org.w3c.dom.ProcessingInstruction;
  72. import org.w3c.dom.Text;
  73. import org.xml.sax.ErrorHandler;
  74. import org.xml.sax.InputSource;
  75. import org.xml.sax.SAXException;
  76. import freemarker.ext.util.WrapperTemplateModel;
  77. import freemarker.log.Logger;
  78. import freemarker.template.AdapterTemplateModel;
  79. import freemarker.template.SimpleScalar;
  80. import freemarker.template.TemplateHashModel;
  81. import freemarker.template.TemplateModel;
  82. import freemarker.template.TemplateModelException;
  83. import freemarker.template.TemplateNodeModel;
  84. import freemarker.template.TemplateSequenceModel;
  85. /**
  86. * A base class for wrapping a W3C DOM Node as a FreeMarker template model.
  87. * @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
  88. * @version $Id: NodeModel.java,v 1.80 2005/06/22 11:33:31 ddekany Exp $
  89. */
  90. abstract public class NodeModel
  91. implements TemplateNodeModel, TemplateHashModel, TemplateSequenceModel,
  92. AdapterTemplateModel, WrapperTemplateModel
  93. {
  94. static final Logger logger = Logger.getLogger("freemarker.dom");
  95. static private DocumentBuilderFactory docBuilderFactory;
  96. static private Map xpathSupportMap = Collections.synchronizedMap(new WeakHashMap());
  97. static private XPathSupport jaxenXPathSupport;
  98. static private ErrorHandler errorHandler;
  99. static Class xpathSupportClass;
  100. static {
  101. try {
  102. useDefaultXPathSupport();
  103. } catch (Exception e) {
  104. // do nothing
  105. }
  106. if (xpathSupportClass == null && logger.isWarnEnabled()) {
  107. logger.warn("No XPath support is available.");
  108. }
  109. }
  110. /**
  111. * The W3C DOM Node being wrapped.
  112. */
  113. final Node node;
  114. private TemplateSequenceModel children;
  115. private NodeModel parent;
  116. /**
  117. * Sets the DOM Parser implementation to be used when building NodeModel
  118. * objects from XML files.
  119. */
  120. static public void setDocumentBuilderFactory(DocumentBuilderFactory docBuilderFactory) {
  121. NodeModel.docBuilderFactory = docBuilderFactory;
  122. }
  123. /**
  124. * @return the DOM Parser implementation that is used when
  125. * building NodeModel objects from XML files.
  126. */
  127. static public DocumentBuilderFactory getDocumentBuilderFactory() {
  128. if (docBuilderFactory == null) {
  129. docBuilderFactory = DocumentBuilderFactory.newInstance();
  130. docBuilderFactory.setNamespaceAware(true);
  131. docBuilderFactory.setIgnoringElementContentWhitespace(true);
  132. }
  133. return docBuilderFactory;
  134. }
  135. /**
  136. * sets the error handler to use when parsing the document.
  137. */
  138. static public void setErrorHandler(ErrorHandler errorHandler) {
  139. NodeModel.errorHandler = errorHandler;
  140. }
  141. /**
  142. * Create a NodeModel from a SAX input source. Adjacent text nodes will be merged (and CDATA sections
  143. * are considered as text nodes).
  144. * @param removeComments whether to remove all comment nodes
  145. * (recursively) from the tree before processing
  146. * @param removePIs whether to remove all processing instruction nodes
  147. * (recursively from the tree before processing
  148. */
  149. static public NodeModel parse(InputSource is, boolean removeComments, boolean removePIs)
  150. throws SAXException, IOException, ParserConfigurationException
  151. {
  152. DocumentBuilder builder = getDocumentBuilderFactory().newDocumentBuilder();
  153. if (errorHandler != null) builder.setErrorHandler(errorHandler);
  154. Document doc = builder.parse(is);
  155. if (removeComments && removePIs) {
  156. simplify(doc);
  157. } else {
  158. if (removeComments) {
  159. removeComments(doc);
  160. }
  161. if (removePIs) {
  162. removePIs(doc);
  163. }
  164. mergeAdjacentText(doc);
  165. }
  166. return wrap(doc);
  167. }
  168. /**
  169. * Create a NodeModel from an XML input source. By default,
  170. * all comments and processing instruction nodes are
  171. * stripped from the tree.
  172. */
  173. static public NodeModel parse(InputSource is)
  174. throws SAXException, IOException, ParserConfigurationException {
  175. return parse(is, true, true);
  176. }
  177. /**
  178. * Create a NodeModel from an XML file.
  179. * @param removeComments whether to remove all comment nodes
  180. * (recursively) from the tree before processing
  181. * @param removePIs whether to remove all processing instruction nodes
  182. * (recursively from the tree before processing
  183. */
  184. static public NodeModel parse(File f, boolean removeComments, boolean removePIs)
  185. throws SAXException, IOException, ParserConfigurationException
  186. {
  187. DocumentBuilder builder = getDocumentBuilderFactory().newDocumentBuilder();
  188. if (errorHandler != null) builder.setErrorHandler(errorHandler);
  189. Document doc = builder.parse(f);
  190. if (removeComments) {
  191. removeComments(doc);
  192. }
  193. if (removePIs) {
  194. removePIs(doc);
  195. }
  196. mergeAdjacentText(doc);
  197. return wrap(doc);
  198. }
  199. /**
  200. * Create a NodeModel from an XML file. By default,
  201. * all comments and processing instruction nodes are
  202. * stripped from the tree.
  203. */
  204. static public NodeModel parse(File f)
  205. throws SAXException, IOException, ParserConfigurationException {
  206. return parse(f, true, true);
  207. }
  208. protected NodeModel(Node node) {
  209. this.node = node;
  210. }
  211. /**
  212. * @return the underling W3C DOM Node object that this TemplateNodeModel
  213. * is wrapping.
  214. */
  215. public Node getNode() {
  216. return node;
  217. }
  218. public TemplateModel get(String key) throws TemplateModelException {
  219. if (key.startsWith("@@")) {
  220. if (key.equals("@@text")) {
  221. return new SimpleScalar(getText(node));
  222. }
  223. if (key.equals("@@namespace")) {
  224. String nsURI = node.getNamespaceURI();
  225. return nsURI == null ? null : new SimpleScalar(nsURI);
  226. }
  227. if (key.equals("@@local_name")) {
  228. String localName = node.getLocalName();
  229. if (localName == null) {
  230. localName = getNodeName();
  231. }
  232. return new SimpleScalar(localName);
  233. }
  234. if (key.equals("@@markup")) {
  235. StringBuffer buf = new StringBuffer();
  236. NodeOutputter nu = new NodeOutputter(node);
  237. nu.outputContent(node, buf);
  238. return new SimpleScalar(buf.toString());
  239. }
  240. if (key.equals("@@nested_markup")) {
  241. StringBuffer buf = new StringBuffer();
  242. NodeOutputter nu = new NodeOutputter(node);
  243. nu.outputContent(node.getChildNodes(), buf);
  244. return new SimpleScalar(buf.toString());
  245. }
  246. if (key.equals("@@qname")) {
  247. String qname = getQualifiedName();
  248. return qname == null ? null : new SimpleScalar(qname);
  249. }
  250. }
  251. XPathSupport xps = getXPathSupport();
  252. if (xps != null) {
  253. return xps.executeQuery(node, key);
  254. } else {
  255. throw new TemplateModelException(
  256. "Can't try to resolve the XML query key, because no XPath support is available. "
  257. + "It's either malformed or an XPath expression: " + key);
  258. }
  259. }
  260. public TemplateNodeModel getParentNode() {
  261. if (parent == null) {
  262. Node parentNode = node.getParentNode();
  263. if (parentNode == null) {
  264. if (node instanceof Attr) {
  265. parentNode = ((Attr) node).getOwnerElement();
  266. }
  267. }
  268. parent = wrap(parentNode);
  269. }
  270. return parent;
  271. }
  272. public TemplateSequenceModel getChildNodes() {
  273. if (children == null) {
  274. children = new NodeListModel(node.getChildNodes(), this);
  275. }
  276. return children;
  277. }
  278. public final String getNodeType() throws TemplateModelException {
  279. short nodeType = node.getNodeType();
  280. switch (nodeType) {
  281. case Node.ATTRIBUTE_NODE : return "attribute";
  282. case Node.CDATA_SECTION_NODE : return "text";
  283. case Node.COMMENT_NODE : return "comment";
  284. case Node.DOCUMENT_FRAGMENT_NODE : return "document_fragment";
  285. case Node.DOCUMENT_NODE : return "document";
  286. case Node.DOCUMENT_TYPE_NODE : return "document_type";
  287. case Node.ELEMENT_NODE : return "element";
  288. case Node.ENTITY_NODE : return "entity";
  289. case Node.ENTITY_REFERENCE_NODE : return "entity_reference";
  290. case Node.NOTATION_NODE : return "notation";
  291. case Node.PROCESSING_INSTRUCTION_NODE : return "pi";
  292. case Node.TEXT_NODE : return "text";
  293. }
  294. throw new TemplateModelException("Unknown node type: " + nodeType + ". This should be impossible!");
  295. }
  296. public TemplateModel exec(List args) throws TemplateModelException {
  297. if (args.size() != 1) {
  298. throw new TemplateModelException("Expecting exactly one arguments");
  299. }
  300. String query = (String) args.get(0);
  301. // Now, we try to behave as if this is an XPath expression
  302. XPathSupport xps = getXPathSupport();
  303. if (xps == null) {
  304. throw new TemplateModelException("No XPath support available");
  305. }
  306. return xps.executeQuery(node, query);
  307. }
  308. public final int size() {return 1;}
  309. public final TemplateModel get(int i) {
  310. return i==0 ? this : null;
  311. }
  312. public String getNodeNamespace() {
  313. int nodeType = node.getNodeType();
  314. if (nodeType != Node.ATTRIBUTE_NODE && nodeType != Node.ELEMENT_NODE) {
  315. return null;
  316. }
  317. String result = node.getNamespaceURI();
  318. if (result == null && nodeType == Node.ELEMENT_NODE) {
  319. result = "";
  320. } else if ("".equals(result) && nodeType == Node.ATTRIBUTE_NODE) {
  321. result = null;
  322. }
  323. return result;
  324. }
  325. public final int hashCode() {
  326. return node.hashCode();
  327. }
  328. public boolean equals(Object other) {
  329. if (other == null) return false;
  330. return other.getClass() == this.getClass()
  331. && ((NodeModel) other).node.equals(this.node);
  332. }
  333. static public NodeModel wrap(Node node) {
  334. if (node == null) {
  335. return null;
  336. }
  337. NodeModel result = null;
  338. switch (node.getNodeType()) {
  339. case Node.DOCUMENT_NODE : result = new DocumentModel((Document) node); break;
  340. case Node.ELEMENT_NODE : result = new ElementModel((Element) node); break;
  341. case Node.ATTRIBUTE_NODE : result = new AttributeNodeModel((Attr) node); break;
  342. case Node.CDATA_SECTION_NODE :
  343. case Node.COMMENT_NODE :
  344. case Node.TEXT_NODE : result = new CharacterDataNodeModel((org.w3c.dom.CharacterData) node); break;
  345. case Node.PROCESSING_INSTRUCTION_NODE : result = new PINodeModel((ProcessingInstruction) node); break;
  346. case Node.DOCUMENT_TYPE_NODE : result = new DocumentTypeModel((DocumentType) node); break;
  347. }
  348. return result;
  349. }
  350. /**
  351. * Recursively removes all comment nodes
  352. * from the subtree.
  353. *
  354. * @see #simplify
  355. */
  356. static public void removeComments(Node node) {
  357. NodeList children = node.getChildNodes();
  358. int i = 0;
  359. int len = children.getLength();
  360. while (i < len) {
  361. Node child = children.item(i);
  362. if (child.hasChildNodes()) {
  363. removeComments(child);
  364. i++;
  365. } else {
  366. if (child.getNodeType() == Node.COMMENT_NODE) {
  367. node.removeChild(child);
  368. len--;
  369. } else {
  370. i++;
  371. }
  372. }
  373. }
  374. }
  375. /**
  376. * Recursively removes all processing instruction nodes
  377. * from the subtree.
  378. *
  379. * @see #simplify
  380. */
  381. static public void removePIs(Node node) {
  382. NodeList children = node.getChildNodes();
  383. int i = 0;
  384. int len = children.getLength();
  385. while (i < len) {
  386. Node child = children.item(i);
  387. if (child.hasChildNodes()) {
  388. removePIs(child);
  389. i++;
  390. } else {
  391. if (child.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
  392. node.removeChild(child);
  393. len--;
  394. } else {
  395. i++;
  396. }
  397. }
  398. }
  399. }
  400. /**
  401. * Merges adjacent text/cdata nodes, so that there are no
  402. * adjacent text/cdata nodes. Operates recursively
  403. * on the entire subtree. You thus lose information
  404. * about any CDATA sections occurring in the doc.
  405. *
  406. * @see #simplify
  407. */
  408. static public void mergeAdjacentText(Node node) {
  409. Node child = node.getFirstChild();
  410. while (child != null) {
  411. if (child instanceof Text || child instanceof CDATASection) {
  412. Node next = child.getNextSibling();
  413. if (next instanceof Text || next instanceof CDATASection) {
  414. String fullText = child.getNodeValue() + next.getNodeValue();
  415. ((CharacterData) child).setData(fullText);
  416. node.removeChild(next);
  417. }
  418. }
  419. else {
  420. mergeAdjacentText(child);
  421. }
  422. child = child.getNextSibling();
  423. }
  424. }
  425. /**
  426. * Removes comments and processing instruction, and then unites adjacent text nodes.
  427. * Note that CDATA sections count as text nodes.
  428. */
  429. static public void simplify(Node node) {
  430. NodeList children = node.getChildNodes();
  431. int i = 0;
  432. int len = children.getLength();
  433. Node prevTextChild = null;
  434. while (i < len) {
  435. Node child = children.item(i);
  436. if (child.hasChildNodes()) {
  437. simplify(child);
  438. prevTextChild = null;
  439. i++;
  440. } else {
  441. int type = child.getNodeType();
  442. if (type == Node.PROCESSING_INSTRUCTION_NODE) {
  443. node.removeChild(child);
  444. len--;
  445. } else if (type == Node.COMMENT_NODE) {
  446. node.removeChild(child);
  447. len--;
  448. } else if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE ) {
  449. if (prevTextChild != null) {
  450. CharacterData ptc = (CharacterData) prevTextChild;
  451. ptc.setData(ptc.getNodeValue() + child.getNodeValue());
  452. node.removeChild(child);
  453. len--;
  454. } else {
  455. prevTextChild = child;
  456. i++;
  457. }
  458. } else {
  459. prevTextChild = null;
  460. i++;
  461. }
  462. }
  463. }
  464. }
  465. NodeModel getDocumentNodeModel() {
  466. if (node instanceof Document) {
  467. return this;
  468. }
  469. else {
  470. return wrap(node.getOwnerDocument());
  471. }
  472. }
  473. /**
  474. * Tells the system to use (restore) the default (initial) XPath system used by
  475. * this FreeMarker version on this system.
  476. */
  477. static public void useDefaultXPathSupport() {
  478. xpathSupportClass = null;
  479. jaxenXPathSupport = null;
  480. try {
  481. useXalanXPathSupport();
  482. } catch (Exception e) {
  483. ; // ignore
  484. }
  485. if (xpathSupportClass == null) try {
  486. useSunInternalXPathSupport();
  487. } catch (Exception e) {
  488. ; // ignore
  489. }
  490. if (xpathSupportClass == null) try {
  491. useJaxenXPathSupport();
  492. } catch (Exception e) {
  493. ; // ignore
  494. }
  495. }
  496. /**
  497. * Convenience method. Tells the system to use Jaxen for XPath queries.
  498. * @throws Exception if the Jaxen classes are not present.
  499. */
  500. static public void useJaxenXPathSupport() throws Exception {
  501. Class.forName("org.jaxen.dom.DOMXPath");
  502. Class c = Class.forName("freemarker.ext.dom.JaxenXPathSupport");
  503. jaxenXPathSupport = (XPathSupport) c.newInstance();
  504. if (logger.isDebugEnabled()) {
  505. logger.debug("Using Jaxen classes for XPath support");
  506. }
  507. xpathSupportClass = c;
  508. }
  509. /**
  510. * Convenience method. Tells the system to use Xalan for XPath queries.
  511. * @throws Exception if the Xalan XPath classes are not present.
  512. */
  513. static public void useXalanXPathSupport() throws Exception {
  514. Class.forName("org.apache.xpath.XPath");
  515. Class c = Class.forName("freemarker.ext.dom.XalanXPathSupport");
  516. if (logger.isDebugEnabled()) {
  517. logger.debug("Using Xalan classes for XPath support");
  518. }
  519. xpathSupportClass = c;
  520. }
  521. static public void useSunInternalXPathSupport() throws Exception {
  522. Class.forName("com.sun.org.apache.xpath.internal.XPath");
  523. Class c = Class.forName("freemarker.ext.dom.SunInternalXalanXPathSupport");
  524. if (logger.isDebugEnabled()) {
  525. logger.debug("Using Sun's internal Xalan classes for XPath support");
  526. }
  527. xpathSupportClass = c;
  528. }
  529. /**
  530. * Set an alternative implementation of freemarker.ext.dom.XPathSupport to use
  531. * as the XPath engine.
  532. * @param cl the class, or <code>null</code> to disable XPath support.
  533. */
  534. static public void setXPathSupportClass(Class cl) {
  535. if (cl != null && !XPathSupport.class.isAssignableFrom(cl)) {
  536. throw new RuntimeException("Class " + cl.getName()
  537. + " does not implement freemarker.ext.dom.XPathSupport");
  538. }
  539. xpathSupportClass = cl;
  540. }
  541. /**
  542. * Get the currently used freemarker.ext.dom.XPathSupport used as the XPath engine.
  543. * Returns <code>null</code> if XPath support is disabled.
  544. */
  545. static public Class getXPathSupportClass() {
  546. return xpathSupportClass;
  547. }
  548. static private String getText(Node node) {
  549. String result = "";
  550. if (node instanceof Text || node instanceof CDATASection) {
  551. result = ((org.w3c.dom.CharacterData) node).getData();
  552. }
  553. else if (node instanceof Element) {
  554. NodeList children = node.getChildNodes();
  555. for (int i= 0; i<children.getLength(); i++) {
  556. result += getText(children.item(i));
  557. }
  558. }
  559. else if (node instanceof Document) {
  560. result = getText(((Document) node).getDocumentElement());
  561. }
  562. return result;
  563. }
  564. XPathSupport getXPathSupport() {
  565. if (jaxenXPathSupport != null) {
  566. return jaxenXPathSupport;
  567. }
  568. XPathSupport xps = null;
  569. Document doc = node.getOwnerDocument();
  570. if (doc == null) {
  571. doc = (Document) node;
  572. }
  573. synchronized (doc) {
  574. WeakReference ref = (WeakReference) xpathSupportMap.get(doc);
  575. if (ref != null) {
  576. xps = (XPathSupport) ref.get();
  577. }
  578. if (xps == null) {
  579. try {
  580. xps = (XPathSupport) xpathSupportClass.newInstance();
  581. xpathSupportMap.put(doc, new WeakReference(xps));
  582. } catch (Exception e) {
  583. logger.error("Error instantiating xpathSupport class", e);
  584. }
  585. }
  586. }
  587. return xps;
  588. }
  589. String getQualifiedName() throws TemplateModelException {
  590. return getNodeName();
  591. }
  592. public Object getAdaptedObject(Class hint) {
  593. return node;
  594. }
  595. public Object getWrappedObject() {
  596. return node;
  597. }
  598. }