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 embedded has been written out.
083   */
084  private HashSet<HTML.Tag> openEmbeddedTagHashSet = 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<Element> 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    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