Writer to write output to
   * @param doc the javax.swing.text.html.HTMLDocument
   *        to output
   * @param pos position to start outputing the document
   * @param len amount to output the document
   */
  public HTMLWriter(Writer writer, HTMLDocument doc, int pos, int len)
  {
    super(writer, doc, pos, len);
    outWriter = writer;
    htmlDoc = doc;
    openEmbeddedTagHashSet = new HashSetjavax.swing.text.html.HTML.Tag,
   * javax.swing.text.StyleConstants or
   * javax.swing.text.html.HTML.Attribute.ENDTAG.
   *
   * @param attrSet attrSet to write out
   *
   * @throws IOException on any I/O exceptions
   */
  protected void writeAttributes(AttributeSet attrSet)
    throws IOException
  {
    Enumeration> attrNameEnum = attrSet.getAttributeNames();
    while (attrNameEnum.hasMoreElements())
      {
        Object key = attrNameEnum.nextElement();
        Object value = attrSet.getAttribute(key);
        // HTML.Attribute.ENDTAG is an instance, not a class.
        if (!((key instanceof HTML.Tag) || (key instanceof StyleConstants)
          || (key == HTML.Attribute.ENDTAG)))
          {
            if (key == HTML.Attribute.SELECTED)
              writeRaw(" selected");
            else if (key == HTML.Attribute.CHECKED)
              writeRaw(" checked");
            else
              writeRaw(" " + key + "=\"" + value + "\"");
          } // if(!((key instanceof HTML.Tag) || (key instanceof
            //   StyleConstants) || (key == HTML.Attribute.ENDTAG)))
      } // while(attrNameEnum.hasMoreElements())
  } // protected void writeAttributes(AttributeSet attrSet) throws IOException
  /**
   * Writes out an empty tag. i.e. a tag without any child elements.
   *
   * @param paramElem the element to output as an empty tag
   *
   * @throws IOException on any I/O exceptions
   * @throws BadLocationException if a pos is not a valid position in the
   *                              html doc element
   */
  protected void emptyTag(Element paramElem)
    throws IOException, BadLocationException
  {
    String elem_name = paramElem.getName();
    AttributeSet attrSet = paramElem.getAttributes();
    writeRaw("<" + elem_name);
    writeAttributes(attrSet);
    writeRaw(">");
    if (isBlockTag(attrSet))
      {
        writeRaw("" + elem_name + ">");
      } // if(isBlockTag(attrSet))
  } // protected void emptyTag(Element paramElem)
    //   throws IOException, BadLocationException
  /**
   * Determines if it is a block tag or not.
   *
   * @param attrSet the attrSet of the element
   *
   * @return true if it is a block tag
   *         false if it is a not block tag
   */
  protected boolean isBlockTag(AttributeSet attrSet)
  {
    return ((HTML.Tag)
      attrSet.getAttribute(StyleConstants.NameAttribute)).isBlock();
  } // protected boolean isBlockTag(AttributeSet attrSet)
  /**
   * Writes out a start tag. Synthesized elements are skipped.
   *
   * @param paramElem the element to output as a start tag
   * @throws IOException on any I/O exceptions
   * @throws BadLocationException if a pos is not a valid position in the
   *                              html doc element
   */
  protected void startTag(Element paramElem)
    throws IOException, BadLocationException
  {
    // NOTE: Sysnthesized elements do no call this method at all.
    String elem_name = paramElem.getName();
    AttributeSet attrSet = paramElem.getAttributes();
    indent();
    writeRaw("<" + elem_name);
    writeAttributes(attrSet);
    writeRaw(">");
    writeLineSeparator(); // Extra formatting to look more like the RI.
    incrIndent();
  } // protected void startTag(Element paramElem)
    //   throws IOException, BadLocationException
  /**
   * Writes out the contents of a textarea.
   *
   * @param attrSet the attrSet of the element to output as a text area
   * @throws IOException on any I/O exceptions
   * @throws BadLocationException if a pos is not a valid position in the
   *                              html doc element
   */
  protected void textAreaContent(AttributeSet attrSet)
    throws IOException, BadLocationException
  {
    writeLineSeparator(); // Extra formatting to look more like the RI.
    indent();
    writeRaw("");
  } // protected void textAreaContent(AttributeSet attrSet)
    //   throws IOException, BadLocationException
  /**
   * Writes out text, within the appropriate range if it is specified.
   *
   * @param paramElem the element to output as a text
   * @throws IOException on any I/O exceptions
   * @throws BadLocationException if a pos is not a valid position in the
   *                              html doc element
   */
  protected void text(Element paramElem)
    throws IOException, BadLocationException
  {
    int offset =  paramElem.getStartOffset();
    int len =  paramElem.getEndOffset() -  paramElem.getStartOffset();
    String txt_value = htmlDoc.getText(offset, len);
    writeContent(txt_value);
  } // protected void text(Element paramElem)
    //   throws IOException, BadLocationException
  /**
   * Writes out the contents of a select element.
   *
   * @param attrSet the attrSet of the element to output as a select box
   *
   * @throws IOException on any I/O exceptions
   */
  protected void selectContent(AttributeSet attrSet)
    throws IOException
  {
    writeLineSeparator(); // Extra formatting to look more like the RI.
    indent();
    writeRaw("");
  } // protected void selectContent(AttributeSet attrSet) throws IOException
  /**
   * Writes out the contents of an option element.
   *
   * @param option the option object to output as a select option
   *
   * @throws IOException on any I/O exceptions
   */
  protected void writeOption(Option option)
    throws IOException
  {
    indent();
    writeRaw("");
    writeLineSeparator(); // extra formatting to look more like the RI.
  } // protected void writeOption(Option option) throws IOException
  /**
   * Writes out an end tag.
   *
   * @param paramElem the element to output as an end tag
   *
   * @throws IOException on any I/O exceptions
   */
  protected void endTag(Element paramElem)
    throws IOException
  {
    String elem_name = paramElem.getName();
    //writeLineSeparator(); // Extra formatting to look more like the RI.
    decrIndent();
    indent();
    writeRaw("" + elem_name + ">");
    writeLineSeparator(); // Extra formatting to look more like the RI.
  } // protected void endTag(Element paramElem) throws IOException
  /**
   * Writes out the comment.
   *
   * @param paramElem the element to output as a comment
   */
  protected void comment(Element paramElem)
    throws IOException, BadLocationException
  {
    AttributeSet attrSet = paramElem.getAttributes();
    String comment_str = (String) attrSet.getAttribute(HTML.Attribute.COMMENT);
    writeRaw("");
  } // protected void comment(Element paramElem)
    //   throws IOException, BadLocationException
  /**
   * Determines if element is a synthesized
   * javax.swing.text.Element or not.
   *
   * @param element the element to test
   *
   * @return true if it is a synthesized element,
   *         false if it is a not synthesized element
   */
  protected boolean synthesizedElement(Element element)
  {
    AttributeSet attrSet = element.getAttributes();
    Object tagType = attrSet.getAttribute(StyleConstants.NameAttribute);
    if (tagType == HTML.Tag.CONTENT || tagType == HTML.Tag.COMMENT
        || tagType == HTML.Tag.IMPLIED)
      return true;
    else
      return false;
  } // protected boolean synthesizedElement(Element element)
  /**
   * Determines if
   * javax.swing.text.StyleConstants.NameAttribute
   * matches tag or not.
   *
   * @param attrSet the javax.swing.text.AttributeSet of
   *        element to be matched
   * @param tag the HTML.Tag to match
   *
   * @return true if it matches,
   *         false if it does not match
   */
  protected boolean matchNameAttribute(AttributeSet attrSet, HTML.Tag tag)
  {
    Object tagType = attrSet.getAttribute(StyleConstants.NameAttribute);
    if (tagType == tag)
      return true;
    else
      return false;
  } // protected boolean matchNameAttribute(AttributeSet attrSet,
    //   HTML.Tag tag)
  /**
   * Writes out an embedded tag. The tags not already in
   * openEmbededTagHashSet will written out.
   *
   * @param attrSet the javax.swing.text.AttributeSet of
   *        the element to write out
   *
   * @throws IOException on any I/O exceptions
   */
  protected void writeEmbeddedTags(AttributeSet attrSet)
    throws IOException
  {
    Enumeration> attrNameEnum = attrSet.getAttributeNames();
    while (attrNameEnum.hasMoreElements())
      {
        Object key = attrNameEnum.nextElement();
        Object value = attrSet.getAttribute(key);
        if (key instanceof HTML.Tag)
          {
            if (!openEmbeddedTagHashSet.contains(key))
              {
                writeRaw("<" + key);
                writeAttributes((AttributeSet) value);
                writeRaw(">");
                openEmbeddedTagHashSet.add((HTML.Tag) key);
              } // if(!openEmbededTagHashSet.contains(key))
          } // if(key instanceof HTML.Tag)
      } // while(attrNameEnum.hasMoreElements())
  } // protected void writeEmbeddedTags(AttributeSet attrSet)
    //   throws IOException
  /**
   * Closes out an unwanted embedded tag. The tags from the
   *  openEmbededTagHashSet not found in attrSet will be written out.
   *
   *  @param attrSet the AttributeSet of the element to write out
   *
   *  @throws IOException on any I/O exceptions
   */
  protected void closeOutUnwantedEmbeddedTags(AttributeSet attrSet)
    throws IOException
  {
    HTML.Tag[] tag_arr =
      openEmbeddedTagHashSet.toArray(new HTML.Tag[openEmbeddedTagHashSet.size()]);
    for (int i = 0; i < tag_arr.length; i++)
      {
        HTML.Tag key = tag_arr[i];
        if (!attrSet.isDefined(key))
          {
            writeRaw("" + key.toString() + ">");
            openEmbeddedTagHashSet.remove(key);
          } // if(!attrSet.isDefined(key))
      } // for(int i = 0; i < tag_arr.length; i++)
  } // protected void closeOutUnwantedEmbeddedTags(AttributeSet attrSet)
    //   throws IOException
  /**
   * Writes out a line separator. Overwrites the parent to write out a new
   * line.
   *
   * @throws IOException on any I/O exceptions.
   */
  protected void writeLineSeparator()
    throws IOException
  {
    writeRaw(new_line_str);
  } // protected void writeLineSeparator() throws IOException
  /**
   * Write to the writer. Character entites such as <, >
   * are escaped appropriately.
   *
   * @param chars char array to write out
   * @param off offset
   * @param len length
   *
   * @throws IOException on any I/O exceptions
   */
  protected void output(char[] chars, int off, int len)
   throws IOException
  {
    CPStringBuilder strBuffer = new CPStringBuilder();
    for (int i = 0; i < chars.length; i++)
      {
        if (isCharHtmlEntity(chars[i]))
          strBuffer.append(escapeCharHtmlEntity(chars[i]));
        else
          strBuffer.append(chars[i]);
      } // for(int i = 0; i < chars.length; i++)
    writeRaw(strBuffer.toString());
  } // protected void output(char[] chars, int off, int len)
    //   throws IOException
  //-------------------------------------------------------------------------
  // private methods
  /**
   * The main method used to traverse through the elements.
   *
   * @param paramElem element to traverse
   *
   * @throws IOException on any I/O exceptions
   */
  private void traverse(Element paramElem)
    throws IOException, BadLocationException
  {
    Element currElem = paramElem;
    AttributeSet attrSet = currElem.getAttributes();
    closeOutUnwantedEmbeddedTags(attrSet);
    // handle the tag
    if (synthesizedElement(paramElem))
      {
        if (matchNameAttribute(attrSet, HTML.Tag.CONTENT))
          {
            writeEmbeddedTags(attrSet);
            text(currElem);
          } // if(matchNameAttribute(attrSet, HTML.Tag.CONTENT))
        else if (matchNameAttribute(attrSet, HTML.Tag.COMMENT))
          {
            comment(currElem);
          } // else if(matchNameAttribute(attrSet, HTML.Tag.COMMENT))
        else if (matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
          {
            int child_elem_count = currElem.getElementCount();
            if (child_elem_count > 0)
              {
                for (int i = 0; i < child_elem_count; i++)
                  {
                    Element childElem = paramElem.getElement(i);
                    traverse(childElem);
                  } // for(int i = 0; i < child_elem_count; i++)
              } // if(child_elem_count > 0)
          } // else if(matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
      } // if(synthesizedElement(paramElem))
    else
      {
        // NOTE: 20061030 - fchoong - title is treated specially here.
        // based on RI behavior.
        if (matchNameAttribute(attrSet, HTML.Tag.TITLE))
          {
            boolean fg_is_end_tag = false;
            Enumeration> attrNameEnum = attrSet.getAttributeNames();
            while (attrNameEnum.hasMoreElements())
              {
                Object key = attrNameEnum.nextElement();
                Object value = attrSet.getAttribute(key);
                if (key == HTML.Attribute.ENDTAG && value.equals("true"))
                  fg_is_end_tag = true;
              } // while(attrNameEnum.hasMoreElements())
            if (fg_is_end_tag)
              writeRaw("");
            else
              {
                indent();
                writeRaw("");
            int child_elem_count = currElem.getElementCount();
            for (int i = 0; i < child_elem_count; i++)
              {
                Element childElem = paramElem.getElement(i);
                traverse(childElem);
              } // for(int i = 0; i < child_elem_count; i++)
            writeRaw("");
          } // else if(matchNameAttribute(attrSet, HTML.Tag.PRE))
        else if (matchNameAttribute(attrSet, HTML.Tag.SELECT))
          {
            selectContent(attrSet);
          } // else if(matchNameAttribute(attrSet, HTML.Tag.SELECT))
        else if (matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
          {
            textAreaContent(attrSet);
          } // else if(matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
        else
          {
            int child_elem_count = currElem.getElementCount();
            if (child_elem_count > 0)
              {
                startTag(currElem);
                for (int i = 0; i < child_elem_count; i++)
                  {
                    Element childElem = paramElem.getElement(i);
                    traverse(childElem);
                  } // for(int i = 0; i < child_elem_count; i++)
                  endTag(currElem);
              } // if(child_elem_count > 0)
            else
              {
                emptyTag(currElem);
              } // else
            } // else
          } // else
  } // private void traverse(Element paramElem)
    //   throws IOException, BadLocationException
  /**
   * The method used to traverse through a html fragment.
   *
   * @param paramElem element to traverse
   *
   * @throws IOException on any I/O exceptions
   */
  private void traverseHtmlFragment(Element paramElem)
    throws IOException, BadLocationException
  {
    // NOTE: This method is similar to traverse(Element paramElem)
    Element currElem = paramElem;
    boolean fg_is_fragment_parent_elem = false;
    boolean fg_is_start_and_end_elem = false;
    if (htmlFragmentParentHashSet.contains(paramElem))
      fg_is_fragment_parent_elem = true;
    if (paramElem == startElem)
      fg_pass_start_elem = true;
    if (paramElem == startElem && paramElem == endElem)
      fg_is_start_and_end_elem = true;
    AttributeSet attrSet = currElem.getAttributes();
    closeOutUnwantedEmbeddedTags(attrSet);
    if (fg_is_fragment_parent_elem || (fg_pass_start_elem
        && fg_pass_end_elem == false) || fg_is_start_and_end_elem)
    {
      // handle the tag
      if (synthesizedElement(paramElem))
        {
          if (matchNameAttribute(attrSet, HTML.Tag.CONTENT))
            {
              writeEmbeddedTags(attrSet);
              int content_offset =  paramElem.getStartOffset();
              int content_length = currElem.getEndOffset() - content_offset;
              if (doc_offset_remaining > 0)
                {
                  if (content_length > doc_offset_remaining)
                    {
                      int split_len = content_length;
                      split_len = split_len - doc_offset_remaining;
                      if (split_len > doc_len_remaining)
                        split_len = doc_len_remaining;
                      // we need to split it.
                      String txt_value = htmlDoc.getText(content_offset
                        + doc_offset_remaining, split_len);
                      writeContent(txt_value);
                      doc_offset_remaining = 0; // the offset is used up.
                      doc_len_remaining = doc_len_remaining - split_len;
                    } // if(content_length > doc_offset_remaining)
                  else
                    {
                      // doc_offset_remaining is greater than the entire
                      //   length of content
                      doc_offset_remaining = doc_offset_remaining
                        - content_length;
                    }  // else
                } // if(doc_offset_remaining > 0)
              else if (content_length <= doc_len_remaining)
                {
                  // we can fit the entire content.
                  text(currElem);
                  doc_len_remaining = doc_len_remaining - content_length;
                } // else if(content_length <= doc_len_remaining)
              else
                {
                  // we need to split it.
                  String txt_value = htmlDoc.getText(content_offset,
                    doc_len_remaining);
                  writeContent(txt_value);
                  doc_len_remaining = 0;
                } // else
            } // if(matchNameAttribute(attrSet, HTML.Tag.CONTENT))
          else if (matchNameAttribute(attrSet, HTML.Tag.COMMENT))
            {
              comment(currElem);
            } // else if(matchNameAttribute(attrSet, HTML.Tag.COMMENT))
          else if (matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
            {
              int child_elem_count = currElem.getElementCount();
              if (child_elem_count > 0)
                {
                  for (int i = 0; i < child_elem_count; i++)
                    {
                      Element childElem = paramElem.getElement(i);
                      traverseHtmlFragment(childElem);
                    } // for(int i = 0; i < child_elem_count; i++)
                } // if(child_elem_count > 0)
            } // else if(matchNameAttribute(attrSet, HTML.Tag.IMPLIED))
        } // if(synthesizedElement(paramElem))
      else
        {
            // NOTE: 20061030 - fchoong - the isLeaf() condition seems to
            // generate the closest behavior to the RI.
            if (paramElem.isLeaf())
              {
                if (doc_offset_remaining > 0)
                  {
                    doc_offset_remaining--;
                  } // if(doc_offset_remaining > 0)
                else if (doc_len_remaining > 0)
                  {
                    doc_len_remaining--;
                  } // else if(doc_len_remaining > 0)
              } // if(paramElem.isLeaf())
          // NOTE: 20061030 - fchoong - title is treated specially here.
          // based on RI behavior.
          if (matchNameAttribute(attrSet, HTML.Tag.TITLE))
            {
              boolean fg_is_end_tag = false;
              Enumeration> attrNameEnum = attrSet.getAttributeNames();
              while (attrNameEnum.hasMoreElements())
                {
                  Object key = attrNameEnum.nextElement();
                  Object value = attrSet.getAttribute(key);
                  if (key == HTML.Attribute.ENDTAG && value.equals("true"))
                    fg_is_end_tag = true;
                } // while(attrNameEnum.hasMoreElements())
              if (fg_is_end_tag)
                writeRaw("");
              int child_elem_count = currElem.getElementCount();
              for (int i = 0; i < child_elem_count; i++)
                {
                  Element childElem = paramElem.getElement(i);
                  traverseHtmlFragment(childElem);
                } // for(int i = 0; i < child_elem_count; i++)
              writeRaw("");
            } // else if(matchNameAttribute(attrSet, HTML.Tag.PRE))
          else if (matchNameAttribute(attrSet, HTML.Tag.SELECT))
            {
              selectContent(attrSet);
            } // else if(matchNameAttribute(attrSet, HTML.Tag.SELECT))
          else if (matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
            {
              textAreaContent(attrSet);
            } // else if(matchNameAttribute(attrSet, HTML.Tag.TEXTAREA))
          else
            {
              int child_elem_count = currElem.getElementCount();
              if (child_elem_count > 0)
                {
                  startTag(currElem);
                  for (int i = 0; i < child_elem_count; i++)
                    {
                      Element childElem = paramElem.getElement(i);
                      traverseHtmlFragment(childElem);
                    } // for(int i = 0; i < child_elem_count; i++)
                    endTag(currElem);
                } // if(child_elem_count > 0)
              else
                {
                  emptyTag(currElem);
                } // else
            } // else
        } // else
    } // if(fg_is_fragment_parent_elem || (fg_pass_start_elem
      //   && fg_pass_end_elem == false) || fg_is_start_and_end_elem)
    if (paramElem == endElem)
      fg_pass_end_elem = true;
  } // private void traverseHtmlFragment(Element paramElem)
    //   throws IOException, BadLocationException
  /**
   * Write to the writer without any modifications.
   *
   * @param param_str the str to write out
   *
   * @throws IOException on any I/O exceptions
   */
  private void writeRaw(String param_str)
    throws IOException
  {
    super.output(param_str.toCharArray(), 0, param_str.length());
  } // private void writeRaw(char[] chars, int off, int len)
    //   throws IOException
  /**
   * Write to the writer, escaping HTML character entitie where neccessary.
   *
   * @param param_str the str to write out
   *
   * @throws IOException on any I/O exceptions
   */
  private void writeContent(String param_str)
    throws IOException
  {
    char[] str_char_arr = param_str.toCharArray();
    if (hasHtmlEntity(param_str))
      output(str_char_arr, 0, str_char_arr.length);
    else
      super.output(str_char_arr, 0, str_char_arr.length);
  } // private void writeContent(String param_str) throws IOException
  /**
   * Use this for debugging. Writes out all attributes regardless of type.
   *
   * @param attrSet the javax.swing.text.AttributeSet to
   *        write out
   *
   * @throws IOException on any I/O exceptions
   */
  private void writeAllAttributes(AttributeSet attrSet)
    throws IOException
  {
    Enumeration> attrNameEnum = attrSet.getAttributeNames();
    while (attrNameEnum.hasMoreElements())
      {
        Object key = attrNameEnum.nextElement();
        Object value = attrSet.getAttribute(key);
        writeRaw(" " + key + "=\"" + value + "\"");
        writeRaw(" " + key.getClass().toString() + "=\""
          + value.getClass().toString() + "\"");
      } // while(attrNameEnum.hasMoreElements())
  } // private void writeAllAttributes(AttributeSet attrSet)
    //   throws IOException
  /**
   * Tests if the str contains any html entities.
   *
   * @param param_str the str to test
   *
   * @return true if it has a html entity
   *         false if it does not have a html entity
   */
  private boolean hasHtmlEntity(String param_str)
  {
    boolean ret_bool = false;
    for (int i = 0; i < html_entity_char_arr.length; i++)
      {
        if (param_str.indexOf(html_entity_char_arr[i]) != -1)
          {
            ret_bool = true;
            break;
          } // if(param_str.indexOf(html_entity_char_arr[i]) != -1)
      } // for(int i = 0; i < html_entity_char_arr.length; i++)
    return ret_bool;
  } // private boolean hasHtmlEntity(String param_str)
  /**
   * Tests if the char is a html entities.
   *
   * @param param_char the char to test
   *
   * @return true if it is a html entity
   *         false if it is not a html entity.
   */
  private boolean isCharHtmlEntity(char param_char)
  {
    boolean ret_bool = false;
    for (int i = 0; i < html_entity_char_arr.length; i++)
      {
        if (param_char == html_entity_char_arr[i])
          {
            ret_bool = true;
            break;
          } // if(param_char == html_entity_char_arr[i])
      } // for(int i = 0; i < html_entity_char_arr.length; i++)
      return ret_bool;
  } // private boolean hasHtmlEntity(String param_str)
  /**
   * Escape html entities.
   *
   * @param param_char the char to escape
   *
   * @return escaped html entity. Original char is returned as a str if is
   *         is not a html entity
   */
  private String escapeCharHtmlEntity(char param_char)
  {
    String ret_str = "" + param_char;
    for (int i = 0; i < html_entity_char_arr.length; i++)
      {
        if (param_char == html_entity_char_arr[i])
          {
            ret_str = html_entity_escape_str_arr[i];
            break;
          } // if(param_char == html_entity_char_arr[i])
      } // for(int i = 0; i < html_entity_char_arr.length; i++)
      return ret_str;
  } // private String escapeCharHtmlEntity(char param_char)
} // public class HTMLWriter extends AbstractWriter