1 /* ========================================================================
2  * JCommon : a free general purpose class library for the Java(tm) platform
3  * ========================================================================
4  *
5  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
6  *
7  * Project Info:  http://www.jfree.org/jcommon/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
22  * USA.
23  *
24  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
25  * in the United States and other countries.]
26  *
27  * ---------------------
28  * XMLWriterSupport.java
29  * ---------------------
30  * (C)opyright 2003-2005, by Thomas Morgner and Contributors.
31  *
32  * Original Author:  Thomas Morgner;
33  * Contributor(s):   David Gilbert (for Object Refinery Limited);
34  *
35  * $Id: XMLWriterSupport.java,v 1.6 2005/11/08 14:35:52 mungady Exp $
36  *
37  * Changes
38  * -------
39  * 21-Jun-2003 : Initial version (TM);
40  * 26-Nov-2003 : Updated Javadocs (DG);
41  *
42  */
43 
44 package org.jfree.xml.writer;
45 
46 import java.io.IOException;
47 import java.io.Writer;
48 import java.util.Enumeration;
49 import java.util.Iterator;
50 import java.util.Properties;
51 
52 /**
53  * A support class for writing XML files.
54  *
55  * @author Thomas Morgner
56  */
57 public class XMLWriterSupport {
58 
59     /** A constant for controlling the indent function. */
60     public static final int OPEN_TAG_INCREASE = 1;
61 
62     /** A constant for controlling the indent function. */
63     public static final int CLOSE_TAG_DECREASE = 2;
64 
65     /** A constant for controlling the indent function. */
66     public static final int INDENT_ONLY = 3;
67 
68     /** A constant for close. */
69     public static final boolean CLOSE = true;
70 
71     /** A constant for open. */
72     public static final boolean OPEN = false;
73 
74     /** The line separator. */
75     private static String lineSeparator;
76 
77     /** A list of safe tags. */
78     private SafeTagList safeTags;
79 
80     /** The indent level for that writer. */
81     private int indentLevel;
82 
83     /** The indent string. */
84     private String indentString;
85 
86     /**
87      * A flag indicating whether to force a linebreak before printing the next
88      * tag.
89      */
90     private boolean newLineOk;
91 
92     /**
93      * Default Constructor. The created XMLWriterSupport will not have no safe
94      * tags and starts with an indention level of 0.
95      */
XMLWriterSupport()96     public XMLWriterSupport() {
97         this(new SafeTagList(), 0);
98     }
99 
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      */
XMLWriterSupport(final SafeTagList safeTags, final int indentLevel)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      */
XMLWriterSupport(final SafeTagList safeTags, final int indentLevel, final String indentString)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      */
startBlock()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      */
endBlock()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      */
allowLineBreak()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      */
getLineSeparator()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      */
writeTag(final Writer w, final String name)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      */
writeCloseTag(final Writer w, final String tag)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      */
writeTag(final Writer w, final String name, final String attributeName, final String attributeValue, final boolean close)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      */
writeTag(final Writer w, final String name, final Properties attributes, final boolean close)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      */
writeTag(final Writer w, final String name, final AttributeList attributes, final boolean close)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      */
normalize(final String s)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("&lt;");
338                         break;
339                     }
340                 case '>':
341                     {
342                         str.append("&gt;");
343                         break;
344                     }
345                 case '&':
346                     {
347                         str.append("&amp;");
348                         break;
349                     }
350                 case '"':
351                     {
352                         str.append("&quot;");
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      */
indent(final Writer writer, final int increase)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      */
getIndentLevel()409     public int getIndentLevel() {
410         return this.indentLevel;
411     }
412 
413     /**
414      * Increases the indention by one level.
415      */
increaseIndent()416     protected void increaseIndent() {
417         this.indentLevel++;
418     }
419 
420     /**
421      * Decreates the indention by one level.
422      */
decreaseIndent()423     protected void decreaseIndent() {
424         this.indentLevel--;
425     }
426 
427     /**
428      * Returns the list of safe tags.
429      *
430      * @return The list.
431      */
getSafeTags()432     public SafeTagList getSafeTags() {
433         return this.safeTags;
434     }
435 }
436