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