001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 /* 019 * $Id: DOM2SAX.java 469688 2006-10-31 22:39:43Z minchau $ 020 */ 021 022 023 package org.apache.xalan.xsltc.trax; 024 025 import java.io.IOException; 026 import java.util.ArrayList; 027 import java.util.Hashtable; 028 import java.util.List; 029 import java.util.Stack; 030 031 import org.apache.xalan.xsltc.dom.SAXImpl; 032 import org.w3c.dom.NamedNodeMap; 033 import org.w3c.dom.Node; 034 import org.xml.sax.ContentHandler; 035 import org.xml.sax.DTDHandler; 036 import org.xml.sax.EntityResolver; 037 import org.xml.sax.ErrorHandler; 038 import org.xml.sax.InputSource; 039 import org.xml.sax.Locator; 040 import org.xml.sax.SAXException; 041 import org.xml.sax.SAXNotRecognizedException; 042 import org.xml.sax.SAXNotSupportedException; 043 import org.xml.sax.XMLReader; 044 import org.xml.sax.ext.LexicalHandler; 045 import org.xml.sax.helpers.AttributesImpl; 046 047 /** 048 * @author G. Todd Miller 049 */ 050 public class DOM2SAX implements XMLReader, Locator { 051 052 private final static String EMPTYSTRING = ""; 053 private static final String XMLNS_PREFIX = "xmlns"; 054 055 private Node _dom = null; 056 private ContentHandler _sax = null; 057 private LexicalHandler _lex = null; 058 private SAXImpl _saxImpl = null; 059 private Hashtable _nsPrefixes = new Hashtable(); 060 061 public DOM2SAX(Node root) { 062 _dom = root; 063 } 064 065 public ContentHandler getContentHandler() { 066 return _sax; 067 } 068 069 public void setContentHandler(ContentHandler handler) throws 070 NullPointerException 071 { 072 _sax = handler; 073 if (handler instanceof LexicalHandler) { 074 _lex = (LexicalHandler) handler; 075 } 076 077 if (handler instanceof SAXImpl) { 078 _saxImpl = (SAXImpl)handler; 079 } 080 } 081 082 /** 083 * Begin the scope of namespace prefix. Forward the event to the 084 * SAX handler only if the prefix is unknown or it is mapped to a 085 * different URI. 086 */ 087 private boolean startPrefixMapping(String prefix, String uri) 088 throws SAXException 089 { 090 boolean pushed = true; 091 Stack uriStack = (Stack) _nsPrefixes.get(prefix); 092 093 if (uriStack != null) { 094 if (uriStack.isEmpty()) { 095 _sax.startPrefixMapping(prefix, uri); 096 uriStack.push(uri); 097 } 098 else { 099 final String lastUri = (String) uriStack.peek(); 100 if (!lastUri.equals(uri)) { 101 _sax.startPrefixMapping(prefix, uri); 102 uriStack.push(uri); 103 } 104 else { 105 pushed = false; 106 } 107 } 108 } 109 else { 110 _sax.startPrefixMapping(prefix, uri); 111 _nsPrefixes.put(prefix, uriStack = new Stack()); 112 uriStack.push(uri); 113 } 114 return pushed; 115 } 116 117 /* 118 * End the scope of a name prefix by popping it from the stack and 119 * passing the event to the SAX Handler. 120 */ 121 private void endPrefixMapping(String prefix) 122 throws SAXException 123 { 124 final Stack uriStack = (Stack) _nsPrefixes.get(prefix); 125 126 if (uriStack != null) { 127 _sax.endPrefixMapping(prefix); 128 uriStack.pop(); 129 } 130 } 131 132 /** 133 * If the DOM was created using a DOM 1.0 API, the local name may be 134 * null. If so, get the local name from the qualified name before 135 * generating the SAX event. 136 */ 137 private static String getLocalName(Node node) { 138 final String localName = node.getLocalName(); 139 140 if (localName == null) { 141 final String qname = node.getNodeName(); 142 final int col = qname.lastIndexOf(':'); 143 return (col > 0) ? qname.substring(col + 1) : qname; 144 } 145 return localName; 146 } 147 148 public void parse(InputSource unused) throws IOException, SAXException { 149 parse(_dom); 150 } 151 152 public void parse() throws IOException, SAXException { 153 if (_dom != null) { 154 boolean isIncomplete = 155 (_dom.getNodeType() != org.w3c.dom.Node.DOCUMENT_NODE); 156 157 if (isIncomplete) { 158 _sax.startDocument(); 159 parse(_dom); 160 _sax.endDocument(); 161 } 162 else { 163 parse(_dom); 164 } 165 } 166 } 167 168 /** 169 * Traverse the DOM and generate SAX events for a handler. A 170 * startElement() event passes all attributes, including namespace 171 * declarations. 172 */ 173 private void parse(Node node) throws IOException, SAXException { 174 Node first = null; 175 if (node == null) return; 176 177 switch (node.getNodeType()) { 178 case Node.ATTRIBUTE_NODE: // handled by ELEMENT_NODE 179 case Node.DOCUMENT_FRAGMENT_NODE: 180 case Node.DOCUMENT_TYPE_NODE : 181 case Node.ENTITY_NODE : 182 case Node.ENTITY_REFERENCE_NODE: 183 case Node.NOTATION_NODE : 184 // These node types are ignored!!! 185 break; 186 case Node.CDATA_SECTION_NODE: 187 final String cdata = node.getNodeValue(); 188 if (_lex != null) { 189 _lex.startCDATA(); 190 _sax.characters(cdata.toCharArray(), 0, cdata.length()); 191 _lex.endCDATA(); 192 } 193 else { 194 // in the case where there is no lex handler, we still 195 // want the text of the cdate to make its way through. 196 _sax.characters(cdata.toCharArray(), 0, cdata.length()); 197 } 198 break; 199 200 case Node.COMMENT_NODE: // should be handled!!! 201 if (_lex != null) { 202 final String value = node.getNodeValue(); 203 _lex.comment(value.toCharArray(), 0, value.length()); 204 } 205 break; 206 case Node.DOCUMENT_NODE: 207 _sax.setDocumentLocator(this); 208 209 _sax.startDocument(); 210 Node next = node.getFirstChild(); 211 while (next != null) { 212 parse(next); 213 next = next.getNextSibling(); 214 } 215 _sax.endDocument(); 216 break; 217 218 case Node.ELEMENT_NODE: 219 String prefix; 220 List pushedPrefixes = new ArrayList(); 221 final AttributesImpl attrs = new AttributesImpl(); 222 final NamedNodeMap map = node.getAttributes(); 223 final int length = map.getLength(); 224 225 // Process all namespace declarations 226 for (int i = 0; i < length; i++) { 227 final Node attr = map.item(i); 228 final String qnameAttr = attr.getNodeName(); 229 230 // Ignore everything but NS declarations here 231 if (qnameAttr.startsWith(XMLNS_PREFIX)) { 232 final String uriAttr = attr.getNodeValue(); 233 final int colon = qnameAttr.lastIndexOf(':'); 234 prefix = (colon > 0) ? qnameAttr.substring(colon + 1) : EMPTYSTRING; 235 if (startPrefixMapping(prefix, uriAttr)) { 236 pushedPrefixes.add(prefix); 237 } 238 } 239 } 240 241 // Process all other attributes 242 for (int i = 0; i < length; i++) { 243 final Node attr = map.item(i); 244 final String qnameAttr = attr.getNodeName(); 245 246 // Ignore NS declarations here 247 if (!qnameAttr.startsWith(XMLNS_PREFIX)) { 248 final String uriAttr = attr.getNamespaceURI(); 249 final String localNameAttr = getLocalName(attr); 250 251 // Uri may be implicitly declared 252 if (uriAttr != null) { 253 final int colon = qnameAttr.lastIndexOf(':'); 254 prefix = (colon > 0) ? qnameAttr.substring(0, colon) : EMPTYSTRING; 255 if (startPrefixMapping(prefix, uriAttr)) { 256 pushedPrefixes.add(prefix); 257 } 258 } 259 260 // Add attribute to list 261 attrs.addAttribute(attr.getNamespaceURI(), getLocalName(attr), 262 qnameAttr, "CDATA", attr.getNodeValue()); 263 } 264 } 265 266 // Now process the element itself 267 final String qname = node.getNodeName(); 268 final String uri = node.getNamespaceURI(); 269 final String localName = getLocalName(node); 270 271 // Uri may be implicitly declared 272 if (uri != null) { 273 final int colon = qname.lastIndexOf(':'); 274 prefix = (colon > 0) ? qname.substring(0, colon) : EMPTYSTRING; 275 if (startPrefixMapping(prefix, uri)) { 276 pushedPrefixes.add(prefix); 277 } 278 } 279 280 // Generate SAX event to start element 281 if (_saxImpl != null) { 282 _saxImpl.startElement(uri, localName, qname, attrs, node); 283 } 284 else { 285 _sax.startElement(uri, localName, qname, attrs); 286 } 287 288 // Traverse all child nodes of the element (if any) 289 next = node.getFirstChild(); 290 while (next != null) { 291 parse(next); 292 next = next.getNextSibling(); 293 } 294 295 // Generate SAX event to close element 296 _sax.endElement(uri, localName, qname); 297 298 // Generate endPrefixMapping() for all pushed prefixes 299 final int nPushedPrefixes = pushedPrefixes.size(); 300 for (int i = 0; i < nPushedPrefixes; i++) { 301 endPrefixMapping((String) pushedPrefixes.get(i)); 302 } 303 break; 304 305 case Node.PROCESSING_INSTRUCTION_NODE: 306 _sax.processingInstruction(node.getNodeName(), 307 node.getNodeValue()); 308 break; 309 310 case Node.TEXT_NODE: 311 final String data = node.getNodeValue(); 312 _sax.characters(data.toCharArray(), 0, data.length()); 313 break; 314 } 315 } 316 317 /** 318 * This class is only used internally so this method should never 319 * be called. 320 */ 321 public DTDHandler getDTDHandler() { 322 return null; 323 } 324 325 /** 326 * This class is only used internally so this method should never 327 * be called. 328 */ 329 public ErrorHandler getErrorHandler() { 330 return null; 331 } 332 333 /** 334 * This class is only used internally so this method should never 335 * be called. 336 */ 337 public boolean getFeature(String name) throws SAXNotRecognizedException, 338 SAXNotSupportedException 339 { 340 return false; 341 } 342 343 /** 344 * This class is only used internally so this method should never 345 * be called. 346 */ 347 public void setFeature(String name, boolean value) throws 348 SAXNotRecognizedException, SAXNotSupportedException 349 { 350 } 351 352 /** 353 * This class is only used internally so this method should never 354 * be called. 355 */ 356 public void parse(String sysId) throws IOException, SAXException { 357 throw new IOException("This method is not yet implemented."); 358 } 359 360 /** 361 * This class is only used internally so this method should never 362 * be called. 363 */ 364 public void setDTDHandler(DTDHandler handler) throws NullPointerException { 365 } 366 367 /** 368 * This class is only used internally so this method should never 369 * be called. 370 */ 371 public void setEntityResolver(EntityResolver resolver) throws 372 NullPointerException 373 { 374 } 375 376 /** 377 * This class is only used internally so this method should never 378 * be called. 379 */ 380 public EntityResolver getEntityResolver() { 381 return null; 382 } 383 384 /** 385 * This class is only used internally so this method should never 386 * be called. 387 */ 388 public void setErrorHandler(ErrorHandler handler) throws 389 NullPointerException 390 { 391 } 392 393 /** 394 * This class is only used internally so this method should never 395 * be called. 396 */ 397 public void setProperty(String name, Object value) throws 398 SAXNotRecognizedException, SAXNotSupportedException { 399 } 400 401 /** 402 * This class is only used internally so this method should never 403 * be called. 404 */ 405 public Object getProperty(String name) throws SAXNotRecognizedException, 406 SAXNotSupportedException 407 { 408 return null; 409 } 410 411 /** 412 * This class is only used internally so this method should never 413 * be called. 414 */ 415 public int getColumnNumber() { 416 return 0; 417 } 418 419 /** 420 * This class is only used internally so this method should never 421 * be called. 422 */ 423 public int getLineNumber() { 424 return 0; 425 } 426 427 /** 428 * This class is only used internally so this method should never 429 * be called. 430 */ 431 public String getPublicId() { 432 return null; 433 } 434 435 /** 436 * This class is only used internally so this method should never 437 * be called. 438 */ 439 public String getSystemId() { 440 return null; 441 } 442 443 // Debugging 444 private String getNodeTypeFromCode(short code) { 445 String retval = null; 446 switch (code) { 447 case Node.ATTRIBUTE_NODE : 448 retval = "ATTRIBUTE_NODE"; break; 449 case Node.CDATA_SECTION_NODE : 450 retval = "CDATA_SECTION_NODE"; break; 451 case Node.COMMENT_NODE : 452 retval = "COMMENT_NODE"; break; 453 case Node.DOCUMENT_FRAGMENT_NODE : 454 retval = "DOCUMENT_FRAGMENT_NODE"; break; 455 case Node.DOCUMENT_NODE : 456 retval = "DOCUMENT_NODE"; break; 457 case Node.DOCUMENT_TYPE_NODE : 458 retval = "DOCUMENT_TYPE_NODE"; break; 459 case Node.ELEMENT_NODE : 460 retval = "ELEMENT_NODE"; break; 461 case Node.ENTITY_NODE : 462 retval = "ENTITY_NODE"; break; 463 case Node.ENTITY_REFERENCE_NODE : 464 retval = "ENTITY_REFERENCE_NODE"; break; 465 case Node.NOTATION_NODE : 466 retval = "NOTATION_NODE"; break; 467 case Node.PROCESSING_INSTRUCTION_NODE : 468 retval = "PROCESSING_INSTRUCTION_NODE"; break; 469 case Node.TEXT_NODE: 470 retval = "TEXT_NODE"; break; 471 } 472 return retval; 473 } 474 }