001 /* 002 * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons-sandbox//xmlio/src/java/org/apache/commons/xmlio/out/XMLWriter.java,v 1.1 2004/10/08 11:56:20 ozeigermann Exp $ 003 * $Revision: 155476 $ 004 * $Date: 2005-02-26 13:31:24 +0000 (Sat, 26 Feb 2005) $ 005 * 006 * ==================================================================== 007 * 008 * Copyright 2004 The Apache Software Foundation 009 * 010 * Licensed under the Apache License, Version 2.0 (the "License"); 011 * you may not use this file except in compliance with the License. 012 * You may obtain a copy of the License at 013 * 014 * http://www.apache.org/licenses/LICENSE-2.0 015 * 016 * Unless required by applicable law or agreed to in writing, software 017 * distributed under the License is distributed on an "AS IS" BASIS, 018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 019 * See the License for the specific language governing permissions and 020 * limitations under the License. 021 * 022 */ 023 024 package org.apache.commons.xmlio.out; 025 026 import java.io.*; 027 028 import org.xml.sax.Attributes; 029 030 /** 031 * {@link FilterWriter} adding formatted and encoded XML export 032 * functionality to the underlying writer. Formatting and 033 * encoding is done as straight forward as possible. <br> 034 * Everything you know better than this class must be done by you, e.g. you will 035 * have to tell <code>XMLWriter</code> where you wish to have 036 * newlines.In effect, no unexpected so called 037 * <em>intelligent</em> behavior is to be feared. Another effect is high speed. 038 * <br> 039 * <br> 040 * A simple example: Suppose your <code>XMLWriter</code> object is xmlWriter. 041 * The following sequence of code <br><br> 042 * <code> 043 * xmlWriter.writeStartTag("<root>");<br> 044 * xmlWriter.writeStartTag("<next1>", false);<br> 045 * xmlWriter.writeEmptyTag("<emptyTag/>", false);<br> 046 * xmlWriter.writeEndTag("</next1>");<br> 047 * xmlWriter.writeStartTag("</root>");<br> 048 * </code> 049 * <br> 050 * will write this to the underlying writer<br><br> 051 * <code> 052 * <root><br> 053 * <next1><emptyTag/></next1><br> 054 * </root><br> 055 *</code> 056 * <br> 057 * <br> 058 * <em>Caution</em>: Do not forget to call {@link #flush} at the end of your 059 * exporting process as otherwise no data might be written. 060 * 061 */ 062 public class XMLWriter extends FilterWriter { 063 064 public final static boolean NEWLINE = true; 065 public final static boolean NO_NEWLINE = false; 066 067 protected int tabWidth = 2; 068 069 /** Current depth of the tree. Do not know what this is good for, but 070 * who knows... 071 */ 072 protected int depth = 0; 073 074 /** Current indentation. Depth does not contain sufficient information as 075 * tabWidth may change during output (should not). 076 */ 077 protected int indent = 0; 078 079 protected boolean prettyPrintMode = true; 080 081 protected boolean nlAfterEmptyTag = true; 082 protected boolean nlAfterStartTag = true; 083 protected boolean nlAfterEndTag = true; 084 085 /** Flag indicating if the XML declaration has already been writter. 086 * Check this using {@link #isXMLDeclarationWritten()}. 087 * It might be useful to 088 * avoid writing twice or more times in different contexts writing 089 * to same writer. 090 * <br> 091 * <em>Caution</em>: If you subclass, be sure to set this in 092 * {@link #writeXMLDeclaration()}. 093 */ 094 protected boolean xmlDeclWritten = false; 095 096 private boolean needsIndent = false; 097 private boolean indentStringCacheValid = true; 098 private String indentStringCache = ""; 099 100 /** Convenience method for creating an end tag. 101 * @param tagName name of the end tag 102 */ 103 public final static String createEndTag(String tagName) { 104 return "</" + tagName + ">"; 105 } 106 107 /** Convenience method for creating a start tag having no attributes. 108 * @param tagName name of the start tag 109 */ 110 public final static String createStartTag(String tagName) { 111 return "<" + tagName + ">"; 112 } 113 114 /** Convenience method for creating an <em>empty</em> tag 115 * having no attributes. E.g. <code><tagName/></code>. 116 * @param tagName name of the tag 117 */ 118 public final static String createEmptyTag(String tagName) { 119 return "<" + tagName + "/>"; 120 } 121 122 /** Convenience method for creating a start tag. 123 * @param tagName name of the start tag 124 * @param attrNames names of attributes to be included into start tag 125 * @param attrValues values of attributes to be included into start tag - 126 * there should be just as many entries as in <code>attrNames</code>, 127 * if a value is <code>null</code> corresponding attribute will not be included 128 * @param isEmpty decides wheter this is start tag is for an empty element 129 */ 130 public final static String createStartTag( 131 String tagName, 132 String[] attrNames, 133 String[] attrValues, 134 boolean isEmpty) { 135 return createStartTag(tagName, attrNames, attrValues, isEmpty, true, '"'); 136 } 137 138 /** Convenience method for creating a <em>non empty</em> start tag. 139 * @param tagName name of the start tag 140 * @param attrNames names of attributes to be included into start tag 141 * @param attrValues values of attributes to be included into start tag - 142 * there should be just as many entries as in <code>attrNames</code>, 143 * if a value is <code>null</code> corresponding attribute will not be included 144 */ 145 public final static String createStartTag(String tagName, String[] attrNames, String[] attrValues) { 146 return createStartTag(tagName, attrNames, attrValues, false); 147 } 148 149 /** Convenience method for creating an <em>empty</em> tag. 150 * @param tagName name of the tag 151 * @param attrNames names of attributes to be included into tag 152 * @param attrValues values of attributes to be included into tag - 153 * there should be just as many entries as in <code>attrNames</code>, 154 * if a value is <code>null</code> corresponding attribute will not be included 155 * @see #createEmptyTag(String) 156 */ 157 public final static String createEmptyTag(String tagName, String[] attrNames, String[] attrValues) { 158 return createStartTag(tagName, attrNames, attrValues, true); 159 } 160 161 /** Convenience method for creating a start tag. 162 * @param tagName name of the start tag 163 * @param attrName name of attribute to be included into start tag 164 * @param attrValue value of attribute to be included into start tag, 165 * if attrValue is <code>null</code> attribute will not be included 166 * @param isEmpty decides wheter this is start tag is for an empty element 167 */ 168 public final static String createStartTag(String tagName, String attrName, String attrValue, boolean isEmpty) { 169 return createStartTag(tagName, new String[] { attrName }, new String[] { attrValue }, isEmpty); 170 } 171 172 /** Convenience method for creating a <em>non empty</em> start tag. 173 * @param tagName name of the start tag 174 * @param attrName name of attribute to be included into start tag 175 * @param attrValue value of attribute to be included into start tag, 176 * if attrValue is <code>null</code> attribute will not be included 177 */ 178 public final static String createStartTag(String tagName, String attrName, String attrValue) { 179 return createStartTag(tagName, attrName, attrValue, false); 180 } 181 182 /** Convenience method for creating an <em>empty</em> tag. 183 * @param tagName name of the tag 184 * @param attrName name of attribute to be included into tag 185 * @param attrValue value of attribute to be included into tag, 186 * if attrValue is <code>null</code> attribute will not be included 187 * @see #createEmptyTag(String) 188 */ 189 public final static String createEmptyTag(String tagName, String attrName, String attrValue) { 190 return createStartTag(tagName, attrName, attrValue, true); 191 } 192 193 /** Convenience method for creating a start tag. 194 * @param tagName name of the start tag 195 * @param attrNames names of attributes to be included into start tag 196 * @param attrValues values of attributes to be included into start tag - 197 * there should be just as many entries as in <code>attrNames</code>, 198 * if a value is <code>null</code> corresponding attribute will not be included 199 * @param isEmpty decides wheter this is start tag is for an empty element 200 * @param encodeAttrs set this to have your attribute values encoded for XML 201 * @param quoteChar if you choose encoding this is the char that quotes 202 * your attributes 203 */ 204 public final static String createStartTag( 205 String tagName, 206 String[] attrNames, 207 String[] attrValues, 208 boolean isEmpty, 209 boolean encodeAttrs, 210 char quoteChar) { 211 // estimate buffer size 212 StringBuffer buf = new StringBuffer((attrNames.length + 1) * 15); 213 buf.append('<').append(tagName); 214 215 if (attrNames.length != 0 && (attrNames.length <= attrValues.length)) { 216 for (int i = 0; i < attrNames.length; i++) { 217 String name = attrNames[i]; 218 String value = attrValues[i]; 219 if (value == null) 220 continue; 221 if (encodeAttrs) 222 value = XMLEncode.xmlEncodeTextForAttribute(value, quoteChar); 223 buf.append(' ').append(name).append('=').append(value); 224 } 225 } 226 227 if (isEmpty) { 228 buf.append("/>"); 229 } else { 230 buf.append('>'); 231 } 232 return buf.toString(); 233 } 234 235 /** Convenience method for creating a start tag. 236 * @param tagName name of the start tag 237 * @param attrPairs name/value pairs of attributes to be included into start tag - 238 * if a value is <code>null</code> corresponding attribute will not be included 239 * @param isEmpty decides wheter this is start tag is for an empty element 240 */ 241 public final static String createStartTag(String tagName, String[][] attrPairs, boolean isEmpty) { 242 return createStartTag(tagName, attrPairs, isEmpty, true, '"'); 243 } 244 245 /** Convenience method for creating a <em>non empty</em> start tag. 246 * @param tagName name of the start tag 247 * @param attrPairs name/value pairs of attributes to be included into start tag - 248 * if a value is <code>null</code> corresponding attribute will not be included 249 */ 250 public final static String createStartTag(String tagName, String[][] attrPairs) { 251 return createStartTag(tagName, attrPairs, false); 252 } 253 254 /** Convenience method for creating an <em>empty</em> tag. 255 * @param tagName name of the tag 256 * @param attrPairs name/value pairs of attributes to be included into tag - 257 * if a value is <code>null</code> corresponding attribute will not be included 258 * @see #createEmptyTag(String) 259 */ 260 public final static String createEmptyTag(String tagName, String[][] attrPairs) { 261 return createStartTag(tagName, attrPairs, true); 262 } 263 264 /** Convenience method for creating a start tag. 265 * @param tagName name of the start tag 266 * @param attrPairs name/value pairs of attributes to be included into start tag - 267 * if a value is <code>null</code> corresponding attribute will not be included 268 * @param isEmpty decides wheter this is start tag is for an empty element 269 * @param encodeAttrs set this to have your attribute values encoded for XML 270 * @param quoteChar if you choose encoding this is the char that quotes 271 * your attributes 272 */ 273 public final static String createStartTag( 274 String tagName, 275 String[][] attrPairs, 276 boolean isEmpty, 277 boolean encodeAttrs, 278 char quoteChar) { 279 // estimate buffer size 280 StringBuffer buf = new StringBuffer((attrPairs.length + 1) * 15); 281 buf.append('<').append(tagName); 282 283 for (int i = 0; i < attrPairs.length; i++) { 284 String name = attrPairs[i][0]; 285 String value = attrPairs[i][1]; 286 if (value == null) 287 continue; 288 if (encodeAttrs) 289 value = XMLEncode.xmlEncodeTextForAttribute(value, quoteChar); 290 buf.append(' ').append(name).append('=').append(value); 291 } 292 293 if (isEmpty) { 294 buf.append("/>"); 295 } else { 296 buf.append('>'); 297 } 298 return buf.toString(); 299 } 300 301 /** Convenience method for creating an <em>empty</em> tag. 302 * @param tagName name of the tag 303 * @param attributes SAX attributes to be included into start tag 304 * @see #createEmptyTag(String) 305 */ 306 public final static String createEmptyTag(String tagName, Attributes attributes) { 307 return createStartTag(tagName, attributes, true); 308 } 309 310 /** Convenience method for creating a start tag. 311 * @param tagName name of the start tag 312 * @param attributes SAX attributes to be included into start tag 313 */ 314 public final static String createStartTag(String tagName, Attributes attributes) { 315 return createStartTag(tagName, attributes, false); 316 } 317 318 /** Convenience method for creating a start tag. 319 * @param tagName name of the start tag 320 * @param attributes SAX attributes to be included into start tag 321 * @param isEmpty decides wheter this is start tag is for an empty element 322 */ 323 public final static String createStartTag(String tagName, Attributes attributes, boolean isEmpty) { 324 return createStartTag(tagName, attributes, isEmpty, true, '"'); 325 } 326 327 /** Convenience method for creating a start tag. 328 * @param tagName name of the start tag 329 * @param attributes SAX attributes to be included into start tag 330 * @param isEmpty decides wheter this is start tag is for an empty element 331 * @param encodeAttrs set this to have your attribute values encoded for XML 332 * @param quoteChar if you choose encoding this is the char that quotes 333 * your attributes 334 */ 335 public final static String createStartTag( 336 String tagName, 337 Attributes attributes, 338 boolean isEmpty, 339 boolean encodeAttrs, 340 char quoteChar) { 341 // estimate buffer size 342 StringBuffer buf = new StringBuffer((attributes.getLength() + 1) * 15); 343 buf.append('<').append(tagName); 344 345 for (int i = 0; i < attributes.getLength(); i++) { 346 String name = attributes.getQName(i); 347 String value = attributes.getValue(i); 348 if (encodeAttrs) 349 value = XMLEncode.xmlEncodeTextForAttribute(value, quoteChar); 350 buf.append(' ').append(name).append('=').append(value); 351 } 352 353 if (isEmpty) { 354 buf.append("/>"); 355 } else { 356 buf.append('>'); 357 } 358 return buf.toString(); 359 } 360 361 /** Convenience method for creating <em>and writing</em> a whole element. 362 * Added to normal non-static write methods purely for my own laziness.<br> 363 * It is non-static as it differs from all other write methods as it 364 * combines generating and writing. This is normally avoided to keep every 365 * everything simple, clear and fast.<br> 366 * <br> 367 * You can write<br> 368 * <code>XMLOutputStreamWriter.generateAndWriteElementWithCData(writer, "tag", "cdata"); 369 * </code><br> 370 * <br> 371 * to generate<br> 372 * <code><tag>cdata</tag> 373 * </code><br> 374 * 375 * @param xmlWriter writer to write generated stuff to 376 * @param tagName name of the element 377 * @param attrPairs name/value pairs of attributes to be included into start tag - 378 * if a value is <code>null</code> corresponding attribute will not be included 379 * @param cData the character data of the element 380 * @see #writeElementWithCData(String, String, String) 381 * @see #createStartTag(String, String[][]) 382 * @see #createEndTag(String) 383 */ 384 public final static void generateAndWriteElementWithCData( 385 XMLWriter xmlWriter, 386 String tagName, 387 String[][] attrPairs, 388 String cData) 389 throws IOException { 390 String startTag = createStartTag(tagName, attrPairs); 391 String endTag = createEndTag(tagName); 392 xmlWriter.writeElementWithCData(startTag, cData, endTag); 393 } 394 395 /** Convenience method for creating <em>and writing</em> a whole element. 396 * @param xmlWriter writer to write generated stuff to 397 * @param tagName name of the element 398 * @param attrNames names of attributes to be included into start tag 399 * @param attrValues values of attributes to be included into start tag - 400 * there should be just as many entries as in <code>attrNames</code>, 401 * if a value is <code>null</code> corresponding attribute will not be included 402 * @param cData the character data of the element 403 * @see #generateAndWriteElementWithCData(XMLWriter, String, String[][], String) 404 * @see #writeElementWithCData(String, String, String) 405 * @see #createStartTag(String, String[], String[]) 406 * @see #createEndTag(String) 407 */ 408 public final static void generateAndWriteElementWithCData( 409 XMLWriter xmlWriter, 410 String tagName, 411 String[] attrNames, 412 String[] attrValues, 413 String cData) 414 throws IOException { 415 String startTag = createStartTag(tagName, attrNames, attrValues); 416 String endTag = createEndTag(tagName); 417 xmlWriter.writeElementWithCData(startTag, cData, endTag); 418 } 419 420 /** Creates a new filter writer for XML export. 421 * @param writer the underlying writer the formatted XML is exported to 422 */ 423 public XMLWriter(Writer writer) { 424 super(writer); 425 } 426 427 /** Switches on/off pretty print mode. 428 * <br> 429 * Having it switched on (which is the default) makes output 430 * pretty as newlines after tags and indentataion is done. Unfortunately, 431 * if your application is sensible to whitespace in CDATA this might lead 432 * to unwanted additional spaces and newlines. 433 * <br> 434 * If it is switched off the output is guaranteed to be correct, but looks 435 * pretty funny. After before markup close (> or />) a newline is inserted 436 * as otherwise you may get extremely long output lines. 437 */ 438 public void setPrettyPrintMode(boolean prettyPrintMode) { 439 this.prettyPrintMode = prettyPrintMode; 440 } 441 442 /** Gets property described in {@link #setPrettyPrintMode}. */ 443 public boolean getPrettyPrintMode() { 444 return prettyPrintMode; 445 } 446 447 /** Sets the amount of spaces to increase indentation with element level. 448 * <br> 449 * This only takes effect when {@link #setPrettyPrintMode} is set to true. 450 * <br> 451 * <em>Caution</em>: You should better avoid to change this property while 452 * exporting as this may result in unexpected output. 453 */ 454 public void setTabWidth(int tabWidth) { 455 this.tabWidth = tabWidth; 456 } 457 458 /** Gets property described in {@link #setTabWidth}. */ 459 public int getTabWidth() { 460 return tabWidth; 461 } 462 463 /** Sets if a newline is inserted after an empty start element 464 * by default. 465 */ 466 public void setNlAfterEmptyTag(boolean nlAfterEmptyTag) { 467 this.nlAfterEmptyTag = nlAfterEmptyTag; 468 } 469 470 /** Gets property described in {@link #setNlAfterEmptyTag}. */ 471 public boolean getNlAfterEmptyTag() { 472 return nlAfterEmptyTag; 473 } 474 475 /** Sets if a newline is inserted after an end tag 476 * by default. */ 477 public void setNlAfterEndTag(boolean nlAfterEndTag) { 478 this.nlAfterEndTag = nlAfterEndTag; 479 } 480 481 /** Gets property described in {@link #setNlAfterEndTag}. */ 482 public boolean getNlAfterEndTag() { 483 return nlAfterEndTag; 484 } 485 486 /** Sets if a newline is inserted after a non empty start tag 487 * by default. */ 488 public void setNlAfterStartTag(boolean nlAfterStartTag) { 489 this.nlAfterStartTag = nlAfterStartTag; 490 } 491 492 /** Gets property described in {@link #setNlAfterStartTag}. */ 493 public boolean getNlAfterStartTag() { 494 return nlAfterStartTag; 495 } 496 497 /** Writes XML declaration. 498 * XML declaration will be written 499 * using version 1.0 and no encoding defaulting 500 * to standard encoding (supports UTF-8 and UTF-16):<br> 501 * <code><?xml version="1.0"?></code> 502 * <br> 503 * If you want to have a different encoding or the standalone declaration 504 * use {@link #writeProlog(String)}.<br> 505 * This sets {@link #setXMLDeclarationWritten xmlDeclWritten} to 506 * <code>true</code>. 507 * 508 */ 509 public void writeXMLDeclaration() throws IOException { 510 xmlDeclWritten = true; 511 needsIndent = false; 512 write("<?xml version=\"1.0\"?>\n"); 513 } 514 515 /** Indicates whether the XML declaration has been written, yet. 516 * As it may only be written once, you can check this when writing 517 * in different contexts to same writer. 518 */ 519 public boolean isXMLDeclarationWritten() { 520 return xmlDeclWritten; 521 } 522 523 /** Manually sets or resets whether XML declaration has been written. 524 * This is done implicly by {@link #writeXMLDeclaration}, but to give you 525 * the full freedom, this can be done here as well. 526 * Use {@link #isXMLDeclarationWritten} to check it. 527 */ 528 public void setXMLDeclarationWritten(boolean xmlDeclWritten) { 529 this.xmlDeclWritten = xmlDeclWritten; 530 } 531 532 /** Writes prolog data like doctype delcaration and 533 * DTD parts followed by a newline. 534 * <br> 535 * Do not misuse this to write plain text, but rather - if you really 536 * have to - use the standard {@link #write} methods. 537 */ 538 public void writeProlog(String prolog) throws IOException { 539 needsIndent = false; 540 write(prolog); 541 writeNl(); 542 } 543 544 /** Writes a single newline. */ 545 public void writeNl() throws IOException { 546 needsIndent = true; 547 write('\n'); 548 } 549 550 /** Writes <code>comment</code> encoded as comment. */ 551 public void writeComment(String comment) throws IOException { 552 needsIndent = false; 553 write("<!-- "); 554 write(comment); 555 write(" -->"); 556 } 557 558 /** Writes a processing instruction. */ 559 public void writePI(String target, String data) throws IOException { 560 needsIndent = false; 561 write("<?" + target + " " + data + "?>"); 562 } 563 564 /** Writes a start tag. 565 * @param startTag the complete start tag, e.g. <code><start></code> 566 * @param nl decides whether there should be a newline after the tag 567 */ 568 public void writeStartTag(String startTag, boolean nl) throws IOException { 569 writeTag(startTag, nl); 570 depthPlus(); 571 } 572 573 /** Writes a start tag. 574 * @param startTag the complete start tag, e.g. <code><start></code> 575 * @see #setNlAfterStartTag 576 */ 577 public void writeStartTag(String startTag) throws IOException { 578 writeStartTag(startTag, nlAfterStartTag); 579 } 580 581 /** Writes an end tag. 582 * @param endTag the complete end tag, e.g. <code></end></code> 583 * @param nl decides whether there should be a newline after the tag 584 */ 585 public void writeEndTag(String endTag, boolean nl) throws IOException { 586 depthMinus(); 587 writeTag(endTag, nl); 588 } 589 590 /** Writes an end tag. 591 * @param endTag the complete end tag, e.g. <code></end></code> 592 * @see #setNlAfterEndTag 593 */ 594 public void writeEndTag(String endTag) throws IOException { 595 writeEndTag(endTag, nlAfterEndTag); 596 } 597 598 /** Writes an empty element. 599 * @param emptyTag the complete tag for an empty element, e.g. <code><empty/></code> 600 * @param nl decides whether there should be a newline after the tag 601 */ 602 public void writeEmptyElement(String emptyTag, boolean nl) throws IOException { 603 writeTag(emptyTag, nl); 604 } 605 606 /** Writes an empty element. 607 * @param emptyTag the complete tag for an empty element, e.g. <code><start/></code> 608 * @see #setNlAfterEmptyTag 609 */ 610 public void writeEmptyElement(String emptyTag) throws IOException { 611 writeEmptyElement(emptyTag, nlAfterEmptyTag); 612 } 613 614 /** Writes character data with encoding. 615 * @param cData the character data to write 616 */ 617 public void writeCData(String cData) throws IOException { 618 String encoded = XMLEncode.xmlEncodeText(cData); 619 writePCData(encoded); 620 } 621 622 /** Writes character data <em>without</em> encoding. 623 * @param pcData the <em>parseable</em> character data to write 624 */ 625 public void writePCData(String pcData) throws IOException { 626 needsIndent = false; 627 write(pcData); 628 } 629 630 /** Writes a full element consisting of a start tag, character data and 631 * an end tag. There will be no newline after start tag, so character data 632 * is literally preserved. 633 * <br> 634 * The character data will be encoded. 635 * 636 * @param startTag the complete start tag, e.g. <code><element></code> 637 * @param cData the character data to write 638 * @param endTag the complete end tag, e.g. <code></element></code> 639 */ 640 public void writeElementWithCData(String startTag, String cData, String endTag) throws IOException { 641 writeStartTag(startTag, false); 642 writeCData(cData); 643 writeEndTag(endTag); 644 } 645 646 /** Writes a full element consisting of a start tag, character data and 647 * an end tag. There will be no newline after start tag, so character data 648 * is literally preserved. 649 * <br> 650 * The character data will <em>not</em> be encoded. 651 * 652 * @param startTag the complete start tag, e.g. <code><element></code> 653 * @param pcData the <em>parseable</em> character data to write 654 * @param endTag the complete end tag, e.g. <code></element></code> 655 */ 656 public void writeElementWithPCData(String startTag, String pcData, String endTag) throws IOException { 657 writeStartTag(startTag, false); 658 writePCData(pcData); 659 writeEndTag(endTag); 660 } 661 662 private void writeTag(String tag, boolean nl) throws IOException { 663 writeIndent(); 664 needsIndent = false; 665 if (nl) { 666 if (getPrettyPrintMode()) { 667 write(tag); 668 writeNl(); 669 } else { 670 // in correct mode we need to break tag before closing > resp. /> 671 int length = tag.length(); 672 int pos; 673 if ((pos = tag.indexOf("/>")) != -1) { 674 write(tag, 0, pos); 675 write('\n'); 676 write(tag, pos, length - pos); 677 } else if ((pos = tag.indexOf(">")) != -1) { 678 write(tag, 0, pos); 679 write('\n'); 680 write(tag, pos, length - pos); 681 } else { 682 write(tag); 683 write('\n'); 684 } 685 } 686 } else { 687 write(tag); 688 } 689 } 690 691 private void writeIndent() throws IOException { 692 // indentation is only needed after a newline in pretty print mode 693 if (!needsIndent) 694 return; 695 696 // every indentation destroys literal write 697 if (!getPrettyPrintMode()) 698 return; 699 700 // shortcut 701 if (indent == 0) 702 return; 703 704 // save some computation time when indent does not change 705 if (!indentStringCacheValid) { 706 StringBuffer buf = new StringBuffer(indent); 707 for (int i = 0; i < indent; i++) { 708 buf.append(' '); 709 } 710 indentStringCache = buf.toString(); 711 indentStringCacheValid = true; 712 } 713 714 write(indentStringCache); 715 } 716 717 private void depthPlus() { 718 indent += tabWidth; 719 depth++; 720 indentStringCacheValid = false; 721 } 722 723 private void depthMinus() { 724 indent -= tabWidth; 725 if (indent < 0) 726 indent = 0; 727 depth--; 728 indentStringCacheValid = false; 729 } 730 }