1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18/* 19 * $Id: TreeWalker.java 468654 2006-10-28 07:09:23Z minchau $ 20 */ 21package org.apache.xml.serializer; 22 23import java.io.File; 24 25import org.apache.xml.serializer.utils.AttList; 26import org.apache.xml.serializer.utils.DOM2Helper; 27import org.w3c.dom.Comment; 28import org.w3c.dom.Element; 29import org.w3c.dom.EntityReference; 30import org.w3c.dom.NamedNodeMap; 31import org.w3c.dom.Node; 32import org.w3c.dom.ProcessingInstruction; 33import org.w3c.dom.Text; 34import org.xml.sax.ContentHandler; 35import org.xml.sax.Locator; 36import org.xml.sax.ext.LexicalHandler; 37import org.xml.sax.helpers.LocatorImpl; 38 39 40/** 41 * This class does a pre-order walk of the DOM tree, calling a ContentHandler 42 * interface as it goes. 43 * 44 * This class is a copy of the one in org.apache.xml.utils. 45 * It exists to cut the serializers dependancy on that package. 46 * 47 * @xsl.usage internal 48 */ 49 50public final class TreeWalker 51{ 52 53 /** Local reference to a ContentHandler */ 54 final private ContentHandler m_contentHandler; 55 /** 56 * If m_contentHandler is a SerializationHandler, then this is 57 * a reference to the same object. 58 */ 59 final private SerializationHandler m_Serializer; 60 61 // ARGHH!! JAXP Uses Xerces without setting the namespace processing to ON! 62 // DOM2Helper m_dh = new DOM2Helper(); 63 64 /** DomHelper for this TreeWalker */ 65 final protected DOM2Helper m_dh; 66 67 /** Locator object for this TreeWalker */ 68 final private LocatorImpl m_locator = new LocatorImpl(); 69 70 /** 71 * Get the ContentHandler used for the tree walk. 72 * 73 * @return the ContentHandler used for the tree walk 74 */ 75 public ContentHandler getContentHandler() 76 { 77 return m_contentHandler; 78 } 79 80 public TreeWalker(ContentHandler ch) { 81 this(ch,null); 82 } 83 /** 84 * Constructor. 85 * @param contentHandler The implemention of the 86 * contentHandler operation (toXMLString, digest, ...) 87 */ 88 public TreeWalker(ContentHandler contentHandler, String systemId) 89 { 90 // Set the content handler 91 m_contentHandler = contentHandler; 92 if (m_contentHandler instanceof SerializationHandler) { 93 m_Serializer = (SerializationHandler) m_contentHandler; 94 } 95 else 96 m_Serializer = null; 97 98 // Set the system ID, if it is given 99 m_contentHandler.setDocumentLocator(m_locator); 100 if (systemId != null) 101 m_locator.setSystemId(systemId); 102 else { 103 try { 104 // Bug see Bugzilla 26741 105 m_locator.setSystemId(System.getProperty("user.dir") + File.separator + "dummy.xsl"); 106 } 107 catch (SecurityException se) {// user.dir not accessible from applet 108 } 109 } 110 111 // Set the document locator 112 if (m_contentHandler != null) 113 m_contentHandler.setDocumentLocator(m_locator); 114 try { 115 // Bug see Bugzilla 26741 116 m_locator.setSystemId(System.getProperty("user.dir") + File.separator + "dummy.xsl"); 117 } 118 catch (SecurityException se){// user.dir not accessible from applet 119 120 } 121 m_dh = new DOM2Helper(); 122 } 123 124 /** 125 * Perform a pre-order traversal non-recursive style. 126 * 127 * Note that TreeWalker assumes that the subtree is intended to represent 128 * a complete (though not necessarily well-formed) document and, during a 129 * traversal, startDocument and endDocument will always be issued to the 130 * SAX listener. 131 * 132 * @param pos Node in the tree where to start traversal 133 * 134 * @throws TransformerException 135 */ 136 public void traverse(Node pos) throws org.xml.sax.SAXException 137 { 138 139 this.m_contentHandler.startDocument(); 140 141 Node top = pos; 142 143 while (null != pos) 144 { 145 startNode(pos); 146 147 Node nextNode = pos.getFirstChild(); 148 149 while (null == nextNode) 150 { 151 endNode(pos); 152 153 if (top.equals(pos)) 154 break; 155 156 nextNode = pos.getNextSibling(); 157 158 if (null == nextNode) 159 { 160 pos = pos.getParentNode(); 161 162 if ((null == pos) || (top.equals(pos))) 163 { 164 if (null != pos) 165 endNode(pos); 166 167 nextNode = null; 168 169 break; 170 } 171 } 172 } 173 174 pos = nextNode; 175 } 176 this.m_contentHandler.endDocument(); 177 } 178 179 /** 180 * Perform a pre-order traversal non-recursive style. 181 182 * Note that TreeWalker assumes that the subtree is intended to represent 183 * a complete (though not necessarily well-formed) document and, during a 184 * traversal, startDocument and endDocument will always be issued to the 185 * SAX listener. 186 * 187 * @param pos Node in the tree where to start traversal 188 * @param top Node in the tree where to end traversal 189 * 190 * @throws TransformerException 191 */ 192 public void traverse(Node pos, Node top) throws org.xml.sax.SAXException 193 { 194 195 this.m_contentHandler.startDocument(); 196 197 while (null != pos) 198 { 199 startNode(pos); 200 201 Node nextNode = pos.getFirstChild(); 202 203 while (null == nextNode) 204 { 205 endNode(pos); 206 207 if ((null != top) && top.equals(pos)) 208 break; 209 210 nextNode = pos.getNextSibling(); 211 212 if (null == nextNode) 213 { 214 pos = pos.getParentNode(); 215 216 if ((null == pos) || ((null != top) && top.equals(pos))) 217 { 218 nextNode = null; 219 220 break; 221 } 222 } 223 } 224 225 pos = nextNode; 226 } 227 this.m_contentHandler.endDocument(); 228 } 229 230 /** Flag indicating whether following text to be processed is raw text */ 231 boolean nextIsRaw = false; 232 233 /** 234 * Optimized dispatch of characters. 235 */ 236 private final void dispatachChars(Node node) 237 throws org.xml.sax.SAXException 238 { 239 if(m_Serializer != null) 240 { 241 this.m_Serializer.characters(node); 242 } 243 else 244 { 245 String data = ((Text) node).getData(); 246 this.m_contentHandler.characters(data.toCharArray(), 0, data.length()); 247 } 248 } 249 250 /** 251 * Start processing given node 252 * 253 * 254 * @param node Node to process 255 * 256 * @throws org.xml.sax.SAXException 257 */ 258 protected void startNode(Node node) throws org.xml.sax.SAXException 259 { 260 261// TODO: <REVIEW> 262// A Serializer implements ContentHandler, but not NodeConsumer 263// so drop this reference to NodeConsumer which would otherwise 264// pull in all sorts of things 265// if (m_contentHandler instanceof NodeConsumer) 266// { 267// ((NodeConsumer) m_contentHandler).setOriginatingNode(node); 268// } 269// TODO: </REVIEW> 270 271 if (node instanceof Locator) 272 { 273 Locator loc = (Locator)node; 274 m_locator.setColumnNumber(loc.getColumnNumber()); 275 m_locator.setLineNumber(loc.getLineNumber()); 276 m_locator.setPublicId(loc.getPublicId()); 277 m_locator.setSystemId(loc.getSystemId()); 278 } 279 else 280 { 281 m_locator.setColumnNumber(0); 282 m_locator.setLineNumber(0); 283 } 284 285 switch (node.getNodeType()) 286 { 287 case Node.COMMENT_NODE : 288 { 289 String data = ((Comment) node).getData(); 290 291 if (m_contentHandler instanceof LexicalHandler) 292 { 293 LexicalHandler lh = ((LexicalHandler) this.m_contentHandler); 294 295 lh.comment(data.toCharArray(), 0, data.length()); 296 } 297 } 298 break; 299 case Node.DOCUMENT_FRAGMENT_NODE : 300 301 // ??; 302 break; 303 case Node.DOCUMENT_NODE : 304 305 break; 306 case Node.ELEMENT_NODE : 307 Element elem_node = (Element) node; 308 { 309 // Make sure the namespace node 310 // for the element itself is declared 311 // to the ContentHandler 312 String uri = elem_node.getNamespaceURI(); 313 if (uri != null) { 314 String prefix = elem_node.getPrefix(); 315 if (prefix==null) 316 prefix=""; 317 this.m_contentHandler.startPrefixMapping(prefix,uri); 318 } 319 } 320 NamedNodeMap atts = elem_node.getAttributes(); 321 int nAttrs = atts.getLength(); 322 // System.out.println("TreeWalker#startNode: "+node.getNodeName()); 323 324 325 // Make sure the namespace node of 326 // each attribute is declared to the ContentHandler 327 for (int i = 0; i < nAttrs; i++) 328 { 329 final Node attr = atts.item(i); 330 final String attrName = attr.getNodeName(); 331 final int colon = attrName.indexOf(':'); 332 final String prefix; 333 334 // System.out.println("TreeWalker#startNode: attr["+i+"] = "+attrName+", "+attr.getNodeValue()); 335 if (attrName.equals("xmlns") || attrName.startsWith("xmlns:")) 336 { 337 // Use "" instead of null, as Xerces likes "" for the 338 // name of the default namespace. Fix attributed 339 // to "Steven Murray" <smurray@ebt.com>. 340 if (colon < 0) 341 prefix = ""; 342 else 343 prefix = attrName.substring(colon + 1); 344 345 this.m_contentHandler.startPrefixMapping(prefix, 346 attr.getNodeValue()); 347 } 348 else if (colon > 0) { 349 prefix = attrName.substring(0,colon); 350 String uri = attr.getNamespaceURI(); 351 if (uri != null) 352 this.m_contentHandler.startPrefixMapping(prefix,uri); 353 } 354 } 355 356 String ns = m_dh.getNamespaceOfNode(node); 357 if(null == ns) 358 ns = ""; 359 this.m_contentHandler.startElement(ns, 360 m_dh.getLocalNameOfNode(node), 361 node.getNodeName(), 362 new AttList(atts, m_dh)); 363 break; 364 case Node.PROCESSING_INSTRUCTION_NODE : 365 { 366 ProcessingInstruction pi = (ProcessingInstruction) node; 367 String name = pi.getNodeName(); 368 369 // String data = pi.getData(); 370 if (name.equals("xslt-next-is-raw")) 371 { 372 nextIsRaw = true; 373 } 374 else 375 { 376 this.m_contentHandler.processingInstruction(pi.getNodeName(), 377 pi.getData()); 378 } 379 } 380 break; 381 case Node.CDATA_SECTION_NODE : 382 { 383 boolean isLexH = (m_contentHandler instanceof LexicalHandler); 384 LexicalHandler lh = isLexH 385 ? ((LexicalHandler) this.m_contentHandler) : null; 386 387 if (isLexH) 388 { 389 lh.startCDATA(); 390 } 391 392 dispatachChars(node); 393 394 { 395 if (isLexH) 396 { 397 lh.endCDATA(); 398 } 399 } 400 } 401 break; 402 case Node.TEXT_NODE : 403 { 404 //String data = ((Text) node).getData(); 405 406 if (nextIsRaw) 407 { 408 nextIsRaw = false; 409 410 m_contentHandler.processingInstruction(javax.xml.transform.Result.PI_DISABLE_OUTPUT_ESCAPING, ""); 411 dispatachChars(node); 412 m_contentHandler.processingInstruction(javax.xml.transform.Result.PI_ENABLE_OUTPUT_ESCAPING, ""); 413 } 414 else 415 { 416 dispatachChars(node); 417 } 418 } 419 break; 420 case Node.ENTITY_REFERENCE_NODE : 421 { 422 EntityReference eref = (EntityReference) node; 423 424 if (m_contentHandler instanceof LexicalHandler) 425 { 426 ((LexicalHandler) this.m_contentHandler).startEntity( 427 eref.getNodeName()); 428 } 429 else 430 { 431 432 // warning("Can not output entity to a pure SAX ContentHandler"); 433 } 434 } 435 break; 436 default : 437 } 438 } 439 440 /** 441 * End processing of given node 442 * 443 * 444 * @param node Node we just finished processing 445 * 446 * @throws org.xml.sax.SAXException 447 */ 448 protected void endNode(Node node) throws org.xml.sax.SAXException 449 { 450 451 switch (node.getNodeType()) 452 { 453 case Node.DOCUMENT_NODE : 454 break; 455 456 case Node.ELEMENT_NODE : 457 String ns = m_dh.getNamespaceOfNode(node); 458 if(null == ns) 459 ns = ""; 460 this.m_contentHandler.endElement(ns, 461 m_dh.getLocalNameOfNode(node), 462 node.getNodeName()); 463 464 if (m_Serializer == null) { 465 // Don't bother with endPrefixMapping calls if the ContentHandler is a 466 // SerializationHandler because SerializationHandler's ignore the 467 // endPrefixMapping() calls anyways. . . . This is an optimization. 468 Element elem_node = (Element) node; 469 NamedNodeMap atts = elem_node.getAttributes(); 470 int nAttrs = atts.getLength(); 471 472 // do the endPrefixMapping calls in reverse order 473 // of the startPrefixMapping calls 474 for (int i = (nAttrs-1); 0 <= i; i--) 475 { 476 final Node attr = atts.item(i); 477 final String attrName = attr.getNodeName(); 478 final int colon = attrName.indexOf(':'); 479 final String prefix; 480 481 if (attrName.equals("xmlns") || attrName.startsWith("xmlns:")) 482 { 483 // Use "" instead of null, as Xerces likes "" for the 484 // name of the default namespace. Fix attributed 485 // to "Steven Murray" <smurray@ebt.com>. 486 if (colon < 0) 487 prefix = ""; 488 else 489 prefix = attrName.substring(colon + 1); 490 491 this.m_contentHandler.endPrefixMapping(prefix); 492 } 493 else if (colon > 0) { 494 prefix = attrName.substring(0, colon); 495 this.m_contentHandler.endPrefixMapping(prefix); 496 } 497 } 498 { 499 String uri = elem_node.getNamespaceURI(); 500 if (uri != null) { 501 String prefix = elem_node.getPrefix(); 502 if (prefix==null) 503 prefix=""; 504 this.m_contentHandler.endPrefixMapping(prefix); 505 } 506 } 507 } 508 break; 509 case Node.CDATA_SECTION_NODE : 510 break; 511 case Node.ENTITY_REFERENCE_NODE : 512 { 513 EntityReference eref = (EntityReference) node; 514 515 if (m_contentHandler instanceof LexicalHandler) 516 { 517 LexicalHandler lh = ((LexicalHandler) this.m_contentHandler); 518 519 lh.endEntity(eref.getNodeName()); 520 } 521 } 522 break; 523 default : 524 } 525 } 526} //TreeWalker 527 528