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 }