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 }