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 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