001    /*
002     * Copyright (c) 2007 Henri Sivonen
003     * Copyright (c) 2008 Mozilla Foundation
004     *
005     * Permission is hereby granted, free of charge, to any person obtaining a 
006     * copy of this software and associated documentation files (the "Software"), 
007     * to deal in the Software without restriction, including without limitation 
008     * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
009     * and/or sell copies of the Software, and to permit persons to whom the 
010     * Software is furnished to do so, subject to the following conditions:
011     *
012     * The above copyright notice and this permission notice shall be included in 
013     * all copies or substantial portions of the Software.
014     *
015     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
016     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
017     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
018     * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
019     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
020     * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
021     * DEALINGS IN THE SOFTWARE.
022     */
023    
024    package nu.validator.saxtree;
025    
026    import java.util.LinkedList;
027    import java.util.List;
028    
029    import org.xml.sax.Attributes;
030    import org.xml.sax.ContentHandler;
031    import org.xml.sax.Locator;
032    import org.xml.sax.SAXException;
033    import org.xml.sax.ext.LexicalHandler;
034    
035    /**
036     * Builds a SAX Tree representation of a document or a fragment 
037     * streamed as <code>ContentHandler</code> and 
038     * <code>LexicalHandler</code> events. The start/end event matching 
039     * is expected to adhere to the SAX API contract. Things will 
040     * simply break if this is not the case. Fragments are expected to
041     * omit <code>startDocument()</code> and <code>endDocument()</code>
042     * calls.
043     * 
044     * @version $Id$
045     * @author hsivonen
046     */
047    public class TreeBuilder implements ContentHandler, LexicalHandler {
048    
049        /**
050         * The locator.
051         */
052        private Locator locator;
053    
054        /**
055         * The current node.
056         */
057        private ParentNode current;
058    
059        /**
060         * Whether to retain attribute objects.
061         */
062        private final boolean retainAttributes;
063    
064        /**
065         * The prefix mappings for the next element to be inserted.
066         */
067        private List<PrefixMapping> prefixMappings;
068        
069        /**
070         * Constructs a reusable <code>TreeBuilder</code> that builds 
071         * <code>Document</code>s and copies attributes.
072         */
073        public TreeBuilder() {
074            this(false, false);
075        }
076        
077        /**
078         * The constructor. The instance will be reusabe if building a full 
079         * document and not reusable if building a fragment.
080         * 
081         * @param fragment whether this <code>TreeBuilder</code> should build 
082         * a <code>DocumentFragment</code> instead of a <code>Document</code>.
083         * @param retainAttributes whether instances of the <code>Attributes</code>
084         * interface passed to <code>startElement</code> should be retained 
085         * (the alternative is copying).
086         */
087        public TreeBuilder(boolean fragment, boolean retainAttributes) {
088            if (fragment) {
089                current = new DocumentFragment();
090            }
091            this.retainAttributes = retainAttributes;
092        }
093    
094        /**
095         * 
096         * @see org.xml.sax.ContentHandler#characters(char[], int, int)
097         */
098        public void characters(char[] ch, int start, int length) throws SAXException {
099            current.appendChild(new Characters(locator, ch, start, length));
100        }
101    
102        /**
103         * 
104         * @see org.xml.sax.ContentHandler#endDocument()
105         */
106        public void endDocument() throws SAXException {
107            current.setEndLocator(locator);
108        }
109    
110        /**
111         * 
112         * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
113         */
114        public void endElement(String uri, String localName, String qName) throws SAXException {
115            current.setEndLocator(locator);
116            current = current.getParentNode();
117        }
118    
119        /**
120         * 
121         * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
122         */
123        public void endPrefixMapping(String prefix) throws SAXException {
124        }
125    
126        /**
127         * 
128         * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
129         */
130        public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
131            current.appendChild(new IgnorableWhitespace(locator, ch, start, length));
132        }
133    
134        /**
135         * 
136         * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String, java.lang.String)
137         */
138        public void processingInstruction(String target, String data) throws SAXException {
139            current.appendChild(new ProcessingInstruction(locator, target, data));
140        }
141    
142        /**
143         * 
144         * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
145         */
146        public void setDocumentLocator(Locator locator) {
147            this.locator = locator;
148        }
149    
150        public void skippedEntity(String name) throws SAXException {
151            current.appendChild(new SkippedEntity(locator, name));
152        }
153    
154        /**
155         * 
156         * @see org.xml.sax.ContentHandler#startDocument()
157         */
158        public void startDocument() throws SAXException {
159            current = new Document(locator);
160        }
161    
162        /**
163         * 
164         * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
165         */
166        public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
167            current = (ParentNode) current.appendChild(new Element(locator, uri, localName, qName, atts, retainAttributes, prefixMappings));
168            prefixMappings = null;
169        }
170    
171        /**
172         * 
173         * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String, java.lang.String)
174         */
175        public void startPrefixMapping(String prefix, String uri) throws SAXException {
176            if (prefixMappings == null) {
177                prefixMappings = new LinkedList<PrefixMapping>();
178            }
179            prefixMappings.add(new PrefixMapping(prefix, uri));
180        }
181    
182        /**
183         * 
184         * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
185         */
186        public void comment(char[] ch, int start, int length) throws SAXException {
187            current.appendChild(new Comment(locator, ch, start, length));
188        }
189    
190        /**
191         * 
192         * @see org.xml.sax.ext.LexicalHandler#endCDATA()
193         */
194        public void endCDATA() throws SAXException {
195            current.setEndLocator(locator);
196            current = current.getParentNode();
197        }
198    
199        /**
200         * 
201         * @see org.xml.sax.ext.LexicalHandler#endDTD()
202         */
203        public void endDTD() throws SAXException {
204            current.setEndLocator(locator);
205            current = current.getParentNode();
206        }
207    
208        /**
209         * 
210         * @see org.xml.sax.ext.LexicalHandler#endEntity(java.lang.String)
211         */
212        public void endEntity(String name) throws SAXException {
213            current.setEndLocator(locator);
214            current = current.getParentNode();
215        }
216    
217        /**
218         * 
219         * @see org.xml.sax.ext.LexicalHandler#startCDATA()
220         */
221        public void startCDATA() throws SAXException {
222            current = (ParentNode) current.appendChild(new CDATA(locator));        
223        }
224    
225        /**
226         * 
227         * @see org.xml.sax.ext.LexicalHandler#startDTD(java.lang.String, java.lang.String, java.lang.String)
228         */
229        public void startDTD(String name, String publicId, String systemId) throws SAXException {
230            current = (ParentNode) current.appendChild(new DTD(locator, name, publicId, systemId));        
231        }
232    
233        /**
234         * 
235         * @see org.xml.sax.ext.LexicalHandler#startEntity(java.lang.String)
236         */
237        public void startEntity(String name) throws SAXException {
238            current = (ParentNode) current.appendChild(new Entity(locator, name));        
239        }
240    
241        /**
242         * Returns the root (<code>Document</code> if building a full document or 
243         * <code>DocumentFragment</code> if building a fragment.).
244         * 
245         * @return the root
246         */
247        public ParentNode getRoot() {
248            return current;
249        }
250    }