001 /* 002 // $Id: //open/util/resgen/src/org/eigenbase/xom/XMLOutput.java#5 $ 003 // Package org.eigenbase.xom is an XML Object Mapper. 004 // Copyright (C) 2005-2008 The Eigenbase Project 005 // Copyright (C) 2005-2008 Disruptive Tech 006 // Copyright (C) 2005-2008 LucidEra, Inc. 007 // Portions Copyright (C) 2000-2005 Kana Software, Inc. and others. 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 the 011 // Free Software Foundation; either version 2 of the License, or (at your 012 // option) any later version approved by The Eigenbase Project. 013 // 014 // This library is distributed in the hope that it will be useful, 015 // but WITHOUT ANY WARRANTY; without even the implied warranty of 016 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 // GNU Lesser General Public License for more details. 018 // 019 // You should have received a copy of the GNU Lesser General Public License 020 // along with this library; if not, write to the Free Software 021 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 022 // 023 // dsommerfield, 12 December, 2000 024 */ 025 026 package org.eigenbase.xom; 027 028 import java.io.*; 029 import java.util.Vector; 030 031 /** 032 * XMLOutput is a class which implements streaming XML output. Use this class 033 * to write XML to any streaming source. While the class itself is 034 * unstructured and doesn't enforce any DTD specification, use of the class 035 * does ensure that the output is syntactically valid XML. 036 */ 037 public class XMLOutput { 038 039 // This Writer is the underlying output stream to which all XML is 040 // written. 041 private PrintWriter out; 042 043 // The tagStack is maintained to check that tags are balanced. 044 private Vector tagStack; 045 046 // The class maintains an indentation level to improve output quality. 047 private int indent; 048 049 // The class also maintains the total number of tags written. This 050 // is used to monitor changes to the output 051 private int tagsWritten; 052 053 // This flag is set to true if the output should be compacted. 054 // Compacted output is free of extraneous whitespace and is designed 055 // for easier transport. 056 private boolean compact; 057 058 /** @see setIndentString **/ 059 private String indentString = "\t"; 060 061 /** @see setGlob **/ 062 private boolean glob; 063 064 /** 065 * Whether we have started but not finished a start tag. This only happens 066 * if <code>glob</code> is true. The start tag is automatically closed 067 * when we start a child node. If there are no child nodes, {@link #endTag} 068 * creates an empty tag. 069 **/ 070 private boolean inTag; 071 072 /** @see #setAlwaysQuoteCData */ 073 private boolean alwaysQuoteCData; 074 075 /** @see #setIgnorePcdata **/ 076 private boolean ignorePcdata; 077 078 /** 079 * Private helper function to display a degree of indentation 080 * @param out the PrintWriter to which to display output. 081 * @param indent the degree of indentation. 082 */ 083 private void displayIndent(PrintWriter out, int indent) 084 { 085 if(!compact) { 086 for (int i = 0; i < indent; i++) { 087 out.print(indentString); 088 } 089 } 090 } 091 092 /** 093 * Constructs a new XMLOutput based on any Writer. 094 * @param out the writer to which this XMLOutput generates results. 095 */ 096 public XMLOutput(Writer out) 097 { 098 this.out = new PrintWriter(out, true); 099 indent = 0; 100 tagsWritten = 0; 101 tagStack = new Vector(); 102 } 103 104 /** 105 * Sets or unsets the compact mode. Compact mode causes the generated 106 * XML to be free of extraneous whitespace and other unnecessary 107 * characters. 108 * 109 * @param compact true to turn on compact mode, or false to turn it off. 110 */ 111 public void setCompact(boolean compact) 112 { 113 this.compact = compact; 114 } 115 116 public boolean getCompact() 117 { 118 return compact; 119 } 120 121 /** 122 * Sets the string to print for each level of indentation. The default is a 123 * tab. The value must not be <code>null</code>. Set this to the empty 124 * string to achieve no indentation (note that <code>{@link 125 * #setCompact}(true)</code> removes indentation <em>and</em> newlines). 126 **/ 127 public void setIndentString(String indentString) 128 { 129 this.indentString = indentString; 130 } 131 132 /** 133 * Sets whether to detect that tags are empty. 134 **/ 135 public void setGlob(boolean glob) 136 { 137 this.glob = glob; 138 } 139 140 /** 141 * Sets whether to always quote cdata segments (even if they don't contain 142 * special characters). 143 **/ 144 public void setAlwaysQuoteCData(boolean alwaysQuoteCData) 145 { 146 this.alwaysQuoteCData = alwaysQuoteCData; 147 } 148 149 /** 150 * Sets whether to ignore unquoted text, such as whitespace. 151 **/ 152 public void setIgnorePcdata(boolean ignorePcdata) 153 { 154 this.ignorePcdata = ignorePcdata; 155 } 156 157 public boolean getIgnorePcdata() 158 { 159 return ignorePcdata; 160 } 161 162 /** 163 * Sends a string directly to the output stream, without escaping any 164 * characters. Use with caution! 165 **/ 166 public void print(String s) 167 { 168 out.print(s); 169 } 170 171 /** 172 * Start writing a new tag to the stream. The tag's name must be given and 173 * its attributes should be specified by a fully constructed AttrVector 174 * object. 175 * @param tagName the name of the tag to write. 176 * @param attributes an XMLAttrVector containing the attributes to include 177 * in the tag. 178 */ 179 public void beginTag(String tagName, XMLAttrVector attributes) 180 { 181 beginBeginTag(tagName); 182 if (attributes != null) { 183 attributes.display(out, indent); 184 } 185 endBeginTag(tagName); 186 } 187 188 public void beginBeginTag(String tagName) 189 { 190 if (inTag) { 191 // complete the parent's start tag 192 if (compact) { 193 out.print(">"); 194 } else { 195 out.println(">"); 196 } 197 inTag = false; 198 } 199 displayIndent(out, indent); 200 out.print("<"); 201 out.print(tagName); 202 } 203 204 public void endBeginTag(String tagName) 205 { 206 if (glob) { 207 inTag = true; 208 } else if (compact) { 209 out.print(">"); 210 } else { 211 out.println(">"); 212 } 213 out.flush(); 214 tagStack.addElement(tagName); 215 indent++; 216 tagsWritten++; 217 } 218 219 /** 220 * Write an attribute. 221 **/ 222 public void attribute(String name, String value) 223 { 224 XMLUtil.printAtt(out, name, value); 225 } 226 227 /** 228 * If we are currently inside the start tag, finish it off. 229 **/ 230 public void beginNode() 231 { 232 if (inTag) { 233 // complete the parent's start tag 234 if (compact) { 235 out.print(">"); 236 } else { 237 out.println(">"); 238 } 239 inTag = false; 240 } 241 } 242 243 /** 244 * Complete a tag. This outputs the end tag corresponding to the 245 * last exposed beginTag. The tag name must match the name of the 246 * corresponding beginTag. 247 * @param tagName the name of the end tag to write. 248 */ 249 public void endTag(String tagName) 250 { 251 // Check that the end tag matches the corresponding start tag 252 int stackSize = tagStack.size(); 253 String matchTag = (String)(tagStack.elementAt(stackSize-1)); 254 if(!tagName.equalsIgnoreCase(matchTag)) 255 throw new AssertFailure( 256 "End tag <" + tagName + "> does not match " + 257 " start tag <" + matchTag + ">"); 258 tagStack.removeElementAt(stackSize-1); 259 260 // Lower the indent and display the end tag 261 indent--; 262 if (inTag) { 263 // we're still in the start tag -- this element had no children 264 if (compact) { 265 out.print("/>"); 266 } else { 267 out.println("/>"); 268 } 269 inTag = false; 270 } else { 271 displayIndent(out, indent); 272 out.print("</"); 273 out.print(tagName); 274 if (compact) { 275 out.print(">"); 276 } else { 277 out.println(">"); 278 } 279 } 280 out.flush(); 281 } 282 283 /** 284 * Write an empty tag to the stream. An empty tag is one with no 285 * tags inside it, although it may still have attributes. 286 * @param tagName the name of the empty tag. 287 * @param attributes an XMLAttrVector containing the attributes to 288 * include in the tag. 289 */ 290 public void emptyTag(String tagName, XMLAttrVector attributes) 291 { 292 if (inTag) { 293 // complete the parent's start tag 294 if (compact) { 295 out.print(">"); 296 } else { 297 out.println(">"); 298 } 299 inTag = false; 300 } 301 displayIndent(out, indent); 302 out.print("<"); 303 out.print(tagName); 304 if(attributes != null) { 305 out.print(" "); 306 attributes.display(out, indent); 307 } 308 309 if(compact) 310 out.print("/>"); 311 else 312 out.println("/>"); 313 out.flush(); 314 tagsWritten++; 315 } 316 317 /** 318 * Write a CDATA section. Such sections always appear on their own line. 319 * The nature in which the CDATA section is written depends on the actual 320 * string content with respect to these special characters/sequences: 321 * <ul> 322 * <li><code>&</code> 323 * <li><code>"</code> 324 * <li><code>'</code> 325 * <li><code><</code> 326 * <li><code>></code> 327 * </ul> 328 * Additionally, the sequence <code>]]></code> is special. 329 * <ul> 330 * <li>Content containing no special characters will be left as-is. 331 * <li>Content containing one or more special characters but not the 332 * sequence <code>]]></code> will be enclosed in a CDATA section. 333 * <li>Content containing special characters AND at least one 334 * <code>]]></code> sequence will be left as-is but have all of its 335 * special characters encoded as entities. 336 * </ul> 337 * These special treatment rules are required to allow cdata sections 338 * to contain XML strings which may themselves contain cdata sections. 339 * Traditional CDATA sections <b>do not nest</b>. 340 */ 341 public void cdata(String data) 342 { 343 cdata(data, false); 344 } 345 346 /** 347 * Writes a CDATA section (as {@link #cdata(String)}). 348 * 349 * @param data string to write 350 * @param quote if true, quote in a <code><![CDATA[</code> 351 * ... <code>]]></code> regardless of the content of 352 * <code>data</code>; if false, quote only if the content needs it 353 **/ 354 public void cdata(String data, boolean quote) 355 { 356 if (inTag) { 357 // complete the parent's start tag 358 if (compact) { 359 out.print(">"); 360 } else { 361 out.println(">"); 362 } 363 inTag = false; 364 } 365 if (data == null) { 366 data = ""; 367 } 368 boolean specials = false; 369 boolean cdataEnd = false; 370 371 // Scan the string for special characters 372 // If special characters are found, scan the string for ']]>' 373 if(XOMUtil.stringHasXMLSpecials(data)) { 374 specials = true; 375 if(data.indexOf("]]>") > -1) 376 cdataEnd = true; 377 } 378 379 // Display the result 380 displayIndent(out, indent); 381 if (quote || alwaysQuoteCData) { 382 out.print("<![CDATA["); 383 out.print(data); 384 out.println("]]>"); 385 } else if (!specials && !cdataEnd) { 386 out.print(data); 387 } else { 388 XMLUtil.stringEncodeXML(data, out); 389 } 390 391 out.flush(); 392 tagsWritten++; 393 } 394 395 /** 396 * Write a String tag; a tag containing nothing but a CDATA section. 397 */ 398 public void stringTag(String name, String data) 399 { 400 beginTag(name, null); 401 cdata(data); 402 endTag(name); 403 } 404 405 /** 406 * Write content. 407 */ 408 public void content(String content) 409 { 410 if(content != null) { 411 indent++; 412 LineNumberReader in = new LineNumberReader(new StringReader(content)); 413 try { 414 String line; 415 while((line = in.readLine()) != null) { 416 displayIndent(out, indent); 417 out.println(line); 418 } 419 } catch (IOException ex) { 420 throw new AssertFailure(ex); 421 } 422 indent--; 423 out.flush(); 424 } 425 tagsWritten++; 426 } 427 428 /** 429 * Write header. Use default version 1.0. 430 */ 431 public void header() 432 { 433 out.println("<?xml version=\"1.0\" ?>"); 434 out.flush(); 435 tagsWritten++; 436 } 437 438 /** 439 * Write header, take version as input. 440 */ 441 public void header(String version) 442 { 443 out.print("<?xml version=\""); 444 out.print(version); 445 out.println("\" ?>"); 446 out.flush(); 447 tagsWritten++; 448 } 449 450 /** 451 * Get the total number of tags written 452 * @return the total number of tags written to the XML stream. 453 */ 454 public int numTagsWritten() 455 { 456 return tagsWritten; 457 } 458 459 } 460 461 462 // End XMLOutput.java