001package com.hfg.xml; 002 003import java.io.OutputStream; 004import java.io.PrintWriter; 005import java.util.ArrayList; 006import java.util.List; 007import java.util.Stack; 008 009import org.xml.sax.Attributes; 010import org.xml.sax.SAXException; 011import org.xml.sax.SAXParseException; 012import org.xml.sax.ext.LexicalHandler; 013import org.xml.sax.helpers.DefaultHandler; 014 015import com.hfg.util.StringUtil; 016 017//------------------------------------------------------------------------------ 018/** 019 020 @author J. Alex Taylor, hairyfatguy.com 021 */ 022//------------------------------------------------------------------------------ 023 024 025public class XMLEmitter extends DefaultHandler implements LexicalHandler 026{ 027 028 //########################################################################### 029 // PUBLIC FIELDS 030 //########################################################################### 031 032 public static final int STYLE_COMPACT = 1; 033 public static final int STYLE_INDENTED = 2; 034 035 //########################################################################### 036 // PRIVATE FIELDS 037 //########################################################################### 038 039 private StringBuffer mCurrentData; 040 private StringBuffer mCurrentComment; 041 private int mPreviousTagType; 042 private int mCurrentIndent; 043 private XMLTag mDanglingStartTag; 044 045 private PrintWriter mWriter; 046 private int mPrintStyle = STYLE_COMPACT; 047 private int mIndentSize = 2; 048 049 private Stack mSafeToIndentStack = new Stack(); 050 private boolean mSafeToIndent; 051 052 private static int START_TAG = 1; 053 private static int END_TAG = -1; 054 055 //########################################################################### 056 // CONSTRUCTORS 057 //########################################################################### 058 059 //--------------------------------------------------------------------------- 060 /** 061 Default constructor - prints to System.out 062 */ 063 public XMLEmitter() 064 { 065 mWriter = new PrintWriter(System.out); 066 } 067 068 //--------------------------------------------------------------------------- 069 /** 070 Prints XML to the specified stream 071 */ 072 public XMLEmitter(OutputStream inOutputStream) 073 { 074 mWriter = new PrintWriter(inOutputStream); 075 } 076 077 //--------------------------------------------------------------------------- 078 /** 079 Prints XML to the specified stream 080 */ 081 public XMLEmitter(PrintWriter inWriter) 082 { 083 mWriter = inWriter; 084 } 085 086 087 //########################################################################### 088 // PUBLIC METHODS 089 //########################################################################### 090 091 //--------------------------------------------------------------------------- 092 public void setPrintStyle(int inValue) 093 { 094 if (inValue < STYLE_COMPACT 095 || inValue > STYLE_INDENTED) 096 { 097 throw new RuntimeException(inValue + " is not a valid print style!"); 098 } 099 100 mPrintStyle = inValue; 101 } 102 103 //--------------------------------------------------------------------------- 104 /** 105 Sets the number of spaces each tag level is indented 106 */ 107 public void setIndentSize(int inValue) 108 { 109 mIndentSize = inValue; 110 } 111 112 //--------------------------------------------------------------------------- 113 public int getIndentSize() 114 { 115 return mIndentSize; 116 } 117 118 //--------------------------------------------------------------------------- 119 public void startDocument() 120 { 121 mCurrentData = new StringBuffer(); 122 mCurrentComment = new StringBuffer(); 123 mPreviousTagType = END_TAG; 124 mCurrentIndent = -mIndentSize; 125 126 mSafeToIndentStack = new Stack(); 127 mSafeToIndent = true; 128 } 129 130 //--------------------------------------------------------------------------- 131 public void endDocument() 132 { 133 mWriter.flush(); 134 } 135 136 //--------------------------------------------------------------------------- 137 public void startElement(String uri, String local, String raw, Attributes attrs) 138 { 139 if (mDanglingStartTag != null) 140 { 141 emitStartTag(false); 142 143 if (mCurrentData.length() > 0) 144 { 145 emitContent(); 146 } 147 } 148 149 List attributes = new ArrayList(); 150 for (int i = 0; i < attrs.getLength(); i++) 151 { 152 attributes.add(new XMLAttribute(attrs.getQName(i), attrs.getValue(i))); 153 } 154 155 mDanglingStartTag = new XMLTag(raw, attributes); 156 157/* 158 boolean safeToIndent = true; 159 160 if (mCurrentData.length() > 0) 161 { 162 if (mPrintStyle == STYLE_INDENTED) 163 { 164 safeToIndent = contentLooksSafeToIndent(mCurrentData.toString()); 165 166 if (safeToIndent) 167 { 168 mPrintStream.println(); 169 mPrintStream.print(StringUtil.polyChar(' ', mCurrentIndent + mIndentSize)); 170 } 171 } 172 173 mPrintStream.print(mCurrentData.toString()); 174 175 if (mPrintStyle == STYLE_INDENTED && safeToIndent) mPrintStream.println(); 176 } 177 178 179 List attributes = new ArrayList(); 180 for (int i = 0; i < attrs.getLength(); i++) 181 { 182 attributes.add(new XMLAttribute(attrs.getQName(i), attrs.getValue(i))); 183 } 184 185 if (mPrintStyle == STYLE_INDENTED) 186 { 187 mCurrentIndent += mIndentSize; 188 mPrintStream.println(); 189 mPrintStream.print(StringUtil.polyChar(' ', mCurrentIndent)); 190 mPreviousTagType = START_TAG; 191 } 192 193 mPrintStream.print(XMLUtil.composeStartTag(raw, attributes)); 194 195 mCurrentData = new StringBuffer(); 196*/ 197 } 198 199 //--------------------------------------------------------------------------- 200 public void endElement(String uri, String local, String raw) 201 { 202/* 203 // Print any tag contents 204 if (mCurrentData.length() > 0) 205 { 206 if (mPrintStyle == STYLE_INDENTED) 207 { 208 safeToIndent = contentLooksSafeToIndent(mCurrentData.toString()); 209 210 if (safeToIndent) 211 { 212 mPrintStream.println(); 213 mPrintStream.print(StringUtil.polyChar(' ', mCurrentIndent + mIndentSize)); 214 } 215 } 216 217 mPrintStream.print(mCurrentData.toString()); 218 219 if (mPrintStyle == STYLE_INDENTED && safeToIndent) mPrintStream.println(); 220 } 221 else if (mPrintStyle == STYLE_INDENTED 222 && mPreviousTagType == START_TAG) 223 { 224 mPrintStream.println(); 225 } 226 227 mCurrentData = new StringBuffer(); 228*/ 229 if (mDanglingStartTag != null 230 && mCurrentData.length() == 0 231 && mCurrentComment.length() == 0) 232 { 233 emitStartTag(true); 234 mDanglingStartTag = null; 235 } 236 else 237 { 238 if (mDanglingStartTag != null) 239 { 240 emitStartTag(false); 241 mDanglingStartTag = null; 242 } 243 /* 244 if (mCurrentComment.length() > 0) 245 { 246 emitComment(); 247 } 248*/ 249 if (mCurrentData.length() > 0) 250 { 251 emitContent(); 252 } 253 254 popIndentState(); 255 256 // Write the end tag 257 if (mPrintStyle == STYLE_INDENTED) 258 { 259 if (mSafeToIndent) 260 { 261 mWriter.println(); 262// if (mPreviousTagType == END_TAG) mPrintStream.println(); 263 mWriter.print(StringUtil.polyChar(' ', mCurrentIndent)); 264 } 265 mCurrentIndent -= mIndentSize; 266 mPreviousTagType = END_TAG; 267 } 268 269 mWriter.print(XMLUtil.composeEndTag(raw)); 270 } 271 } 272 273 274 //--------------------------------------------------------------------------- 275 public void characters(char[] ch, int start, int length) 276 { 277// System.out.print("In characters(): "); 278/* if (mDanglingStartTag != null) 279 { 280 emitStartTag(false); 281 mDanglingStartTag = null; 282 } 283*/ 284 285 if (mCurrentData.length() > 0) 286 { 287 mCurrentData.append(ch, start, length); 288 289 if (mPrintStyle == STYLE_INDENTED 290 && mSafeToIndent == true 291 && mCurrentData.length() > 0) 292 { 293 mSafeToIndent = contentLooksSafeToIndent(mCurrentData.toString()); 294 } 295 296 if (mDanglingStartTag != null) 297 { 298 emitStartTag(false); 299 mDanglingStartTag = null; 300 } 301 302 emitContent(); 303 } 304 else 305 { 306 mCurrentData.append(ch, start, length); 307 308 if (mPrintStyle == STYLE_INDENTED 309 && mSafeToIndent == true 310 && mCurrentData.length() > 0) 311 { 312 mSafeToIndent = contentLooksSafeToIndent(mCurrentData.toString()); 313 } 314 315 if (null == mDanglingStartTag) 316 { 317 emitContent(); 318 } 319 320 } 321 322/* 323 String content = new String(ch, start, length); 324 325 boolean safeToIndent = true; 326 327 // Print any tag contents 328 if (content.trim().length() > 0) 329 { 330 if (mPrintStyle == STYLE_INDENTED) 331 { 332 safeToIndent = contentLooksSafeToIndent(content); 333 334 if (safeToIndent) 335 { 336 mPrintStream.println(); 337 mPrintStream.print(StringUtil.polyChar(' ', mCurrentIndent + mIndentSize)); 338 } 339 } 340 341 mPrintStream.print(content); 342 343// if (mPrintStyle == STYLE_INDENTED && safeToIndent) mPrintStream.println(); 344 } 345 else if (mPrintStyle == STYLE_INDENTED 346 && mPreviousTagType == START_TAG) 347 { 348// mPrintStream.println(); 349 } 350*/ 351 352 } 353 354 355 356 357 358 //--------------------------------------------------------------------------- 359 public void warning(SAXParseException e) 360 { 361 e.printStackTrace(); 362 } 363 364 365 //--------------------------------------------------------------------------- 366 public void error(SAXParseException e) 367 { 368 e.printStackTrace(); 369 } 370 371 372 //--------------------------------------------------------------------------- 373 public void skippedEntity(String name) 374 throws SAXException 375 { 376 // System.out.println("In skippedEntity(): " + name); 377 378 } 379 380 //--------------------------------------------------------------------------- 381 public void unparsedEntityDecl(String name, 382 String publicId, 383 String systemId, 384 String notationName) 385 throws SAXException 386 { 387 // System.out.println("In unparsedEntityDecl(): " + name); 388 389 } 390 391 392 393 // LexicalHandler methods 394 395 //-------------------------------------------------------------------------- 396 public void comment(char[] inChars, int offset, int length) 397 { 398// System.out.println("comment: '" + new String(inChars, offset, length) + "'"); 399 400 401 if (mDanglingStartTag != null) 402 { 403 emitStartTag(false); 404 mDanglingStartTag = null; 405 } 406 407 if (mCurrentData.length() > 0) 408 { 409 emitContent(); 410 } 411 412 // popIndentState(); 413 414 mCurrentComment.append(inChars, offset, length); 415 emitComment(); 416/* 417 if (mCurrentComment.length() > 0) 418 { 419 mCurrentComment.append(inChars, offset, length); 420 421 if (mPrintStyle == STYLE_INDENTED 422 && mSafeToIndent == true 423 && mCurrentComment.length() > 0) 424 { 425 mSafeToIndent = contentLooksSafeToIndent(mCurrentComment.toString()); 426 } 427 428 if (mDanglingStartTag != null) 429 { 430 emitStartTag(false); 431 mDanglingStartTag = null; 432 } 433 434 emitComment(); 435 } 436 else 437 { 438 mCurrentComment.append(inChars, offset, length); 439 440 if (mPrintStyle == STYLE_INDENTED 441 && mSafeToIndent == true 442 && mCurrentComment.length() > 0) 443 { 444 mSafeToIndent = contentLooksSafeToIndent(mCurrentComment.toString()); 445 } 446 447 if (null == mDanglingStartTag) 448 { 449 emitComment(); 450 } 451 } 452*/ 453 454 } 455 456 //-------------------------------------------------------------------------- 457 public void startCDATA() 458 { 459 } 460 461 //-------------------------------------------------------------------------- 462 public void endCDATA() 463 { 464 } 465 466 //-------------------------------------------------------------------------- 467 public void startEntity(String name) 468 { 469 } 470 471 //-------------------------------------------------------------------------- 472 public void endEntity(String name) 473 { 474 } 475 476 //-------------------------------------------------------------------------- 477 public void startDTD(String name, String publicId, String systemId) 478 { 479 } 480 481 //-------------------------------------------------------------------------- 482 public void endDTD() 483 { 484 } 485 486 487 488 489 490 491 492 493 494 //--------------------------------------------------------------------------- 495 // Tags like the HTML pre tag are sensitive to leading and trailing spaces. 496 private boolean contentLooksSafeToIndent(String inContent) 497 { 498 boolean response = true; 499 500 if (Character.isWhitespace(inContent.charAt(0)) 501 || Character.isWhitespace(inContent.charAt(inContent.length() - 1))) 502 { 503 response = false; 504 } 505 506 return response; 507 } 508 509 //--------------------------------------------------------------------------- 510 private void emitStartTag(boolean inIsEmptyTag) 511 { 512/* 513 if (mCurrentData.length() > 0) 514 { 515 if (mPrintStyle == STYLE_INDENTED) 516 { 517 safeToIndent = contentLooksSafeToIndent(mCurrentData.toString()); 518 519 if (safeToIndent) 520 { 521 mPrintStream.println(); 522 mPrintStream.print(StringUtil.polyChar(' ', mCurrentIndent + mIndentSize)); 523 } 524 } 525 526 mPrintStream.print(mCurrentData.toString()); 527 528// if (mPrintStyle == STYLE_INDENTED && safeToIndent) mPrintStream.println(); 529 } 530 */ 531 532 if (mPrintStyle == STYLE_INDENTED) 533 { 534 mCurrentIndent += mIndentSize; 535 if (mSafeToIndentStack.size() == 0 536 || ((Boolean) mSafeToIndentStack.peek()).booleanValue()) 537 { 538 mWriter.println(); 539 mWriter.print(StringUtil.polyChar(' ', mCurrentIndent)); 540 } 541 542 if (inIsEmptyTag) 543 { 544 mCurrentIndent -= mIndentSize; 545// mPreviousTagType = END_TAG; 546 } 547 else 548 { 549 mPreviousTagType = START_TAG; 550 pushIndentState(); 551 mSafeToIndent = true; 552 } 553 } 554 555 mWriter.print(XMLUtil.composeStartTag(mDanglingStartTag.getTagName(), 556 mDanglingStartTag.getAttributes(), 557 inIsEmptyTag)); 558 559 } 560 561 //--------------------------------------------------------------------------- 562 private void emitContent() 563 { 564 565 if (mPrintStyle == STYLE_INDENTED) 566 { 567 if (mSafeToIndent) 568 { 569 // Re-evaluate 570 mSafeToIndent = contentLooksSafeToIndent(mCurrentData.toString()); 571 } 572 573 if (mSafeToIndent) 574 { 575 mWriter.println(); 576 mWriter.print(StringUtil.polyChar(' ', mCurrentIndent + mIndentSize)); 577 } 578 } 579 580 mWriter.print(mCurrentData.toString()); 581 mCurrentData.setLength(0); 582 583 } 584 585 //--------------------------------------------------------------------------- 586 private void emitComment() 587 { 588 589 if (mPrintStyle == STYLE_INDENTED) 590 { 591 /* 592 if (mSafeToIndent) 593 { 594 // Re-evaluate 595 mSafeToIndent = contentLooksSafeToIndent(mCurrentComment.toString()); 596 } 597*/ 598 mSafeToIndent = true; 599 600 if (mSafeToIndent) 601 { 602 mWriter.println(); 603 mWriter.print(StringUtil.polyChar(' ', mCurrentIndent + mIndentSize)); 604 } 605 } 606 607 mWriter.print("<!--"); 608 mWriter.print(mCurrentComment.toString()); 609 mWriter.print("-->"); 610 611 mCurrentComment.setLength(0); 612 613 } 614 615 //--------------------------------------------------------------------------- 616 private void pushIndentState() 617 { 618 mSafeToIndentStack.push(new Boolean(mSafeToIndent)); 619 } 620 621 //--------------------------------------------------------------------------- 622 private void popIndentState() 623 { 624 mSafeToIndent = ((Boolean) mSafeToIndentStack.pop()).booleanValue(); 625 } 626 627}