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