1 52 53 package freemarker.ext.dom; 54 55 56 import java.io.File ; 57 import java.io.IOException ; 58 import java.lang.ref.WeakReference ; 59 import java.util.Collections ; 60 import java.util.List ; 61 import java.util.Map ; 62 import java.util.WeakHashMap ; 63 64 import javax.xml.parsers.DocumentBuilder ; 65 import javax.xml.parsers.DocumentBuilderFactory ; 66 import javax.xml.parsers.ParserConfigurationException ; 67 68 import org.w3c.dom.Attr ; 69 import org.w3c.dom.CDATASection ; 70 import org.w3c.dom.CharacterData ; 71 import org.w3c.dom.Document ; 72 import org.w3c.dom.DocumentType ; 73 import org.w3c.dom.Element ; 74 import org.w3c.dom.Node ; 75 import org.w3c.dom.NodeList ; 76 import org.w3c.dom.ProcessingInstruction ; 77 import org.w3c.dom.Text ; 78 import org.xml.sax.ErrorHandler ; 79 import org.xml.sax.InputSource ; 80 import org.xml.sax.SAXException ; 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 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 docBuilderFactory; 105 106 static private Map xpathSupportMap = Collections.synchronizedMap(new WeakHashMap ()); 107 108 static private XPathSupport jaxenXPathSupport; 109 110 static private ErrorHandler errorHandler; 111 112 static Class xpathSupportClass; 113 114 static { 115 try { 116 useDefaultXPathSupport(); 117 } catch (Exception e) { 118 } 120 if (xpathSupportClass == null && logger.isWarnEnabled()) { 121 logger.warn("No XPath support is available."); 122 } 123 } 124 125 128 final Node node; 129 private TemplateSequenceModel children; 130 private NodeModel parent; 131 132 136 static public void setDocumentBuilderFactory(DocumentBuilderFactory docBuilderFactory) { 137 NodeModel.docBuilderFactory = docBuilderFactory; 138 } 139 140 144 static public DocumentBuilderFactory getDocumentBuilderFactory() { 145 if (docBuilderFactory == null) { 146 docBuilderFactory = DocumentBuilderFactory.newInstance(); 147 docBuilderFactory.setNamespaceAware(true); 148 docBuilderFactory.setIgnoringElementContentWhitespace(true); 149 } 150 return docBuilderFactory; 151 } 152 153 156 static public void setErrorHandler(ErrorHandler errorHandler) { 157 NodeModel.errorHandler = errorHandler; 158 } 159 160 168 static public NodeModel parse(InputSource is, boolean removeComments, boolean removePIs) 169 throws SAXException , IOException , ParserConfigurationException 170 { 171 DocumentBuilder builder = getDocumentBuilderFactory().newDocumentBuilder(); 172 if (errorHandler != null) builder.setErrorHandler(errorHandler); 173 Document 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 193 static public NodeModel parse(InputSource is) 194 throws SAXException , IOException , ParserConfigurationException { 195 return parse(is, true, true); 196 } 197 198 199 206 static public NodeModel parse(File f, boolean removeComments, boolean removePIs) 207 throws SAXException , IOException , ParserConfigurationException 208 { 209 DocumentBuilder builder = getDocumentBuilderFactory().newDocumentBuilder(); 210 if (errorHandler != null) builder.setErrorHandler(errorHandler); 211 Document 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 227 static public NodeModel parse(File f) 228 throws SAXException , IOException , ParserConfigurationException { 229 return parse(f, true, true); 230 } 231 232 protected NodeModel(Node node) { 233 this.node = node; 234 } 235 236 240 public Node getNode() { 241 return node; 242 } 243 244 public TemplateModel get(String 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 nsURI = node.getNamespaceURI(); 251 return nsURI == null ? null : new SimpleScalar(nsURI); 252 } 253 if (key.equals("@@local_name")) { 254 String localName = node.getLocalName(); 255 if (localName == null) { 256 localName = getNodeName(); 257 } 258 return new SimpleScalar(localName); 259 } 260 if (key.equals("@@markup")) { 261 StringBuffer buf = new StringBuffer (); 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 buf = new StringBuffer (); 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 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 parentNode = node.getParentNode(); 290 if (parentNode == null) { 291 if (node instanceof Attr ) { 292 parentNode = ((Attr ) 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 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 args) throws TemplateModelException { 327 if (args.size() != 1) { 328 throw new TemplateModelException("Expecting exactly one arguments"); 329 } 330 String query = (String ) args.get(0); 331 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 getNodeNamespace() { 346 int nodeType = node.getNodeType(); 347 if (nodeType != Node.ATTRIBUTE_NODE && nodeType != Node.ELEMENT_NODE) { 348 return null; 349 } 350 String 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 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 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 ) node); break; 376 case Node.ELEMENT_NODE : result = new ElementModel((Element ) node); break; 377 case Node.ATTRIBUTE_NODE : result = new AttributeNodeModel((Attr ) 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 ) node); break; 381 case Node.PROCESSING_INSTRUCTION_NODE : result = new PINodeModel((ProcessingInstruction ) node); break; 382 case Node.DOCUMENT_TYPE_NODE : result = new DocumentTypeModel((DocumentType ) node); break; 383 } 384 return result; 385 } 386 387 393 static public void removeComments(Node node) { 394 NodeList children = node.getChildNodes(); 395 int i = 0; 396 int len = children.getLength(); 397 while (i < len) { 398 Node 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 419 static public void removePIs(Node node) { 420 NodeList children = node.getChildNodes(); 421 int i = 0; 422 int len = children.getLength(); 423 while (i < len) { 424 Node 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 447 static public void mergeAdjacentText(Node node) { 448 Node child = node.getFirstChild(); 449 while (child != null) { 450 if (child instanceof Text || child instanceof CDATASection ) { 451 Node next = child.getNextSibling(); 452 if (next instanceof Text || next instanceof CDATASection ) { 453 String fullText = child.getNodeValue() + next.getNodeValue(); 454 ((CharacterData ) child).setData(fullText); 455 node.removeChild(next); 456 } 457 } 458 else { 459 mergeAdjacentText(child); 460 } 461 child = child.getNextSibling(); 462 } 463 } 464 465 469 static public void simplify(Node node) { 470 NodeList children = node.getChildNodes(); 471 int i = 0; 472 int len = children.getLength(); 473 Node prevTextChild = null; 474 while (i < len) { 475 Node 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 ptc = (CharacterData ) 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 ) { 508 return this; 509 } 510 else { 511 return wrap(node.getOwnerDocument()); 512 } 513 } 514 515 519 static public void useDefaultXPathSupport() { 520 xpathSupportClass = null; 521 jaxenXPathSupport = null; 522 try { 523 useXalanXPathSupport(); 524 } catch (Exception e) { 525 ; } 527 if (xpathSupportClass == null) try { 528 useJaxenXPathSupport(); 529 } catch (Exception e) { 530 ; } 532 } 533 534 538 static public void useJaxenXPathSupport() throws Exception { 539 Class.forName("org.jaxen.dom.DOMXPath"); 540 Class 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 552 static public void useXalanXPathSupport() throws Exception { 553 Class.forName("org.apache.xpath.XPath"); 554 Class 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 566 static public void setXPathSupportClass(Class cl) { 567 if (cl != null && !cl.isAssignableFrom(XPathSupport.class)) { 568 throw new RuntimeException ("Class " + cl.getName() 569 + " does not implement freemarker.ext.dom.XPathSupport"); 570 } 571 xpathSupportClass = cl; 572 } 573 574 578 static public Class getXPathSupportClass() { 579 return xpathSupportClass; 580 } 581 582 static private String getText(Node node) { 583 String result = ""; 584 if (node instanceof Text || node instanceof CDATASection ) { 585 result = ((org.w3c.dom.CharacterData ) node).getData(); 586 } 587 else if (node instanceof Element ) { 588 NodeList 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 ) { 594 result = getText(((Document ) 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 doc = node.getOwnerDocument(); 605 if (doc == null) { 606 doc = (Document ) node; 607 } 608 synchronized (doc) { 609 WeakReference ref = (WeakReference ) 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 (xps)); 617 } catch (Exception e) { 618 logger.error("Error instantiating xpathSupport class"); 619 } 620 } 621 } 622 return xps; 623 } 624 625 626 String getQualifiedName() throws TemplateModelException { 627 return getNodeName(); 628 } 629 630 public Object getAdaptedObject(Class hint) { 631 return node; 632 } 633 634 public Object getWrappedObject() { 635 return node; 636 } 637 } 638 | Popular Tags |