001/* HTMLDocument.java --
002   Copyright (C) 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text.html;
040
041import gnu.classpath.NotImplementedException;
042
043import java.io.IOException;
044import java.io.StringReader;
045import java.net.MalformedURLException;
046import java.net.URL;
047import java.util.ArrayList;
048import java.util.HashMap;
049import java.util.Stack;
050import java.util.Vector;
051
052import javax.swing.ButtonGroup;
053import javax.swing.DefaultButtonModel;
054import javax.swing.JEditorPane;
055import javax.swing.ListSelectionModel;
056import javax.swing.event.DocumentEvent;
057import javax.swing.event.UndoableEditEvent;
058import javax.swing.text.AbstractDocument;
059import javax.swing.text.AttributeSet;
060import javax.swing.text.BadLocationException;
061import javax.swing.text.DefaultStyledDocument;
062import javax.swing.text.Element;
063import javax.swing.text.ElementIterator;
064import javax.swing.text.GapContent;
065import javax.swing.text.MutableAttributeSet;
066import javax.swing.text.PlainDocument;
067import javax.swing.text.SimpleAttributeSet;
068import javax.swing.text.StyleConstants;
069import javax.swing.text.html.HTML.Tag;
070
071/**
072 * Represents the HTML document that is constructed by defining the text and
073 * other components (images, buttons, etc) in HTML language. This class can
074 * becomes the default document for {@link JEditorPane} after setting its
075 * content type to "text/html". HTML document also serves as an intermediate
076 * data structure when it is needed to parse HTML and then obtain the content of
077 * the certain types of tags. This class also has methods for modifying the HTML
078 * content.
079 * 
080 * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
081 * @author Anthony Balkissoon (abalkiss@redhat.com)
082 * @author Lillian Angel (langel@redhat.com)
083 */
084public class HTMLDocument extends DefaultStyledDocument
085{
086  /** A key for document properies.  The value for the key is
087   * a Vector of Strings of comments not found in the body.
088   */  
089  public static final String AdditionalComments = "AdditionalComments";
090  URL baseURL = null;
091  boolean preservesUnknownTags = true;
092  int tokenThreshold = Integer.MAX_VALUE;
093  HTMLEditorKit.Parser parser;
094
095  /**
096   * Indicates whether this document is inside a frame or not.
097   */
098  private boolean frameDocument;
099
100  /**
101   * Package private to avoid accessor methods.
102   */
103  String baseTarget;
104
105  /**
106   * Constructs an HTML document using the default buffer size and a default
107   * StyleSheet.
108   */
109  public HTMLDocument()
110  {
111    this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet());
112  }
113  
114  /**
115   * Constructs an HTML document with the default content storage 
116   * implementation and the specified style/attribute storage mechanism.
117   * 
118   * @param styles - the style sheet
119   */
120  public HTMLDocument(StyleSheet styles)
121  {
122   this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
123  }
124  
125  /**
126   * Constructs an HTML document with the given content storage implementation 
127   * and the given style/attribute storage mechanism.
128   * 
129   * @param c - the document's content
130   * @param styles - the style sheet
131   */
132  public HTMLDocument(AbstractDocument.Content c, StyleSheet styles)
133  {
134    super(c, styles);
135  }
136  
137  /**
138   * Gets the style sheet with the document display rules (CSS) that were specified 
139   * in the HTML document.
140   * 
141   * @return - the style sheet
142   */
143  public StyleSheet getStyleSheet()
144  {
145    return (StyleSheet) getAttributeContext();
146  }
147  
148  /**
149   * This method creates a root element for the new document.
150   * 
151   * @return the new default root
152   */
153  protected AbstractElement createDefaultRoot()
154  {
155    AbstractDocument.AttributeContext ctx = getAttributeContext();
156
157    // Create html element.
158    AttributeSet atts = ctx.getEmptySet();
159    atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.HTML);
160    BranchElement html = (BranchElement) createBranchElement(null, atts);
161
162    // Create body element.
163    atts = ctx.getEmptySet();
164    atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.BODY);
165    BranchElement body = (BranchElement) createBranchElement(html, atts);
166    html.replace(0, 0, new Element[] { body });
167
168    // Create p element.
169    atts = ctx.getEmptySet();
170    atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.P);
171    BranchElement p = (BranchElement) createBranchElement(body, atts);
172    body.replace(0, 0, new Element[] { p });
173
174    // Create an empty leaf element.
175    atts = ctx.getEmptySet();
176    atts = ctx.addAttribute(atts, StyleConstants.NameAttribute,
177                            HTML.Tag.CONTENT);
178    Element leaf = createLeafElement(p, atts, 0, 1);
179    p.replace(0, 0, new Element[]{ leaf });
180
181    return html;
182  }
183  
184  /**
185   * This method returns an HTMLDocument.RunElement object attached to
186   * parent representing a run of text from p0 to p1. The run has 
187   * attributes described by a.
188   * 
189   * @param parent - the parent element
190   * @param a - the attributes for the element
191   * @param p0 - the beginning of the range >= 0
192   * @param p1 - the end of the range >= p0
193   *
194   * @return the new element
195   */
196  protected Element createLeafElement(Element parent, AttributeSet a, int p0,
197                                      int p1)
198  {
199    return new RunElement(parent, a, p0, p1);
200  }
201
202  /**
203   * This method returns an HTMLDocument.BlockElement object representing the
204   * attribute set a and attached to parent.
205   * 
206   * @param parent - the parent element
207   * @param a - the attributes for the element
208   *
209   * @return the new element
210   */
211  protected Element createBranchElement(Element parent, AttributeSet a)
212  {
213    return new BlockElement(parent, a);
214  }
215  
216  /**
217   * Returns the parser used by this HTMLDocument to insert HTML.
218   * 
219   * @return the parser used by this HTMLDocument to insert HTML.
220   */
221  public HTMLEditorKit.Parser getParser()
222  {
223    return parser; 
224  }
225  
226  /**
227   * Sets the parser used by this HTMLDocument to insert HTML.
228   * 
229   * @param p the parser to use
230   */
231  public void setParser (HTMLEditorKit.Parser p)
232  {
233    parser = p;
234  }
235  /**
236   * Sets the number of tokens to buffer before trying to display the
237   * Document.
238   * 
239   * @param n the number of tokens to buffer
240   */
241  public void setTokenThreshold (int n)
242  {
243    tokenThreshold = n;
244  }
245  
246  /**
247   * Returns the number of tokens that are buffered before the document
248   * is rendered.
249   * 
250   * @return the number of tokens buffered
251   */
252  public int getTokenThreshold ()
253  {
254    return tokenThreshold;
255  }
256  
257  /**
258   * Returns the location against which to resolve relative URLs.
259   * This is the document's URL if the document was loaded from a URL.
260   * If a <code>base</code> tag is found, it will be used.
261   * @return the base URL
262   */
263  public URL getBase()
264  {
265    return baseURL;
266  }
267  
268  /**
269   * Sets the location against which to resolve relative URLs.
270   * @param u the new base URL
271   */
272  public void setBase(URL u)
273  {
274    baseURL = u;
275    getStyleSheet().setBase(u);
276  }
277  
278  /**
279   * Returns whether or not the parser preserves unknown HTML tags.
280   * @return true if the parser preserves unknown tags
281   */
282  public boolean getPreservesUnknownTags()
283  {
284    return preservesUnknownTags;
285  }
286  
287  /**
288   * Sets the behaviour of the parser when it encounters unknown HTML tags.
289   * @param preservesTags true if the parser should preserve unknown tags.
290   */
291  public void setPreservesUnknownTags(boolean preservesTags)
292  {
293    preservesUnknownTags = preservesTags;
294  }
295  
296  /**
297   * An iterator to iterate through LeafElements in the document.
298   */
299  class LeafIterator extends Iterator
300  {
301    HTML.Tag tag;
302    HTMLDocument doc;
303    ElementIterator it;
304
305    public LeafIterator (HTML.Tag t, HTMLDocument d)
306    {
307      doc = d;
308      tag = t;
309      it = new ElementIterator(doc);
310    }
311    
312    /**
313     * Return the attributes for the tag associated with this iteartor
314     * @return the AttributeSet
315     */
316    public AttributeSet getAttributes()
317    {
318      if (it.current() != null)
319        return it.current().getAttributes();
320      return null;
321    }
322
323    /**
324     * Get the end of the range for the current occurrence of the tag
325     * being defined and having the same attributes.
326     * @return the end of the range
327     */
328    public int getEndOffset()
329    {
330      if (it.current() != null)
331        return it.current().getEndOffset();
332      return -1;
333    }
334
335    /**
336     * Get the start of the range for the current occurrence of the tag
337     * being defined and having the same attributes.
338     * @return the start of the range (-1 if it can't be found).
339     */
340
341    public int getStartOffset()
342    {
343      if (it.current() != null)
344        return it.current().getStartOffset();
345      return -1;
346    }
347
348    /**
349     * Advance the iterator to the next LeafElement .
350     */
351    public void next()
352    {
353      it.next();
354      while (it.current()!= null && !it.current().isLeaf())
355        it.next();
356    }
357
358    /**
359     * Indicates whether or not the iterator currently represents an occurrence
360     * of the tag.
361     * @return true if the iterator currently represents an occurrence of the
362     * tag.
363     */
364    public boolean isValid()
365    {
366      return it.current() != null;
367    }
368
369    /**
370     * Type of tag for this iterator.
371     */
372    public Tag getTag()
373    {
374      return tag;
375    }
376
377  }
378
379  public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent event)
380  {
381    String target = event.getTarget();
382    Element el = event.getSourceElement();
383    URL url = event.getURL();
384    if (target.equals("_self"))
385      {
386        updateFrame(el, url);
387      }
388    else if (target.equals("_parent"))
389      {
390        updateFrameSet(el.getParentElement(), url);
391      }
392    else
393      {
394        Element targetFrame = findFrame(target);
395        if (targetFrame != null)
396          updateFrame(targetFrame, url);
397      }
398  }
399
400  /**
401   * Finds the named frame inside this document.
402   *
403   * @param target the name to look for
404   *
405   * @return the frame if there is a matching frame, <code>null</code>
406   *         otherwise
407   */
408  private Element findFrame(String target)
409  {
410    ElementIterator i = new ElementIterator(this);
411    Element next = null;
412    while ((next = i.next()) != null)
413      {
414        AttributeSet atts = next.getAttributes();
415        if (atts.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.FRAME)
416          {
417            String name = (String) atts.getAttribute(HTML.Attribute.NAME);
418            if (name != null && name.equals(target))
419              break;
420          }
421      }
422    return next;
423  }
424
425  /**
426   * Updates the frame that is represented by the specified element to
427   * refer to the specified URL.
428   *
429   * @param el the element
430   * @param url the new url
431   */
432  private void updateFrame(Element el, URL url)
433  {
434    try
435      {
436        writeLock();
437        DefaultDocumentEvent ev =
438          new DefaultDocumentEvent(el.getStartOffset(), 1,
439                                   DocumentEvent.EventType.CHANGE);
440        AttributeSet elAtts = el.getAttributes();
441        AttributeSet copy = elAtts.copyAttributes();
442        MutableAttributeSet matts = (MutableAttributeSet) elAtts;
443        ev.addEdit(new AttributeUndoableEdit(el, copy, false));
444        matts.removeAttribute(HTML.Attribute.SRC);
445        matts.addAttribute(HTML.Attribute.SRC, url.toString());
446        ev.end();
447        fireChangedUpdate(ev);
448        fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
449      }
450    finally
451      {
452        writeUnlock();
453      }
454  }
455
456  /**
457   * Updates the frameset that is represented by the specified element
458   * to create a frame that refers to the specified URL.
459   *
460   * @param el the element
461   * @param url the url
462   */
463  private void updateFrameSet(Element el, URL url)
464  {
465    int start = el.getStartOffset();
466    int end = el.getEndOffset();
467    
468    StringBuilder html = new StringBuilder();
469    html.append("<frame");
470    if (url != null)
471      {
472        html.append(" src=\"");
473        html.append(url.toString());
474        html.append("\"");
475      }
476    html.append('>');
477    if (getParser() == null)
478      setParser(new HTMLEditorKit().getParser());
479    try
480      {
481        setOuterHTML(el, html.toString());
482      }
483    catch (BadLocationException ex)
484      {
485        ex.printStackTrace();
486      }
487    catch (IOException ex)
488      {
489        ex.printStackTrace();
490      }
491  }
492
493  /**
494   * Gets an iterator for the given HTML.Tag.
495   * @param t the requested HTML.Tag
496   * @return the Iterator
497   */
498  public HTMLDocument.Iterator getIterator (HTML.Tag t)
499  {
500    return new HTMLDocument.LeafIterator(t, this);
501  }
502  
503  /**
504   * An iterator over a particular type of tag.
505   */
506  public abstract static class Iterator
507  {
508    /**
509     * Return the attribute set for this tag.
510     * @return the <code>AttributeSet</code> (null if none found).
511     */
512    public abstract AttributeSet getAttributes();
513    
514    /**
515     * Get the end of the range for the current occurrence of the tag
516     * being defined and having the same attributes.
517     * @return the end of the range
518     */
519    public abstract int getEndOffset();
520    
521    /**
522     * Get the start of the range for the current occurrence of the tag
523     * being defined and having the same attributes.
524     * @return the start of the range (-1 if it can't be found).
525     */
526    public abstract int getStartOffset();
527    
528    /**
529     * Move the iterator forward.
530     */
531    public abstract void next();
532    
533    /**
534     * Indicates whether or not the iterator currently represents an occurrence
535     * of the tag.
536     * @return true if the iterator currently represents an occurrence of the
537     * tag.
538     */
539    public abstract boolean isValid();
540    
541    /**
542     * Type of tag this iterator represents.
543     * @return the tag.
544     */
545    public abstract HTML.Tag getTag();
546  }
547  
548  public class BlockElement extends AbstractDocument.BranchElement
549  {
550    public BlockElement (Element parent, AttributeSet a)
551    {
552      super(parent, a);
553    }
554    
555    /**
556     * Gets the resolving parent.  Since HTML attributes are not 
557     * inherited at the model level, this returns null.
558     */
559    public AttributeSet getResolveParent()
560    {
561      return null;
562    }
563    
564    /**
565     * Gets the name of the element.
566     * 
567     * @return the name of the element if it exists, null otherwise.
568     */
569    public String getName()
570    {
571      Object tag = getAttribute(StyleConstants.NameAttribute);
572      String name = null;
573      if (tag != null)
574        name = tag.toString();
575      if (name == null)
576        name = super.getName();
577      return name;
578    }
579  }
580
581  /**
582   * RunElement represents a section of text that has a set of 
583   * HTML character level attributes assigned to it.
584   */
585  public class RunElement extends AbstractDocument.LeafElement
586  {
587    
588    /**
589     * Constructs an element that has no children. It represents content
590     * within the document.
591     * 
592     * @param parent - parent of this
593     * @param a - elements attributes
594     * @param start - the start offset >= 0
595     * @param end - the end offset 
596     */
597    public RunElement(Element parent, AttributeSet a, int start, int end)
598    {
599      super(parent, a, start, end);
600    }
601    
602    /**
603     * Gets the name of the element.
604     * 
605     * @return the name of the element if it exists, null otherwise.
606     */
607    public String getName()
608    {
609      Object tag = getAttribute(StyleConstants.NameAttribute);
610      String name = null;
611      if (tag != null)
612        name = tag.toString();
613      if (name == null)
614        name = super.getName();
615      return name;
616    }
617    
618    /**
619     * Gets the resolving parent. HTML attributes do not inherit at the
620     * model level, so this method returns null.
621     * 
622     * @return null
623     */
624    public AttributeSet getResolveParent()
625    {
626      return null;
627    }
628  }
629  
630  /**
631   * A reader to load an HTMLDocument with HTML structure.
632   * 
633   * @author Anthony Balkissoon abalkiss at redhat dot com
634   */
635  public class HTMLReader extends HTMLEditorKit.ParserCallback
636  {
637    /**
638     * The maximum token threshold. We don't grow it larger than this.
639     */
640    private static final int MAX_THRESHOLD = 10000;
641
642    /**
643     * The threshold growth factor.
644     */
645    private static final int GROW_THRESHOLD = 5;
646
647    /**
648     * Holds the current character attribute set *
649     */
650    protected MutableAttributeSet charAttr = new SimpleAttributeSet();
651    
652    protected Vector<ElementSpec> parseBuffer = new Vector<ElementSpec>();   
653
654    /**
655     * The parse stack. It holds the current element tree path.
656     */
657    private Stack<HTML.Tag> parseStack = new Stack<HTML.Tag>();
658
659    /** 
660     * A stack for character attribute sets *
661     */
662    Stack charAttrStack = new Stack();
663   
664    /** A mapping between HTML.Tag objects and the actions that handle them **/
665    HashMap tagToAction;
666    
667    /** Tells us whether we've received the '</html>' tag yet **/
668    boolean endHTMLEncountered = false;
669    
670    /** 
671     * Related to the constructor with explicit insertTag 
672     */
673    int popDepth;
674    
675    /**
676     * Related to the constructor with explicit insertTag
677     */    
678    int pushDepth;
679    
680    /** 
681     * Related to the constructor with explicit insertTag
682     */    
683    int offset;
684    
685    /**
686     * The tag (inclusve), after that the insertion should start.
687     */
688    HTML.Tag insertTag;
689    
690    /**
691     * This variable becomes true after the insert tag has been encountered.
692     */
693    boolean insertTagEncountered;
694
695    
696    /** A temporary variable that helps with the printing out of debug information **/
697    boolean debug = false;
698
699    /**
700     * This is true when we are inside a pre tag.
701     */
702    boolean inPreTag = false;
703
704    /**
705     * This is true when we are inside a style tag. This will add text
706     * content inside this style tag beeing parsed as CSS.
707     *
708     * This is package private to avoid accessor methods.
709     */
710    boolean inStyleTag = false;
711
712    /**
713     * This is true when we are inside a &lt;textarea&gt; tag. Any text
714     * content will then be added to the text area.
715     *
716     * This is package private to avoid accessor methods.
717     */
718    boolean inTextArea = false;
719
720    /**
721     * This contains all stylesheets that are somehow read, either
722     * via embedded style tags, or via linked stylesheets. The
723     * elements will be String objects containing a stylesheet each.
724     */
725    ArrayList styles;
726
727    /**
728     * The document model for a textarea.
729     *
730     * This is package private to avoid accessor methods.
731     */
732    ResetablePlainDocument textAreaDocument;
733
734    /**
735     * The current model of a select tag. Can be a ComboBoxModel or a
736     * ListModel depending on the type of the select box.
737     */
738    Object selectModel;
739
740    /**
741     * The current option beeing read.
742     */
743    Option option;
744
745    /**
746     * The current number of options in the current select model.
747     */
748    int numOptions;
749
750    /**
751     * The current button groups mappings.
752     */
753    HashMap buttonGroups;
754
755    /**
756     * The token threshold. This gets increased while loading.
757     */
758    private int threshold;
759
760    public class TagAction
761    {
762      /**
763       * This method is called when a start tag is seen for one of the types
764       * of tags associated with this Action.  By default this does nothing.
765       */
766      public void start(HTML.Tag t, MutableAttributeSet a)
767      {
768        // Nothing to do here.
769      }
770      
771      /**
772       * Called when an end tag is seen for one of the types of tags associated
773       * with this Action.  By default does nothing.
774       */
775      public void end(HTML.Tag t)
776      {
777        // Nothing to do here.
778      }
779    }
780
781    public class BlockAction extends TagAction
782    {      
783      /**
784       * This method is called when a start tag is seen for one of the types
785       * of tags associated with this Action.
786       */
787      public void start(HTML.Tag t, MutableAttributeSet a)
788      {
789        // Tell the parse buffer to open a new block for this tag.
790        blockOpen(t, a);
791      }
792      
793      /**
794       * Called when an end tag is seen for one of the types of tags associated
795       * with this Action.
796       */
797      public void end(HTML.Tag t)
798      {
799        // Tell the parse buffer to close this block.
800        blockClose(t);
801      }
802    }
803    
804    public class CharacterAction extends TagAction
805    {
806      /**
807       * This method is called when a start tag is seen for one of the types
808       * of tags associated with this Action.
809       */
810      public void start(HTML.Tag t, MutableAttributeSet a)
811      {
812        // Put the old attribute set on the stack.
813        pushCharacterStyle();
814
815        // Initialize with link pseudo class.
816        if (t == HTML.Tag.A)
817          a.addAttribute(HTML.Attribute.PSEUDO_CLASS, "link");
818
819        // Just add the attributes in <code>a</code>.
820        charAttr.addAttribute(t, a.copyAttributes());
821      }
822
823      /**
824       * Called when an end tag is seen for one of the types of tags associated
825       * with this Action.
826       */
827      public void end(HTML.Tag t)
828      {
829        popCharacterStyle();
830      } 
831    }
832
833    /**
834     * Processes elements that make up forms: &lt;input&gt;, &lt;textarea&gt;,
835     * &lt;select&gt; and &lt;option&gt;.
836     */
837    public class FormAction extends SpecialAction
838    {
839      /**
840       * This method is called when a start tag is seen for one of the types
841       * of tags associated with this Action.
842       */
843      public void start(HTML.Tag t, MutableAttributeSet a)
844      {
845        if (t == HTML.Tag.INPUT)
846          {
847            String type = (String) a.getAttribute(HTML.Attribute.TYPE);
848            if (type == null)
849              {
850                type = "text"; // Default to 'text' when nothing was specified.
851                a.addAttribute(HTML.Attribute.TYPE, type);
852              }
853            setModel(type, a);
854          }
855        else if (t == HTML.Tag.TEXTAREA)
856          {
857            inTextArea = true;
858            textAreaDocument = new ResetablePlainDocument();
859            a.addAttribute(StyleConstants.ModelAttribute, textAreaDocument);
860          }
861        else if (t == HTML.Tag.SELECT)
862          {
863            int size = HTML.getIntegerAttributeValue(a, HTML.Attribute.SIZE,
864                                                     1);
865            boolean multi = a.getAttribute(HTML.Attribute.MULTIPLE) != null;
866            if (size > 1 || multi)
867              {
868                SelectListModel m = new SelectListModel();
869                if (multi)
870                  m.getSelectionModel().setSelectionMode(ListSelectionModel
871                                                 .MULTIPLE_INTERVAL_SELECTION);
872                selectModel = m;
873              }
874            else
875              {
876                selectModel = new SelectComboBoxModel();
877              }
878            a.addAttribute(StyleConstants.ModelAttribute, selectModel);
879          }
880        if (t == HTML.Tag.OPTION)
881          {
882            option = new Option(a);
883            if (selectModel instanceof SelectListModel)
884              {
885                SelectListModel m = (SelectListModel) selectModel;
886                m.addElement(option);
887                if (option.isSelected())
888                  {
889                    m.getSelectionModel().addSelectionInterval(numOptions,
890                                                               numOptions);
891                    m.addInitialSelection(numOptions);
892                  }
893              }
894            else if (selectModel instanceof SelectComboBoxModel)
895              {
896                SelectComboBoxModel m = (SelectComboBoxModel) selectModel;
897                m.addElement(option);
898                if (option.isSelected())
899                  {
900                    m.setSelectedItem(option);
901                    m.setInitialSelection(option);
902                  }
903              }
904            numOptions++;
905          }
906        else
907          {
908            // Build the element.
909            super.start(t, a);
910          }
911      }
912      
913      /**
914       * Called when an end tag is seen for one of the types of tags associated
915       * with this Action.
916       */
917      public void end(HTML.Tag t)
918      {
919        if (t == HTML.Tag.OPTION)
920          {
921            option = null;
922          }
923        else
924          {
925            if (t == HTML.Tag.TEXTAREA)
926              {
927                inTextArea = false;
928              }
929            else if (t == HTML.Tag.SELECT)
930              {
931                selectModel = null;
932                numOptions = 0;
933              }
934            // Finish the element.
935            super.end(t);
936          }
937      }
938
939      private void setModel(String type, MutableAttributeSet attrs)
940      {
941        if (type.equals("submit") || type.equals("reset")
942            || type.equals("image"))
943          {
944            // Create button.
945            attrs.addAttribute(StyleConstants.ModelAttribute,
946                               new DefaultButtonModel());
947          }
948        else if (type.equals("text") || type.equals("password"))
949          {
950            String text = (String) attrs.getAttribute(HTML.Attribute.VALUE);
951            ResetablePlainDocument doc = new ResetablePlainDocument();
952            if (text != null)
953              {
954                doc.setInitialText(text);
955                try
956                  {
957                    doc.insertString(0, text, null);
958                  }
959                catch (BadLocationException ex)
960                  {
961                    // Shouldn't happen.
962                    assert false;
963                  }
964              }
965            attrs.addAttribute(StyleConstants.ModelAttribute, doc);
966          }
967        else if (type.equals("file"))
968          {
969            attrs.addAttribute(StyleConstants.ModelAttribute,
970                               new PlainDocument());
971          }
972        else if (type.equals("checkbox") || type.equals("radio"))
973          {
974            ResetableToggleButtonModel model =
975              new ResetableToggleButtonModel();
976            if (attrs.getAttribute(HTML.Attribute.SELECTED) != null)
977              {
978                model.setSelected(true);
979                model.setInitial(true);
980              }
981            if (type.equals("radio"))
982              {
983                String name = (String) attrs.getAttribute(HTML.Attribute.NAME);
984                if (name != null)
985                  {
986                    if (buttonGroups == null)
987                      buttonGroups = new HashMap();
988                    ButtonGroup group = (ButtonGroup) buttonGroups.get(name);
989                    if (group == null)
990                      {
991                        group = new ButtonGroup();
992                        buttonGroups.put(name, group);
993                      }
994                    model.setGroup(group);
995                  }
996              }
997            attrs.addAttribute(StyleConstants.ModelAttribute, model);
998          }
999      }
1000    }
1001
1002    /**
1003     * Called for form tags.
1004     */
1005    class FormTagAction
1006      extends BlockAction
1007    {
1008      /**
1009       * Clears the button group mapping.
1010       */
1011      public void end(HTML.Tag t)
1012      {
1013        super.end(t);
1014        buttonGroups = null;
1015      } 
1016    }
1017
1018    /**
1019     * This action indicates that the content between starting and closing HTML
1020     * elements (like script - /script) should not be visible. The content is
1021     * still inserted and can be accessed when iterating the HTML document. The
1022     * parser will only fire
1023     * {@link javax.swing.text.html.HTMLEditorKit.ParserCallback#handleText} for
1024     * the hidden tags, regardless from that html tags the hidden section may
1025     * contain.
1026     */
1027    public class HiddenAction
1028        extends TagAction
1029    {
1030      /**
1031       * This method is called when a start tag is seen for one of the types
1032       * of tags associated with this Action.
1033       */
1034      public void start(HTML.Tag t, MutableAttributeSet a)
1035      {
1036        blockOpen(t, a);
1037      }
1038      
1039      /**
1040       * Called when an end tag is seen for one of the types of tags associated
1041       * with this Action.
1042       */
1043      public void end(HTML.Tag t)
1044      {
1045        blockClose(t);
1046      } 
1047    }
1048
1049    /**
1050     * Handles &lt;isindex&gt; tags.
1051     */
1052    public class IsindexAction extends TagAction
1053    {
1054      /**
1055       * This method is called when a start tag is seen for one of the types
1056       * of tags associated with this Action.
1057       */
1058      public void start(HTML.Tag t, MutableAttributeSet a)
1059      {
1060        blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
1061        addSpecialElement(t, a);
1062        blockClose(HTML.Tag.IMPLIED);
1063      }
1064    }
1065    
1066    public class ParagraphAction extends BlockAction
1067    {
1068      /**
1069       * This method is called when a start tag is seen for one of the types
1070       * of tags associated with this Action.
1071       */
1072      public void start(HTML.Tag t, MutableAttributeSet a)
1073      {
1074        super.start(t, a);
1075      }
1076      
1077      /**
1078       * Called when an end tag is seen for one of the types of tags associated
1079       * with this Action.
1080       */
1081      public void end(HTML.Tag t)
1082      {
1083        super.end(t);
1084      } 
1085    }
1086
1087    /**
1088     * This action is performed when a &lt;pre&gt; tag is parsed.
1089     */
1090    public class PreAction extends BlockAction
1091    {
1092      /**
1093       * This method is called when a start tag is seen for one of the types
1094       * of tags associated with this Action.
1095       */
1096      public void start(HTML.Tag t, MutableAttributeSet a)
1097      {
1098        inPreTag = true;
1099        blockOpen(t, a);
1100        a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
1101        blockOpen(HTML.Tag.IMPLIED, a);
1102      }
1103      
1104      /**
1105       * Called when an end tag is seen for one of the types of tags associated
1106       * with this Action.
1107       */
1108      public void end(HTML.Tag t)
1109      {
1110        blockClose(HTML.Tag.IMPLIED);
1111        inPreTag = false;
1112        blockClose(t);
1113      } 
1114    }
1115    
1116    /**
1117     * Inserts the elements that are represented by ths single tag with 
1118     * attributes (only). The closing tag, even if present, mut follow
1119     * immediately after the starting tag without providing any additional
1120     * information. Hence the {@link TagAction#end} method need not be
1121     * overridden and still does nothing.
1122     */
1123    public class SpecialAction extends TagAction
1124    {
1125      /**
1126       * The functionality is delegated to {@link HTMLReader#addSpecialElement}
1127       */
1128      public void start(HTML.Tag t, MutableAttributeSet a)
1129      {
1130        addSpecialElement(t, a);
1131      }
1132    }
1133    
1134    class AreaAction extends TagAction
1135    {
1136      /**
1137       * This method is called when a start tag is seen for one of the types
1138       * of tags associated with this Action.
1139       */
1140      public void start(HTML.Tag t, MutableAttributeSet a)
1141        throws NotImplementedException
1142      {
1143        // FIXME: Implement.
1144      }
1145      
1146      /**
1147       * Called when an end tag is seen for one of the types of tags associated
1148       * with this Action.
1149       */
1150      public void end(HTML.Tag t)
1151        throws NotImplementedException
1152      {
1153        // FIXME: Implement.
1154      } 
1155    }
1156
1157    /**
1158     * Converts HTML tags to CSS attributes.
1159     */
1160    class ConvertAction
1161      extends TagAction
1162    {
1163
1164      public void start(HTML.Tag tag, MutableAttributeSet atts)
1165      {
1166        pushCharacterStyle();
1167        charAttr.addAttribute(tag, atts.copyAttributes());
1168        StyleSheet styleSheet = getStyleSheet();
1169        // TODO: Add other tags here.
1170        if (tag == HTML.Tag.FONT)
1171          {
1172            String color = (String) atts.getAttribute(HTML.Attribute.COLOR);
1173            if (color != null)
1174              styleSheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color);
1175            String face = (String) atts.getAttribute(HTML.Attribute.FACE);
1176            if (face != null)
1177              styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY,
1178                                         face);
1179            String size = (String) atts.getAttribute(HTML.Attribute.SIZE);
1180            if (size != null)
1181              styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_SIZE,
1182                                         size);
1183          }
1184      }
1185
1186      public void end(HTML.Tag tag)
1187      {
1188        popCharacterStyle();
1189      }
1190    }
1191
1192    class BaseAction extends TagAction
1193    {
1194      /**
1195       * This method is called when a start tag is seen for one of the types
1196       * of tags associated with this Action.
1197       */
1198      public void start(HTML.Tag t, MutableAttributeSet a)
1199      {
1200        baseTarget = (String) a.getAttribute(HTML.Attribute.TARGET);
1201      }
1202    }
1203    
1204    class HeadAction extends BlockAction
1205    {
1206      /**
1207       * This method is called when a start tag is seen for one of the types
1208       * of tags associated with this Action.
1209       */
1210      public void start(HTML.Tag t, MutableAttributeSet a)
1211        throws NotImplementedException
1212      {
1213        // FIXME: Implement.
1214        super.start(t, a);
1215      }
1216      
1217      /**
1218       * Called when an end tag is seen for one of the types of tags associated
1219       * with this Action.
1220       */
1221      public void end(HTML.Tag t)
1222      {
1223        // We read in all the stylesheets that are embedded or referenced
1224        // inside the header.
1225        if (styles != null)
1226          {
1227            int numStyles = styles.size();
1228            for (int i = 0; i < numStyles; i++)
1229              {
1230                String style = (String) styles.get(i);
1231                getStyleSheet().addRule(style);
1232              }
1233          }
1234        super.end(t);
1235      }
1236    }
1237    
1238    class LinkAction extends HiddenAction
1239    {
1240      /**
1241       * This method is called when a start tag is seen for one of the types
1242       * of tags associated with this Action.
1243       */
1244      public void start(HTML.Tag t, MutableAttributeSet a)
1245      {
1246        super.start(t, a);
1247        String type = (String) a.getAttribute(HTML.Attribute.TYPE);
1248        if (type == null)
1249          type = "text/css";
1250        if (type.equals("text/css"))
1251          {
1252            String rel = (String) a.getAttribute(HTML.Attribute.REL);
1253            String media = (String) a.getAttribute(HTML.Attribute.MEDIA);
1254            String title = (String) a.getAttribute(HTML.Attribute.TITLE);
1255            if (media == null)
1256              media = "all";
1257            else
1258              media = media.toLowerCase();
1259            if (rel != null)
1260              {
1261                rel = rel.toLowerCase();
1262                if ((media.indexOf("all") != -1
1263                     || media.indexOf("screen") != -1)
1264                    && (rel.equals("stylesheet")))
1265                  {
1266                    String href = (String) a.getAttribute(HTML.Attribute.HREF);
1267                    URL url = null;
1268                    try
1269                      {
1270                        url = new URL(baseURL, href);
1271                      }
1272                    catch (MalformedURLException ex)
1273                      {
1274                        try
1275                          {
1276                            url = new URL(href);
1277                          }
1278                        catch (MalformedURLException ex2)
1279                          {
1280                            url = null;
1281                          }
1282                      }
1283                    if (url != null)
1284                      {
1285                        try
1286                          {
1287                            getStyleSheet().importStyleSheet(url);
1288                          }
1289                        catch (Exception ex)
1290                          {
1291                            // Don't let exceptions and runtime exceptions
1292                            // in CSS parsing disprupt the HTML parsing
1293                            // process. But inform the user/developer
1294                            // on the console about it.
1295                            ex.printStackTrace();
1296                          }
1297                      }
1298                  }                  
1299              }
1300          }
1301      }
1302      
1303    }
1304    
1305    class MapAction extends TagAction
1306    {
1307      /**
1308       * This method is called when a start tag is seen for one of the types
1309       * of tags associated with this Action.
1310       */
1311      public void start(HTML.Tag t, MutableAttributeSet a)
1312        throws NotImplementedException
1313      {
1314        // FIXME: Implement.
1315      }
1316      
1317      /**
1318       * Called when an end tag is seen for one of the types of tags associated
1319       * with this Action.
1320       */
1321      public void end(HTML.Tag t)
1322        throws NotImplementedException
1323      {
1324        // FIXME: Implement.
1325      } 
1326    }
1327    
1328    class MetaAction extends TagAction
1329    {
1330      /**
1331       * This method is called when a start tag is seen for one of the types
1332       * of tags associated with this Action.
1333       */
1334      public void start(HTML.Tag t, MutableAttributeSet a)
1335        throws NotImplementedException
1336      {
1337        // FIXME: Implement.
1338      }
1339      
1340      /**
1341       * Called when an end tag is seen for one of the types of tags associated
1342       * with this Action.
1343       */
1344      public void end(HTML.Tag t)
1345        throws NotImplementedException
1346      {
1347        // FIXME: Implement.
1348      } 
1349    }
1350
1351    class StyleAction extends TagAction
1352    {
1353      /**
1354       * This method is called when a start tag is seen for one of the types
1355       * of tags associated with this Action.
1356       */
1357      public void start(HTML.Tag t, MutableAttributeSet a)
1358      {
1359        inStyleTag = true;
1360      }
1361      
1362      /**
1363       * Called when an end tag is seen for one of the types of tags associated
1364       * with this Action.
1365       */
1366      public void end(HTML.Tag t)
1367      {
1368        inStyleTag = false;
1369      } 
1370    }
1371    
1372    class TitleAction extends TagAction
1373    {
1374      /**
1375       * This method is called when a start tag is seen for one of the types
1376       * of tags associated with this Action.
1377       */
1378      public void start(HTML.Tag t, MutableAttributeSet a)
1379        throws NotImplementedException
1380      {
1381        // FIXME: Implement.
1382      }
1383      
1384      /**
1385       * Called when an end tag is seen for one of the types of tags associated
1386       * with this Action.
1387       */
1388      public void end(HTML.Tag t)
1389        throws NotImplementedException
1390      {
1391        // FIXME: Implement.
1392      } 
1393    }    
1394    
1395    public HTMLReader(int offset)
1396    {
1397      this (offset, 0, 0, null);
1398    }
1399    
1400    public HTMLReader(int offset, int popDepth, int pushDepth,
1401                      HTML.Tag insertTag)
1402    {
1403      this.insertTag = insertTag;
1404      this.offset = offset;
1405      this.popDepth = popDepth;
1406      this.pushDepth = pushDepth;
1407      threshold = getTokenThreshold();
1408      initTags();
1409    }
1410    
1411    void initTags()
1412    {
1413      tagToAction = new HashMap(72);
1414      CharacterAction characterAction = new CharacterAction();
1415      HiddenAction hiddenAction = new HiddenAction();
1416      AreaAction areaAction = new AreaAction();
1417      BaseAction baseAction = new BaseAction();
1418      BlockAction blockAction = new BlockAction();
1419      SpecialAction specialAction = new SpecialAction();
1420      ParagraphAction paragraphAction = new ParagraphAction();
1421      HeadAction headAction = new HeadAction();
1422      FormAction formAction = new FormAction();
1423      IsindexAction isindexAction = new IsindexAction();
1424      LinkAction linkAction = new LinkAction();
1425      MapAction mapAction = new MapAction();
1426      PreAction preAction = new PreAction();
1427      MetaAction metaAction = new MetaAction();
1428      StyleAction styleAction = new StyleAction();
1429      TitleAction titleAction = new TitleAction();
1430      
1431      ConvertAction convertAction = new ConvertAction();
1432      tagToAction.put(HTML.Tag.A, characterAction);
1433      tagToAction.put(HTML.Tag.ADDRESS, characterAction);
1434      tagToAction.put(HTML.Tag.APPLET, hiddenAction);
1435      tagToAction.put(HTML.Tag.AREA, areaAction);
1436      tagToAction.put(HTML.Tag.B, characterAction);
1437      tagToAction.put(HTML.Tag.BASE, baseAction);
1438      tagToAction.put(HTML.Tag.BASEFONT, characterAction);
1439      tagToAction.put(HTML.Tag.BIG, characterAction);
1440      tagToAction.put(HTML.Tag.BLOCKQUOTE, blockAction);
1441      tagToAction.put(HTML.Tag.BODY, blockAction);
1442      tagToAction.put(HTML.Tag.BR, specialAction);
1443      tagToAction.put(HTML.Tag.CAPTION, blockAction);
1444      tagToAction.put(HTML.Tag.CENTER, blockAction);
1445      tagToAction.put(HTML.Tag.CITE, characterAction);
1446      tagToAction.put(HTML.Tag.CODE, characterAction);
1447      tagToAction.put(HTML.Tag.DD, blockAction);
1448      tagToAction.put(HTML.Tag.DFN, characterAction);
1449      tagToAction.put(HTML.Tag.DIR, blockAction);
1450      tagToAction.put(HTML.Tag.DIV, blockAction);
1451      tagToAction.put(HTML.Tag.DL, blockAction);
1452      tagToAction.put(HTML.Tag.DT, paragraphAction);
1453      tagToAction.put(HTML.Tag.EM, characterAction);
1454      tagToAction.put(HTML.Tag.FONT, convertAction);
1455      tagToAction.put(HTML.Tag.FORM, new FormTagAction());
1456      tagToAction.put(HTML.Tag.FRAME, specialAction);
1457      tagToAction.put(HTML.Tag.FRAMESET, blockAction);
1458      tagToAction.put(HTML.Tag.H1, paragraphAction);
1459      tagToAction.put(HTML.Tag.H2, paragraphAction);
1460      tagToAction.put(HTML.Tag.H3, paragraphAction);
1461      tagToAction.put(HTML.Tag.H4, paragraphAction);
1462      tagToAction.put(HTML.Tag.H5, paragraphAction);
1463      tagToAction.put(HTML.Tag.H6, paragraphAction);
1464      tagToAction.put(HTML.Tag.HEAD, headAction);
1465      tagToAction.put(HTML.Tag.HR, specialAction);
1466      tagToAction.put(HTML.Tag.HTML, blockAction);
1467      tagToAction.put(HTML.Tag.I, characterAction);
1468      tagToAction.put(HTML.Tag.IMG, specialAction);
1469      tagToAction.put(HTML.Tag.INPUT, formAction);
1470      tagToAction.put(HTML.Tag.ISINDEX, isindexAction);
1471      tagToAction.put(HTML.Tag.KBD, characterAction);
1472      tagToAction.put(HTML.Tag.LI, blockAction);
1473      tagToAction.put(HTML.Tag.LINK, linkAction);
1474      tagToAction.put(HTML.Tag.MAP, mapAction);
1475      tagToAction.put(HTML.Tag.MENU, blockAction);
1476      tagToAction.put(HTML.Tag.META, metaAction);
1477      tagToAction.put(HTML.Tag.NOFRAMES, blockAction);
1478      tagToAction.put(HTML.Tag.OBJECT, specialAction);
1479      tagToAction.put(HTML.Tag.OL, blockAction);
1480      tagToAction.put(HTML.Tag.OPTION, formAction);
1481      tagToAction.put(HTML.Tag.P, paragraphAction);
1482      tagToAction.put(HTML.Tag.PARAM, hiddenAction);
1483      tagToAction.put(HTML.Tag.PRE, preAction);
1484      tagToAction.put(HTML.Tag.SAMP, characterAction);
1485      tagToAction.put(HTML.Tag.SCRIPT, hiddenAction);
1486      tagToAction.put(HTML.Tag.SELECT, formAction);
1487      tagToAction.put(HTML.Tag.SMALL, characterAction);
1488      tagToAction.put(HTML.Tag.STRIKE, characterAction);
1489      tagToAction.put(HTML.Tag.S, characterAction);      
1490      tagToAction.put(HTML.Tag.STRONG, characterAction);
1491      tagToAction.put(HTML.Tag.STYLE, styleAction);
1492      tagToAction.put(HTML.Tag.SUB, characterAction);
1493      tagToAction.put(HTML.Tag.SUP, characterAction);
1494      tagToAction.put(HTML.Tag.TABLE, blockAction);
1495      tagToAction.put(HTML.Tag.TD, blockAction);
1496      tagToAction.put(HTML.Tag.TEXTAREA, formAction);
1497      tagToAction.put(HTML.Tag.TH, blockAction);
1498      tagToAction.put(HTML.Tag.TITLE, titleAction);
1499      tagToAction.put(HTML.Tag.TR, blockAction);
1500      tagToAction.put(HTML.Tag.TT, characterAction);
1501      tagToAction.put(HTML.Tag.U, characterAction);
1502      tagToAction.put(HTML.Tag.UL, blockAction);
1503      tagToAction.put(HTML.Tag.VAR, characterAction);
1504    }
1505    
1506    /**
1507     * Pushes the current character style onto the stack.
1508     *
1509     */
1510    protected void pushCharacterStyle()
1511    {
1512      charAttrStack.push(charAttr.copyAttributes());
1513    }
1514    
1515    /**
1516     * Pops a character style off of the stack and uses it as the 
1517     * current character style.
1518     *
1519     */
1520    protected void popCharacterStyle()
1521    {
1522      if (!charAttrStack.isEmpty())
1523        charAttr = (MutableAttributeSet) charAttrStack.pop();
1524    }
1525    
1526    /**
1527     * Registers a given tag with a given Action.  All of the well-known tags
1528     * are registered by default, but this method can change their behaviour
1529     * or add support for custom or currently unsupported tags.
1530     * 
1531     * @param t the Tag to register
1532     * @param a the Action for the Tag
1533     */
1534    protected void registerTag(HTML.Tag t, HTMLDocument.HTMLReader.TagAction a)
1535    {
1536      tagToAction.put (t, a);
1537    }
1538    
1539    /**
1540     * This is the last method called on the HTMLReader, allowing any pending
1541     * changes to be flushed to the HTMLDocument.
1542     */
1543    public void flush() throws BadLocationException
1544    {
1545      flushImpl();
1546    }
1547
1548    /**
1549     * Flushes the buffer and handle partial inserts.
1550     *
1551     */
1552    private void flushImpl()
1553      throws BadLocationException
1554    {
1555      int oldLen = getLength();
1556      int size = parseBuffer.size();
1557      ElementSpec[] elems = new ElementSpec[size];
1558      parseBuffer.copyInto(elems);
1559      if (oldLen == 0)
1560        create(elems);
1561      else
1562        insert(offset, elems);
1563      parseBuffer.removeAllElements();
1564      offset += getLength() - oldLen;
1565    }
1566
1567    /**
1568     * This method is called by the parser to indicate a block of 
1569     * text was encountered.  Should insert the text appropriately.
1570     * 
1571     * @param data the text that was inserted
1572     * @param pos the position at which the text was inserted
1573     */
1574    public void handleText(char[] data, int pos)
1575    {
1576      if (shouldInsert() && data != null && data.length > 0)
1577        {
1578          if (inTextArea)
1579            textAreaContent(data);
1580          else if (inPreTag)
1581            preContent(data);
1582          else if (option != null)
1583            option.setLabel(new String(data));
1584          else if (inStyleTag)
1585            {
1586              if (styles == null)
1587                styles = new ArrayList();
1588              styles.add(new String(data));
1589            }
1590          else
1591            addContent(data, 0, data.length);
1592            
1593        }
1594    }
1595    
1596    /**
1597     * Checks if the HTML tag should be inserted. The tags before insert tag (if
1598     * specified) are not inserted. Also, the tags after the end of the html are
1599     * not inserted.
1600     * 
1601     * @return true if the tag should be inserted, false otherwise.
1602     */
1603    private boolean shouldInsert()
1604    {
1605      return ! endHTMLEncountered
1606             && (insertTagEncountered || insertTag == null);
1607    }
1608    
1609    /**
1610     * This method is called by the parser and should route the call to the
1611     * proper handler for the tag.
1612     * 
1613     * @param t the HTML.Tag
1614     * @param a the attribute set
1615     * @param pos the position at which the tag was encountered
1616     */
1617    public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos)
1618    {
1619      if (t == insertTag)
1620        insertTagEncountered = true;
1621
1622      if (shouldInsert())
1623        {
1624          TagAction action = (TagAction) tagToAction.get(t);
1625          if (action != null)
1626            action.start(t, a);
1627        }
1628    }
1629    
1630    /**
1631     * This method called by parser to handle a comment block.
1632     * 
1633     * @param data the comment
1634     * @param pos the position at which the comment was encountered
1635     */
1636    public void handleComment(char[] data, int pos)
1637    {
1638      if (shouldInsert())
1639        {
1640          TagAction action = (TagAction) tagToAction.get(HTML.Tag.COMMENT);
1641          if (action != null)
1642            {
1643              action.start(HTML.Tag.COMMENT, new SimpleAttributeSet());
1644              action.end(HTML.Tag.COMMENT);
1645            }
1646        }
1647    }
1648    
1649    /**
1650     * This method is called by the parser and should route the call to the
1651     * proper handler for the tag.
1652     * 
1653     * @param t the HTML.Tag
1654     * @param pos the position at which the tag was encountered
1655     */
1656    public void handleEndTag(HTML.Tag t, int pos)
1657    {
1658      if (shouldInsert())
1659        {
1660          // If this is the </html> tag we need to stop calling the Actions
1661          if (t == HTML.Tag.HTML)
1662            endHTMLEncountered = true;
1663
1664          TagAction action = (TagAction) tagToAction.get(t);
1665          if (action != null)
1666            action.end(t);
1667        }
1668    }
1669    
1670    /**
1671     * This is a callback from the parser that should be routed to the
1672     * appropriate handler for the tag.
1673     * 
1674     * @param t the HTML.Tag that was encountered
1675     * @param a the attribute set
1676     * @param pos the position at which the tag was encountered
1677     */
1678    public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos)
1679    {
1680      if (t == insertTag)
1681        insertTagEncountered = true;
1682
1683      if (shouldInsert())
1684        {
1685          TagAction action = (TagAction) tagToAction.get(t);
1686          if (action != null)
1687            {
1688              action.start(t, a);
1689              action.end(t);
1690            }
1691        }
1692    }
1693    
1694    /**
1695     * This is invoked after the stream has been parsed but before it has been
1696     * flushed.
1697     * 
1698     * @param eol one of \n, \r, or \r\n, whichever was encountered the most in 
1699     * parsing the stream
1700     * @since 1.3
1701     */
1702    public void handleEndOfLineString(String eol)
1703    {
1704      // FIXME: Implement.
1705    }
1706    
1707    /**
1708     * Adds the given text to the textarea document.  Called only when we are
1709     * within a textarea.  
1710     * 
1711     * @param data the text to add to the textarea
1712     */
1713    protected void textAreaContent(char[] data)
1714    {
1715      try
1716        {
1717          int offset = textAreaDocument.getLength();
1718          String text = new String(data);
1719          textAreaDocument.setInitialText(text);
1720          textAreaDocument.insertString(offset, text, null);
1721        }
1722      catch (BadLocationException ex)
1723        {
1724          // Must not happen as we insert at a model location that we
1725          // got from the document itself.
1726          assert false;
1727        }
1728    }
1729    
1730    /**
1731     * Adds the given text that was encountered in a <PRE> element.
1732     * This adds synthesized lines to hold the text runs.
1733     *
1734     * @param data the text
1735     */
1736    protected void preContent(char[] data)
1737    {
1738      int start = 0;
1739      for (int i = 0; i < data.length; i++)
1740        {
1741          if (data[i] == '\n')
1742            {
1743              addContent(data, start, i - start + 1);
1744              blockClose(HTML.Tag.IMPLIED);
1745              MutableAttributeSet atts = new SimpleAttributeSet();
1746              atts.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
1747              blockOpen(HTML.Tag.IMPLIED, atts);
1748              start = i + 1;
1749            }
1750        }
1751      if (start < data.length)
1752        {
1753          // Add remaining last line.
1754          addContent(data, start, data.length - start);
1755        }
1756    }
1757    
1758    /**
1759     * Instructs the parse buffer to create a block element with the given 
1760     * attributes.
1761     * 
1762     * @param t the tag that requires opening a new block
1763     * @param attr the attribute set for the new block
1764     */
1765    protected void blockOpen(HTML.Tag t, MutableAttributeSet attr)
1766    {
1767      if (inImpliedParagraph())
1768        blockClose(HTML.Tag.IMPLIED);
1769
1770      // Push the new tag on top of the stack.
1771      parseStack.push(t);
1772
1773      DefaultStyledDocument.ElementSpec element;
1774
1775      AbstractDocument.AttributeContext ctx = getAttributeContext();
1776      AttributeSet copy = attr.copyAttributes();
1777      copy = ctx.addAttribute(copy, StyleConstants.NameAttribute, t);
1778      element = new DefaultStyledDocument.ElementSpec(copy,
1779                               DefaultStyledDocument.ElementSpec.StartTagType);
1780      parseBuffer.addElement(element);
1781    }
1782
1783    /**
1784     * Returns true when we are currently inside a paragraph, either
1785     * a real one or an implied, false otherwise.
1786     *
1787     * @return
1788     */
1789    private boolean inParagraph()
1790    {
1791      boolean inParagraph = false;
1792      if (! parseStack.isEmpty())
1793        {
1794          HTML.Tag top = parseStack.peek();
1795          inParagraph = top == HTML.Tag.P || top == HTML.Tag.IMPLIED;
1796        }
1797      return inParagraph;
1798    }
1799
1800    private boolean inImpliedParagraph()
1801    {
1802      boolean inParagraph = false;
1803      if (! parseStack.isEmpty())
1804        {
1805          HTML.Tag top = parseStack.peek();
1806          inParagraph = top == HTML.Tag.IMPLIED;
1807        }
1808      return inParagraph;
1809    }
1810
1811    /**
1812     * Instructs the parse buffer to close the block element associated with 
1813     * the given HTML.Tag
1814     * 
1815     * @param t the HTML.Tag that is closing its block
1816     */
1817    protected void blockClose(HTML.Tag t)
1818    {
1819      DefaultStyledDocument.ElementSpec element;
1820
1821      if (inImpliedParagraph() && t != HTML.Tag.IMPLIED)
1822        blockClose(HTML.Tag.IMPLIED);
1823
1824      // Pull the token from the stack.
1825      if (! parseStack.isEmpty()) // Just to be sure.
1826        parseStack.pop();
1827
1828      // If the previous tag is a start tag then we insert a synthetic
1829      // content tag.
1830      DefaultStyledDocument.ElementSpec prev;
1831      prev = parseBuffer.size() > 0 ? (DefaultStyledDocument.ElementSpec)
1832                                parseBuffer.get(parseBuffer.size() - 1) : null;
1833      if (prev != null &&
1834          prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType)
1835        {
1836          addContent(new char[]{' '}, 0, 1);
1837        }
1838
1839      element = new DefaultStyledDocument.ElementSpec(null,
1840                                DefaultStyledDocument.ElementSpec.EndTagType);
1841      parseBuffer.addElement(element);
1842    }
1843    
1844    /**
1845     * Adds text to the appropriate context using the current character
1846     * attribute set.
1847     * 
1848     * @param data the text to add
1849     * @param offs the offset at which to add it
1850     * @param length the length of the text to add
1851     */
1852    protected void addContent(char[] data, int offs, int length)
1853    {
1854      addContent(data, offs, length, true);
1855    }
1856    
1857    /**
1858     * Adds text to the appropriate context using the current character
1859     * attribute set, and possibly generating an IMPLIED Tag if necessary.
1860     * 
1861     * @param data the text to add
1862     * @param offs the offset at which to add it
1863     * @param length the length of the text to add
1864     * @param generateImpliedPIfNecessary whether or not we should generate
1865     * an HTML.Tag.IMPLIED tag if necessary
1866     */
1867    protected void addContent(char[] data, int offs, int length,
1868                              boolean generateImpliedPIfNecessary)
1869    {
1870      if (generateImpliedPIfNecessary && ! inParagraph())
1871        {
1872          blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
1873        }
1874
1875      AbstractDocument.AttributeContext ctx = getAttributeContext();
1876      DefaultStyledDocument.ElementSpec element;
1877      AttributeSet attributes = null;
1878
1879      // Copy the attribute set, don't use the same object because 
1880      // it may change
1881      if (charAttr != null)
1882        attributes = charAttr.copyAttributes();
1883      else
1884        attributes = ctx.getEmptySet();
1885      attributes = ctx.addAttribute(attributes, StyleConstants.NameAttribute,
1886                                    HTML.Tag.CONTENT);
1887      element = new DefaultStyledDocument.ElementSpec(attributes,
1888                                DefaultStyledDocument.ElementSpec.ContentType,
1889                                data, offs, length);
1890      
1891      // Add the element to the buffer
1892      parseBuffer.addElement(element);
1893
1894      if (parseBuffer.size() > threshold)
1895        {
1896          if (threshold <= MAX_THRESHOLD)
1897            threshold *= GROW_THRESHOLD;
1898          try
1899            {
1900              flushImpl();
1901            }
1902          catch (BadLocationException ble)
1903            {
1904              // TODO: what to do here?
1905            }
1906        }
1907    }
1908    
1909    /**
1910     * Adds content that is specified in the attribute set.
1911     * 
1912     * @param t the HTML.Tag
1913     * @param a the attribute set specifying the special content
1914     */
1915    protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a)
1916    {
1917      if (t != HTML.Tag.FRAME && ! inParagraph())
1918        {
1919          blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
1920        }
1921
1922      a.addAttribute(StyleConstants.NameAttribute, t);
1923      
1924      // The two spaces are required because some special elements like HR
1925      // must be broken. At least two characters are needed to break into the
1926      // two parts.
1927      DefaultStyledDocument.ElementSpec spec =
1928        new DefaultStyledDocument.ElementSpec(a.copyAttributes(),
1929          DefaultStyledDocument.ElementSpec.ContentType, 
1930          new char[] {' '}, 0, 1 );
1931      parseBuffer.add(spec);
1932    }
1933    
1934  }
1935  
1936  /**
1937   * Gets the reader for the parser to use when loading the document with HTML. 
1938   * 
1939   * @param pos - the starting position
1940   * @return - the reader
1941   */
1942  public HTMLEditorKit.ParserCallback getReader(int pos)
1943  {
1944    return new HTMLReader(pos);
1945  }  
1946  
1947  /**
1948   * Gets the reader for the parser to use when loading the document with HTML. 
1949   * 
1950   * @param pos - the starting position
1951   * @param popDepth - the number of EndTagTypes to generate before inserting
1952   * @param pushDepth - the number of StartTagTypes with a direction 
1953   * of JoinNextDirection that should be generated before inserting, 
1954   * but after the end tags have been generated.
1955   * @param insertTag - the first tag to start inserting into document
1956   * @return - the reader
1957   */
1958  public HTMLEditorKit.ParserCallback getReader(int pos,
1959                                                int popDepth,
1960                                                int pushDepth,
1961                                                HTML.Tag insertTag)
1962  {
1963    return new HTMLReader(pos, popDepth, pushDepth, insertTag);
1964  }
1965  
1966  /**
1967   * Gets the reader for the parser to use when inserting the HTML fragment into
1968   * the document. Checks if the parser is present, sets the parent in the
1969   * element stack and removes any actions for BODY (it can be only one body in
1970   * a HTMLDocument).
1971   * 
1972   * @param pos - the starting position
1973   * @param popDepth - the number of EndTagTypes to generate before inserting
1974   * @param pushDepth - the number of StartTagTypes with a direction of
1975   *          JoinNextDirection that should be generated before inserting, but
1976   *          after the end tags have been generated.
1977   * @param insertTag - the first tag to start inserting into document
1978   * @param parent the element that will be the parent in the document. HTML
1979   *          parsing includes checks for the parent, so it must be available.
1980   * @return - the reader
1981   * @throws IllegalStateException if the parsert is not set.
1982   */
1983  public HTMLEditorKit.ParserCallback getInsertingReader(int pos, int popDepth,
1984                                                         int pushDepth,
1985                                                         HTML.Tag insertTag,
1986                                                         final Element parent)
1987      throws IllegalStateException
1988  {
1989    if (parser == null)
1990      throw new IllegalStateException("Parser has not been set");
1991
1992    HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth, insertTag)
1993    {
1994      /**
1995       * Ignore BODY.
1996       */
1997      public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos)
1998      {
1999        if (t != HTML.Tag.BODY)
2000          super.handleStartTag(t, a, pos);
2001      }
2002
2003      /**
2004       * Ignore BODY.
2005       */
2006      public void handleEndTag(HTML.Tag t, int pos)
2007      {
2008        if (t != HTML.Tag.BODY)
2009          super.handleEndTag(t, pos);
2010      }
2011    };
2012      
2013    return reader;
2014  }   
2015  
2016  /**
2017   * Gets the child element that contains the attribute with the value or null.
2018   * Not thread-safe.
2019   * 
2020   * @param e - the element to begin search at
2021   * @param attribute - the desired attribute
2022   * @param value - the desired value
2023   * @return the element found with the attribute and value specified or null if
2024   *         it is not found.
2025   */
2026  public Element getElement(Element e, Object attribute, Object value)
2027  {
2028    if (e != null)
2029      {
2030        if (e.getAttributes().containsAttribute(attribute, value))
2031          return e;
2032        
2033        int count = e.getElementCount();
2034        for (int j = 0; j < count; j++)
2035          {
2036            Element child = e.getElement(j);
2037            if (child.getAttributes().containsAttribute(attribute, value))
2038              return child;
2039            
2040            Element grandChild = getElement(child, attribute, value);
2041            if (grandChild != null)
2042              return grandChild;
2043          }
2044      }
2045    return null;
2046  }
2047  
2048  /**
2049   * Returns the element that has the given id Attribute (for instance, &lt;p id
2050   * ='my paragraph &gt;'). If it is not found, null is returned. The HTML tag,
2051   * having this attribute, is not checked by this method and can be any. The
2052   * method is not thread-safe.
2053   * 
2054   * @param attrId - the value of the attribute id to look for
2055   * @return the element that has the given id.
2056   */
2057  public Element getElement(String attrId)
2058  {
2059    return getElement(getDefaultRootElement(), HTML.Attribute.ID,
2060                      attrId);
2061  }
2062  
2063  /**
2064   * Replaces the children of the given element with the contents of
2065   * the string. The document must have an HTMLEditorKit.Parser set.
2066   * This will be seen as at least two events, n inserts followed by a remove.
2067   * 
2068   * @param elem - the brance element whose children will be replaced
2069   * @param htmlText - the string to be parsed and assigned to element.
2070   * @throws BadLocationException
2071   * @throws IOException
2072   * @throws IllegalArgumentException - if elem is a leaf 
2073   * @throws IllegalStateException - if an HTMLEditorKit.Parser has not been set
2074   */
2075  public void setInnerHTML(Element elem, String htmlText) 
2076    throws BadLocationException, IOException
2077  {
2078    if (elem.isLeaf())
2079      throw new IllegalArgumentException("Element is a leaf");
2080    
2081    int start = elem.getStartOffset();
2082    int end = elem.getEndOffset();
2083
2084    HTMLEditorKit.ParserCallback reader = getInsertingReader(
2085      end, 0, 0, HTML.Tag.BODY, elem);
2086
2087    // TODO charset
2088    getParser().parse(new StringReader(htmlText), reader, true);
2089    
2090    // Remove the previous content
2091    remove(start, end - start);
2092  }
2093  
2094  /**
2095   * Replaces the given element in the parent with the string. When replacing a
2096   * leaf, this will attempt to make sure there is a newline present if one is
2097   * needed. This may result in an additional element being inserted. This will
2098   * be seen as at least two events, n inserts followed by a remove. The
2099   * HTMLEditorKit.Parser must be set.
2100   * 
2101   * @param elem - the branch element whose parent will be replaced
2102   * @param htmlText - the string to be parsed and assigned to elem
2103   * @throws BadLocationException
2104   * @throws IOException
2105   * @throws IllegalStateException - if parser is not set
2106   */
2107public void setOuterHTML(Element elem, String htmlText)
2108      throws BadLocationException, IOException
2109  {
2110    // Remove the current element:
2111    int start = elem.getStartOffset();
2112    int end = elem.getEndOffset();
2113
2114    remove(start, end-start);
2115       
2116    HTMLEditorKit.ParserCallback reader = getInsertingReader(
2117      start, 0, 0, HTML.Tag.BODY, elem);
2118
2119    // TODO charset
2120    getParser().parse(new StringReader(htmlText), reader, true);
2121  }
2122  
2123  /**
2124   * Inserts the string before the start of the given element. The parser must
2125   * be set.
2126   * 
2127   * @param elem - the element to be the root for the new text.
2128   * @param htmlText - the string to be parsed and assigned to elem
2129   * @throws BadLocationException
2130   * @throws IOException
2131   * @throws IllegalStateException - if parser has not been set
2132   */
2133  public void insertBeforeStart(Element elem, String htmlText)
2134      throws BadLocationException, IOException
2135  {
2136    HTMLEditorKit.ParserCallback reader = getInsertingReader(
2137      elem.getStartOffset(), 0, 0, HTML.Tag.BODY, elem);
2138
2139    // TODO charset
2140    getParser().parse(new StringReader(htmlText), reader, true);
2141  }
2142  
2143  /**
2144   * Inserts the string at the end of the element. If elem's children are
2145   * leaves, and the character at elem.getEndOffset() - 1 is a newline, then it
2146   * will be inserted before the newline. The parser must be set.
2147   * 
2148   * @param elem - the element to be the root for the new text
2149   * @param htmlText - the text to insert
2150   * @throws BadLocationException
2151   * @throws IOException
2152   * @throws IllegalStateException - if parser is not set
2153   */
2154  public void insertBeforeEnd(Element elem, String htmlText)
2155      throws BadLocationException, IOException
2156  {
2157    HTMLEditorKit.ParserCallback reader = getInsertingReader(
2158      elem.getEndOffset(), 0, 0, HTML.Tag.BODY, elem);
2159
2160    // TODO charset
2161    getParser().parse(new StringReader(htmlText), reader, true);
2162
2163  }
2164  
2165  /**
2166   * Inserts the string after the end of the given element.
2167   * The parser must be set.
2168   * 
2169   * @param elem - the element to be the root for the new text
2170   * @param htmlText - the text to insert
2171   * @throws BadLocationException
2172   * @throws IOException
2173   * @throws IllegalStateException - if parser is not set
2174   */
2175  public void insertAfterEnd(Element elem, String htmlText)
2176      throws BadLocationException, IOException
2177  {
2178    HTMLEditorKit.ParserCallback reader = getInsertingReader(
2179      elem.getEndOffset(), 0, 0, HTML.Tag.BODY, elem);
2180
2181    // TODO charset
2182    getParser().parse(new StringReader(htmlText), reader, true);
2183  }
2184  
2185  /**
2186   * Inserts the string at the start of the element.
2187   * The parser must be set.
2188   * 
2189   * @param elem - the element to be the root for the new text
2190   * @param htmlText - the text to insert
2191   * @throws BadLocationException
2192   * @throws IOException
2193   * @throws IllegalStateException - if parser is not set
2194   */
2195  public void insertAfterStart(Element elem, String htmlText)
2196      throws BadLocationException, IOException
2197  {
2198    HTMLEditorKit.ParserCallback reader = getInsertingReader(
2199      elem.getStartOffset(), 0, 0, HTML.Tag.BODY, elem);
2200
2201    // TODO charset
2202    getParser().parse(new StringReader(htmlText), reader, true);
2203  }
2204
2205  /**
2206   * Overridden to tag content with the synthetic HTML.Tag.CONTENT
2207   * tag.
2208   */
2209  protected void insertUpdate(DefaultDocumentEvent evt, AttributeSet att)
2210  {
2211    if (att == null)
2212      {
2213        SimpleAttributeSet sas = new SimpleAttributeSet();
2214        sas.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
2215        att = sas;
2216      }
2217    super.insertUpdate(evt, att);
2218  }
2219
2220  /**
2221   * Returns <code>true</code> when this document is inside a frame,
2222   * <code>false</code> otherwise.
2223   *
2224   * @return <code>true</code> when this document is inside a frame,
2225   *         <code>false</code> otherwise
2226   */
2227  boolean isFrameDocument()
2228  {
2229    return frameDocument;
2230  }
2231
2232  /**
2233   * Set <code>true</code> when this document is inside a frame,
2234   * <code>false</code> otherwise.
2235   *
2236   * @param frameDoc <code>true</code> when this document is inside a frame,
2237   *                 <code>false</code> otherwise
2238   */
2239  void setFrameDocument(boolean frameDoc)
2240  {
2241    frameDocument = frameDoc;
2242  }
2243
2244  /**
2245   * Returns the target that is specified in the base tag, if this is the case.
2246   *
2247   * @return the target that is specified in the base tag, if this is the case
2248   */
2249  String getBaseTarget()
2250  {
2251    return baseTarget;
2252  }
2253
2254  /**
2255   * Updates the A tag's pseudo class value in response to a hyperlink
2256   * action.
2257   *
2258   * @param el the corresponding element
2259   * @param value the new value
2260   */
2261  void updateSpecialClass(Element el, HTML.Attribute cl, String value)
2262  {
2263    try
2264    {
2265      writeLock();
2266      DefaultDocumentEvent ev =
2267        new DefaultDocumentEvent(el.getStartOffset(), 1,
2268                                 DocumentEvent.EventType.CHANGE);
2269      AttributeSet elAtts = el.getAttributes();
2270      AttributeSet anchorAtts = (AttributeSet) elAtts.getAttribute(HTML.Tag.A);
2271      if (anchorAtts != null)
2272        {
2273          AttributeSet copy = elAtts.copyAttributes();
2274          StyleSheet ss = getStyleSheet();
2275          if (value != null)
2276            {
2277              anchorAtts = ss.addAttribute(anchorAtts, cl, value);
2278            }
2279          else
2280            {
2281              anchorAtts = ss.removeAttribute(anchorAtts, cl);
2282            }
2283          MutableAttributeSet matts = (MutableAttributeSet) elAtts;
2284          ev.addEdit(new AttributeUndoableEdit(el, copy, false));
2285          matts.removeAttribute(HTML.Tag.A);
2286          matts.addAttribute(HTML.Tag.A, anchorAtts);
2287          ev.end();
2288          fireChangedUpdate(ev);
2289          fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
2290        }
2291    }
2292  finally
2293    {
2294      writeUnlock();
2295    }
2296  }
2297
2298}