Source for org.jfree.xml.writer.XMLWriterSupport

   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:      */
  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:      */
 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("&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:      */
 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: }