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    }