001    /*
002     * Copyright (c) 2008 Mozilla Foundation
003     *
004     * Permission is hereby granted, free of charge, to any person obtaining a 
005     * copy of this software and associated documentation files (the "Software"), 
006     * to deal in the Software without restriction, including without limitation 
007     * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
008     * and/or sell copies of the Software, and to permit persons to whom the 
009     * Software is furnished to do so, subject to the following conditions:
010     *
011     * The above copyright notice and this permission notice shall be included in 
012     * all copies or substantial portions of the Software.
013     *
014     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
015     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
016     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
017     * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
018     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
019     * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
020     * DEALINGS IN THE SOFTWARE.
021     */
022    
023    package nu.validator.htmlparser.dom;
024    
025    import org.w3c.dom.DocumentType;
026    import org.w3c.dom.NamedNodeMap;
027    import org.w3c.dom.Node;
028    import org.xml.sax.Attributes;
029    import org.xml.sax.ContentHandler;
030    import org.xml.sax.SAXException;
031    import org.xml.sax.ext.LexicalHandler;
032    
033    public class Dom2Sax {
034    
035        private static String emptyIfNull(String namespaceURI) {
036            return namespaceURI == null ? "" : namespaceURI;
037        }
038        
039        private final NamedNodeMapAttributes attributes = new NamedNodeMapAttributes();
040    
041        private final ContentHandler contentHandler;
042    
043        private final LexicalHandler lexicalHandler;
044    
045        /**
046         * @param contentHandler
047         * @param lexicalHandler
048         */
049        public Dom2Sax(ContentHandler contentHandler, LexicalHandler lexicalHandler) {
050            if (contentHandler == null) {
051                throw new IllegalArgumentException("ContentHandler must not be null.");
052            }
053            this.contentHandler = contentHandler;
054            this.lexicalHandler = lexicalHandler;
055        }
056    
057        public void parse(Node node) throws SAXException {
058            Node current = node;
059            Node next;
060            char[] buf;
061            for (;;) {
062                switch (current.getNodeType()) {
063                    case Node.ELEMENT_NODE:
064                        attributes.setNamedNodeMap(current.getAttributes());
065                        // To work around severe bogosity in the default DOM
066                        // impl, use the node name if local name is null.
067                        String localName = current.getLocalName();
068                        contentHandler.startElement(
069                                emptyIfNull(current.getNamespaceURI()),
070                                localName == null ? current.getNodeName()
071                                        : localName, null, attributes);
072                        attributes.clear();
073                        break;
074                    case Node.TEXT_NODE:
075                        buf = current.getNodeValue().toCharArray();
076                        contentHandler.characters(buf, 0, buf.length);
077                        break;
078                    case Node.CDATA_SECTION_NODE:
079                        if (lexicalHandler != null) {
080                            lexicalHandler.startCDATA();
081                        }
082                        buf = current.getNodeValue().toCharArray();
083                        contentHandler.characters(buf, 0, buf.length);
084                        if (lexicalHandler != null) {
085                            lexicalHandler.endCDATA();
086                        }
087                        break;
088                    case Node.COMMENT_NODE:
089                        if (lexicalHandler != null) {
090                            buf = current.getNodeValue().toCharArray();
091                            lexicalHandler.comment(buf, 0, buf.length);
092                        }
093                        break;
094                    case Node.DOCUMENT_NODE:
095                        contentHandler.startDocument();
096                        break;
097                    case Node.DOCUMENT_TYPE_NODE:
098                        if (lexicalHandler != null) {
099                            DocumentType doctype = (DocumentType) current;
100                            lexicalHandler.startDTD(doctype.getName(),
101                                    doctype.getPublicId(), doctype.getSystemId());
102                            lexicalHandler.endDTD();
103                        }
104                        break;
105                    case Node.PROCESSING_INSTRUCTION_NODE:
106                        contentHandler.processingInstruction(current.getNodeName(), current.getNodeValue());
107                        break;
108                    case Node.ENTITY_REFERENCE_NODE:
109                        contentHandler.skippedEntity(current.getNodeName());
110                        break;
111                }
112                if ((next = current.getFirstChild()) != null) {
113                    current = next;
114                    continue;
115                }
116                for (;;) {
117                    switch (current.getNodeType()) {
118                        case Node.ELEMENT_NODE:
119                            // To work around severe bogosity in the default DOM
120                            // impl, use the node name if local name is null.
121                            String localName = current.getLocalName();
122                            contentHandler.endElement(
123                                    emptyIfNull(current.getNamespaceURI()),
124                                    localName == null ? current.getNodeName()
125                                            : localName, null);
126                            break;
127                        case Node.DOCUMENT_NODE:
128                            contentHandler.endDocument();
129                            break;
130                    }
131                    if (current == node) {
132                        return;
133                    }
134                    if ((next = current.getNextSibling()) != null) {
135                        current = next;
136                        break;
137                    }
138                    current = current.getParentNode();
139                }
140            }
141        }
142    
143        private class NamedNodeMapAttributes implements Attributes {
144    
145            private NamedNodeMap map;
146            
147            private int length;
148            
149            public void setNamedNodeMap(NamedNodeMap attributes) {
150                this.map = attributes;
151                this.length = attributes.getLength();
152            }
153            
154            public void clear() {
155                this.map = null;
156            }
157    
158            public int getIndex(String qName) {
159                for (int i = 0; i < length; i++) {
160                    Node n = map.item(i);
161                    if (n.getNodeName().equals(qName)) {
162                        return i;
163                    }
164                }
165                return -1;
166            }
167    
168            public int getIndex(String uri, String localName) {
169                for (int i = 0; i < length; i++) {
170                    Node n = map.item(i);
171                    if (n.getLocalName().equals(localName) && emptyIfNull(n.getNamespaceURI()).equals(uri)) {
172                        return i;
173                    }
174                }
175                return -1;
176            }
177    
178            public int getLength() {
179                return length;
180            }
181    
182            public String getLocalName(int index) {
183                if (index < length && index >= 0) {
184                    return map.item(index).getLocalName();
185                } else {
186                    return null;
187                }
188            }
189    
190            public String getQName(int index) {
191                if (index < length && index >= 0) {
192                    return map.item(index).getNodeName();
193                } else {
194                    return null;
195                }
196            }
197    
198            public String getType(int index) {
199                if (index < length && index >= 0) {
200                    return "id".equals(map.item(index).getLocalName()) ? "ID" : "CDATA";
201                } else {
202                    return null;
203                }
204            }
205    
206            public String getType(String qName) {
207                int index = getIndex(qName);
208                if (index == -1) {
209                    return null;
210                } else {
211                    return getType(index);
212                }
213            }
214    
215            public String getType(String uri, String localName) {
216                int index = getIndex(uri, localName);
217                if (index == -1) {
218                    return null;
219                } else {
220                    return getType(index);
221                }
222            }
223    
224            public String getURI(int index) {
225                if (index < length && index >= 0) {
226                    return emptyIfNull(map.item(index).getNamespaceURI());
227                } else {
228                    return null;
229                }
230            }
231    
232            public String getValue(int index) {
233                if (index < length && index >= 0) {
234                    return map.item(index).getNodeValue();
235                } else {
236                    return null;
237                }
238            }
239    
240            public String getValue(String qName) {
241                int index = getIndex(qName);
242                if (index == -1) {
243                    return null;
244                } else {
245                    return getValue(index);
246                }
247            }
248    
249            public String getValue(String uri, String localName) {
250                int index = getIndex(uri, localName);
251                if (index == -1) {
252                    return null;
253                } else {
254                    return getValue(index);
255                }
256            }
257    
258        }
259    }