001    /* HTMLWriter.java -- 
002       Copyright (C) 2006 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    package javax.swing.text.html;
039    
040    import gnu.java.lang.CPStringBuilder;
041    
042    import java.io.IOException;
043    import java.io.Writer;
044    
045    import java.util.Enumeration;
046    import java.util.HashSet;
047    
048    import javax.swing.ComboBoxModel;
049    
050    import javax.swing.text.AbstractWriter;
051    import javax.swing.text.AttributeSet;
052    import javax.swing.text.BadLocationException;
053    import javax.swing.text.Document;
054    import javax.swing.text.Element;
055    import javax.swing.text.StyleConstants;
056    
057    import javax.swing.text.html.HTML;
058    import javax.swing.text.html.HTMLDocument;
059    import 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    
068    public 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