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 044package org.jfree.xml.writer; 045 046import java.io.IOException; 047import java.io.Writer; 048import java.util.Enumeration; 049import java.util.Iterator; 050import java.util.Properties; 051 052/** 053 * A support class for writing XML files. 054 * 055 * @author Thomas Morgner 056 */ 057public 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}