001: /* ====================================================================
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 1997-2003 The Apache Software Foundation. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution,
020: * if any, must include the following acknowledgment:
021: * "This product includes software developed by the
022: * Apache Software Foundation (http://www.apache.org/)."
023: * Alternately, this acknowledgment may appear in the software
024: * itself, if and wherever such third-party acknowledgments
025: * normally appear.
026: *
027: * 4. The names "Jakarta", "Avalon", and "Apache Software Foundation"
028: * must not be used to endorse or promote products derived from this
029: * software without prior written permission. For written
030: * permission, please contact apache@apache.org.
031: *
032: * 5. Products derived from this software may not be called "Apache",
033: * nor may "Apache" appear in their name, without prior written
034: * permission of the Apache Software Foundation.
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
040: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: * ====================================================================
049: *
050: * This software consists of voluntary contributions made by many
051: * individuals on behalf of the Apache Software Foundation. For more
052: * information on the Apache Software Foundation, please see
053: * <http://www.apache.org/>.
054: */
055: package org.jicarilla.collections;
056:
057: import org.xml.sax.Attributes;
058: import org.xml.sax.ErrorHandler;
059: import org.xml.sax.Locator;
060: import org.xml.sax.SAXException;
061: import org.xml.sax.SAXParseException;
062:
063: import java.util.ArrayList;
064: import java.util.BitSet;
065: import java.util.Iterator;
066: import java.util.List;
067:
068: /**
069: * Helps {@link NodeBuilder} build {@link Node} Trees out of sax events.
070: *
071: * This class originally came from the Apache Avalon server framework at <a
072: * href="http://avalon.apache.org/">http://avalon.apache.org/</a>.
073: *
074: * @author <a href="lsimons at jicarilla dot org">Leo Simons</a>
075: * @author <a href="mailto:dev@avalon.apache.org">Avalon Development Team</a>
076: * @version $Id: DefaultXMLNodeHandler.java,v 1.2 2003/09/28 20:35:13 lsimons
077: * Exp $
078: */
079: public class DefaultXMLNodeHandler extends AbstractXMLNodeHandler
080: implements ErrorHandler {
081: /**
082: * Likely number of nested context items. If more is encountered the lists
083: * will grow automatically.
084: */
085: protected static final int EXPECTED_DEPTH = 5;
086: /** top level element trims space by default. */
087: protected final boolean PRESERVE_SPACE_BY_DEFAULT = false;
088:
089: protected final static String PRESERVE_SPACE_KEY = "xml:space";
090: protected final static String PRESERVE_SPACE_ON = "preserve";
091:
092: /** Element stack. */
093: protected final List m_elements = new ArrayList(EXPECTED_DEPTH);
094: /** Value stack. */
095: protected final List m_values = new ArrayList(EXPECTED_DEPTH);
096: /**
097: * Contains true at index <code>n</code> if space in the context with depth
098: * <code>n</code> is to be preserved.
099: */
100: protected final BitSet m_preserveSpace = new BitSet();
101: /** Root of the tree. */
102: protected DefaultNode m_top;
103: /** Where we are in the xml document. */
104: protected Locator m_locator;
105:
106: /**
107: * Get the node object that was built.
108: *
109: * @return a <code>TreeContext</code> object
110: */
111: public DefaultNode getNode() {
112: return m_top;
113: }
114:
115: /**
116: * Clears all data from this context handler.
117: */
118: public void recycle() {
119: m_elements.clear();
120: m_values.clear();
121: m_locator = null;
122: }
123:
124: /**
125: * Set the document <code>Locator</code> to use.
126: *
127: * @param locator a <code>Locator</code> value
128: */
129: public void setDocumentLocator(final Locator locator) {
130: m_locator = locator;
131: }
132:
133: /**
134: * Handling hook for character data.
135: *
136: * @param ch a <code>char[]</code> of data
137: * @param start offset in the character array from which to start reading
138: * @param end length of character data
139: *
140: * @throws org.xml.sax.SAXException if an error occurs
141: */
142: public void characters(final char[] ch, final int start,
143: final int end) throws SAXException {
144: // it is possible to play micro-optimization here by doing
145: // manual trimming and thus preserve some precious bits
146: // of memory, but it's really not important enough to justify
147: // resulting code complexity
148: final int depth = m_values.size() - 1;
149: final StringBuffer valueBuffer = (StringBuffer) m_values
150: .get(depth);
151: valueBuffer.append(ch, start, end);
152: }
153:
154: /**
155: * Handling hook for finishing parsing of an element.
156: *
157: * @param namespaceURI a <code>String</code> value
158: * @param localName a <code>String</code> value
159: * @param rawName a <code>String</code> value
160: *
161: * @throws org.xml.sax.SAXException if an error occurs
162: */
163: public void endElement(final String namespaceURI,
164: final String localName, final String rawName)
165: throws SAXException {
166: final int depth = m_elements.size() - 1;
167: final DefaultNode finishedNode = (DefaultNode) m_elements
168: .remove(depth);
169: final String accumulatedValue = m_values.remove(depth)
170: .toString();
171:
172: foundNodeValue(finishedNode, depth, accumulatedValue);
173:
174: if (0 == depth) {
175: m_top = finishedNode;
176: }
177: }
178:
179: protected void foundNodeValue(final DefaultNode node,
180: final int depth, final String accumulatedValue) {
181: if (node.childrenToList().size() == 0) {
182: // leaf node; always add contents
183: final String finishedValue;
184: if (m_preserveSpace.get(depth)) {
185: finishedValue = accumulatedValue;
186: } else if (0 == accumulatedValue.length()) {
187: finishedValue = null;
188: } else {
189: finishedValue = accumulatedValue.trim();
190: }
191: node.put(NodeBuilder.STRING_CONTENTS_CONTEXT_KEY,
192: finishedValue);
193: } else {
194: // has subnodes, only add contents if nonempty
195: final String trimmedValue = accumulatedValue.trim();
196: if (trimmedValue.length() > 0) {
197: node.put(NodeBuilder.STRING_CONTENTS_CONTEXT_KEY,
198: trimmedValue);
199: }
200: }
201: }
202:
203: /**
204: * Create a new <code>DefaultTreeContext</code> with the specified local
205: * name and location.
206: *
207: * @param localName a <code>String</code> value
208: * @param location a <code>String</code> value
209: *
210: * @return a <code>DefaultTreeContext</code> value
211: */
212: protected DefaultNode createNode(final String localName,
213: final String location) {
214: final DefaultNode n = new DefaultNode(localName);
215: n.put(NodeBuilder.LOCATION_CONTEXT_KEY, location);
216:
217: return n;
218: }
219:
220: /**
221: * Handling hook for starting parsing of an element.
222: *
223: * @param namespaceURI a <code>String</code> value
224: * @param localName a <code>String</code> value
225: * @param rawName a <code>String</code> value
226: * @param attributes an <code>Attributes</code> value
227: *
228: * @throws org.xml.sax.SAXException if an error occurs
229: */
230: public void startElement(final String namespaceURI,
231: final String localName, final String rawName,
232: final Attributes attributes) throws SAXException {
233: final DefaultNode node = createNode(rawName,
234: getLocationString());
235:
236: // depth of new node (not decrementing here, node is to be added)
237: final int depth = m_elements.size();
238:
239: // add to parent
240: final boolean hasParent = (depth > 0);
241: if (hasParent) {
242: addChildToParent((DefaultNode) m_elements.get(depth - 1),
243: node);
244: }
245:
246: // add to stack
247: m_elements.add(node);
248: m_values.add(new StringBuffer());
249:
250: // add attributes
251: final int attributesSize = attributes.getLength();
252: for (int i = 0; i < attributesSize; i++) {
253: final String name = attributes.getQName(i);
254: final String value = attributes.getValue(i);
255:
256: node.put(name, value);
257: }
258:
259: // spaces
260: handleSpacePreservation(node, hasParent);
261: }
262:
263: private void addChildToParent(final DefaultNode parent,
264: final DefaultNode node) {
265: parent.addChild(node);
266: }
267:
268: private void handleSpacePreservation(final DefaultNode node,
269: final boolean hasParent) {
270: // depth of new node (node has been added)
271: final int depth = m_elements.size() - 1;
272: boolean preserveSpace;
273:
274: if (hasParent) // inherits parent's space preservation policy
275: {
276: preserveSpace = m_preserveSpace.get(depth - 1);
277: } else {
278: preserveSpace = PRESERVE_SPACE_BY_DEFAULT;
279: }
280:
281: // look for xml:space attribute
282: final Iterator it = node.keySet().iterator();
283: while (it.hasNext()) {
284: final String name = (String) it.next();
285:
286: if (name.equals(PRESERVE_SPACE_KEY)) {
287: final String value = (String) node.get(name);
288: it.remove();
289: preserveSpace = PRESERVE_SPACE_ON.equals(value);
290: }
291: }
292:
293: // store result
294: if (preserveSpace) {
295: m_preserveSpace.set(depth);
296: } else {
297: m_preserveSpace.clear(depth);
298: }
299: }
300:
301: /**
302: * This just throws an exception on a parse error.
303: *
304: * @param exception the parse error
305: *
306: * @throws org.xml.sax.SAXException if an error occurs
307: */
308: public void error(final SAXParseException exception)
309: throws SAXException {
310: throw exception;
311: }
312:
313: /**
314: * This just throws an exception on a parse error.
315: *
316: * @param exception the parse error
317: *
318: * @throws org.xml.sax.SAXException if an error occurs
319: */
320: public void warning(final SAXParseException exception)
321: throws SAXException {
322: throw exception;
323: }
324:
325: /**
326: * This just throws an exception on a parse error.
327: *
328: * @param exception the parse error
329: *
330: * @throws org.xml.sax.SAXException if an error occurs
331: */
332: public void fatalError(final SAXParseException exception)
333: throws SAXException {
334: throw exception;
335: }
336:
337: /**
338: * Returns a string showing the current system ID, line number and column
339: * number.
340: *
341: * @return a <code>String</code> value
342: */
343: protected String getLocationString() {
344: if (null == m_locator) {
345: return "Unknown";
346: } else {
347: return m_locator.getSystemId() + ":"
348: + m_locator.getLineNumber() + ":"
349: + m_locator.getColumnNumber();
350: }
351: }
352: }
|