001: /* ========================================================================
002: * JCommon : a free general purpose class library for the Java(tm) platform
003: * ========================================================================
004: *
005: * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jcommon/index.html
008: *
009: * This library is free software; you can redistribute it and/or modify it
010: * under the terms of the GNU Lesser General Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but
015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017: * License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022: * USA.
023: *
024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025: * in the United States and other countries.]
026: *
027: * ---------------------
028: * XMLWriterSupport.java
029: * ---------------------
030: * (C)opyright 2003-2005, by Thomas Morgner and Contributors.
031: *
032: * Original Author: Thomas Morgner;
033: * Contributor(s): David Gilbert (for Object Refinery Limited);
034: *
035: * $Id: XMLWriterSupport.java,v 1.6 2005/11/08 14:35:52 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 21-Jun-2003 : Initial version (TM);
040: * 26-Nov-2003 : Updated Javadocs (DG);
041: *
042: */
043:
044: package org.jfree.xml.writer;
045:
046: import java.io.IOException;
047: import java.io.Writer;
048: import java.util.Enumeration;
049: import java.util.Iterator;
050: import java.util.Properties;
051:
052: /**
053: * A support class for writing XML files.
054: *
055: * @author Thomas Morgner
056: */
057: public class XMLWriterSupport {
058:
059: /** A constant for controlling the indent function. */
060: public static final int OPEN_TAG_INCREASE = 1;
061:
062: /** A constant for controlling the indent function. */
063: public static final int CLOSE_TAG_DECREASE = 2;
064:
065: /** A constant for controlling the indent function. */
066: public static final int INDENT_ONLY = 3;
067:
068: /** A constant for close. */
069: public static final boolean CLOSE = true;
070:
071: /** A constant for open. */
072: public static final boolean OPEN = false;
073:
074: /** The line separator. */
075: private static String lineSeparator;
076:
077: /** A list of safe tags. */
078: private SafeTagList safeTags;
079:
080: /** The indent level for that writer. */
081: private int indentLevel;
082:
083: /** The indent string. */
084: private String indentString;
085:
086: /**
087: * A flag indicating whether to force a linebreak before printing the next
088: * tag.
089: */
090: private boolean newLineOk;
091:
092: /**
093: * Default Constructor. The created XMLWriterSupport will not have no safe
094: * tags and starts with an indention level of 0.
095: */
096: public XMLWriterSupport() {
097: this (new SafeTagList(), 0);
098: }
099:
100: /**
101: * Creates a new support instance.
102: *
103: * @param safeTags tags that are safe for line breaks.
104: * @param indentLevel the index level.
105: */
106: public XMLWriterSupport(final SafeTagList safeTags,
107: final int indentLevel) {
108: this (safeTags, indentLevel, " ");
109: }
110:
111: /**
112: * Creates a new support instance.
113: *
114: * @param safeTags the tags that are safe for line breaks.
115: * @param indentLevel the indent level.
116: * @param indentString the indent string.
117: */
118: public XMLWriterSupport(final SafeTagList safeTags,
119: final int indentLevel, final String indentString) {
120: if (indentString == null) {
121: throw new NullPointerException(
122: "IndentString must not be null");
123: }
124:
125: this .safeTags = safeTags;
126: this .indentLevel = indentLevel;
127: this .indentString = indentString;
128: }
129:
130: /**
131: * Starts a new block by increasing the indent level.
132: *
133: * @throws IOException if an IO error occurs.
134: */
135: public void startBlock() throws IOException {
136: this .indentLevel++;
137: allowLineBreak();
138: }
139:
140: /**
141: * Ends the current block by decreasing the indent level.
142: *
143: * @throws IOException if an IO error occurs.
144: */
145: public void endBlock() throws IOException {
146: this .indentLevel--;
147: allowLineBreak();
148: }
149:
150: /**
151: * Forces a linebreak on the next call to writeTag or writeCloseTag.
152: *
153: * @throws IOException if an IO error occurs.
154: */
155: public void allowLineBreak() throws IOException {
156: this .newLineOk = true;
157: }
158:
159: /**
160: * Returns the line separator.
161: *
162: * @return the line separator.
163: */
164: public static String getLineSeparator() {
165: if (lineSeparator == null) {
166: try {
167: lineSeparator = System.getProperty("line.separator",
168: "\n");
169: } catch (SecurityException se) {
170: lineSeparator = "\n";
171: }
172: }
173: return lineSeparator;
174: }
175:
176: /**
177: * Writes an opening XML tag that has no attributes.
178: *
179: * @param w the writer.
180: * @param name the tag name.
181: *
182: * @throws java.io.IOException if there is an I/O problem.
183: */
184: public void writeTag(final Writer w, final String name)
185: throws IOException {
186: if (this .newLineOk) {
187: w.write(getLineSeparator());
188: }
189: indent(w, OPEN_TAG_INCREASE);
190:
191: w.write("<");
192: w.write(name);
193: w.write(">");
194: if (getSafeTags().isSafeForOpen(name)) {
195: w.write(getLineSeparator());
196: }
197: }
198:
199: /**
200: * Writes a closing XML tag.
201: *
202: * @param w the writer.
203: * @param tag the tag name.
204: *
205: * @throws java.io.IOException if there is an I/O problem.
206: */
207: public void writeCloseTag(final Writer w, final String tag)
208: throws IOException {
209: // check whether the tag contains CData - we ma not indent such tags
210: if (this .newLineOk || getSafeTags().isSafeForOpen(tag)) {
211: if (this .newLineOk) {
212: w.write(getLineSeparator());
213: }
214: indent(w, CLOSE_TAG_DECREASE);
215: } else {
216: decreaseIndent();
217: }
218: w.write("</");
219: w.write(tag);
220: w.write(">");
221: if (getSafeTags().isSafeForClose(tag)) {
222: w.write(getLineSeparator());
223: }
224: this .newLineOk = false;
225: }
226:
227: /**
228: * Writes an opening XML tag with an attribute/value pair.
229: *
230: * @param w the writer.
231: * @param name the tag name.
232: * @param attributeName the attribute name.
233: * @param attributeValue the attribute value.
234: * @param close controls whether the tag is closed.
235: *
236: * @throws java.io.IOException if there is an I/O problem.
237: */
238: public void writeTag(final Writer w, final String name,
239: final String attributeName, final String attributeValue,
240: final boolean close) throws IOException {
241: final AttributeList attr = new AttributeList();
242: if (attributeName != null) {
243: attr.setAttribute(attributeName, attributeValue);
244: }
245: writeTag(w, name, attr, close);
246: }
247:
248: /**
249: * Writes an opening XML tag along with a list of attribute/value pairs.
250: *
251: * @param w the writer.
252: * @param name the tag name.
253: * @param attributes the attributes.
254: * @param close controls whether the tag is closed.
255: *
256: * @throws java.io.IOException if there is an I/O problem.
257: * @deprecated use the attribute list instead of the properties.
258: */
259: public void writeTag(final Writer w, final String name,
260: final Properties attributes, final boolean close)
261: throws IOException {
262: final AttributeList attList = new AttributeList();
263: final Enumeration keys = attributes.keys();
264: while (keys.hasMoreElements()) {
265: final String key = (String) keys.nextElement();
266: attList.setAttribute(key, attributes.getProperty(key));
267: }
268: writeTag(w, name, attList, close);
269: }
270:
271: /**
272: * Writes an opening XML tag along with a list of attribute/value pairs.
273: *
274: * @param w the writer.
275: * @param name the tag name.
276: * @param attributes the attributes.
277: * @param close controls whether the tag is closed.
278: *
279: * @throws java.io.IOException if there is an I/O problem.
280: */
281: public void writeTag(final Writer w, final String name,
282: final AttributeList attributes, final boolean close)
283: throws IOException {
284:
285: if (this .newLineOk) {
286: w.write(getLineSeparator());
287: this .newLineOk = false;
288: }
289: indent(w, OPEN_TAG_INCREASE);
290:
291: w.write("<");
292: w.write(name);
293: final Iterator keys = attributes.keys();
294: while (keys.hasNext()) {
295: final String key = (String) keys.next();
296: final String value = attributes.getAttribute(key);
297: w.write(" ");
298: w.write(key);
299: w.write("=\"");
300: w.write(normalize(value));
301: w.write("\"");
302: }
303: if (close) {
304: w.write("/>");
305: if (getSafeTags().isSafeForClose(name)) {
306: w.write(getLineSeparator());
307: }
308: decreaseIndent();
309: } else {
310: w.write(">");
311: if (getSafeTags().isSafeForOpen(name)) {
312: w.write(getLineSeparator());
313: }
314: }
315: }
316:
317: /**
318: * Normalises a string, replacing certain characters with their escape
319: * sequences so that the XML text is not corrupted.
320: *
321: * @param s the string.
322: *
323: * @return the normalised string.
324: */
325: public static String normalize(final String s) {
326: if (s == null) {
327: return "";
328: }
329: final StringBuffer str = new StringBuffer();
330: final int len = s.length();
331:
332: for (int i = 0; i < len; i++) {
333: final char ch = s.charAt(i);
334:
335: switch (ch) {
336: case '<': {
337: str.append("<");
338: break;
339: }
340: case '>': {
341: str.append(">");
342: break;
343: }
344: case '&': {
345: str.append("&");
346: break;
347: }
348: case '"': {
349: str.append(""");
350: break;
351: }
352: case '\n': {
353: if (i > 0) {
354: final char lastChar = str.charAt(str.length() - 1);
355:
356: if (lastChar != '\r') {
357: str.append(getLineSeparator());
358: } else {
359: str.append('\n');
360: }
361: } else {
362: str.append(getLineSeparator());
363: }
364: break;
365: }
366: default: {
367: str.append(ch);
368: }
369: }
370: }
371:
372: return (str.toString());
373: }
374:
375: /**
376: * Indent the line. Called for proper indenting in various places.
377: *
378: * @param writer the writer which should receive the indentention.
379: * @param increase the current indent level.
380: * @throws java.io.IOException if writing the stream failed.
381: */
382: public void indent(final Writer writer, final int increase)
383: throws IOException {
384: if (increase == CLOSE_TAG_DECREASE) {
385: decreaseIndent();
386: }
387: for (int i = 0; i < this .indentLevel; i++) {
388: writer.write(this .indentString); // 4 spaces, we could also try tab,
389: // but I do not know whether this works
390: // with our XML edit pane
391: }
392: if (increase == OPEN_TAG_INCREASE) {
393: increaseIndent();
394: }
395: }
396:
397: /**
398: * Returns the current indent level.
399: *
400: * @return the current indent level.
401: */
402: public int getIndentLevel() {
403: return this .indentLevel;
404: }
405:
406: /**
407: * Increases the indention by one level.
408: */
409: protected void increaseIndent() {
410: this .indentLevel++;
411: }
412:
413: /**
414: * Decreates the indention by one level.
415: */
416: protected void decreaseIndent() {
417: this .indentLevel--;
418: }
419:
420: /**
421: * Returns the list of safe tags.
422: *
423: * @return The list.
424: */
425: public SafeTagList getSafeTags() {
426: return this.safeTags;
427: }
428: }
|