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, final int indentLevel) {
107 this(safeTags, indentLevel, " ");
108 }
109
110 /**
111 * Creates a new support instance.
112 *
113 * @param safeTags the tags that are safe for line breaks.
114 * @param indentLevel the indent level.
115 * @param indentString the indent string.
116 */
117 public XMLWriterSupport(final SafeTagList safeTags, final int indentLevel,
118 final String indentString) {
119 if (indentString == null) {
120 throw new NullPointerException("IndentString must not be null");
121 }
122
123 this.safeTags = safeTags;
124 this.indentLevel = indentLevel;
125 this.indentString = indentString;
126 }
127
128 /**
129 * Starts a new block by increasing the indent level.
130 *
131 * @throws IOException if an IO error occurs.
132 */
133 public void startBlock() throws IOException {
134 this.indentLevel++;
135 allowLineBreak();
136 }
137
138 /**
139 * Ends the current block by decreasing the indent level.
140 *
141 * @throws IOException if an IO error occurs.
142 */
143 public void endBlock() throws IOException {
144 this.indentLevel--;
145 allowLineBreak();
146 }
147
148 /**
149 * Forces a linebreak on the next call to writeTag or writeCloseTag.
150 *
151 * @throws IOException if an IO error occurs.
152 */
153 public void allowLineBreak() throws IOException {
154 this.newLineOk = true;
155 }
156
157 /**
158 * Returns the line separator.
159 *
160 * @return the line separator.
161 */
162 public static String getLineSeparator() {
163 if (lineSeparator == null) {
164 try {
165 lineSeparator = System.getProperty("line.separator", "\n");
166 }
167 catch (SecurityException se) {
168 lineSeparator = "\n";
169 }
170 }
171 return lineSeparator;
172 }
173
174 /**
175 * Writes an opening XML tag that has no attributes.
176 *
177 * @param w the writer.
178 * @param name the tag name.
179 *
180 * @throws java.io.IOException if there is an I/O problem.
181 */
182 public void writeTag(final Writer w, final String name) throws IOException {
183 if (this.newLineOk) {
184 w.write(getLineSeparator());
185 }
186 indent(w, OPEN_TAG_INCREASE);
187
188 w.write("<");
189 w.write(name);
190 w.write(">");
191 if (getSafeTags().isSafeForOpen(name)) {
192 w.write(getLineSeparator());
193 }
194 }
195
196 /**
197 * Writes a closing XML tag.
198 *
199 * @param w the writer.
200 * @param tag the tag name.
201 *
202 * @throws java.io.IOException if there is an I/O problem.
203 */
204 public void writeCloseTag(final Writer w, final String tag)
205 throws IOException {
206 // check whether the tag contains CData - we ma not indent such tags
207 if (this.newLineOk || getSafeTags().isSafeForOpen(tag)) {
208 if (this.newLineOk) {
209 w.write(getLineSeparator());
210 }
211 indent(w, CLOSE_TAG_DECREASE);
212 }
213 else {
214 decreaseIndent();
215 }
216 w.write("</");
217 w.write(tag);
218 w.write(">");
219 if (getSafeTags().isSafeForClose(tag)) {
220 w.write(getLineSeparator());
221 }
222 this.newLineOk = false;
223 }
224
225 /**
226 * Writes an opening XML tag with an attribute/value pair.
227 *
228 * @param w the writer.
229 * @param name the tag name.
230 * @param attributeName the attribute name.
231 * @param attributeValue the attribute value.
232 * @param close controls whether the tag is closed.
233 *
234 * @throws java.io.IOException if there is an I/O problem.
235 */
236 public void writeTag(final Writer w, final String name,
237 final String attributeName, final String attributeValue,
238 final boolean close) throws IOException {
239 final AttributeList attr = new AttributeList();
240 if (attributeName != null) {
241 attr.setAttribute(attributeName, attributeValue);
242 }
243 writeTag(w, name, attr, close);
244 }
245
246 /**
247 * Writes an opening XML tag along with a list of attribute/value pairs.
248 *
249 * @param w the writer.
250 * @param name the tag name.
251 * @param attributes the attributes.
252 * @param close controls whether the tag is closed.
253 *
254 * @throws java.io.IOException if there is an I/O problem.
255 * @deprecated use the attribute list instead of the properties.
256 */
257 public void writeTag(final Writer w, final String name,
258 final Properties attributes, final boolean close)
259 throws IOException {
260 final AttributeList attList = new AttributeList();
261 final Enumeration keys = attributes.keys();
262 while (keys.hasMoreElements()) {
263 final String key = (String) keys.nextElement();
264 attList.setAttribute(key, attributes.getProperty(key));
265 }
266 writeTag(w, name, attList, close);
267 }
268
269 /**
270 * Writes an opening XML tag along with a list of attribute/value pairs.
271 *
272 * @param w the writer.
273 * @param name the tag name.
274 * @param attributes the attributes.
275 * @param close controls whether the tag is closed.
276 *
277 * @throws java.io.IOException if there is an I/O problem.
278 */
279 public void writeTag(final Writer w, final String name,
280 final AttributeList attributes, final boolean close)
281 throws IOException {
282
283 if (this.newLineOk) {
284 w.write(getLineSeparator());
285 this.newLineOk = false;
286 }
287 indent(w, OPEN_TAG_INCREASE);
288
289 w.write("<");
290 w.write(name);
291 final Iterator keys = attributes.keys();
292 while (keys.hasNext()) {
293 final String key = (String) keys.next();
294 final String value = attributes.getAttribute(key);
295 w.write(" ");
296 w.write(key);
297 w.write("=\"");
298 w.write(normalize(value));
299 w.write("\"");
300 }
301 if (close) {
302 w.write("/>");
303 if (getSafeTags().isSafeForClose(name)) {
304 w.write(getLineSeparator());
305 }
306 decreaseIndent();
307 }
308 else {
309 w.write(">");
310 if (getSafeTags().isSafeForOpen(name)) {
311 w.write(getLineSeparator());
312 }
313 }
314 }
315
316 /**
317 * Normalises a string, replacing certain characters with their escape
318 * sequences so that the XML text is not corrupted.
319 *
320 * @param s the string.
321 *
322 * @return the normalised string.
323 */
324 public static String normalize(final String s) {
325 if (s == null) {
326 return "";
327 }
328 final StringBuffer str = new StringBuffer();
329 final int len = s.length();
330
331 for (int i = 0; i < len; i++) {
332 final char ch = s.charAt(i);
333
334 switch (ch) {
335 case '<':
336 {
337 str.append("<");
338 break;
339 }
340 case '>':
341 {
342 str.append(">");
343 break;
344 }
345 case '&':
346 {
347 str.append("&");
348 break;
349 }
350 case '"':
351 {
352 str.append(""");
353 break;
354 }
355 case '\n':
356 {
357 if (i > 0) {
358 final char lastChar = str.charAt(str.length() - 1);
359
360 if (lastChar != '\r') {
361 str.append(getLineSeparator());
362 }
363 else {
364 str.append('\n');
365 }
366 }
367 else {
368 str.append(getLineSeparator());
369 }
370 break;
371 }
372 default :
373 {
374 str.append(ch);
375 }
376 }
377 }
378
379 return (str.toString());
380 }
381
382 /**
383 * Indent the line. Called for proper indenting in various places.
384 *
385 * @param writer the writer which should receive the indentention.
386 * @param increase the current indent level.
387 * @throws java.io.IOException if writing the stream failed.
388 */
389 public void indent(final Writer writer, final int increase)
390 throws IOException {
391 if (increase == CLOSE_TAG_DECREASE) {
392 decreaseIndent();
393 }
394 for (int i = 0; i < this.indentLevel; i++) {
395 writer.write(this.indentString); // 4 spaces, we could also try tab,
396 // but I do not know whether this works
397 // with our XML edit pane
398 }
399 if (increase == OPEN_TAG_INCREASE) {
400 increaseIndent();
401 }
402 }
403
404 /**
405 * Returns the current indent level.
406 *
407 * @return the current indent level.
408 */
409 public int getIndentLevel() {
410 return this.indentLevel;
411 }
412
413 /**
414 * Increases the indention by one level.
415 */
416 protected void increaseIndent() {
417 this.indentLevel++;
418 }
419
420 /**
421 * Decreates the indention by one level.
422 */
423 protected void decreaseIndent() {
424 this.indentLevel--;
425 }
426
427 /**
428 * Returns the list of safe tags.
429 *
430 * @return The list.
431 */
432 public SafeTagList getSafeTags() {
433 return this.safeTags;
434 }
435 }