OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / text / html / HTMLDocument.java
1 /* HTMLDocument.java --
2    Copyright (C) 2005  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.swing.text.html;
40
41 import gnu.classpath.NotImplementedException;
42
43 import java.io.IOException;
44 import java.io.StringReader;
45 import java.net.MalformedURLException;
46 import java.net.URL;
47 import java.util.ArrayList;
48 import java.util.HashMap;
49 import java.util.Stack;
50 import java.util.Vector;
51
52 import javax.swing.ButtonGroup;
53 import javax.swing.DefaultButtonModel;
54 import javax.swing.JEditorPane;
55 import javax.swing.ListSelectionModel;
56 import javax.swing.event.DocumentEvent;
57 import javax.swing.event.UndoableEditEvent;
58 import javax.swing.text.AbstractDocument;
59 import javax.swing.text.AttributeSet;
60 import javax.swing.text.BadLocationException;
61 import javax.swing.text.DefaultStyledDocument;
62 import javax.swing.text.Element;
63 import javax.swing.text.ElementIterator;
64 import javax.swing.text.GapContent;
65 import javax.swing.text.MutableAttributeSet;
66 import javax.swing.text.PlainDocument;
67 import javax.swing.text.SimpleAttributeSet;
68 import javax.swing.text.StyleConstants;
69 import javax.swing.text.html.HTML.Tag;
70
71 /**
72  * Represents the HTML document that is constructed by defining the text and
73  * other components (images, buttons, etc) in HTML language. This class can
74  * becomes the default document for {@link JEditorPane} after setting its
75  * content type to "text/html". HTML document also serves as an intermediate
76  * data structure when it is needed to parse HTML and then obtain the content of
77  * the certain types of tags. This class also has methods for modifying the HTML
78  * content.
79  * 
80  * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
81  * @author Anthony Balkissoon (abalkiss@redhat.com)
82  * @author Lillian Angel (langel@redhat.com)
83  */
84 public class HTMLDocument extends DefaultStyledDocument
85 {
86   /** A key for document properies.  The value for the key is
87    * a Vector of Strings of comments not found in the body.
88    */  
89   public static final String AdditionalComments = "AdditionalComments";
90   URL baseURL = null;
91   boolean preservesUnknownTags = true;
92   int tokenThreshold = Integer.MAX_VALUE;
93   HTMLEditorKit.Parser parser;
94
95   /**
96    * Indicates whether this document is inside a frame or not.
97    */
98   private boolean frameDocument;
99
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 }