001: /*--
002:
003: Copyright (C) 2000 Brett McLaughlin & Jason Hunter.
004: All rights reserved.
005:
006: Redistribution and use in source and binary forms, with or without
007: modification, are permitted provided that the following conditions
008: are met:
009:
010: 1. Redistributions of source code must retain the above copyright
011: notice, this list of conditions, and the following disclaimer.
012:
013: 2. Redistributions in binary form must reproduce the above copyright
014: notice, this list of conditions, and the disclaimer that follows
015: these conditions in the documentation and/or other materials
016: provided with the distribution.
017:
018: 3. The name "JDOM" must not be used to endorse or promote products
019: derived from this software without prior written permission. For
020: written permission, please contact license@jdom.org.
021:
022: 4. Products derived from this software may not be called "JDOM", nor
023: may "JDOM" appear in their name, without prior written permission
024: from the JDOM Project Management (pm@jdom.org).
025:
026: In addition, we request (but do not require) that you include in the
027: end-user documentation provided with the redistribution and/or in the
028: software itself an acknowledgement equivalent to the following:
029: "This product includes software developed by the
030: JDOM Project (http://www.jdom.org/)."
031: Alternatively, the acknowledgment may be graphical using the logos
032: available at http://www.jdom.org/images/logos.
033:
034: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
035: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
036: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
037: DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
038: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
039: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
040: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
041: USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
042: ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
043: OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
044: OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
045: SUCH DAMAGE.
046:
047: This software consists of voluntary contributions made by many
048: individuals on behalf of the JDOM Project and was originally
049: created by Brett McLaughlin <brett@jdom.org> and
050: Jason Hunter <jhunter@jdom.org>. For more information on the
051: JDOM Project, please see <http://www.jdom.org/>.
052:
053: */
054: package sax;
055:
056: import java.util.Stack;
057:
058: import org.xml.sax.Attributes;
059: import org.xml.sax.SAXException;
060: import org.xml.sax.XMLReader;
061:
062: /**
063: * Filter for data- or field-oriented XML.
064: *
065: * <i>Code and comments adapted from DataWriter-0.2, written
066: * by David Megginson and released into the public domain,
067: * without warranty.</i>
068: *
069: * <p>This filter adds indentation and newlines to field-oriented
070: * XML without mixed content. All added indentation and newlines
071: * will be passed on down the filter chain.</p>
072: *
073: * <p>In general, all whitespace in an XML document is potentially
074: * significant. There is, however, a large class of XML documents
075: * where information is strictly fielded: each element contains either
076: * character data or other elements, but not both. For this special
077: * case, it is possible for a filter to provide automatic indentation
078: * and newlines. Note that this class will likely not yield appropriate
079: * results for document-oriented XML like XHTML pages, which mix character
080: * data and elements together.</p>
081: *
082: * <p>This filter will automatically place each start tag on a new line,
083: * optionally indented if an indent step is provided (by default, there
084: * is no indentation). If an element contains other elements, the end
085: * tag will also appear on a new line with leading indentation. Consider,
086: * for example, the following code:</p>
087: *
088: * <pre>
089: * DataFormatFilter df = new DataFormatFilter();
090: * df.setContentHandler(new XMLWriter());
091: *
092: * df.setIndentStep(2);
093: * df.startDocument();
094: * df.startElement("Person");
095: * df.dataElement("name", "Jane Smith");
096: * df.dataElement("date-of-birth", "1965-05-23");
097: * df.dataElement("citizenship", "US");
098: * df.endElement("Person");
099: * df.endDocument();
100: * </pre>
101: *
102: * <p>This code will produce the following document:</p>
103: *
104: * <pre>
105: * <?xml version="1.0"?>
106: *
107: * <Person>
108: * <name>Jane Smith</name>
109: * <date-of-birth>1965-05-23</date-of-birth>
110: * <citizenship>US</citizenship>
111: * </Person>
112: * </pre>
113: *
114: * @see DataUnformatFilter
115: */
116: public class DataFormatFilter extends XMLFilterBase {
117:
118: ////////////////////////////////////////////////////////////////////
119: // Constructors.
120: ////////////////////////////////////////////////////////////////////
121:
122: /**
123: * Create a new filter.
124: */
125: public DataFormatFilter() {
126: }
127:
128: /**
129: * Create a new filter.
130: *
131: * <p>Use the XMLReader provided as the source of events.</p>
132: *
133: * @param xmlreader The parent in the filter chain.
134: */
135: public DataFormatFilter(XMLReader xmlreader) {
136: super (xmlreader);
137: }
138:
139: ////////////////////////////////////////////////////////////////////
140: // Accessors and setters.
141: ////////////////////////////////////////////////////////////////////
142:
143: /**
144: * Return the current indent step.
145: *
146: * <p>Return the current indent step: each start tag will be
147: * indented by this number of spaces times the number of
148: * ancestors that the element has.</p>
149: *
150: * @return The number of spaces in each indentation step,
151: * or 0 or less for no indentation.
152: * @see #setIndentStep
153: */
154: public int getIndentStep() {
155: return indentStep;
156: }
157:
158: /**
159: * Set the current indent step.
160: *
161: * @param indentStep The new indent step (0 or less for no
162: * indentation).
163: * @see #getIndentStep
164: */
165: public void setIndentStep(int indentStep) {
166: this .indentStep = indentStep;
167: }
168:
169: ////////////////////////////////////////////////////////////////////
170: // Public methods.
171: ////////////////////////////////////////////////////////////////////
172:
173: /**
174: * Reset the filter so that it can be reused.
175: *
176: * <p>This method is especially useful if the filter failed
177: * with an exception the last time through.</p>
178: */
179: public void reset() {
180: state = SEEN_NOTHING;
181: stateStack = new Stack();
182: }
183:
184: ////////////////////////////////////////////////////////////////////
185: // Methods from org.xml.sax.ContentHandler.
186: ////////////////////////////////////////////////////////////////////
187:
188: /**
189: * Filter a start document event.
190: *
191: * <p>Reset state and pass the event on for further processing.</p>
192: *
193: * @exception org.xml.sax.SAXException If a filter
194: * further down the chain raises an exception.
195: * @see org.xml.sax.ContentHandler#startDocument
196: */
197: public void startDocument() throws SAXException {
198: reset();
199: super .startDocument();
200: }
201:
202: /**
203: * Add newline and indentation prior to start tag.
204: *
205: * <p>Each tag will begin on a new line, and will be
206: * indented by the current indent step times the number
207: * of ancestors that the element has.</p>
208: *
209: * <p>The newline and indentation will be passed on down
210: * the filter chain through regular characters events.</p>
211: *
212: * @param uri The element's Namespace URI.
213: * @param localName The element's local name.
214: * @param qName The element's qualified (prefixed) name.
215: * @param atts The element's attribute list.
216: * @exception org.xml.sax.SAXException If a filter
217: * further down the chain raises an exception.
218: * @see org.xml.sax.ContentHandler#startElement
219: */
220: public void startElement(String uri, String localName,
221: String qName, Attributes atts) throws SAXException {
222: if (!stateStack.empty()) {
223: doNewline();
224: doIndent();
225: }
226: stateStack.push(SEEN_ELEMENT);
227: state = SEEN_NOTHING;
228: super .startElement(uri, localName, qName, atts);
229: }
230:
231: /**
232: * Add newline and indentation prior to end tag.
233: *
234: * <p>If the element has contained other elements, the tag
235: * will appear indented on a new line; otherwise, it will
236: * appear immediately following whatever came before.</p>
237: *
238: * <p>The newline and indentation will be passed on down
239: * the filter chain through regular characters events.</p>
240: *
241: * @param uri The element's Namespace URI.
242: * @param localName The element's local name.
243: * @param qName The element's qualified (prefixed) name.
244: * @exception org.xml.sax.SAXException If a filter
245: * further down the chain raises an exception.
246: * @see org.xml.sax.ContentHandler#endElement
247: */
248: public void endElement(String uri, String localName, String qName)
249: throws SAXException {
250: boolean seenElement = (state == SEEN_ELEMENT);
251: state = stateStack.pop();
252: if (seenElement) {
253: doNewline();
254: doIndent();
255: }
256: super .endElement(uri, localName, qName);
257: }
258:
259: /**
260: * Filter a character data event.
261: *
262: * @param ch The characters to write.
263: * @param start The starting position in the array.
264: * @param length The number of characters to use.
265: * @exception org.xml.sax.SAXException If a filter
266: * further down the chain raises an exception.
267: * @see org.xml.sax.ContentHandler#characters
268: */
269: public void characters(char ch[], int start, int length)
270: throws SAXException {
271: state = SEEN_DATA;
272: super .characters(ch, start, length);
273: }
274:
275: ////////////////////////////////////////////////////////////////////
276: // Internal methods.
277: ////////////////////////////////////////////////////////////////////
278:
279: /**
280: * Add newline.
281: *
282: * @exception org.xml.sax.SAXException If a filter
283: * further down the chain raises an exception.
284: */
285: private void doNewline() throws SAXException {
286: super .characters(NEWLINE, 0, NEWLINE.length);
287: }
288:
289: /**
290: * Add indentation for the current level.
291: *
292: * @exception org.xml.sax.SAXException If a filter
293: * further down the chain raises an exception.
294: */
295: private void doIndent() throws SAXException {
296: int n = indentStep * stateStack.size();
297: if (n > 0) {
298: char ch[] = new char[n];
299: for (int i = 0; i < n; i++) {
300: ch[i] = INDENT_CHAR;
301: }
302: super .characters(ch, 0, n);
303: }
304: }
305:
306: ////////////////////////////////////////////////////////////////////
307: // Constants.
308: ////////////////////////////////////////////////////////////////////
309:
310: private static final Object SEEN_NOTHING = new Object();
311: private static final Object SEEN_ELEMENT = new Object();
312: private static final Object SEEN_DATA = new Object();
313:
314: private static final char[] NEWLINE = new char[] { '\n' };
315: private static final char INDENT_CHAR = ' ';
316:
317: ////////////////////////////////////////////////////////////////////
318: // Internal state.
319: ////////////////////////////////////////////////////////////////////
320:
321: private Object state = SEEN_NOTHING;
322: private Stack stateStack = new Stack();
323:
324: private int indentStep = 0;
325:
326: }
327:
328: // end of DataFormatFilter.java
|