001    /*
002     * Copyright (c) 2007 Henri Sivonen
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.saxtree;
024    
025    import java.util.LinkedList;
026    import java.util.List;
027    
028    import org.xml.sax.Attributes;
029    import org.xml.sax.ContentHandler;
030    import org.xml.sax.Locator;
031    import org.xml.sax.SAXException;
032    import org.xml.sax.ext.LexicalHandler;
033    
034    /**
035     * Builds a SAX Tree representation of a document or a fragment 
036     * streamed as <code>ContentHandler</code> and 
037     * <code>LexicalHandler</code> events. The start/end event matching 
038     * is expected to adhere to the SAX API contract. Things will 
039     * simply break if this is not the case. Fragments are expected to
040     * omit <code>startDocument()</code> and <code>endDocument()</code>
041     * calls.
042     * 
043     * @version $Id: TreeBuilder.java 150 2007-08-16 19:21:25Z hsivonen $
044     * @author hsivonen
045     */
046    public class TreeBuilder implements ContentHandler, LexicalHandler {
047    
048        private Locator locator;
049    
050        private ParentNode current;
051    
052        private final boolean retainAttributes;
053    
054        private List<PrefixMapping> prefixMappings;
055        
056        /**
057         * Constructs a reusable <code>TreeBuilder</code> that builds 
058         * <code>Document</code>s and copies attributes.
059         */
060        public TreeBuilder() {
061            this(false, false);
062        }
063        
064        /**
065         * The constructor. The instance will be reusabe if building a full 
066         * document and not reusable if building a fragment.
067         * 
068         * @param fragment whether this <code>TreeBuilder</code> should build 
069         * a <code>DocumentFragment</code> instead of a <code>Document</code>.
070         * @param retainAttributes whether instances of the <code>Attributes</code>
071         * interface passed to <code>startElement</code> should be retained 
072         * (the alternative is copying).
073         */
074        public TreeBuilder(boolean fragment, boolean retainAttributes) {
075            if (fragment) {
076                current = new DocumentFragment();
077            }
078            this.retainAttributes = retainAttributes;
079        }
080    
081        public void characters(char[] ch, int start, int length) throws SAXException {
082            current.appendChild(new Characters(locator, ch, start, length));
083        }
084    
085        public void endDocument() throws SAXException {
086            current.setEndLocator(locator);
087        }
088    
089        public void endElement(String uri, String localName, String qName) throws SAXException {
090            current.setEndLocator(locator);
091            current = current.getParentNode();
092        }
093    
094        public void endPrefixMapping(String prefix) throws SAXException {
095        }
096    
097        public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
098            current.appendChild(new IgnorableWhitespace(locator, ch, start, length));
099        }
100    
101        public void processingInstruction(String target, String data) throws SAXException {
102            current.appendChild(new ProcessingInstruction(locator, target, data));
103        }
104    
105        public void setDocumentLocator(Locator locator) {
106            this.locator = locator;
107        }
108    
109        public void skippedEntity(String name) throws SAXException {
110            current.appendChild(new SkippedEntity(locator, name));
111        }
112    
113        public void startDocument() throws SAXException {
114            current = new Document(locator);
115        }
116    
117        public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
118            current = (ParentNode) current.appendChild(new Element(locator, uri, localName, qName, atts, retainAttributes, prefixMappings));
119            prefixMappings = null;
120        }
121    
122        public void startPrefixMapping(String prefix, String uri) throws SAXException {
123            if (prefixMappings == null) {
124                prefixMappings = new LinkedList<PrefixMapping>();
125            }
126            prefixMappings.add(new PrefixMapping(prefix, uri));
127        }
128    
129        public void comment(char[] ch, int start, int length) throws SAXException {
130            current.appendChild(new Comment(locator, ch, start, length));
131        }
132    
133        public void endCDATA() throws SAXException {
134            current.setEndLocator(locator);
135            current = current.getParentNode();
136        }
137    
138        public void endDTD() throws SAXException {
139            current.setEndLocator(locator);
140            current = current.getParentNode();
141        }
142    
143        public void endEntity(String name) throws SAXException {
144            current.setEndLocator(locator);
145            current = current.getParentNode();
146        }
147    
148        public void startCDATA() throws SAXException {
149            current = (ParentNode) current.appendChild(new CDATA(locator));        
150        }
151    
152        public void startDTD(String name, String publicId, String systemId) throws SAXException {
153            current = (ParentNode) current.appendChild(new DTD(locator, name, publicId, systemId));        
154        }
155    
156        public void startEntity(String name) throws SAXException {
157            current = (ParentNode) current.appendChild(new Entity(locator, name));        
158        }
159    
160        /**
161         * Returns the root (<code>Document</code> if building a full document or 
162         * <code>DocumentFragment</code> if building a fragment.).
163         * 
164         * @return the root
165         */
166        public ParentNode getRoot() {
167            return current;
168        }
169    }