Source for javax.swing.text.html.HTMLWriter

   1: /* HTMLWriter.java --
   2:    Copyright (C) 2006 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: package javax.swing.text.html;
  39: 
  40: import gnu.java.lang.CPStringBuilder;
  41: 
  42: import java.io.IOException;
  43: import java.io.Writer;
  44: 
  45: import java.util.Enumeration;
  46: import java.util.HashSet;
  47: 
  48: import javax.swing.ComboBoxModel;
  49: 
  50: import javax.swing.text.AbstractWriter;
  51: import javax.swing.text.AttributeSet;
  52: import javax.swing.text.BadLocationException;
  53: import javax.swing.text.Document;
  54: import javax.swing.text.Element;
  55: import javax.swing.text.StyleConstants;
  56: 
  57: import javax.swing.text.html.HTML;
  58: import javax.swing.text.html.HTMLDocument;
  59: import javax.swing.text.html.Option;
  60: 
  61: /**
  62:  * HTMLWriter,
  63:  * A Writer for HTMLDocuments.
  64:  *
  65:  * @author David Fu (fchoong at netbeans.jp)
  66:  */
  67: 
  68: public class HTMLWriter
  69:   extends AbstractWriter
  70: {
  71:   /**
  72:    * We keep a reference of the writer passed by the construct.
  73:    */
  74:   private Writer outWriter = null;
  75: 
  76:   /**
  77:    * We keep a reference of the HTMLDocument passed by the construct.
  78:    */
  79:   private HTMLDocument htmlDoc = null;
  80: 
  81:   /**
  82:    * Used to keep track of which embedded has been written out.
  83:    */
  84:   private HashSet<HTML.Tag> openEmbeddedTagHashSet = null;
  85: 
  86:   private String new_line_str = "" + NEWLINE;
  87: 
  88:   private char[] html_entity_char_arr = {'<',    '>',    '&',     '"'};
  89: 
  90:   private String[] html_entity_escape_str_arr = {"&lt;", "&gt;", "&amp;",
  91:                                                  "&quot;"};
  92: 
  93:   // variables used to output Html Fragment
  94:   private int doc_pos = -1;
  95:   private int doc_len = -1;
  96:   private int doc_offset_remaining = -1;
  97:   private int doc_len_remaining = -1;
  98:   private HashSet<Element> htmlFragmentParentHashSet = null;
  99:   private Element startElem = null;
 100:   private Element endElem = null;
 101:   private boolean fg_pass_start_elem = false;
 102:   private boolean fg_pass_end_elem = false;
 103: 
 104:   /**
 105:    * Constructs a HTMLWriter.
 106:    *
 107:    * @param writer writer to write output to
 108:    * @param doc the HTMLDocument to output
 109:    */
 110:   public HTMLWriter(Writer writer, HTMLDocument doc)
 111:   {
 112:     super(writer, doc);
 113:     outWriter = writer;
 114:     htmlDoc = doc;
 115:     openEmbeddedTagHashSet = new HashSet<HTML.Tag>();
 116:   } // public HTMLWriter(Writer writer, HTMLDocument doc)
 117: 
 118:   /**
 119:    * Constructs a HTMLWriter which outputs a Html Fragment.
 120:    *
 121:    * @param writer <code>Writer</code> to write output to
 122:    * @param doc the <code>javax.swing.text.html.HTMLDocument</code>
 123:    *        to output
 124:    * @param pos position to start outputing the document
 125:    * @param len amount to output the document
 126:    */
 127:   public HTMLWriter(Writer writer, HTMLDocument doc, int pos, int len)
 128:   {
 129:     super(writer, doc, pos, len);
 130:     outWriter = writer;
 131:     htmlDoc = doc;
 132:     openEmbeddedTagHashSet = new HashSet<HTML.Tag>();
 133: 
 134:     doc_pos = pos;
 135:     doc_offset_remaining = pos;
 136:     doc_len = len;
 137:     doc_len_remaining = len;
 138:     htmlFragmentParentHashSet = new HashSet<Element>();
 139:   } // public HTMLWriter(Writer writer, HTMLDocument doc, int pos, int len)
 140: 
 141:   /**
 142:    * Call this method to start outputing HTML.
 143:    *
 144:    * @throws IOException on any I/O exceptions
 145:    * @throws BadLocationException if a pos is not a valid position in the
 146:    *                              html doc element
 147:    */
 148:   public void write()
 149:     throws IOException, BadLocationException
 150:   {
 151:     Element rootElem = htmlDoc.getDefaultRootElement();
 152: 
 153:     if (doc_pos == -1 && doc_len == -1)
 154:       {
 155:         // Normal traversal.
 156:         traverse(rootElem);
 157:       } // if(doc_pos == -1 && doc_len == -1)
 158:     else
 159:       {
 160:         // Html fragment traversal.
 161:         if (doc_pos == -1 || doc_len == -1)
 162:           throw new BadLocationException("Bad Location("
 163:           + doc_pos + ", " + doc_len + ")", doc_pos);
 164: 
 165:         startElem = htmlDoc.getCharacterElement(doc_pos);
 166: 
 167:         int start_offset = startElem.getStartOffset();
 168: 
 169:         // Positions before start_offset will not be traversed, and thus
 170:         // will not be counted.
 171:         if (start_offset > 0)
 172:           doc_offset_remaining = doc_offset_remaining - start_offset;
 173: 
 174:         Element tempParentElem = startElem;
 175: 
 176:         while ((tempParentElem = tempParentElem.getParentElement()) != null)
 177:           {
 178:             if (!htmlFragmentParentHashSet.contains(tempParentElem))
 179:               htmlFragmentParentHashSet.add(tempParentElem);
 180:           } // while((tempParentElem = tempParentElem.getParentElement())
 181:             //   != null)
 182: 
 183:         // NOTE: 20061030 - fchoong - the last index should not be included.
 184:         endElem = htmlDoc.getCharacterElement(doc_pos + doc_len - 1);
 185: 
 186:         tempParentElem = endElem;
 187: 
 188:         while ((tempParentElem = tempParentElem.getParentElement()) != null)
 189:           {
 190:             if (!htmlFragmentParentHashSet.contains(tempParentElem))
 191:               htmlFragmentParentHashSet.add(tempParentElem);
 192:           } // while((tempParentElem = tempParentElem.getParentElement())
 193:             //   != null)
 194: 
 195:         traverseHtmlFragment(rootElem);
 196: 
 197:       } // else
 198: 
 199:     // NOTE: close out remaining open embeded tags.
 200:     HTML.Tag[] tag_arr =
 201:       openEmbeddedTagHashSet.toArray(new HTML.Tag[openEmbeddedTagHashSet.size()]);
 202: 
 203:     for (int i = 0; i < tag_arr.length; i++)
 204:       {
 205:         writeRaw("</" + tag_arr[i].toString() + ">");
 206:       } // for(int i = 0; i < tag_arr.length; i++)
 207: 
 208:   } // public void write() throws IOException, BadLocationException
 209: 
 210:   /**
 211:    * Writes all the attributes in the attrSet, except for attrbutes with
 212:    * keys of <code>javax.swing.text.html.HTML.Tag</code>,
 213:    * <code>javax.swing.text.StyleConstants</code> or
 214:    * <code>javax.swing.text.html.HTML.Attribute.ENDTAG</code>.
 215:    *
 216:    * @param attrSet attrSet to write out
 217:    *
 218:    * @throws IOException on any I/O exceptions
 219:    */
 220:   protected void writeAttributes(AttributeSet attrSet)
 221:     throws IOException
 222:   {
 223:     Enumeration<?> attrNameEnum = attrSet.getAttributeNames();
 224: 
 225:     while (attrNameEnum.hasMoreElements())
 226:       {
 227:         Object key = attrNameEnum.nextElement();
 228:         Object value = attrSet.getAttribute(key);
 229: 
 230:         // HTML.Attribute.ENDTAG is an instance, not a class.
 231:         if (!((key instanceof HTML.Tag) || (key instanceof StyleConstants)
 232:           || (key == HTML.Attribute.ENDTAG)))
 233:           {
 234:             if (key == HTML.Attribute.SELECTED)
 235:               writeRaw(" selected");
 236:             else if (key == HTML.Attribute.CHECKED)
 237:               writeRaw(" checked");
 238:             else
 239:               writeRaw(" " + key + "=\"" + value + "\"");
 240:           } // if(!((key instanceof HTML.Tag) || (key instanceof
 241:             //   StyleConstants) || (key == HTML.Attribute.ENDTAG)))
 242:       } // while(attrNameEnum.hasMoreElements())
 243: 
 244:   } // protected void writeAttributes(AttributeSet attrSet) throws IOException
 245: 
 246:   /**
 247:    * Writes out an empty tag. i.e. a tag without any child elements.
 248:    *
 249:    * @param paramElem the element to output as an empty tag
 250:    *
 251:    * @throws IOException on any I/O exceptions
 252:    * @throws BadLocationException if a pos is not a valid position in the
 253:    *                              html doc element
 254:    */
 255:   protected void emptyTag(Element paramElem)
 256:     throws IOException, BadLocationException
 257:   {
 258:     String elem_name = paramElem.getName();
 259:     AttributeSet attrSet = paramElem.getAttributes();
 260: 
 261:     writeRaw("<" + elem_name);
 262:     writeAttributes(attrSet);
 263:     writeRaw(">");
 264: 
 265:     if (isBlockTag(attrSet))
 266:       {
 267:         writeRaw("</" + elem_name + ">");
 268:       } // if(isBlockTag(attrSet))
 269: 
 270:   } // protected void emptyTag(Element paramElem)
 271:     //   throws IOException, BadLocationException
 272: 
 273:   /**
 274:    * Determines if it is a block tag or not.
 275:    *
 276:    * @param attrSet the attrSet of the element
 277:    *
 278:    * @return <code>true</code> if it is a block tag
 279:    *         <code>false</code> if it is a not block tag
 280:    */
 281:   protected boolean isBlockTag(AttributeSet attrSet)
 282:   {
 283:     return ((HTML.Tag)
 284:       attrSet.getAttribute(StyleConstants.NameAttribute)).isBlock();
 285:   } // protected boolean isBlockTag(AttributeSet attrSet)
 286: 
 287:   /**
 288:    * Writes out a start tag. Synthesized elements are skipped.
 289:    *
 290:    * @param paramElem the element to output as a start tag
 291:    * @throws IOException on any I/O exceptions
 292:    * @throws BadLocationException if a pos is not a valid position in the
 293:    *                              html doc element
 294:    */
 295:   protected void startTag(Element paramElem)
 296:     throws IOException, BadLocationException
 297:   {
 298:     // NOTE: Sysnthesized elements do no call this method at all.
 299:     String elem_name = paramElem.getName();
 300:     AttributeSet attrSet = paramElem.getAttributes();
 301: 
 302:     indent();
 303:     writeRaw("<" + elem_name);
 304:     writeAttributes(attrSet);
 305:     writeRaw(">");
 306:     writeLineSeparator(); // Extra formatting to look more like the RI.
 307:     incrIndent();
 308: 
 309:   } // protected void startTag(Element paramElem)
 310:     //   throws IOException, BadLocationException
 311: 
 312:   /**
 313:    * Writes out the contents of a textarea.
 314:    *
 315:    * @param attrSet the attrSet of the element to output as a text area
 316:    * @throws IOException on any I/O exceptions
 317:    * @throws BadLocationException if a pos is not a valid position in the
 318:    *                              html doc element
 319:    */
 320:   protected void textAreaContent(AttributeSet attrSet)
 321:     throws IOException, BadLocationException
 322:   {
 323:     writeLineSeparator(); // Extra formatting to look more like the RI.
 324:     indent();
 325:     writeRaw("<textarea");
 326:     writeAttributes(attrSet);
 327:     writeRaw(">");
 328: 
 329:     Document tempDocument =
 330:       (Document) attrSet.getAttribute(StyleConstants.ModelAttribute);
 331: 
 332:     writeRaw(tempDocument.getText(0, tempDocument.getLength()));
 333:     indent();
 334:     writeRaw("</textarea>");
 335: 
 336:   } // protected void textAreaContent(AttributeSet attrSet)
 337:     //   throws IOException, BadLocationException
 338: 
 339:   /**
 340:    * Writes out text, within the appropriate range if it is specified.
 341:    *
 342:    * @param paramElem the element to output as a text
 343:    * @throws IOException on any I/O exceptions
 344:    * @throws BadLocationException if a pos is not a valid position in the
 345:    *                              html doc element
 346:    */
 347:   protected void text(Element paramElem)
 348:     throws IOException, BadLocationException
 349:   {
 350:     int offset =  paramElem.getStartOffset();
 351:     int len =  paramElem.getEndOffset() -  paramElem.getStartOffset();
 352:     String txt_value = htmlDoc.getText(offset, len);
 353: 
 354:     writeContent(txt_value);
 355: 
 356:   } // protected void text(Element paramElem)
 357:     //   throws IOException, BadLocationException
 358: 
 359:   /**
 360:    * Writes out the contents of a select element.
 361:    *
 362:    * @param attrSet the attrSet of the element to output as a select box
 363:    *
 364:    * @throws IOException on any I/O exceptions
 365:    */
 366:   protected void selectContent(AttributeSet attrSet)
 367:     throws IOException
 368:   {
 369:     writeLineSeparator(); // Extra formatting to look more like the RI.
 370:     indent();
 371:     writeRaw("<select");
 372:     writeAttributes(attrSet);
 373:     writeRaw(">");
 374:     incrIndent();
 375:     writeLineSeparator(); // extra formatting to look more like the RI.
 376: 
 377:     ComboBoxModel comboBoxModel =
 378:       (ComboBoxModel) attrSet.getAttribute(StyleConstants.ModelAttribute);
 379: 
 380:     for (int i = 0; i < comboBoxModel.getSize(); i++)
 381:       {
 382:         writeOption((Option) comboBoxModel.getElementAt(i));
 383:       } // for(int i = 0; i < comboBoxModel.getSize(); i++)
 384: 
 385:     decrIndent();
 386:     indent();
 387:     writeRaw("</select>");
 388: 
 389:   } // protected void selectContent(AttributeSet attrSet) throws IOException
 390: 
 391:   /**
 392:    * Writes out the contents of an option element.
 393:    *
 394:    * @param option the option object to output as a select option
 395:    *
 396:    * @throws IOException on any I/O exceptions
 397:    */
 398:   protected void writeOption(Option option)
 399:     throws IOException
 400:   {
 401:     indent();
 402:     writeRaw("<option");
 403:     writeAttributes(option.getAttributes());
 404:     writeRaw(">");
 405: 
 406:     writeContent(option.getLabel());
 407: 
 408:     writeRaw("</option>");
 409:     writeLineSeparator(); // extra formatting to look more like the RI.
 410: 
 411:   } // protected void writeOption(Option option) throws IOException
 412: 
 413:   /**
 414:    * Writes out an end tag.
 415:    *
 416:    * @param paramElem the element to output as an end tag
 417:    *
 418:    * @throws IOException on any I/O exceptions
 419:    */
 420:   protected void endTag(Element paramElem)
 421:     throws IOException
 422:   {
 423:     String elem_name = paramElem.getName();
 424: 
 425:     //writeLineSeparator(); // Extra formatting to look more like the RI.
 426:     decrIndent();
 427:     indent();
 428:     writeRaw("</" + elem_name + ">");
 429:     writeLineSeparator(); // Extra formatting to look more like the RI.
 430: 
 431:   } // protected void endTag(Element paramElem) throws IOException
 432: 
 433:   /**
 434:    * Writes out the comment.
 435:    *
 436:    * @param paramElem the element to output as a comment
 437:    */
 438:   protected void comment(Element paramElem)
 439:     throws IOException, BadLocationException
 440:   {
 441:     AttributeSet attrSet = paramElem.getAttributes();
 442: 
 443:     String comment_str = (String) attrSet.getAttribute(HTML.Attribute.COMMENT);
 444: 
 445:     writeRaw("<!--" + comment_str + "-->");
 446: 
 447:   } // protected void comment(Element paramElem)
 448:     //   throws IOException, BadLocationException
 449: 
 450:   /**
 451:    * Determines if element is a synthesized
 452:    * <code>javax.swing.text.Element</code> or not.
 453:    *
 454:    * @param element the element to test
 455:    *
 456:    * @return <code>true</code> if it is a synthesized element,
 457:    *         <code>false</code> if it is a not synthesized element
 458:    */
 459:   protected boolean synthesizedElement(Element element)
 460:   {
 461:     AttributeSet attrSet = element.getAttributes();
 462:     Object tagType = attrSet.getAttribute(StyleConstants.NameAttribute);
 463: 
 464:     if (tagType == HTML.Tag.CONTENT || tagType == HTML.Tag.COMMENT
 465:         || tagType == HTML.Tag.IMPLIED)
 466:       return true;
 467:     else
 468:       return false;
 469:   } // protected boolean synthesizedElement(Element element)
 470: 
 471:   /**
 472:    * Determines if
 473:    * <code>javax.swing.text.StyleConstants.NameAttribute</code>
 474:    * matches tag or not.
 475:    *
 476:    * @param attrSet the <code>javax.swing.text.AttributeSet</code> of
 477:    *        element to be matched
 478:    * @param tag the HTML.Tag to match
 479:    *
 480:    * @return <code>true</code> if it matches,
 481:    *         <code>false</code> if it does not match
 482:    */
 483:   protected boolean matchNameAttribute(AttributeSet attrSet, HTML.Tag tag)
 484:   {
 485:     Object tagType = attrSet.getAttribute(StyleConstants.NameAttribute);
 486: 
 487:     if (tagType == tag)
 488:       return true;
 489:     else
 490:       return false;
 491:   } // protected boolean matchNameAttribute(AttributeSet attrSet,
 492:     //   HTML.Tag tag)
 493: 
 494:   /**
 495:    * Writes out an embedded tag. The tags not already in
 496:    * openEmbededTagHashSet will written out.
 497:    *
 498:    * @param attrSet the <code>javax.swing.text.AttributeSet</code> of
 499:    *        the element to write out
 500:    *
 501:    * @throws IOException on any I/O exceptions
 502:    */
 503:   protected void writeEmbeddedTags(AttributeSet attrSet)
 504:     throws IOException
 505:   {
 506:     Enumeration<?> attrNameEnum = attrSet.getAttributeNames();
 507: 
 508:     while (attrNameEnum.hasMoreElements())
 509:       {
 510:         Object key = attrNameEnum.nextElement();
 511:         Object value = attrSet.getAttribute(key);
 512: 
 513:         if (key instanceof HTML.Tag)
 514:           {
 515:             if (!openEmbeddedTagHashSet.contains(key))
 516:               {
 517:                 writeRaw("<" + key);
 518:                 writeAttributes((AttributeSet) value);
 519:                 writeRaw(">");
 520:                 openEmbeddedTagHashSet.add((HTML.Tag) key);
 521:               } // if(!openEmbededTagHashSet.contains(key))
 522:           } // if(key instanceof HTML.Tag)
 523:       } // while(attrNameEnum.hasMoreElements())
 524: 
 525:   } // protected void writeEmbeddedTags(AttributeSet attrSet)
 526:     //   throws IOException
 527: 
 528:   /**
 529:    * Closes out an unwanted embedded tag. The tags from the
 530:    *  openEmbededTagHashSet not found in attrSet will be written out.
 531:    *
 532:    *  @param attrSet the AttributeSet of the element to write out
 533:    *
 534:    *  @throws IOException on any I/O exceptions
 535:    */
 536:   protected void closeOutUnwantedEmbeddedTags(AttributeSet attrSet)
 537:     throws IOException
 538:   {
 539:     HTML.Tag[] tag_arr =
 540:       openEmbeddedTagHashSet.toArray(new HTML.Tag[openEmbeddedTagHashSet.size()]);
 541: 
 542:     for (int i = 0; i < tag_arr.length; i++)
 543:       {
 544:         HTML.Tag key = tag_arr[i];
 545: 
 546:         if (!attrSet.isDefined(key))
 547:           {
 548:             writeRaw("</" + key.toString() + ">");
 549:             openEmbeddedTagHashSet.remove(key);
 550:           } // if(!attrSet.isDefined(key))
 551:       } // for(int i = 0; i < tag_arr.length; i++)
 552: 
 553:   } // protected void closeOutUnwantedEmbeddedTags(AttributeSet attrSet)
 554:     //   throws IOException
 555: 
 556:   /**
 557:    * Writes out a line separator. Overwrites the parent to write out a new
 558:    * line.
 559:    *
 560:    * @throws IOException on any I/O exceptions.
 561:    */
 562:   protected void writeLineSeparator()
 563:     throws IOException
 564:   {
 565:     writeRaw(new_line_str);
 566:   } // protected void writeLineSeparator() throws IOException
 567: 
 568:   /**
 569:    * Write to the writer. Character entites such as &lt;, &gt;
 570:    * are escaped appropriately.
 571:    *
 572:    * @param chars char array to write out
 573:    * @param off offset
 574:    * @param len length
 575:    *
 576:    * @throws IOException on any I/O exceptions
 577:    */
 578:   protected void output(char[] chars, int off, int len)
 579:    throws IOException
 580:   {
 581:     CPStringBuilder strBuffer = new CPStringBuilder();
 582: 
 583:     for (int i = 0; i < chars.length; i++)
 584:       {
 585:         if (isCharHtmlEntity(chars[i]))
 586:           strBuffer.append(escapeCharHtmlEntity(chars[i]));
 587:         else
 588:           strBuffer.append(chars[i]);
 589:       } // for(int i = 0; i < chars.length; i++)
 590: 
 591:     writeRaw(strBuffer.toString());
 592: 
 593:   } // protected void output(char[] chars, int off, int len)
 594:     //   throws IOException
 595: 
 596:   //-------------------------------------------------------------------------
 597:   // private methods
 598: 
 599:   /**
 600:    * The main method used to traverse through the elements.
 601:    *
 602:    * @param paramElem element to traverse
 603:    *
 604:    * @throws IOException on any I/O exceptions
 605:    */
 606:   private void traverse(Element paramElem)
 607:     throws IOException, BadLocationException
 608:   {
 609:     Element currElem = paramElem;
 610: 
 611:     AttributeSet attrSet = currElem.getAttributes();
 612: 
 613:     closeOutUnwantedEmbeddedTags(attrSet);
 614: 
 615:     // handle the tag
 616:     if (synthesizedElement(paramElem))
 617:       {
 618:         if (matchNameAttribute(attrSet, HTML.Tag.CONTENT))
 619:           {
 620:             writeEmbeddedTags(attrSet);
 621:             text(currElem);
 622:           } // if(matchNameAttribute(attrSet, HTML.Tag.CONTENT))
 623:         else if (matchNameAttribute(attrSet, HTML.Tag.COMMENT))
 624:           {
 625:             comment(currElem);
 626:           } // else if(matchNameAttribute(attrSet, HTML.Tag.COMMENT))
 627:         else if (matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
 628:           {
 629:             int child_elem_count = currElem.getElementCount();
 630: 
 631:             if (child_elem_count > 0)
 632:               {
 633:                 for (int i = 0; i < child_elem_count; i++)
 634:                   {
 635:                     Element childElem = paramElem.getElement(i);
 636: 
 637:                     traverse(childElem);
 638: 
 639:                   } // for(int i = 0; i < child_elem_count; i++)
 640:               } // if(child_elem_count > 0)
 641:           } // else if(matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
 642:       } // if(synthesizedElement(paramElem))
 643:     else
 644:       {
 645:         // NOTE: 20061030 - fchoong - title is treated specially here.
 646:         // based on RI behavior.
 647:         if (matchNameAttribute(attrSet, HTML.Tag.TITLE))
 648:           {
 649:             boolean fg_is_end_tag = false;
 650:             Enumeration<?> attrNameEnum = attrSet.getAttributeNames();
 651: 
 652:             while (attrNameEnum.hasMoreElements())
 653:               {
 654:                 Object key = attrNameEnum.nextElement();
 655:                 Object value = attrSet.getAttribute(key);
 656: 
 657:                 if (key == HTML.Attribute.ENDTAG && value.equals("true"))
 658:                   fg_is_end_tag = true;
 659:               } // while(attrNameEnum.hasMoreElements())
 660: 
 661:             if (fg_is_end_tag)
 662:               writeRaw("</title>");
 663:             else
 664:               {
 665:                 indent();
 666:                 writeRaw("<title>");
 667: 
 668:                 String title_str =
 669:                   (String) htmlDoc.getProperty(HTMLDocument.TitleProperty);
 670: 
 671:                 if (title_str != null)
 672:                   writeContent(title_str);
 673: 
 674:               } // else
 675:           } // if(matchNameAttribute(attrSet, HTML.Tag.TITLE))
 676:         else if (matchNameAttribute(attrSet, HTML.Tag.PRE))
 677:           {
 678:             // We pursue more stringent formating here.
 679:             attrSet = paramElem.getAttributes();
 680: 
 681:             indent();
 682:             writeRaw("<pre");
 683:             writeAttributes(attrSet);
 684:             writeRaw(">");
 685: 
 686:             int child_elem_count = currElem.getElementCount();
 687: 
 688:             for (int i = 0; i < child_elem_count; i++)
 689:               {
 690:                 Element childElem = paramElem.getElement(i);
 691: 
 692:                 traverse(childElem);
 693: 
 694:               } // for(int i = 0; i < child_elem_count; i++)
 695: 
 696:             writeRaw("</pre>");
 697: 
 698:           } // else if(matchNameAttribute(attrSet, HTML.Tag.PRE))
 699:         else if (matchNameAttribute(attrSet, HTML.Tag.SELECT))
 700:           {
 701:             selectContent(attrSet);
 702:           } // else if(matchNameAttribute(attrSet, HTML.Tag.SELECT))
 703:         else if (matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
 704:           {
 705:             textAreaContent(attrSet);
 706:           } // else if(matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
 707:         else
 708:           {
 709:             int child_elem_count = currElem.getElementCount();
 710: 
 711:             if (child_elem_count > 0)
 712:               {
 713:                 startTag(currElem);
 714: 
 715:                 for (int i = 0; i < child_elem_count; i++)
 716:                   {
 717:                     Element childElem = paramElem.getElement(i);
 718: 
 719:                     traverse(childElem);
 720: 
 721:                   } // for(int i = 0; i < child_elem_count; i++)
 722: 
 723:                   endTag(currElem);
 724: 
 725:               } // if(child_elem_count > 0)
 726:             else
 727:               {
 728:                 emptyTag(currElem);
 729:               } // else
 730:             } // else
 731:           } // else
 732: 
 733:   } // private void traverse(Element paramElem)
 734:     //   throws IOException, BadLocationException
 735: 
 736:   /**
 737:    * The method used to traverse through a html fragment.
 738:    *
 739:    * @param paramElem element to traverse
 740:    *
 741:    * @throws IOException on any I/O exceptions
 742:    */
 743:   private void traverseHtmlFragment(Element paramElem)
 744:     throws IOException, BadLocationException
 745:   {
 746:     // NOTE: This method is similar to traverse(Element paramElem)
 747:     Element currElem = paramElem;
 748: 
 749:     boolean fg_is_fragment_parent_elem = false;
 750:     boolean fg_is_start_and_end_elem = false;
 751: 
 752:     if (htmlFragmentParentHashSet.contains(paramElem))
 753:       fg_is_fragment_parent_elem = true;
 754: 
 755:     if (paramElem == startElem)
 756:       fg_pass_start_elem = true;
 757: 
 758:     if (paramElem == startElem && paramElem == endElem)
 759:       fg_is_start_and_end_elem = true;
 760: 
 761:     AttributeSet attrSet = currElem.getAttributes();
 762: 
 763:     closeOutUnwantedEmbeddedTags(attrSet);
 764: 
 765:     if (fg_is_fragment_parent_elem || (fg_pass_start_elem
 766:         && fg_pass_end_elem == false) || fg_is_start_and_end_elem)
 767:     {
 768:       // handle the tag
 769:       if (synthesizedElement(paramElem))
 770:         {
 771:           if (matchNameAttribute(attrSet, HTML.Tag.CONTENT))
 772:             {
 773:               writeEmbeddedTags(attrSet);
 774: 
 775:               int content_offset =  paramElem.getStartOffset();
 776:               int content_length = currElem.getEndOffset() - content_offset;
 777: 
 778:               if (doc_offset_remaining > 0)
 779:                 {
 780:                   if (content_length > doc_offset_remaining)
 781:                     {
 782:                       int split_len = content_length;
 783: 
 784:                       split_len = split_len - doc_offset_remaining;
 785: 
 786:                       if (split_len > doc_len_remaining)
 787:                         split_len = doc_len_remaining;
 788: 
 789:                       // we need to split it.
 790:                       String txt_value = htmlDoc.getText(content_offset
 791:                         + doc_offset_remaining, split_len);
 792: 
 793:                       writeContent(txt_value);
 794: 
 795:                       doc_offset_remaining = 0; // the offset is used up.
 796:                       doc_len_remaining = doc_len_remaining - split_len;
 797:                     } // if(content_length > doc_offset_remaining)
 798:                   else
 799:                     {
 800:                       // doc_offset_remaining is greater than the entire
 801:                       //   length of content
 802:                       doc_offset_remaining = doc_offset_remaining
 803:                         - content_length;
 804:                     }  // else
 805:                 } // if(doc_offset_remaining > 0)
 806:               else if (content_length <= doc_len_remaining)
 807:                 {
 808:                   // we can fit the entire content.
 809:                   text(currElem);
 810:                   doc_len_remaining = doc_len_remaining - content_length;
 811:                 } // else if(content_length <= doc_len_remaining)
 812:               else
 813:                 {
 814:                   // we need to split it.
 815:                   String txt_value = htmlDoc.getText(content_offset,
 816:                     doc_len_remaining);
 817: 
 818:                   writeContent(txt_value);
 819: 
 820:                   doc_len_remaining = 0;
 821:                 } // else
 822: 
 823:             } // if(matchNameAttribute(attrSet, HTML.Tag.CONTENT))
 824:           else if (matchNameAttribute(attrSet, HTML.Tag.COMMENT))
 825:             {
 826:               comment(currElem);
 827:             } // else if(matchNameAttribute(attrSet, HTML.Tag.COMMENT))
 828:           else if (matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
 829:             {
 830:               int child_elem_count = currElem.getElementCount();
 831: 
 832:               if (child_elem_count > 0)
 833:                 {
 834:                   for (int i = 0; i < child_elem_count; i++)
 835:                     {
 836:                       Element childElem = paramElem.getElement(i);
 837: 
 838:                       traverseHtmlFragment(childElem);
 839: 
 840:                     } // for(int i = 0; i < child_elem_count; i++)
 841:                 } // if(child_elem_count > 0)
 842:             } // else if(matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
 843:         } // if(synthesizedElement(paramElem))
 844:       else
 845:         {
 846:             // NOTE: 20061030 - fchoong - the isLeaf() condition seems to
 847:             // generate the closest behavior to the RI.
 848:             if (paramElem.isLeaf())
 849:               {
 850:                 if (doc_offset_remaining > 0)
 851:                   {
 852:                     doc_offset_remaining--;
 853:                   } // if(doc_offset_remaining > 0)
 854:                 else if (doc_len_remaining > 0)
 855:                   {
 856:                     doc_len_remaining--;
 857:                   } // else if(doc_len_remaining > 0)
 858:               } // if(paramElem.isLeaf())
 859: 
 860:           // NOTE: 20061030 - fchoong - title is treated specially here.
 861:           // based on RI behavior.
 862:           if (matchNameAttribute(attrSet, HTML.Tag.TITLE))
 863:             {
 864:               boolean fg_is_end_tag = false;
 865:               Enumeration<?> attrNameEnum = attrSet.getAttributeNames();
 866: 
 867:               while (attrNameEnum.hasMoreElements())
 868:                 {
 869:                   Object key = attrNameEnum.nextElement();
 870:                   Object value = attrSet.getAttribute(key);
 871: 
 872:                   if (key == HTML.Attribute.ENDTAG && value.equals("true"))
 873:                     fg_is_end_tag = true;
 874:                 } // while(attrNameEnum.hasMoreElements())
 875: 
 876:               if (fg_is_end_tag)
 877:                 writeRaw("</title>");
 878:               else
 879:                 {
 880:                   indent();
 881:                   writeRaw("<title>");
 882: 
 883:                   String title_str =
 884:                     (String) htmlDoc.getProperty(HTMLDocument.TitleProperty);
 885: 
 886:                   if (title_str != null)
 887:                     writeContent(title_str);
 888: 
 889:                 } // else
 890:             } // if(matchNameAttribute(attrSet, HTML.Tag.TITLE))
 891:           else if (matchNameAttribute(attrSet, HTML.Tag.PRE))
 892:             {
 893:               // We pursue more stringent formating here.
 894:               attrSet = paramElem.getAttributes();
 895: 
 896:               indent();
 897:               writeRaw("<pre");
 898:               writeAttributes(attrSet);
 899:               writeRaw(">");
 900: 
 901:               int child_elem_count = currElem.getElementCount();
 902: 
 903:               for (int i = 0; i < child_elem_count; i++)
 904:                 {
 905:                   Element childElem = paramElem.getElement(i);
 906: 
 907:                   traverseHtmlFragment(childElem);
 908: 
 909:                 } // for(int i = 0; i < child_elem_count; i++)
 910: 
 911:               writeRaw("</pre>");
 912: 
 913:             } // else if(matchNameAttribute(attrSet, HTML.Tag.PRE))
 914:           else if (matchNameAttribute(attrSet, HTML.Tag.SELECT))
 915:             {
 916:               selectContent(attrSet);
 917:             } // else if(matchNameAttribute(attrSet, HTML.Tag.SELECT))
 918:           else if (matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
 919:             {
 920:               textAreaContent(attrSet);
 921:             } // else if(matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
 922:           else
 923:             {
 924:               int child_elem_count = currElem.getElementCount();
 925: 
 926:               if (child_elem_count > 0)
 927:                 {
 928:                   startTag(currElem);
 929: 
 930:                   for (int i = 0; i < child_elem_count; i++)
 931:                     {
 932:                       Element childElem = paramElem.getElement(i);
 933: 
 934:                       traverseHtmlFragment(childElem);
 935: 
 936:                     } // for(int i = 0; i < child_elem_count; i++)
 937: 
 938:                     endTag(currElem);
 939: 
 940:                 } // if(child_elem_count > 0)
 941:               else
 942:                 {
 943:                   emptyTag(currElem);
 944:                 } // else
 945:             } // else
 946:         } // else
 947: 
 948:     } // if(fg_is_fragment_parent_elem || (fg_pass_start_elem
 949:       //   && fg_pass_end_elem == false) || fg_is_start_and_end_elem)
 950: 
 951:     if (paramElem == endElem)
 952:       fg_pass_end_elem = true;
 953: 
 954:   } // private void traverseHtmlFragment(Element paramElem)
 955:     //   throws IOException, BadLocationException
 956: 
 957:   /**
 958:    * Write to the writer without any modifications.
 959:    *
 960:    * @param param_str the str to write out
 961:    *
 962:    * @throws IOException on any I/O exceptions
 963:    */
 964:   private void writeRaw(String param_str)
 965:     throws IOException
 966:   {
 967:     super.output(param_str.toCharArray(), 0, param_str.length());
 968:   } // private void writeRaw(char[] chars, int off, int len)
 969:     //   throws IOException
 970: 
 971:   /**
 972:    * Write to the writer, escaping HTML character entitie where neccessary.
 973:    *
 974:    * @param param_str the str to write out
 975:    *
 976:    * @throws IOException on any I/O exceptions
 977:    */
 978:   private void writeContent(String param_str)
 979:     throws IOException
 980:   {
 981:     char[] str_char_arr = param_str.toCharArray();
 982: 
 983:     if (hasHtmlEntity(param_str))
 984:       output(str_char_arr, 0, str_char_arr.length);
 985:     else
 986:       super.output(str_char_arr, 0, str_char_arr.length);
 987: 
 988:   } // private void writeContent(String param_str) throws IOException
 989: 
 990:   /**
 991:    * Use this for debugging. Writes out all attributes regardless of type.
 992:    *
 993:    * @param attrSet the <code>javax.swing.text.AttributeSet</code> to
 994:    *        write out
 995:    *
 996:    * @throws IOException on any I/O exceptions
 997:    */
 998:   private void writeAllAttributes(AttributeSet attrSet)
 999:     throws IOException
1000:   {
1001:     Enumeration<?> attrNameEnum = attrSet.getAttributeNames();
1002: 
1003:     while (attrNameEnum.hasMoreElements())
1004:       {
1005:         Object key = attrNameEnum.nextElement();
1006:         Object value = attrSet.getAttribute(key);
1007: 
1008:         writeRaw(" " + key + "=\"" + value + "\"");
1009:         writeRaw(" " + key.getClass().toString() + "=\""
1010:           + value.getClass().toString() + "\"");
1011:       } // while(attrNameEnum.hasMoreElements())
1012: 
1013:   } // private void writeAllAttributes(AttributeSet attrSet)
1014:     //   throws IOException
1015: 
1016:   /**
1017:    * Tests if the str contains any html entities.
1018:    *
1019:    * @param param_str the str to test
1020:    *
1021:    * @return <code>true</code> if it has a html entity
1022:    *         <code>false</code> if it does not have a html entity
1023:    */
1024:   private boolean hasHtmlEntity(String param_str)
1025:   {
1026:     boolean ret_bool = false;
1027: 
1028:     for (int i = 0; i < html_entity_char_arr.length; i++)
1029:       {
1030:         if (param_str.indexOf(html_entity_char_arr[i]) != -1)
1031:           {
1032:             ret_bool = true;
1033:             break;
1034:           } // if(param_str.indexOf(html_entity_char_arr[i]) != -1)
1035:       } // for(int i = 0; i < html_entity_char_arr.length; i++)
1036: 
1037:     return ret_bool;
1038:   } // private boolean hasHtmlEntity(String param_str)
1039: 
1040:   /**
1041:    * Tests if the char is a html entities.
1042:    *
1043:    * @param param_char the char to test
1044:    *
1045:    * @return <code>true</code> if it is a html entity
1046:    *         <code>false</code> if it is not a html entity.
1047:    */
1048:   private boolean isCharHtmlEntity(char param_char)
1049:   {
1050:     boolean ret_bool = false;
1051: 
1052:     for (int i = 0; i < html_entity_char_arr.length; i++)
1053:       {
1054:         if (param_char == html_entity_char_arr[i])
1055:           {
1056:             ret_bool = true;
1057:             break;
1058:           } // if(param_char == html_entity_char_arr[i])
1059:       } // for(int i = 0; i < html_entity_char_arr.length; i++)
1060: 
1061:       return ret_bool;
1062:   } // private boolean hasHtmlEntity(String param_str)
1063: 
1064:   /**
1065:    * Escape html entities.
1066:    *
1067:    * @param param_char the char to escape
1068:    *
1069:    * @return escaped html entity. Original char is returned as a str if is
1070:    *         is not a html entity
1071:    */
1072:   private String escapeCharHtmlEntity(char param_char)
1073:   {
1074:     String ret_str = "" + param_char;
1075: 
1076:     for (int i = 0; i < html_entity_char_arr.length; i++)
1077:       {
1078:         if (param_char == html_entity_char_arr[i])
1079:           {
1080:             ret_str = html_entity_escape_str_arr[i];
1081:             break;
1082:           } // if(param_char == html_entity_char_arr[i])
1083:       } // for(int i = 0; i < html_entity_char_arr.length; i++)
1084: 
1085:       return ret_str;
1086:   } // private String escapeCharHtmlEntity(char param_char)
1087: 
1088: } // public class HTMLWriter extends AbstractWriter