001    /* HTMLDocument.java --
002       Copyright (C) 2005  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    
039    package javax.swing.text.html;
040    
041    import gnu.classpath.NotImplementedException;
042    
043    import java.io.IOException;
044    import java.io.StringReader;
045    import java.net.MalformedURLException;
046    import java.net.URL;
047    import java.util.ArrayList;
048    import java.util.HashMap;
049    import java.util.Stack;
050    import java.util.Vector;
051    
052    import javax.swing.ButtonGroup;
053    import javax.swing.DefaultButtonModel;
054    import javax.swing.JEditorPane;
055    import javax.swing.ListSelectionModel;
056    import javax.swing.event.DocumentEvent;
057    import javax.swing.event.UndoableEditEvent;
058    import javax.swing.text.AbstractDocument;
059    import javax.swing.text.AttributeSet;
060    import javax.swing.text.BadLocationException;
061    import javax.swing.text.DefaultStyledDocument;
062    import javax.swing.text.Element;
063    import javax.swing.text.ElementIterator;
064    import javax.swing.text.GapContent;
065    import javax.swing.text.MutableAttributeSet;
066    import javax.swing.text.PlainDocument;
067    import javax.swing.text.SimpleAttributeSet;
068    import javax.swing.text.StyleConstants;
069    import 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     */
084    public 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       */
2107    public 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    }