KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > html > HTMLDocument


1 /*
2  * @(#)HTMLDocument.java 1.170 05/05/27
3  *
4  * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text.html;
8
9 import java.awt.Color JavaDoc;
10 import java.awt.Component JavaDoc;
11 import java.awt.font.TextAttribute JavaDoc;
12 import java.util.*;
13 import java.net.URL JavaDoc;
14 import java.net.URLEncoder JavaDoc;
15 import java.net.MalformedURLException JavaDoc;
16 import java.io.*;
17 import javax.swing.*;
18 import javax.swing.event.*;
19 import javax.swing.text.*;
20 import javax.swing.undo.*;
21 import java.text.Bidi JavaDoc;
22
23 /**
24  * A document that models HTML. The purpose of this model
25  * is to support both browsing and editing. As a result,
26  * the structure described by an HTML document is not
27  * exactly replicated by default. The element structure that
28  * is modeled by default, is built by the class
29  * <code>HTMLDocument.HTMLReader</code>, which implements
30  * the <code>HTMLEditorKit.ParserCallback</code> protocol
31  * that the parser expects. To change the structure one
32  * can subclass <code>HTMLReader</code>, and reimplement the method
33  * {@link #getReader(int)} to return the new reader
34  * implementation. The documentation for <code>HTMLReader</code>
35  * should be consulted for the details of
36  * the default structure created. The intent is that
37  * the document be non-lossy (although reproducing the
38  * HTML format may result in a different format).
39  * <p>
40  * The document models only HTML, and makes no attempt to
41  * store view attributes in it. The elements are identified
42  * by the <code>StyleContext.NameAttribute</code> attribute,
43  * which should always have a value of type <code>HTML.Tag</code>
44  * that identifies the kind of element. Some of the elements
45  * (such as comments) are synthesized. The <code>HTMLFactory</code>
46  * uses this attribute to determine what kind of view to build.
47  * <p>
48  * This document supports incremental loading. The
49  * <code>TokenThreshold</code> property controls how
50  * much of the parse is buffered before trying to update
51  * the element structure of the document. This property
52  * is set by the <code>EditorKit</code> so that subclasses can disable
53  * it.
54  * <p>
55  * The <code>Base</code> property determines the URL
56  * against which relative URLs are resolved.
57  * By default, this will be the
58  * <code>Document.StreamDescriptionProperty</code> if
59  * the value of the property is a URL. If a &lt;BASE&gt;
60  * tag is encountered, the base will become the URL specified
61  * by that tag. Because the base URL is a property, it
62  * can of course be set directly.
63  * <p>
64  * The default content storage mechanism for this document
65  * is a gap buffer (<code>GapContent</code>).
66  * Alternatives can be supplied by using the constructor
67  * that takes a <code>Content</code> implementation.
68  *
69  * @author Timothy Prinzing
70  * @author Scott Violet
71  * @author Sunita Mani
72  * @version 1.170 05/27/05
73  */

74 public class HTMLDocument extends DefaultStyledDocument {
75     /**
76      * Constructs an HTML document using the default buffer size
77      * and a default <code>StyleSheet</code>. This is a convenience
78      * method for the constructor
79      * <code>HTMLDocument(Content, StyleSheet)</code>.
80      */

81     public HTMLDocument() {
82     this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet JavaDoc());
83     }
84     
85     /**
86      * Constructs an HTML document with the default content
87      * storage implementation and the specified style/attribute
88      * storage mechanism. This is a convenience method for the
89      * constructor
90      * <code>HTMLDocument(Content, StyleSheet)</code>.
91      *
92      * @param styles the styles
93      */

94     public HTMLDocument(StyleSheet JavaDoc styles) {
95     this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
96     }
97
98     /**
99      * Constructs an HTML document with the given content
100      * storage implementation and the given style/attribute
101      * storage mechanism.
102      *
103      * @param c the container for the content
104      * @param styles the styles
105      */

106     public HTMLDocument(Content c, StyleSheet JavaDoc styles) {
107         super(c, styles);
108     }
109
110     /**
111      * Fetches the reader for the parser to use when loading the document
112      * with HTML. This is implemented to return an instance of
113      * <code>HTMLDocument.HTMLReader</code>.
114      * Subclasses can reimplement this
115      * method to change how the document gets structured if desired.
116      * (For example, to handle custom tags, or structurally represent character
117      * style elements.)
118      *
119      * @param pos the starting position
120      * @return the reader used by the parser to load the document
121      */

122     public HTMLEditorKit.ParserCallback JavaDoc getReader(int pos) {
123     Object JavaDoc desc = getProperty(Document.StreamDescriptionProperty);
124     if (desc instanceof URL JavaDoc) {
125         setBase((URL JavaDoc)desc);
126     }
127     HTMLReader reader = new HTMLReader(pos);
128     return reader;
129     }
130
131     /**
132      * Returns the reader for the parser to use to load the document
133      * with HTML. This is implemented to return an instance of
134      * <code>HTMLDocument.HTMLReader</code>.
135      * Subclasses can reimplement this
136      * method to change how the document gets structured if desired.
137      * (For example, to handle custom tags, or structurally represent character
138      * style elements.)
139      * <p>This is a convenience method for
140      * <code>getReader(int, int, int, HTML.Tag, TRUE)</code>.
141      *
142      * @param popDepth the number of <code>ElementSpec.EndTagTypes</code>
143      * to generate before inserting
144      * @param pushDepth the number of <code>ElementSpec.StartTagTypes</code>
145      * with a direction of <code>ElementSpec.JoinNextDirection</code>
146      * that should be generated before inserting,
147      * but after the end tags have been generated
148      * @param insertTag the first tag to start inserting into document
149      * @return the reader used by the parser to load the document
150      */

151     public HTMLEditorKit.ParserCallback JavaDoc getReader(int pos, int popDepth,
152                           int pushDepth,
153                           HTML.Tag JavaDoc insertTag) {
154     return getReader(pos, popDepth, pushDepth, insertTag, true);
155     }
156
157     /**
158      * Fetches the reader for the parser to use to load the document
159      * with HTML. This is implemented to return an instance of
160      * HTMLDocument.HTMLReader. Subclasses can reimplement this
161      * method to change how the document get structured if desired
162      * (e.g. to handle custom tags, structurally represent character
163      * style elements, etc.).
164      *
165      * @param popDepth the number of <code>ElementSpec.EndTagTypes</code>
166      * to generate before inserting
167      * @param pushDepth the number of <code>ElementSpec.StartTagTypes</code>
168      * with a direction of <code>ElementSpec.JoinNextDirection</code>
169      * that should be generated before inserting,
170      * but after the end tags have been generated
171      * @param insertTag the first tag to start inserting into document
172      * @param insertInsertTag false if all the Elements after insertTag should
173      * be inserted; otherwise insertTag will be inserted
174      * @return the reader used by the parser to load the document
175      */

176     HTMLEditorKit.ParserCallback JavaDoc getReader(int pos, int popDepth,
177                        int pushDepth,
178                        HTML.Tag JavaDoc insertTag,
179                        boolean insertInsertTag) {
180     Object JavaDoc desc = getProperty(Document.StreamDescriptionProperty);
181     if (desc instanceof URL JavaDoc) {
182         setBase((URL JavaDoc)desc);
183     }
184     HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth,
185                        insertTag, insertInsertTag, false,
186                        true);
187     return reader;
188     }
189
190     /**
191      * Returns the location to resolve relative URLs against. By
192      * default this will be the document's URL if the document
193      * was loaded from a URL. If a base tag is found and
194      * can be parsed, it will be used as the base location.
195      *
196      * @return the base location
197      */

198     public URL JavaDoc getBase() {
199     return base;
200     }
201
202     /**
203      * Sets the location to resolve relative URLs against. By
204      * default this will be the document's URL if the document
205      * was loaded from a URL. If a base tag is found and
206      * can be parsed, it will be used as the base location.
207      * <p>This also sets the base of the <code>StyleSheet</code>
208      * to be <code>u</code> as well as the base of the document.
209      *
210      * @param u the desired base URL
211      */

212     public void setBase(URL JavaDoc u) {
213     base = u;
214     getStyleSheet().setBase(u);
215     }
216
217     /**
218      * Inserts new elements in bulk. This is how elements get created
219      * in the document. The parsing determines what structure is needed
220      * and creates the specification as a set of tokens that describe the
221      * edit while leaving the document free of a write-lock. This method
222      * can then be called in bursts by the reader to acquire a write-lock
223      * for a shorter duration (i.e. while the document is actually being
224      * altered).
225      *
226      * @param offset the starting offset
227      * @param data the element data
228      * @exception BadLocationException if the given position does not
229      * represent a valid location in the associated document.
230      */

231     protected void insert(int offset, ElementSpec[] data) throws BadLocationException {
232     super.insert(offset, data);
233     }
234
235     /**
236      * Updates document structure as a result of text insertion. This
237      * will happen within a write lock. This implementation simply
238      * parses the inserted content for line breaks and builds up a set
239      * of instructions for the element buffer.
240      *
241      * @param chng a description of the document change
242      * @param attr the attributes
243      */

244     protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
245     if(attr == null) {
246         attr = contentAttributeSet;
247     }
248
249     // If this is the composed text element, merge the content attribute to it
250
else if (attr.isDefined(StyleConstants.ComposedTextAttribute)) {
251         ((MutableAttributeSet)attr).addAttributes(contentAttributeSet);
252     }
253
254     if (attr.isDefined(IMPLIED_CR)) {
255         ((MutableAttributeSet)attr).removeAttribute(IMPLIED_CR);
256     }
257
258     super.insertUpdate(chng, attr);
259     }
260
261     /**
262      * Replaces the contents of the document with the given
263      * element specifications. This is called before insert if
264      * the loading is done in bursts. This is the only method called
265      * if loading the document entirely in one burst.
266      *
267      * @param data the new contents of the document
268      */

269     protected void create(ElementSpec[] data) {
270     super.create(data);
271     }
272
273     /**
274      * Sets attributes for a paragraph.
275      * <p>
276      * This method is thread safe, although most Swing methods
277      * are not. Please see
278      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
279      * and Swing</A> for more information.
280      *
281      * @param offset the offset into the paragraph (must be at least 0)
282      * @param length the number of characters affected (must be at least 0)
283      * @param s the attributes
284      * @param replace whether to replace existing attributes, or merge them
285      */

286     public void setParagraphAttributes(int offset, int length, AttributeSet s,
287                        boolean replace) {
288     try {
289         writeLock();
290         // Make sure we send out a change for the length of the paragraph.
291
int end = Math.min(offset + length, getLength());
292         Element e = getParagraphElement(offset);
293         offset = e.getStartOffset();
294         e = getParagraphElement(end);
295         length = Math.max(0, e.getEndOffset() - offset);
296         DefaultDocumentEvent changes =
297         new DefaultDocumentEvent(offset, length,
298                      DocumentEvent.EventType.CHANGE);
299         AttributeSet sCopy = s.copyAttributes();
300         int lastEnd = Integer.MAX_VALUE;
301         for (int pos = offset; pos <= end; pos = lastEnd) {
302         Element paragraph = getParagraphElement(pos);
303         if (lastEnd == paragraph.getEndOffset()) {
304             lastEnd++;
305         }
306         else {
307             lastEnd = paragraph.getEndOffset();
308         }
309         MutableAttributeSet attr =
310             (MutableAttributeSet) paragraph.getAttributes();
311         changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
312         if (replace) {
313             attr.removeAttributes(attr);
314         }
315         attr.addAttributes(s);
316         }
317         changes.end();
318         fireChangedUpdate(changes);
319         fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
320     } finally {
321         writeUnlock();
322     }
323     }
324
325     /**
326      * Fetches the <code>StyleSheet</code> with the document-specific display
327      * rules (CSS) that were specified in the HTML document itself.
328      *
329      * @return the <code>StyleSheet</code>
330      */

331     public StyleSheet JavaDoc getStyleSheet() {
332     return (StyleSheet JavaDoc) getAttributeContext();
333     }
334
335     /**
336      * Fetches an iterator for the specified HTML tag.
337      * This can be used for things like iterating over the
338      * set of anchors contained, or iterating over the input
339      * elements.
340      *
341      * @param t the requested <code>HTML.Tag</code>
342      * @return the <code>Iterator</code> for the given HTML tag
343      * @see javax.swing.text.html.HTML.Tag
344      */

345     public Iterator getIterator(HTML.Tag JavaDoc t) {
346     if (t.isBlock()) {
347         // TBD
348
return null;
349     }
350     return new LeafIterator(t, this);
351     }
352
353     /**
354      * Creates a document leaf element that directly represents
355      * text (doesn't have any children). This is implemented
356      * to return an element of type
357      * <code>HTMLDocument.RunElement</code>.
358      *
359      * @param parent the parent element
360      * @param a the attributes for the element
361      * @param p0 the beginning of the range (must be at least 0)
362      * @param p1 the end of the range (must be at least p0)
363      * @return the new element
364      */

365     protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
366     return new RunElement(parent, a, p0, p1);
367     }
368
369     /**
370      * Creates a document branch element, that can contain other elements.
371      * This is implemented to return an element of type
372      * <code>HTMLDocument.BlockElement</code>.
373      *
374      * @param parent the parent element
375      * @param a the attributes
376      * @return the element
377      */

378     protected Element createBranchElement(Element parent, AttributeSet a) {
379     return new BlockElement(parent, a);
380     }
381
382     /**
383      * Creates the root element to be used to represent the
384      * default document structure.
385      *
386      * @return the element base
387      */

388     protected AbstractElement createDefaultRoot() {
389     // grabs a write-lock for this initialization and
390
// abandon it during initialization so in normal
391
// operation we can detect an illegitimate attempt
392
// to mutate attributes.
393
writeLock();
394     MutableAttributeSet a = new SimpleAttributeSet();
395     a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.HTML);
396     BlockElement html = new BlockElement(null, a.copyAttributes());
397     a.removeAttributes(a);
398     a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.BODY);
399     BlockElement body = new BlockElement(html, a.copyAttributes());
400     a.removeAttributes(a);
401     a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.P);
402         getStyleSheet().addCSSAttributeFromHTML(a, CSS.Attribute.MARGIN_TOP, "0");
403     BlockElement paragraph = new BlockElement(body, a.copyAttributes());
404     a.removeAttributes(a);
405     a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
406     RunElement brk = new RunElement(paragraph, a, 0, 1);
407     Element[] buff = new Element[1];
408     buff[0] = brk;
409     paragraph.replace(0, 0, buff);
410     buff[0] = paragraph;
411     body.replace(0, 0, buff);
412     buff[0] = body;
413     html.replace(0, 0, buff);
414     writeUnlock();
415     return html;
416     }
417
418     /**
419      * Sets the number of tokens to buffer before trying to update
420      * the documents element structure.
421      *
422      * @param n the number of tokens to buffer
423      */

424     public void setTokenThreshold(int n) {
425     putProperty(TokenThreshold, new Integer JavaDoc(n));
426     }
427
428     /**
429      * Gets the number of tokens to buffer before trying to update
430      * the documents element structure. The default value is
431      * <code>Integer.MAX_VALUE</code>.
432      *
433      * @return the number of tokens to buffer
434      */

435     public int getTokenThreshold() {
436     Integer JavaDoc i = (Integer JavaDoc) getProperty(TokenThreshold);
437     if (i != null) {
438         return i.intValue();
439     }
440     return Integer.MAX_VALUE;
441     }
442
443     /**
444      * Determines how unknown tags are handled by the parser.
445      * If set to true, unknown
446      * tags are put in the model, otherwise they are dropped.
447      *
448      * @param preservesTags true if unknown tags should be
449      * saved in the model, otherwise tags are dropped
450      * @see javax.swing.text.html.HTML.Tag
451      */

452     public void setPreservesUnknownTags(boolean preservesTags) {
453     preservesUnknownTags = preservesTags;
454     }
455
456     /**
457      * Returns the behavior the parser observes when encountering
458      * unknown tags.
459      *
460      * @see javax.swing.text.html.HTML.Tag
461      * @return true if unknown tags are to be preserved when parsing
462      */

463     public boolean getPreservesUnknownTags() {
464     return preservesUnknownTags;
465     }
466
467     /**
468      * Processes <code>HyperlinkEvents</code> that
469      * are generated by documents in an HTML frame.
470      * The <code>HyperlinkEvent</code> type, as the parameter suggests,
471      * is <code>HTMLFrameHyperlinkEvent</code>.
472      * In addition to the typical information contained in a
473      * <code>HyperlinkEvent</code>,
474      * this event contains the element that corresponds to the frame in
475      * which the click happened (the source element) and the
476      * target name. The target name has 4 possible values:
477      * <ul>
478      * <li> _self
479      * <li> _parent
480      * <li> _top
481      * <li> a named frame
482      * </ul>
483      *
484      * If target is _self, the action is to change the value of the
485      * <code>HTML.Attribute.SRC</code> attribute and fires a
486      * <code>ChangedUpdate</code> event.
487      *<p>
488      * If the target is _parent, then it deletes the parent element,
489      * which is a &lt;FRAMESET&gt; element, and inserts a new &lt;FRAME&gt;
490      * element, and sets its <code>HTML.Attribute.SRC</code> attribute
491      * to have a value equal to the destination URL and fire a
492      * <code>RemovedUpdate</code> and <code>InsertUpdate</code>.
493      *<p>
494      * If the target is _top, this method does nothing. In the implementation
495      * of the view for a frame, namely the <code>FrameView</code>,
496      * the processing of _top is handled. Given that _top implies
497      * replacing the entire document, it made sense to handle this outside
498      * of the document that it will replace.
499      *<p>
500      * If the target is a named frame, then the element hierarchy is searched
501      * for an element with a name equal to the target, its
502      * <code>HTML.Attribute.SRC</code> attribute is updated and a
503      * <code>ChangedUpdate</code> event is fired.
504      *
505      * @param e the event
506      */

507     public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent JavaDoc e) {
508     String JavaDoc frameName = e.getTarget();
509     Element element = e.getSourceElement();
510     String JavaDoc urlStr = e.getURL().toString();
511
512     if (frameName.equals("_self")) {
513         /*
514           The source and destination elements
515           are the same.
516         */

517         updateFrame(element, urlStr);
518     } else if (frameName.equals("_parent")) {
519         /*
520           The destination is the parent of the frame.
521         */

522         updateFrameSet(element.getParentElement(), urlStr);
523     } else {
524         /*
525           locate a named frame
526         */

527         Element targetElement = findFrame(frameName);
528         if (targetElement != null) {
529         updateFrame(targetElement, urlStr);
530         }
531     }
532     }
533
534     
535     /**
536      * Searches the element hierarchy for an FRAME element
537      * that has its name attribute equal to the <code>frameName</code>.
538      *
539      * @param frameName
540      * @return the element whose NAME attribute has a value of
541      * <code>frameName</code>; returns <code>null</code>
542      * if not found
543      */

544     private Element findFrame(String JavaDoc frameName) {
545     ElementIterator it = new ElementIterator(this);
546     Element next = null;
547
548     while ((next = it.next()) != null) {
549         AttributeSet attr = next.getAttributes();
550         if (matchNameAttribute(attr, HTML.Tag.FRAME)) {
551         String JavaDoc frameTarget = (String JavaDoc)attr.getAttribute(HTML.Attribute.NAME);
552         if (frameTarget != null && frameTarget.equals(frameName)) {
553             break;
554         }
555         }
556     }
557     return next;
558     }
559
560     /**
561      * Returns true if <code>StyleConstants.NameAttribute</code> is
562      * equal to the tag that is passed in as a parameter.
563      *
564      * @param attr the attributes to be matched
565      * @param tag the value to be matched
566      * @return true if there is a match, false otherwise
567      * @see javax.swing.text.html.HTML.Attribute
568      */

569     static boolean matchNameAttribute(AttributeSet attr, HTML.Tag JavaDoc tag) {
570     Object JavaDoc o = attr.getAttribute(StyleConstants.NameAttribute);
571     if (o instanceof HTML.Tag JavaDoc) {
572         HTML.Tag JavaDoc name = (HTML.Tag JavaDoc) o;
573         if (name == tag) {
574         return true;
575         }
576     }
577     return false;
578     }
579
580     /**
581      * Replaces a frameset branch Element with a frame leaf element.
582      *
583      * @param element the frameset element to remove
584      * @param url the value for the SRC attribute for the
585      * new frame that will replace the frameset
586      */

587     private void updateFrameSet(Element element, String JavaDoc url) {
588     try {
589         int startOffset = element.getStartOffset();
590         int endOffset = Math.min(getLength(), element.getEndOffset());
591             String JavaDoc html = "<frame";
592             if (url != null) {
593                 html += " SRC=\"" + url + "\"";
594             }
595             html += ">";
596             installParserIfNecessary();
597             setOuterHTML(element, html);
598     } catch (BadLocationException e1) {
599         // Should handle this better
600
} catch (IOException ioe) {
601         // Should handle this better
602
}
603     }
604
605
606     /**
607      * Updates the Frame elements <code>HTML.Attribute.SRC attribute</code>
608      * and fires a <code>ChangedUpdate</code> event.
609      *
610      * @param element a FRAME element whose SRC attribute will be updated
611      * @param url a string specifying the new value for the SRC attribute
612      */

613     private void updateFrame(Element element, String JavaDoc url) {
614
615     try {
616         writeLock();
617         DefaultDocumentEvent changes = new DefaultDocumentEvent(element.getStartOffset(),
618                                     1,
619                                     DocumentEvent.EventType.CHANGE);
620         AttributeSet sCopy = element.getAttributes().copyAttributes();
621         MutableAttributeSet attr = (MutableAttributeSet) element.getAttributes();
622         changes.addEdit(new AttributeUndoableEdit(element, sCopy, false));
623         attr.removeAttribute(HTML.Attribute.SRC);
624         attr.addAttribute(HTML.Attribute.SRC, url);
625         changes.end();
626         fireChangedUpdate(changes);
627         fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
628     } finally {
629         writeUnlock();
630     }
631     }
632
633
634     /**
635      * Returns true if the document will be viewed in a frame.
636      * @return true if document will be viewed in a frame, otherwise false
637      */

638     boolean isFrameDocument() {
639     return frameDocument;
640     }
641     
642     /**
643      * Sets a boolean state about whether the document will be
644      * viewed in a frame.
645      * @param frameDoc true if the document will be viewed in a frame,
646      * otherwise false
647      */

648     void setFrameDocumentState(boolean frameDoc) {
649     this.frameDocument = frameDoc;
650     }
651
652     /**
653      * Adds the specified map, this will remove a Map that has been
654      * previously registered with the same name.
655      *
656      * @param map the <code>Map</code> to be registered
657      */

658     void addMap(Map JavaDoc map) {
659     String JavaDoc name = map.getName();
660
661     if (name != null) {
662         Object JavaDoc maps = getProperty(MAP_PROPERTY);
663
664         if (maps == null) {
665         maps = new Hashtable(11);
666         putProperty(MAP_PROPERTY, maps);
667         }
668         if (maps instanceof Hashtable) {
669         ((Hashtable)maps).put("#" + name, map);
670         }
671     }
672     }
673
674     /**
675      * Removes a previously registered map.
676      * @param map the <code>Map</code> to be removed
677      */

678     void removeMap(Map JavaDoc map) {
679     String JavaDoc name = map.getName();
680
681     if (name != null) {
682         Object JavaDoc maps = getProperty(MAP_PROPERTY);
683
684         if (maps instanceof Hashtable) {
685         ((Hashtable)maps).remove("#" + name);
686         }
687     }
688     }
689
690     /**
691      * Returns the Map associated with the given name.
692      * @param the name of the desired <code>Map</code>
693      * @return the <code>Map</code> or <code>null</code> if it can't
694      * be found, or if <code>name</code> is <code>null</code>
695      */

696     Map JavaDoc getMap(String JavaDoc name) {
697     if (name != null) {
698         Object JavaDoc maps = getProperty(MAP_PROPERTY);
699
700         if (maps != null && (maps instanceof Hashtable)) {
701         return (Map JavaDoc)((Hashtable)maps).get(name);
702         }
703     }
704     return null;
705     }
706
707     /**
708      * Returns an <code>Enumeration</code> of the possible Maps.
709      * @return the enumerated list of maps, or <code>null</code>
710      * if the maps are not an instance of <code>Hashtable</code>
711      */

712     Enumeration getMaps() {
713     Object JavaDoc maps = getProperty(MAP_PROPERTY);
714
715     if (maps instanceof Hashtable) {
716         return ((Hashtable)maps).elements();
717     }
718     return null;
719     }
720
721     /**
722      * Sets the content type language used for style sheets that do not
723      * explicitly specify the type. The default is text/css.
724      * @param contentType the content type language for the style sheets
725      */

726     /* public */
727     void setDefaultStyleSheetType(String JavaDoc contentType) {
728     putProperty(StyleType, contentType);
729     }
730
731     /**
732      * Returns the content type language used for style sheets. The default
733      * is text/css.
734      * @return the content type language used for the style sheets
735      */

736     /* public */
737     String JavaDoc getDefaultStyleSheetType() {
738     String JavaDoc retValue = (String JavaDoc)getProperty(StyleType);
739     if (retValue == null) {
740         return "text/css";
741     }
742     return retValue;
743     }
744
745     /**
746      * Sets the parser that is used by the methods that insert html
747      * into the existing document, such as <code>setInnerHTML</code>,
748      * and <code>setOuterHTML</code>.
749      * <p>
750      * <code>HTMLEditorKit.createDefaultDocument</code> will set the parser
751      * for you. If you create an <code>HTMLDocument</code> by hand,
752      * be sure and set the parser accordingly.
753      * @param parser the parser to be used for text insertion
754      *
755      * @since 1.3
756      */

757     public void setParser(HTMLEditorKit.Parser JavaDoc parser) {
758     this.parser = parser;
759     putProperty("__PARSER__", null);
760     }
761
762     /**
763      * Returns the parser that is used when inserting HTML into the existing
764      * document.
765      * @return the parser used for text insertion
766      *
767      * @since 1.3
768      */

769     public HTMLEditorKit.Parser JavaDoc getParser() {
770     Object JavaDoc p = getProperty("__PARSER__");
771
772     if (p instanceof HTMLEditorKit.Parser JavaDoc) {
773         return (HTMLEditorKit.Parser JavaDoc)p;
774     }
775     return parser;
776     }
777
778     /**
779      * Replaces the children of the given element with the contents
780      * specified as an HTML string.
781      * <p>This will be seen as at least two events, n inserts followed by
782      * a remove.
783      * <p>For this to work correcty, the document must have an
784      * <code>HTMLEditorKit.Parser</code> set. This will be the case
785      * if the document was created from an HTMLEditorKit via the
786      * <code>createDefaultDocument</code> method.
787      *
788      * @param elem the branch element whose children will be replaced
789      * @param htmlText the string to be parsed and assigned to <code>elem</code>
790      * @throws IllegalArgumentException if <code>elem</code> is a leaf
791      * @throws IllegalStateException if an <code>HTMLEditorKit.Parser</code>
792      * has not been defined
793      * @since 1.3
794      */

795     public void setInnerHTML(Element elem, String JavaDoc htmlText) throws
796                          BadLocationException, IOException {
797     verifyParser();
798     if (elem != null && elem.isLeaf()) {
799         throw new IllegalArgumentException JavaDoc
800         ("Can not set inner HTML of a leaf");
801     }
802     if (elem != null && htmlText != null) {
803         int oldCount = elem.getElementCount();
804         int insertPosition = elem.getStartOffset();
805         insertHTML(elem, elem.getStartOffset(), htmlText, true);
806         if (elem.getElementCount() > oldCount) {
807         // Elements were inserted, do the cleanup.
808
removeElements(elem, elem.getElementCount() - oldCount,
809                    oldCount);
810         }
811     }
812     }
813
814     /**
815      * Replaces the given element in the parent with the contents
816      * specified as an HTML string.
817      * <p>This will be seen as at least two events, n inserts followed by
818      * a remove.
819      * <p>When replacing a leaf this will attempt to make sure there is
820      * a newline present if one is needed. This may result in an additional
821      * element being inserted. Consider, if you were to replace a character
822      * element that contained a newline with &lt;img&gt; this would create
823      * two elements, one for the image, ane one for the newline.
824      * <p>If you try to replace the element at length you will most likely
825      * end up with two elements, eg setOuterHTML(getCharacterElement
826      * (getLength()), "blah") will result in two leaf elements at the end,
827      * one representing 'blah', and the other representing the end element.
828      * <p>For this to work correcty, the document must have an
829      * HTMLEditorKit.Parser set. This will be the case if the document
830      * was created from an HTMLEditorKit via the
831      * <code>createDefaultDocument</code> method.
832      *
833      * @param elem the branch element whose children will be replaced
834      * @param htmlText the string to be parsed and assigned to <code>elem</code>
835      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
836      * been set
837      * @since 1.3
838      */

839     public void setOuterHTML(Element elem, String JavaDoc htmlText) throws
840                         BadLocationException, IOException {
841     verifyParser();
842     if (elem != null && elem.getParentElement() != null &&
843         htmlText != null) {
844         int start = elem.getStartOffset();
845         int end = elem.getEndOffset();
846         int startLength = getLength();
847         // We don't want a newline if elem is a leaf, and doesn't contain
848
// a newline.
849
boolean wantsNewline = !elem.isLeaf();
850         if (!wantsNewline && (end > startLength ||
851                  getText(end - 1, 1).charAt(0) == NEWLINE[0])){
852         wantsNewline = true;
853         }
854         Element parent = elem.getParentElement();
855         int oldCount = parent.getElementCount();
856         insertHTML(parent, start, htmlText, wantsNewline);
857         // Remove old.
858
int newLength = getLength();
859         if (oldCount != parent.getElementCount()) {
860         int removeIndex = parent.getElementIndex(start + newLength -
861                              startLength);
862         removeElements(parent, removeIndex, 1);
863         }
864     }
865     }
866
867     /**
868      * Inserts the HTML specified as a string at the start
869      * of the element.
870      * <p>For this to work correcty, the document must have an
871      * <code>HTMLEditorKit.Parser</code> set. This will be the case
872      * if the document was created from an HTMLEditorKit via the
873      * <code>createDefaultDocument</code> method.
874      *
875      * @param elem the branch element to be the root for the new text
876      * @param htmlText the string to be parsed and assigned to <code>elem</code>
877      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
878      * been set on the document
879      * @since 1.3
880      */

881     public void insertAfterStart(Element elem, String JavaDoc htmlText) throws
882                              BadLocationException, IOException {
883     verifyParser();
884     if (elem != null && elem.isLeaf()) {
885         throw new IllegalArgumentException JavaDoc
886         ("Can not insert HTML after start of a leaf");
887     }
888     insertHTML(elem, elem.getStartOffset(), htmlText, false);
889     }
890
891     /**
892      * Inserts the HTML specified as a string at the end of
893      * the element.
894      * <p> If <code>elem</code>'s children are leaves, and the
895      * character at a <code>elem.getEndOffset() - 1</code> is a newline,
896      * this will insert before the newline so that there isn't text after
897      * the newline.
898      * <p>For this to work correcty, the document must have an
899      * <code>HTMLEditorKit.Parser</code> set. This will be the case
900      * if the document was created from an HTMLEditorKit via the
901      * <code>createDefaultDocument</code> method.
902      *
903      * @param elem the element to be the root for the new text
904      * @param htmlText the string to be parsed and assigned to <code>elem</code>
905      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
906      * been set on the document
907      * @since 1.3
908      */

909     public void insertBeforeEnd(Element elem, String JavaDoc htmlText) throws
910                             BadLocationException, IOException {
911     verifyParser();
912     if (elem != null && elem.isLeaf()) {
913         throw new IllegalArgumentException JavaDoc
914         ("Can not set inner HTML before end of leaf");
915     }
916     int offset = elem.getEndOffset();
917     if (elem.getElement(elem.getElementIndex(offset - 1)).isLeaf() &&
918         getText(offset - 1, 1).charAt(0) == NEWLINE[0]) {
919         offset--;
920     }
921     insertHTML(elem, offset, htmlText, false);
922     }
923
924     /**
925      * Inserts the HTML specified as a string before the start of
926      * the given element.
927      * <p>For this to work correcty, the document must have an
928      * <code>HTMLEditorKit.Parser</code> set. This will be the case
929      * if the document was created from an HTMLEditorKit via the
930      * <code>createDefaultDocument</code> method.
931      *
932      * @param elem the element to be the root for the new text
933      * @param htmlText the string to be parsed and assigned to <code>elem</code>
934      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
935      * been set on the document
936      * @since 1.3
937      */

938     public void insertBeforeStart(Element elem, String JavaDoc htmlText) throws
939                               BadLocationException, IOException {
940     verifyParser();
941     if (elem != null) {
942         Element parent = elem.getParentElement();
943
944         if (parent != null) {
945         insertHTML(parent, elem.getStartOffset(), htmlText, false);
946         }
947     }
948     }
949
950     /**
951      * Inserts the HTML specified as a string after the
952      * the end of the given element.
953      * <p>For this to work correcty, the document must have an
954      * <code>HTMLEditorKit.Parser</code> set. This will be the case
955      * if the document was created from an HTMLEditorKit via the
956      * <code>createDefaultDocument</code> method.
957      *
958      * @param elem the element to be the root for the new text
959      * @param htmlText the string to be parsed and assigned to <code>elem</code>
960      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
961      * been set on the document
962      * @since 1.3
963      */

964     public void insertAfterEnd(Element elem, String JavaDoc htmlText) throws
965                            BadLocationException, IOException {
966     verifyParser();
967     if (elem != null) {
968         Element parent = elem.getParentElement();
969
970         if (parent != null) {
971         int offset = elem.getEndOffset();
972                 if (offset > getLength()) {
973                     offset--;
974                 }
975         else if (elem.isLeaf() && getText(offset - 1, 1).
976             charAt(0) == NEWLINE[0]) {
977             offset--;
978         }
979         insertHTML(parent, offset, htmlText, false);
980         }
981     }
982     }
983
984     /**
985      * Returns the element that has the given id <code>Attribute</code>.
986      * If the element can't be found, <code>null</code> is returned.
987      * Note that this method works on an <code>Attribute</code>,
988      * <i>not</i> a character tag. In the following HTML snippet:
989      * <code>&lt;a id="HelloThere"&gt;</code> the attribute is
990      * 'id' and the character tag is 'a'.
991      * This is a convenience method for
992      * <code>getElement(RootElement, HTML.Attribute.id, id)</code>.
993      * This is not thread-safe.
994      *
995      * @param id the string representing the desired <code>Attribute</code>
996      * @return the element with the specified <code>Attribute</code>
997      * or <code>null</code> if it can't be found,
998      * or <code>null</code> if <code>id</code> is <code>null</code>
999      * @see javax.swing.text.html.HTML.Attribute
1000     * @since 1.3
1001     */

1002    public Element getElement(String JavaDoc id) {
1003    if (id == null) {
1004        return null;
1005    }
1006    return getElement(getDefaultRootElement(), HTML.Attribute.ID, id,
1007              true);
1008    }
1009
1010    /**
1011     * Returns the child element of <code>e</code> that contains the
1012     * attribute, <code>attribute</code> with value <code>value</code>, or
1013     * <code>null</code> if one isn't found. This is not thread-safe.
1014     *
1015     * @param e the root element where the search begins
1016     * @param attribute the desired <code>Attribute</code>
1017     * @param value the values for the specified <code>Attribute</code>
1018     * @return the element with the specified <code>Attribute</code>
1019     * and the specified <code>value</code>, or <code>null</code>
1020     * if it can't be found
1021     * @see javax.swing.text.html.HTML.Attribute
1022     * @since 1.3
1023     */

1024    public Element getElement(Element e, Object JavaDoc attribute, Object JavaDoc value) {
1025    return getElement(e, attribute, value, true);
1026    }
1027
1028    /**
1029     * Returns the child element of <code>e</code> that contains the
1030     * attribute, <code>attribute</code> with value <code>value</code>, or
1031     * <code>null</code> if one isn't found. This is not thread-safe.
1032     * <p>
1033     * If <code>searchLeafAttributes</code> is true, and <code>e</code> is
1034     * a leaf, any attributes that are instances of <code>HTML.Tag</code>
1035     * with a value that is an <code>AttributeSet</code> will also be checked.
1036     *
1037     * @param e the root element where the search begins
1038     * @param attribute the desired <code>Attribute</code>
1039     * @param value the values for the specified <code>Attribute</code>
1040     * @return the element with the specified <code>Attribute</code>
1041     * and the specified <code>value</code>, or <code>null</code>
1042     * if it can't be found
1043     * @see javax.swing.text.html.HTML.Attribute
1044     */

1045    private Element getElement(Element e, Object JavaDoc attribute, Object JavaDoc value,
1046                   boolean searchLeafAttributes) {
1047    AttributeSet attr = e.getAttributes();
1048
1049    if (attr != null && attr.isDefined(attribute)) {
1050        if (value.equals(attr.getAttribute(attribute))) {
1051        return e;
1052        }
1053    }
1054    if (!e.isLeaf()) {
1055        for (int counter = 0, maxCounter = e.getElementCount();
1056         counter < maxCounter; counter++) {
1057        Element retValue = getElement(e.getElement(counter), attribute,
1058                          value, searchLeafAttributes);
1059
1060        if (retValue != null) {
1061            return retValue;
1062        }
1063        }
1064    }
1065    else if (searchLeafAttributes && attr != null) {
1066        // For some leaf elements we store the actual attributes inside
1067
// the AttributeSet of the Element (such as anchors).
1068
Enumeration names = attr.getAttributeNames();
1069        if (names != null) {
1070        while (names.hasMoreElements()) {
1071            Object JavaDoc name = names.nextElement();
1072            if ((name instanceof HTML.Tag JavaDoc) &&
1073            (attr.getAttribute(name) instanceof AttributeSet)) {
1074
1075            AttributeSet check = (AttributeSet)attr.
1076                                 getAttribute(name);
1077            if (check.isDefined(attribute) &&
1078                value.equals(check.getAttribute(attribute))) {
1079                return e;
1080            }
1081            }
1082        }
1083        }
1084    }
1085    return null;
1086    }
1087
1088    /**
1089     * Verifies the document has an <code>HTMLEditorKit.Parser</code> set.
1090     * If <code>getParser</code> returns <code>null</code>, this will throw an
1091     * IllegalStateException.
1092     *
1093     * @throws IllegalStateException if the document does not have a Parser
1094     */

1095    private void verifyParser() {
1096    if (getParser() == null) {
1097        throw new IllegalStateException JavaDoc("No HTMLEditorKit.Parser");
1098    }
1099    }
1100
1101    /**
1102     * Installs a default Parser if one has not been installed yet.
1103     */

1104    private void installParserIfNecessary() {
1105        if (getParser() == null) {
1106            setParser(new HTMLEditorKit JavaDoc().getParser());
1107        }
1108    }
1109
1110    /**
1111     * Inserts a string of HTML into the document at the given position.
1112     * <code>parent</code> is used to identify the location to insert the
1113     * <code>html</code>. If <code>parent</code> is a leaf this can have
1114     * unexpected results.
1115     */

1116    private void insertHTML(Element parent, int offset, String JavaDoc html,
1117                boolean wantsTrailingNewline)
1118             throws BadLocationException, IOException {
1119    if (parent != null && html != null) {
1120        HTMLEditorKit.Parser JavaDoc parser = getParser();
1121        if (parser != null) {
1122        int lastOffset = Math.max(0, offset - 1);
1123        Element charElement = getCharacterElement(lastOffset);
1124        Element commonParent = parent;
1125        int pop = 0;
1126        int push = 0;
1127
1128        if (parent.getStartOffset() > lastOffset) {
1129            while (commonParent != null &&
1130               commonParent.getStartOffset() > lastOffset) {
1131            commonParent = commonParent.getParentElement();
1132            push++;
1133            }
1134            if (commonParent == null) {
1135            throw new BadLocationException("No common parent",
1136                               offset);
1137            }
1138        }
1139        while (charElement != null && charElement != commonParent) {
1140            pop++;
1141            charElement = charElement.getParentElement();
1142        }
1143        if (charElement != null) {
1144            // Found it, do the insert.
1145
HTMLReader reader = new HTMLReader(offset, pop - 1, push,
1146                               null, false, true,
1147                               wantsTrailingNewline);
1148
1149            parser.parse(new StringReader(html), reader, true);
1150            reader.flush();
1151        }
1152        }
1153    }
1154    }
1155
1156    /**
1157     * Removes child Elements of the passed in Element <code>e</code>. This
1158     * will do the necessary cleanup to ensure the element representing the
1159     * end character is correctly created.
1160     * <p>This is not a general purpose method, it assumes that <code>e</code>
1161     * will still have at least one child after the remove, and it assumes
1162     * the character at <code>e.getStartOffset() - 1</code> is a newline and
1163     * is of length 1.
1164     */

1165    private void removeElements(Element e, int index, int count) throws BadLocationException {
1166    writeLock();
1167    try {
1168        int start = e.getElement(index).getStartOffset();
1169        int end = e.getElement(index + count - 1).getEndOffset();
1170        if (end > getLength()) {
1171        removeElementsAtEnd(e, index, count, start, end);
1172        }
1173        else {
1174        removeElements(e, index, count, start, end);
1175        }
1176    } finally {
1177        writeUnlock();
1178    }
1179    }
1180
1181    /**
1182     * Called to remove child elements of <code>e</code> when one of the
1183     * elements to remove is representing the end character.
1184     * <p>Since the Content will not allow a removal to the end character
1185     * this will do a remove from <code>start - 1</code> to <code>end</code>.
1186     * The end Element(s) will be removed, and the element representing
1187     * <code>start - 1</code> to <code>start</code> will be recreated. This
1188     * Element has to be recreated as after the content removal its offsets
1189     * become <code>start - 1</code> to <code>start - 1</code>.
1190     */

1191    private void removeElementsAtEnd(Element e, int index, int count,
1192             int start, int end) throws BadLocationException {
1193    // index must be > 0 otherwise no insert would have happened.
1194
boolean isLeaf = (e.getElement(index - 1).isLeaf());
1195        DefaultDocumentEvent dde = new DefaultDocumentEvent(
1196                       start - 1, end - start + 1, DocumentEvent.
1197                       EventType.REMOVE);
1198
1199    if (isLeaf) {
1200            Element endE = getCharacterElement(getLength());
1201            // e.getElement(index - 1) should represent the newline.
1202
index--;
1203            if (endE.getParentElement() != e) {
1204                // The hiearchies don't match, we'll have to manually
1205
// recreate the leaf at e.getElement(index - 1)
1206
replace(dde, e, index, ++count, start, end, true, true);
1207            }
1208            else {
1209                // The hierarchies for the end Element and
1210
// e.getElement(index - 1), match, we can safely remove
1211
// the Elements and the end content will be aligned
1212
// appropriately.
1213
replace(dde, e, index, count, start, end, true, false);
1214            }
1215        }
1216        else {
1217        // Not a leaf, descend until we find the leaf representing
1218
// start - 1 and remove it.
1219
Element newLineE = e.getElement(index - 1);
1220        while (!newLineE.isLeaf()) {
1221        newLineE = newLineE.getElement(newLineE.getElementCount() - 1);
1222        }
1223            newLineE = newLineE.getParentElement();
1224            replace(dde, e, index, count, start, end, false, false);
1225            replace(dde, newLineE, newLineE.getElementCount() - 1, 1, start,
1226                    end, true, true);
1227        }
1228    postRemoveUpdate(dde);
1229    dde.end();
1230    fireRemoveUpdate(dde);
1231        fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
1232    }
1233
1234    /**
1235     * This is used by <code>removeElementsAtEnd</code>, it removes
1236     * <code>count</code> elements starting at <code>start</code> from
1237     * <code>e</code>. If <code>remove</code> is true text of length
1238     * <code>start - 1</code> to <code>end - 1</code> is removed. If
1239     * <code>create</code> is true a new leaf is created of length 1.
1240     */

1241    private void replace(DefaultDocumentEvent dde, Element e, int index,
1242                         int count, int start, int end, boolean remove,
1243                         boolean create) throws BadLocationException {
1244        Element[] added;
1245        AttributeSet attrs = e.getElement(index).getAttributes();
1246        Element[] removed = new Element[count];
1247
1248        for (int counter = 0; counter < count; counter++) {
1249            removed[counter] = e.getElement(counter + index);
1250        }
1251        if (remove) {
1252            UndoableEdit u = getContent().remove(start - 1, end - start);
1253            if (u != null) {
1254                dde.addEdit(u);
1255            }
1256        }
1257        if (create) {
1258            added = new Element[1];
1259            added[0] = createLeafElement(e, attrs, start - 1, start);
1260        }
1261        else {
1262            added = new Element[0];
1263        }
1264        dde.addEdit(new ElementEdit(e, index, removed, added));
1265        ((AbstractDocument.BranchElement)e).replace(
1266                                             index, removed.length, added);
1267    }
1268
1269    /**
1270     * Called to remove child Elements when the end is not touched.
1271     */

1272    private void removeElements(Element e, int index, int count,
1273                 int start, int end) throws BadLocationException {
1274    Element[] removed = new Element[count];
1275    Element[] added = new Element[0];
1276    for (int counter = 0; counter < count; counter++) {
1277        removed[counter] = e.getElement(counter + index);
1278    }
1279    DefaultDocumentEvent dde = new DefaultDocumentEvent
1280        (start, end - start, DocumentEvent.EventType.REMOVE);
1281    ((AbstractDocument.BranchElement)e).replace(index, removed.length,
1282                            added);
1283    dde.addEdit(new ElementEdit(e, index, removed, added));
1284    UndoableEdit u = getContent().remove(start, end - start);
1285    if (u != null) {
1286        dde.addEdit(u);
1287    }
1288    postRemoveUpdate(dde);
1289    dde.end();
1290    fireRemoveUpdate(dde);
1291    if (u != null) {
1292        fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
1293    }
1294    }
1295
1296
1297    // These two are provided for inner class access. The are named different
1298
// than the super class as the super class implementations are final.
1299
void obtainLock() {
1300    writeLock();
1301    }
1302
1303    void releaseLock() {
1304    writeUnlock();
1305    }
1306
1307    //
1308
// Provided for inner class access.
1309
//
1310

1311    /**
1312     * Notifies all listeners that have registered interest for
1313     * notification on this event type. The event instance
1314     * is lazily created using the parameters passed into
1315     * the fire method.
1316     *
1317     * @param e the event
1318     * @see EventListenerList
1319     */

1320    protected void fireChangedUpdate(DocumentEvent e) {
1321    super.fireChangedUpdate(e);
1322    }
1323
1324    /**
1325     * Notifies all listeners that have registered interest for
1326     * notification on this event type. The event instance
1327     * is lazily created using the parameters passed into
1328     * the fire method.
1329     *
1330     * @param e the event
1331     * @see EventListenerList
1332     */

1333    protected void fireUndoableEditUpdate(UndoableEditEvent e) {
1334    super.fireUndoableEditUpdate(e);
1335    }
1336
1337    boolean hasBaseTag() {
1338    return hasBaseTag;
1339    }
1340
1341    String JavaDoc getBaseTarget() {
1342    return baseTarget;
1343    }
1344
1345    /*
1346     * state defines whether the document is a frame document
1347     * or not.
1348     */

1349    private boolean frameDocument = false;
1350    private boolean preservesUnknownTags = true;
1351
1352    /*
1353     * Used to store button groups for radio buttons in
1354     * a form.
1355     */

1356    private HashMap radioButtonGroupsMap;
1357
1358    /**
1359     * Document property for the number of tokens to buffer
1360     * before building an element subtree to represent them.
1361     */

1362    static final String JavaDoc TokenThreshold = "token threshold";
1363
1364    private static final int MaxThreshold = 10000;
1365    
1366    private static final int StepThreshold = 5;
1367
1368
1369    /**
1370     * Document property key value. The value for the key will be a Vector
1371     * of Strings that are comments not found in the body.
1372     */

1373    public static final String JavaDoc AdditionalComments = "AdditionalComments";
1374
1375    /**
1376     * Document property key value. The value for the key will be a
1377     * String indicating the default type of stylesheet links.
1378     */

1379    /* public */ static final String JavaDoc StyleType = "StyleType";
1380
1381    /**
1382     * The location to resolve relative URLs against. By
1383     * default this will be the document's URL if the document
1384     * was loaded from a URL. If a base tag is found and
1385     * can be parsed, it will be used as the base location.
1386     */

1387    URL JavaDoc base;
1388
1389    /**
1390     * does the document have base tag
1391     */

1392    boolean hasBaseTag = false;
1393
1394    /**
1395     * BASE tag's TARGET attribute value
1396     */

1397    private String JavaDoc baseTarget = null;
1398
1399    /**
1400     * The parser that is used when inserting html into the existing
1401     * document.
1402     */

1403    private HTMLEditorKit.Parser JavaDoc parser;
1404
1405    /**
1406     * Used for inserts when a null AttributeSet is supplied.
1407     */

1408    private static AttributeSet contentAttributeSet;
1409
1410    /**
1411     * Property Maps are registered under, will be a Hashtable.
1412     */

1413    static String JavaDoc MAP_PROPERTY = "__MAP__";
1414
1415    private static char[] NEWLINE;
1416    private static final String JavaDoc IMPLIED_CR = "CR";
1417
1418    /**
1419     * I18N property key.
1420     *
1421     * @see AbstractDocument.I18NProperty
1422     */

1423    private static final String JavaDoc I18NProperty = "i18n";
1424
1425    private static final boolean isComplex(char ch) {
1426        return (ch >= '\u0900' && ch <= '\u0D7F') || // Indic
1427
(ch >= '\u0E00' && ch <= '\u0E7F'); // Thai
1428
}
1429
1430    private static final boolean isComplex(char[] text, int start, int limit) {
1431    for (int i = start; i < limit; ++i) {
1432        if (isComplex(text[i])) {
1433        return true;
1434        }
1435    }
1436    return false;
1437    }
1438
1439    static {
1440    contentAttributeSet = new SimpleAttributeSet();
1441    ((MutableAttributeSet)contentAttributeSet).
1442                    addAttribute(StyleConstants.NameAttribute,
1443                     HTML.Tag.CONTENT);
1444    NEWLINE = new char[1];
1445    NEWLINE[0] = '\n';
1446    }
1447
1448
1449    /**
1450     * An iterator to iterate over a particular type of
1451     * tag. The iterator is not thread safe. If reliable
1452     * access to the document is not already ensured by
1453     * the context under which the iterator is being used,
1454     * its use should be performed under the protection of
1455     * Document.render.
1456     */

1457    public static abstract class Iterator {
1458
1459    /**
1460     * Return the attributes for this tag.
1461         * @return the <code>AttributeSet</code> for this tag, or
1462         * <code>null</code> if none can be found
1463     */

1464    public abstract AttributeSet getAttributes();
1465
1466    /**
1467     * Returns the start of the range for which the current occurrence of
1468     * the tag is defined and has the same attributes.
1469         *
1470         * @return the start of the range, or -1 if it can't be found
1471     */

1472    public abstract int getStartOffset();
1473    
1474    /**
1475     * Returns the end of the range for which the current occurrence of
1476     * the tag is defined and has the same attributes.
1477         *
1478         * @return the end of the range
1479     */

1480    public abstract int getEndOffset();
1481
1482    /**
1483     * Move the iterator forward to the next occurrence
1484     * of the tag it represents.
1485     */

1486    public abstract void next();
1487
1488    /**
1489     * Indicates if the iterator is currently
1490     * representing an occurrence of a tag. If
1491     * false there are no more tags for this iterator.
1492         * @return true if the iterator is currently representing an
1493         * occurrence of a tag, otherwise returns false
1494     */

1495    public abstract boolean isValid();
1496
1497    /**
1498     * Type of tag this iterator represents.
1499     */

1500    public abstract HTML.Tag JavaDoc getTag();
1501    }
1502
1503    /**
1504     * An iterator to iterate over a particular type of tag.
1505     */

1506    static class LeafIterator extends Iterator {
1507
1508    LeafIterator(HTML.Tag JavaDoc t, Document doc) {
1509        tag = t;
1510        pos = new ElementIterator(doc);
1511        endOffset = 0;
1512        next();
1513    }
1514
1515    /**
1516     * Returns the attributes for this tag.
1517         * @return the <code>AttributeSet</code> for this tag,
1518         * or <code>null</code> if none can be found
1519     */

1520    public AttributeSet getAttributes() {
1521        Element elem = pos.current();
1522        if (elem != null) {
1523        AttributeSet a = (AttributeSet)
1524            elem.getAttributes().getAttribute(tag);
1525        return a;
1526        }
1527        return null;
1528    }
1529
1530    /**
1531     * Returns the start of the range for which the current occurrence of
1532     * the tag is defined and has the same attributes.
1533         *
1534         * @return the start of the range, or -1 if it can't be found
1535     */

1536    public int getStartOffset() {
1537        Element elem = pos.current();
1538        if (elem != null) {
1539        return elem.getStartOffset();
1540        }
1541        return -1;
1542    }
1543    
1544    /**
1545     * Returns the end of the range for which the current occurrence of
1546     * the tag is defined and has the same attributes.
1547         *
1548         * @return the end of the range
1549     */

1550    public int getEndOffset() {
1551        return endOffset;
1552    }
1553
1554    /**
1555     * Moves the iterator forward to the next occurrence
1556     * of the tag it represents.
1557     */

1558    public void next() {
1559        for (nextLeaf(pos); isValid(); nextLeaf(pos)) {
1560        Element elem = pos.current();
1561        if (elem.getStartOffset() >= endOffset) {
1562            AttributeSet a = pos.current().getAttributes();
1563
1564            if (a.isDefined(tag) ||
1565                        a.getAttribute(StyleConstants.NameAttribute) == tag) {
1566
1567            // we found the next one
1568
setEndOffset();
1569            break;
1570            }
1571        }
1572        }
1573    }
1574
1575    /**
1576     * Returns the type of tag this iterator represents.
1577         *
1578         * @return the <code>HTML.Tag</code> that this iterator represents.
1579         * @see javax.swing.text.html.HTML.Tag
1580     */

1581        public HTML.Tag JavaDoc getTag() {
1582        return tag;
1583    }
1584
1585        /**
1586         * Returns true if the current position is not <code>null</code>.
1587         * @return true if current position is not <code>null</code>,
1588         * otherwise returns false
1589         */

1590    public boolean isValid() {
1591        return (pos.current() != null);
1592    }
1593
1594    /**
1595     * Moves the given iterator to the next leaf element.
1596         * @param iter the iterator to be scanned
1597     */

1598    void nextLeaf(ElementIterator iter) {
1599        for (iter.next(); iter.current() != null; iter.next()) {
1600        Element e = iter.current();
1601        if (e.isLeaf()) {
1602            break;
1603        }
1604        }
1605    }
1606
1607    /**
1608     * Marches a cloned iterator forward to locate the end
1609     * of the run. This sets the value of <code>endOffset</code>.
1610     */

1611    void setEndOffset() {
1612        AttributeSet a0 = getAttributes();
1613        endOffset = pos.current().getEndOffset();
1614        ElementIterator fwd = (ElementIterator) pos.clone();
1615        for (nextLeaf(fwd); fwd.current() != null; nextLeaf(fwd)) {
1616        Element e = fwd.current();
1617        AttributeSet a1 = (AttributeSet) e.getAttributes().getAttribute(tag);
1618        if ((a1 == null) || (! a1.equals(a0))) {
1619            break;
1620        }
1621        endOffset = e.getEndOffset();
1622        }
1623    }
1624
1625    private int endOffset;
1626    private HTML.Tag JavaDoc tag;
1627    private ElementIterator pos;
1628
1629    }
1630
1631    /**
1632     * An HTML reader to load an HTML document with an HTML
1633     * element structure. This is a set of callbacks from
1634     * the parser, implemented to create a set of elements
1635     * tagged with attributes. The parse builds up tokens
1636     * (ElementSpec) that describe the element subtree desired,
1637     * and burst it into the document under the protection of
1638     * a write lock using the insert method on the document
1639     * outer class.
1640     * <p>
1641     * The reader can be configured by registering actions
1642     * (of type <code>HTMLDocument.HTMLReader.TagAction</code>)
1643     * that describe how to handle the action. The idea behind
1644     * the actions provided is that the most natural text editing
1645     * operations can be provided if the element structure boils
1646     * down to paragraphs with runs of some kind of style
1647     * in them. Some things are more naturally specified
1648     * structurally, so arbitrary structure should be allowed
1649     * above the paragraphs, but will need to be edited with structural
1650     * actions. The implication of this is that some of the
1651     * HTML elements specified in the stream being parsed will
1652     * be collapsed into attributes, and in some cases paragraphs
1653     * will be synthesized. When HTML elements have been
1654     * converted to attributes, the attribute key will be of
1655     * type HTML.Tag, and the value will be of type AttributeSet
1656     * so that no information is lost. This enables many of the
1657     * existing actions to work so that the user can type input,
1658     * hit the return key, backspace, delete, etc and have a
1659     * reasonable result. Selections can be created, and attributes
1660     * applied or removed, etc. With this in mind, the work done
1661     * by the reader can be categorized into the following kinds
1662     * of tasks:
1663     * <dl>
1664     * <dt>Block
1665     * <dd>Build the structure like it's specified in the stream.
1666     * This produces elements that contain other elements.
1667     * <dt>Paragraph
1668     * <dd>Like block except that it's expected that the element
1669     * will be used with a paragraph view so a paragraph element
1670     * won't need to be synthesized.
1671     * <dt>Character
1672     * <dd>Contribute the element as an attribute that will start
1673     * and stop at arbitrary text locations. This will ultimately
1674     * be mixed into a run of text, with all of the currently
1675     * flattened HTML character elements.
1676     * <dt>Special
1677     * <dd>Produce an embedded graphical element.
1678     * <dt>Form
1679     * <dd>Produce an element that is like the embedded graphical
1680     * element, except that it also has a component model associated
1681     * with it.
1682     * <dt>Hidden
1683     * <dd>Create an element that is hidden from view when the
1684     * document is being viewed read-only, and visible when the
1685     * document is being edited. This is useful to keep the
1686     * model from losing information, and used to store things
1687     * like comments and unrecognized tags.
1688     *
1689     * </dl>
1690     * <p>
1691     * Currently, &lt;APPLET&gt;, &lt;PARAM&gt;, &lt;MAP&gt;, &lt;AREA&gt;, &lt;LINK&gt;,
1692     * &lt;SCRIPT&gt; and &lt;STYLE&gt; are unsupported.
1693     *
1694     * <p>
1695     * The assignment of the actions described is shown in the
1696     * following table for the tags defined in <code>HTML.Tag</code>.<P>
1697     * <table border=1 summary="HTML tags and assigned actions">
1698     * <tr><th>Tag</th><th>Action</th></tr>
1699     * <tr><td><code>HTML.Tag.A</code> <td>CharacterAction
1700     * <tr><td><code>HTML.Tag.ADDRESS</code> <td>CharacterAction
1701     * <tr><td><code>HTML.Tag.APPLET</code> <td>HiddenAction
1702     * <tr><td><code>HTML.Tag.AREA</code> <td>AreaAction
1703     * <tr><td><code>HTML.Tag.B</code> <td>CharacterAction
1704     * <tr><td><code>HTML.Tag.BASE</code> <td>BaseAction
1705     * <tr><td><code>HTML.Tag.BASEFONT</code> <td>CharacterAction
1706     * <tr><td><code>HTML.Tag.BIG</code> <td>CharacterAction
1707     * <tr><td><code>HTML.Tag.BLOCKQUOTE</code><td>BlockAction
1708     * <tr><td><code>HTML.Tag.BODY</code> <td>BlockAction
1709     * <tr><td><code>HTML.Tag.BR</code> <td>SpecialAction
1710     * <tr><td><code>HTML.Tag.CAPTION</code> <td>BlockAction
1711     * <tr><td><code>HTML.Tag.CENTER</code> <td>BlockAction
1712     * <tr><td><code>HTML.Tag.CITE</code> <td>CharacterAction
1713     * <tr><td><code>HTML.Tag.CODE</code> <td>CharacterAction
1714     * <tr><td><code>HTML.Tag.DD</code> <td>BlockAction
1715     * <tr><td><code>HTML.Tag.DFN</code> <td>CharacterAction
1716     * <tr><td><code>HTML.Tag.DIR</code> <td>BlockAction
1717     * <tr><td><code>HTML.Tag.DIV</code> <td>BlockAction
1718     * <tr><td><code>HTML.Tag.DL</code> <td>BlockAction
1719     * <tr><td><code>HTML.Tag.DT</code> <td>ParagraphAction
1720     * <tr><td><code>HTML.Tag.EM</code> <td>CharacterAction
1721     * <tr><td><code>HTML.Tag.FONT</code> <td>CharacterAction
1722     * <tr><td><code>HTML.Tag.FORM</code> <td>As of 1.4 a BlockAction
1723     * <tr><td><code>HTML.Tag.FRAME</code> <td>SpecialAction
1724     * <tr><td><code>HTML.Tag.FRAMESET</code> <td>BlockAction
1725     * <tr><td><code>HTML.Tag.H1</code> <td>ParagraphAction
1726     * <tr><td><code>HTML.Tag.H2</code> <td>ParagraphAction
1727     * <tr><td><code>HTML.Tag.H3</code> <td>ParagraphAction
1728     * <tr><td><code>HTML.Tag.H4</code> <td>ParagraphAction
1729     * <tr><td><code>HTML.Tag.H5</code> <td>ParagraphAction
1730     * <tr><td><code>HTML.Tag.H6</code> <td>ParagraphAction
1731     * <tr><td><code>HTML.Tag.HEAD</code> <td>HeadAction
1732     * <tr><td><code>HTML.Tag.HR</code> <td>SpecialAction
1733     * <tr><td><code>HTML.Tag.HTML</code> <td>BlockAction
1734     * <tr><td><code>HTML.Tag.I</code> <td>CharacterAction
1735     * <tr><td><code>HTML.Tag.IMG</code> <td>SpecialAction
1736     * <tr><td><code>HTML.Tag.INPUT</code> <td>FormAction
1737     * <tr><td><code>HTML.Tag.ISINDEX</code> <td>IsndexAction
1738     * <tr><td><code>HTML.Tag.KBD</code> <td>CharacterAction
1739     * <tr><td><code>HTML.Tag.LI</code> <td>BlockAction
1740     * <tr><td><code>HTML.Tag.LINK</code> <td>LinkAction
1741     * <tr><td><code>HTML.Tag.MAP</code> <td>MapAction
1742     * <tr><td><code>HTML.Tag.MENU</code> <td>BlockAction
1743     * <tr><td><code>HTML.Tag.META</code> <td>MetaAction
1744     * <tr><td><code>HTML.Tag.NOFRAMES</code> <td>BlockAction
1745     * <tr><td><code>HTML.Tag.OBJECT</code> <td>SpecialAction
1746     * <tr><td><code>HTML.Tag.OL</code> <td>BlockAction
1747     * <tr><td><code>HTML.Tag.OPTION</code> <td>FormAction
1748     * <tr><td><code>HTML.Tag.P</code> <td>ParagraphAction
1749     * <tr><td><code>HTML.Tag.PARAM</code> <td>HiddenAction
1750     * <tr><td><code>HTML.Tag.PRE</code> <td>PreAction
1751     * <tr><td><code>HTML.Tag.SAMP</code> <td>CharacterAction
1752     * <tr><td><code>HTML.Tag.SCRIPT</code> <td>HiddenAction
1753     * <tr><td><code>HTML.Tag.SELECT</code> <td>FormAction
1754     * <tr><td><code>HTML.Tag.SMALL</code> <td>CharacterAction
1755     * <tr><td><code>HTML.Tag.STRIKE</code> <td>CharacterAction
1756     * <tr><td><code>HTML.Tag.S</code> <td>CharacterAction
1757     * <tr><td><code>HTML.Tag.STRONG</code> <td>CharacterAction
1758     * <tr><td><code>HTML.Tag.STYLE</code> <td>StyleAction
1759     * <tr><td><code>HTML.Tag.SUB</code> <td>CharacterAction
1760     * <tr><td><code>HTML.Tag.SUP</code> <td>CharacterAction
1761     * <tr><td><code>HTML.Tag.TABLE</code> <td>BlockAction
1762     * <tr><td><code>HTML.Tag.TD</code> <td>BlockAction
1763     * <tr><td><code>HTML.Tag.TEXTAREA</code> <td>FormAction
1764     * <tr><td><code>HTML.Tag.TH</code> <td>BlockAction
1765     * <tr><td><code>HTML.Tag.TITLE</code> <td>TitleAction
1766     * <tr><td><code>HTML.Tag.TR</code> <td>BlockAction
1767     * <tr><td><code>HTML.Tag.TT</code> <td>CharacterAction
1768     * <tr><td><code>HTML.Tag.U</code> <td>CharacterAction
1769     * <tr><td><code>HTML.Tag.UL</code> <td>BlockAction
1770     * <tr><td><code>HTML.Tag.VAR</code> <td>CharacterAction
1771     * </table>
1772     * <p>
1773     * Once &lt;/html> is encountered, the Actions are no longer notified.
1774     */

1775    public class HTMLReader extends HTMLEditorKit.ParserCallback JavaDoc {
1776
1777    public HTMLReader(int offset) {
1778        this(offset, 0, 0, null);
1779    }
1780
1781        public HTMLReader(int offset, int popDepth, int pushDepth,
1782              HTML.Tag JavaDoc insertTag) {
1783        this(offset, popDepth, pushDepth, insertTag, true, false, true);
1784    }
1785
1786    /**
1787     * Generates a RuntimeException (will eventually generate
1788     * a BadLocationException when API changes are alloced) if inserting
1789     * into non empty document, <code>insertTag</code> is
1790         * non-<code>null</code>, and <code>offset</code> is not in the body.
1791     */

1792    // PENDING(sky): Add throws BadLocationException and remove
1793
// RuntimeException
1794
HTMLReader(int offset, int popDepth, int pushDepth,
1795           HTML.Tag JavaDoc insertTag, boolean insertInsertTag,
1796           boolean insertAfterImplied, boolean wantsTrailingNewline) {
1797        emptyDocument = (getLength() == 0);
1798        isStyleCSS = "text/css".equals(getDefaultStyleSheetType());
1799        this.offset = offset;
1800        threshold = HTMLDocument.this.getTokenThreshold();
1801        tagMap = new Hashtable(57);
1802        TagAction na = new TagAction();
1803        TagAction ba = new BlockAction();
1804        TagAction pa = new ParagraphAction();
1805        TagAction ca = new CharacterAction();
1806        TagAction sa = new SpecialAction();
1807        TagAction fa = new FormAction();
1808        TagAction ha = new HiddenAction();
1809        TagAction conv = new ConvertAction();
1810
1811        // register handlers for the well known tags
1812
tagMap.put(HTML.Tag.A, new AnchorAction());
1813        tagMap.put(HTML.Tag.ADDRESS, ca);
1814        tagMap.put(HTML.Tag.APPLET, ha);
1815        tagMap.put(HTML.Tag.AREA, new AreaAction());
1816        tagMap.put(HTML.Tag.B, conv);
1817        tagMap.put(HTML.Tag.BASE, new BaseAction());
1818        tagMap.put(HTML.Tag.BASEFONT, ca);
1819        tagMap.put(HTML.Tag.BIG, ca);
1820        tagMap.put(HTML.Tag.BLOCKQUOTE, ba);
1821        tagMap.put(HTML.Tag.BODY, ba);
1822        tagMap.put(HTML.Tag.BR, sa);
1823        tagMap.put(HTML.Tag.CAPTION, ba);
1824        tagMap.put(HTML.Tag.CENTER, ba);
1825        tagMap.put(HTML.Tag.CITE, ca);
1826        tagMap.put(HTML.Tag.CODE, ca);
1827        tagMap.put(HTML.Tag.DD, ba);
1828        tagMap.put(HTML.Tag.DFN, ca);
1829        tagMap.put(HTML.Tag.DIR, ba);
1830        tagMap.put(HTML.Tag.DIV, ba);
1831        tagMap.put(HTML.Tag.DL, ba);
1832        tagMap.put(HTML.Tag.DT, pa);
1833        tagMap.put(HTML.Tag.EM, ca);
1834        tagMap.put(HTML.Tag.FONT, conv);
1835        tagMap.put(HTML.Tag.FORM, new FormTagAction());
1836        tagMap.put(HTML.Tag.FRAME, sa);
1837        tagMap.put(HTML.Tag.FRAMESET, ba);
1838        tagMap.put(HTML.Tag.H1, pa);
1839        tagMap.put(HTML.Tag.H2, pa);
1840        tagMap.put(HTML.Tag.H3, pa);
1841        tagMap.put(HTML.Tag.H4, pa);
1842        tagMap.put(HTML.Tag.H5, pa);
1843        tagMap.put(HTML.Tag.H6, pa);
1844        tagMap.put(HTML.Tag.HEAD, new HeadAction());
1845        tagMap.put(HTML.Tag.HR, sa);
1846        tagMap.put(HTML.Tag.HTML, ba);
1847        tagMap.put(HTML.Tag.I, conv);
1848        tagMap.put(HTML.Tag.IMG, sa);
1849        tagMap.put(HTML.Tag.INPUT, fa);
1850        tagMap.put(HTML.Tag.ISINDEX, new IsindexAction());
1851        tagMap.put(HTML.Tag.KBD, ca);
1852        tagMap.put(HTML.Tag.LI, ba);
1853        tagMap.put(HTML.Tag.LINK, new LinkAction());
1854        tagMap.put(HTML.Tag.MAP, new MapAction());
1855        tagMap.put(HTML.Tag.MENU, ba);
1856        tagMap.put(HTML.Tag.META, new MetaAction());
1857        tagMap.put(HTML.Tag.NOBR, ca);
1858        tagMap.put(HTML.Tag.NOFRAMES, ba);
1859        tagMap.put(HTML.Tag.OBJECT, sa);
1860        tagMap.put(HTML.Tag.OL, ba);
1861        tagMap.put(HTML.Tag.OPTION, fa);
1862        tagMap.put(HTML.Tag.P, pa);
1863        tagMap.put(HTML.Tag.PARAM, new ObjectAction());
1864        tagMap.put(HTML.Tag.PRE, new PreAction());
1865        tagMap.put(HTML.Tag.SAMP, ca);
1866        tagMap.put(HTML.Tag.SCRIPT, ha);
1867        tagMap.put(HTML.Tag.SELECT, fa);
1868        tagMap.put(HTML.Tag.SMALL, ca);
1869            tagMap.put(HTML.Tag.SPAN, ca);
1870        tagMap.put(HTML.Tag.STRIKE, conv);
1871        tagMap.put(HTML.Tag.S, ca);
1872        tagMap.put(HTML.Tag.STRONG, ca);
1873        tagMap.put(HTML.Tag.STYLE, new StyleAction());
1874        tagMap.put(HTML.Tag.SUB, conv);
1875        tagMap.put(HTML.Tag.SUP, conv);
1876        tagMap.put(HTML.Tag.TABLE, ba);
1877        tagMap.put(HTML.Tag.TD, ba);
1878        tagMap.put(HTML.Tag.TEXTAREA, fa);
1879        tagMap.put(HTML.Tag.TH, ba);
1880        tagMap.put(HTML.Tag.TITLE, new TitleAction());
1881        tagMap.put(HTML.Tag.TR, ba);
1882        tagMap.put(HTML.Tag.TT, ca);
1883        tagMap.put(HTML.Tag.U, conv);
1884        tagMap.put(HTML.Tag.UL, ba);
1885        tagMap.put(HTML.Tag.VAR, ca);
1886
1887        if (insertTag != null) {
1888        this.insertTag = insertTag;
1889        this.popDepth = popDepth;
1890        this.pushDepth = pushDepth;
1891        this.insertInsertTag = insertInsertTag;
1892        foundInsertTag = false;
1893        }
1894        else {
1895        foundInsertTag = true;
1896        }
1897        if (insertAfterImplied) {
1898        this.popDepth = popDepth;
1899        this.pushDepth = pushDepth;
1900        this.insertAfterImplied = true;
1901        foundInsertTag = false;
1902        midInsert = false;
1903        this.insertInsertTag = true;
1904        this.wantsTrailingNewline = wantsTrailingNewline;
1905        }
1906        else {
1907        midInsert = (!emptyDocument && insertTag == null);
1908        if (midInsert) {
1909            generateEndsSpecsForMidInsert();
1910        }
1911        }
1912    }
1913
1914    /**
1915     * Generates an initial batch of end <code>ElementSpecs</code>
1916         * in parseBuffer to position future inserts into the body.
1917     */

1918    private void generateEndsSpecsForMidInsert() {
1919        int count = heightToElementWithName(HTML.Tag.BODY,
1920                           Math.max(0, offset - 1));
1921        boolean joinNext = false;
1922
1923        if (count == -1 && offset > 0) {
1924        count = heightToElementWithName(HTML.Tag.BODY, offset);
1925        if (count != -1) {
1926            // Previous isn't in body, but current is. Have to
1927
// do some end specs, followed by join next.
1928
count = depthTo(offset - 1) - 1;
1929            joinNext = true;
1930        }
1931        }
1932        if (count == -1) {
1933        throw new RuntimeException JavaDoc("Must insert new content into body element-");
1934        }
1935        if (count != -1) {
1936        // Insert a newline, if necessary.
1937
try {
1938            if (!joinNext && offset > 0 &&
1939            !getText(offset - 1, 1).equals("\n")) {
1940            SimpleAttributeSet newAttrs = new SimpleAttributeSet();
1941            newAttrs.addAttribute(StyleConstants.NameAttribute,
1942                          HTML.Tag.CONTENT);
1943            ElementSpec spec = new ElementSpec(newAttrs,
1944                    ElementSpec.ContentType, NEWLINE, 0, 1);
1945            parseBuffer.addElement(spec);
1946            }
1947            // Should never throw, but will catch anyway.
1948
} catch (BadLocationException ble) {}
1949        while (count-- > 0) {
1950            parseBuffer.addElement(new ElementSpec
1951                       (null, ElementSpec.EndTagType));
1952        }
1953        if (joinNext) {
1954            ElementSpec spec = new ElementSpec(null, ElementSpec.
1955                               StartTagType);
1956
1957            spec.setDirection(ElementSpec.JoinNextDirection);
1958            parseBuffer.addElement(spec);
1959        }
1960        }
1961        // We should probably throw an exception if (count == -1)
1962
// Or look for the body and reset the offset.
1963
}
1964
1965    /**
1966     * @return number of parents to reach the child at offset.
1967     */

1968    private int depthTo(int offset) {
1969        Element e = getDefaultRootElement();
1970        int count = 0;
1971
1972        while (!e.isLeaf()) {
1973        count++;
1974        e = e.getElement(e.getElementIndex(offset));
1975        }
1976        return count;
1977    }
1978
1979    /**
1980     * @return number of parents of the leaf at <code>offset</code>
1981     * until a parent with name, <code>name</code> has been
1982     * found. -1 indicates no matching parent with
1983     * <code>name</code>.
1984     */

1985    private int heightToElementWithName(Object JavaDoc name, int offset) {
1986        Element e = getCharacterElement(offset).getParentElement();
1987        int count = 0;
1988
1989        while (e != null && e.getAttributes().getAttribute
1990           (StyleConstants.NameAttribute) != name) {
1991        count++;
1992        e = e.getParentElement();
1993        }
1994        return (e == null) ? -1 : count;
1995    }
1996
1997    /**
1998     * This will make sure there aren't two BODYs (the second is
1999         * typically created when you do a remove all, and then an insert).
2000     */

2001    private void adjustEndElement() {
2002        int length = getLength();
2003        if (length == 0) {
2004        return;
2005        }
2006        obtainLock();
2007        try {
2008        Element[] pPath = getPathTo(length - 1);
2009                int pLength = pPath.length;
2010        if (pLength > 1 && pPath[1].getAttributes().getAttribute
2011                 (StyleConstants.NameAttribute) == HTML.Tag.BODY &&
2012                 pPath[1].getEndOffset() == length) {
2013            String JavaDoc lastText = getText(length - 1, 1);
2014            DefaultDocumentEvent event = null;
2015            Element[] added;
2016            Element[] removed;
2017            int index;
2018            // Remove the fake second body.
2019
added = new Element[0];
2020            removed = new Element[1];
2021            index = pPath[0].getElementIndex(length);
2022            removed[0] = pPath[0].getElement(index);
2023            ((BranchElement)pPath[0]).replace(index, 1, added);
2024            ElementEdit firstEdit = new ElementEdit(pPath[0], index,
2025                                removed, added);
2026
2027                    // Insert a new element to represent the end that the
2028
// second body was representing.
2029
SimpleAttributeSet sas = new SimpleAttributeSet();
2030                    sas.addAttribute(StyleConstants.NameAttribute,
2031                                         HTML.Tag.CONTENT);
2032            sas.addAttribute(IMPLIED_CR, Boolean.TRUE);
2033                    added = new Element[1];
2034                    added[0] = createLeafElement(pPath[pLength - 1],
2035                                                     sas, length, length + 1);
2036                    index = pPath[pLength - 1].getElementCount();
2037                    ((BranchElement)pPath[pLength - 1]).replace(index, 0,
2038                                                                added);
2039                    event = new DefaultDocumentEvent(length, 1,
2040                        DocumentEvent.EventType.CHANGE);
2041                    event.addEdit(new ElementEdit(pPath[pLength - 1],
2042                                         index, new Element[0], added));
2043                    event.addEdit(firstEdit);
2044                    event.end();
2045                    fireChangedUpdate(event);
2046            fireUndoableEditUpdate(new UndoableEditEvent(this, event));
2047
2048            if (lastText.equals("\n")) {
2049                        // We now have two \n's, one part of the Document.
2050
// We need to remove one
2051
event = new DefaultDocumentEvent(length - 1, 1,
2052                                           DocumentEvent.EventType.REMOVE);
2053                        removeUpdate(event);
2054                        UndoableEdit u = getContent().remove(length - 1, 1);
2055                        if (u != null) {
2056                            event.addEdit(u);
2057                        }
2058                        postRemoveUpdate(event);
2059                        // Mark the edit as done.
2060
event.end();
2061                        fireRemoveUpdate(event);
2062                        fireUndoableEditUpdate(new UndoableEditEvent(
2063                                               this, event));
2064                    }
2065        }
2066        }
2067        catch (BadLocationException ble) {
2068        }
2069        finally {
2070        releaseLock();
2071        }
2072    }
2073
2074    private Element[] getPathTo(int offset) {
2075        Stack elements = new Stack();
2076        Element e = getDefaultRootElement();
2077        int index;
2078        while (!e.isLeaf()) {
2079        elements.push(e);
2080        e = e.getElement(e.getElementIndex(offset));
2081        }
2082        Element[] retValue = new Element[elements.size()];
2083        elements.copyInto(retValue);
2084        return retValue;
2085    }
2086        
2087    // -- HTMLEditorKit.ParserCallback methods --------------------
2088

2089    /**
2090     * The last method called on the reader. It allows
2091     * any pending changes to be flushed into the document.
2092     * Since this is currently loading synchronously, the entire
2093     * set of changes are pushed in at this point.
2094     */

2095        public void flush() throws BadLocationException {
2096        if (emptyDocument && !insertAfterImplied) {
2097                if (HTMLDocument.this.getLength() > 0 ||
2098                                      parseBuffer.size() > 0) {
2099                    flushBuffer(true);
2100                    adjustEndElement();
2101                }
2102                // We won't insert when
2103
}
2104            else {
2105                flushBuffer(true);
2106            }
2107    }
2108
2109    /**
2110     * Called by the parser to indicate a block of text was
2111     * encountered.
2112     */

2113        public void handleText(char[] data, int pos) {
2114        if (receivedEndHTML || (midInsert && !inBody)) {
2115        return;
2116        }
2117
2118        // see if complex glyph layout support is needed
2119
if(HTMLDocument.this.getProperty(I18NProperty).equals( Boolean.FALSE ) ) {
2120        // if a default direction of right-to-left has been specified,
2121
// we want complex layout even if the text is all left to right.
2122
Object JavaDoc d = getProperty(TextAttribute.RUN_DIRECTION);
2123        if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
2124            HTMLDocument.this.putProperty( I18NProperty, Boolean.TRUE);
2125        } else {
2126            if (Bidi.requiresBidi(data, 0, data.length) ||
2127            isComplex(data, 0, data.length)) {
2128            //
2129
HTMLDocument.this.putProperty( I18NProperty, Boolean.TRUE);
2130            }
2131        }
2132        }
2133
2134        if (inTextArea) {
2135            textAreaContent(data);
2136        } else if (inPre) {
2137        preContent(data);
2138        } else if (inTitle) {
2139        putProperty(Document.TitleProperty, new String JavaDoc(data));
2140        } else if (option != null) {
2141        option.setLabel(new String JavaDoc(data));
2142        } else if (inStyle) {
2143        if (styles != null) {
2144            styles.addElement(new String JavaDoc(data));
2145        }
2146        } else if (inBlock > 0) {
2147        if (!foundInsertTag && insertAfterImplied) {
2148            // Assume content should be added.
2149
foundInsertTag(false);
2150            foundInsertTag = true;
2151            inParagraph = impliedP = true;
2152        }
2153        if (data.length >= 1) {
2154            addContent(data, 0, data.length);
2155        }
2156        }
2157    }
2158
2159    /**
2160     * Callback from the parser. Route to the appropriate
2161     * handler for the tag.
2162     */

2163    public void handleStartTag(HTML.Tag JavaDoc t, MutableAttributeSet a, int pos) {
2164            if (receivedEndHTML) {
2165                return;
2166            }
2167        if (midInsert && !inBody) {
2168        if (t == HTML.Tag.BODY) {
2169            inBody = true;
2170            // Increment inBlock since we know we are in the body,
2171
// this is needed incase an implied-p is needed. If
2172
// inBlock isn't incremented, and an implied-p is
2173
// encountered, addContent won't be called!
2174
inBlock++;
2175        }
2176        return;
2177        }
2178        if (!inBody && t == HTML.Tag.BODY) {
2179        inBody = true;
2180        }
2181        if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
2182        // Map the style attributes.
2183
String JavaDoc decl = (String JavaDoc)a.getAttribute(HTML.Attribute.STYLE);
2184        a.removeAttribute(HTML.Attribute.STYLE);
2185        styleAttributes = getStyleSheet().getDeclaration(decl);
2186        a.addAttributes(styleAttributes);
2187        }
2188        else {
2189        styleAttributes = null;
2190        }
2191        TagAction action = (TagAction) tagMap.get(t);
2192
2193        if (action != null) {
2194        action.start(t, a);
2195        }
2196    }
2197
2198        public void handleComment(char[] data, int pos) {
2199            if (receivedEndHTML) {
2200                addExternalComment(new String JavaDoc(data));
2201                return;
2202            }
2203        if (inStyle) {
2204        if (styles != null) {
2205            styles.addElement(new String JavaDoc(data));
2206        }
2207        }
2208        else if (getPreservesUnknownTags()) {
2209        if (inBlock == 0 && (foundInsertTag ||
2210                                     insertTag != HTML.Tag.COMMENT)) {
2211                    // Comment outside of body, will not be able to show it,
2212
// but can add it as a property on the Document.
2213
addExternalComment(new String JavaDoc(data));
2214            return;
2215        }
2216        SimpleAttributeSet sas = new SimpleAttributeSet();
2217        sas.addAttribute(HTML.Attribute.COMMENT, new String JavaDoc(data));
2218        addSpecialElement(HTML.Tag.COMMENT, sas);
2219        }
2220
2221            TagAction action = (TagAction)tagMap.get(HTML.Tag.COMMENT);
2222            if (action != null) {
2223                action.start(HTML.Tag.COMMENT, new SimpleAttributeSet());
2224                action.end(HTML.Tag.COMMENT);
2225            }
2226    }
2227
2228        /**
2229         * Adds the comment <code>comment</code> to the set of comments
2230         * maintained outside of the scope of elements.
2231         */

2232        private void addExternalComment(String JavaDoc comment) {
2233            Object JavaDoc comments = getProperty(AdditionalComments);
2234            if (comments != null && !(comments instanceof Vector)) {
2235                // No place to put comment.
2236
return;
2237            }
2238            if (comments == null) {
2239                comments = new Vector();
2240                putProperty(AdditionalComments, comments);
2241            }
2242            ((Vector)comments).addElement(comment);
2243        }
2244
2245    /**
2246     * Callback from the parser. Route to the appropriate
2247     * handler for the tag.
2248     */

2249    public void handleEndTag(HTML.Tag JavaDoc t, int pos) {
2250        if (receivedEndHTML || (midInsert && !inBody)) {
2251        return;
2252        }
2253            if (t == HTML.Tag.HTML) {
2254                receivedEndHTML = true;
2255            }
2256        if (t == HTML.Tag.BODY) {
2257        inBody = false;
2258        if (midInsert) {
2259            inBlock--;
2260        }
2261        }
2262        TagAction action = (TagAction) tagMap.get(t);
2263        if (action != null) {
2264        action.end(t);
2265        }
2266    }
2267
2268    /**
2269     * Callback from the parser. Route to the appropriate
2270     * handler for the tag.
2271     */

2272    public void handleSimpleTag(HTML.Tag JavaDoc t, MutableAttributeSet a, int pos) {
2273        if (receivedEndHTML || (midInsert && !inBody)) {
2274        return;
2275        }
2276
2277        if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
2278        // Map the style attributes.
2279
String JavaDoc decl = (String JavaDoc)a.getAttribute(HTML.Attribute.STYLE);
2280        a.removeAttribute(HTML.Attribute.STYLE);
2281        styleAttributes = getStyleSheet().getDeclaration(decl);
2282        a.addAttributes(styleAttributes);
2283        }
2284        else {
2285        styleAttributes = null;
2286        }
2287
2288        TagAction action = (TagAction) tagMap.get(t);
2289        if (action != null) {
2290        action.start(t, a);
2291        action.end(t);
2292        }
2293        else if (getPreservesUnknownTags()) {
2294        // unknown tag, only add if should preserve it.
2295
addSpecialElement(t, a);
2296        }
2297    }
2298
2299    /**
2300     * This is invoked after the stream has been parsed, but before
2301     * <code>flush</code>. <code>eol</code> will be one of \n, \r
2302     * or \r\n, which ever is encountered the most in parsing the
2303     * stream.
2304     *
2305     * @since 1.3
2306     */

2307    public void handleEndOfLineString(String JavaDoc eol) {
2308        if (emptyDocument && eol != null) {
2309        putProperty(DefaultEditorKit.EndOfLineStringProperty,
2310                eol);
2311        }
2312    }
2313
2314    // ---- tag handling support ------------------------------
2315

2316    /**
2317     * Registers a handler for the given tag. By default
2318     * all of the well-known tags will have been registered.
2319     * This can be used to change the handling of a particular
2320     * tag or to add support for custom tags.
2321     */

2322        protected void registerTag(HTML.Tag JavaDoc t, TagAction a) {
2323        tagMap.put(t, a);
2324    }
2325
2326    /**
2327     * An action to be performed in response
2328     * to parsing a tag. This allows customization
2329     * of how each tag is handled and avoids a large
2330     * switch statement.
2331     */

2332    public class TagAction {
2333
2334        /**
2335         * Called when a start tag is seen for the
2336         * type of tag this action was registered
2337         * to. The tag argument indicates the actual
2338         * tag for those actions that are shared across
2339         * many tags. By default this does nothing and
2340         * completely ignores the tag.
2341         */

2342        public void start(HTML.Tag JavaDoc t, MutableAttributeSet a) {
2343        }
2344
2345        /**
2346         * Called when an end tag is seen for the
2347         * type of tag this action was registered
2348         * to. The tag argument indicates the actual
2349         * tag for those actions that are shared across
2350         * many tags. By default this does nothing and
2351         * completely ignores the tag.
2352         */

2353        public void end(HTML.Tag JavaDoc t) {
2354        }
2355
2356    }
2357
2358    public class BlockAction extends TagAction {
2359
2360        public void start(HTML.Tag JavaDoc t, MutableAttributeSet attr) {
2361        blockOpen(t, attr);
2362        }
2363
2364        public void end(HTML.Tag JavaDoc t) {
2365        blockClose(t);
2366        }
2367    }
2368
2369
2370        /**
2371         * Action used for the actual element form tag. This is named such
2372         * as there was already a public class named FormAction.
2373         */

2374        private class FormTagAction extends BlockAction {
2375            public void start(HTML.Tag JavaDoc t, MutableAttributeSet attr) {
2376                super.start(t, attr);
2377                // initialize a ButtonGroupsMap when
2378
// FORM tag is encountered. This will
2379
// be used for any radio buttons that
2380
// might be defined in the FORM.
2381
// for new group new ButtonGroup will be created (fix for 4529702)
2382
// group name is a key in radioButtonGroupsMap
2383
radioButtonGroupsMap = new HashMap();
2384            }
2385
2386        public void end(HTML.Tag JavaDoc t) {
2387                super.end(t);
2388                // reset the button group to null since
2389
// the form has ended.
2390
radioButtonGroupsMap = null;
2391            }
2392        }
2393
2394
2395    public class ParagraphAction extends BlockAction {
2396
2397        public void start(HTML.Tag JavaDoc t, MutableAttributeSet a) {
2398        super.start(t, a);
2399        inParagraph = true;
2400        }
2401
2402        public void end(HTML.Tag JavaDoc t) {
2403        super.end(t);
2404        inParagraph = false;
2405        }
2406    }
2407
2408    public class SpecialAction extends TagAction {
2409
2410        public void start(HTML.Tag JavaDoc t, MutableAttributeSet a) {
2411        addSpecialElement(t, a);
2412        }
2413
2414    }
2415
2416    public class IsindexAction extends TagAction {
2417
2418        public void start(HTML.Tag JavaDoc t, MutableAttributeSet a) {
2419        blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
2420        addSpecialElement(t, a);
2421        blockClose(HTML.Tag.IMPLIED);
2422        }
2423
2424    }
2425
2426
2427    public class HiddenAction extends TagAction {
2428
2429        public void start(HTML.Tag JavaDoc t, MutableAttributeSet a) {
2430        addSpecialElement(t, a);
2431        }
2432
2433        public void end(HTML.Tag JavaDoc t) {
2434        if (!isEmpty(t)) {
2435            MutableAttributeSet a = new SimpleAttributeSet();
2436            a.addAttribute(HTML.Attribute.ENDTAG, "true");
2437            addSpecialElement(t, a);
2438        }
2439        }
2440
2441        boolean isEmpty(HTML.Tag JavaDoc t) {
2442        if (t == HTML.Tag.APPLET ||
2443            t == HTML.Tag.SCRIPT) {
2444            return false;
2445        }
2446        return true;
2447        }
2448    }
2449
2450
2451    /**
2452     * Subclass of HiddenAction to set the content type for style sheets,
2453     * and to set the name of the default style sheet.
2454     */

2455    class MetaAction extends HiddenAction {
2456
2457        public void start(HTML.Tag JavaDoc t, MutableAttributeSet a) {
2458        Object JavaDoc equiv = a.getAttribute(HTML.Attribute.HTTPEQUIV);
2459        if (equiv != null) {
2460            equiv = ((String JavaDoc)equiv).toLowerCase();
2461            if (equiv.equals("content-style-type")) {
2462            String JavaDoc value = (String JavaDoc)a.getAttribute
2463                           (HTML.Attribute.CONTENT);
2464            setDefaultStyleSheetType(value);
2465            isStyleCSS = "text/css".equals
2466                          (getDefaultStyleSheetType());
2467            }
2468            else if (equiv.equals("default-style")) {
2469            defaultStyle = (String JavaDoc)a.getAttribute
2470                           (HTML.Attribute.CONTENT);
2471            }
2472        }
2473        super.start(t, a);
2474        }
2475
2476        boolean isEmpty(HTML.Tag JavaDoc t) {
2477        return true;
2478        }
2479    }
2480
2481
2482    /**
2483     * End if overridden to create the necessary stylesheets that
2484     * are referenced via the link tag. It is done in this manner
2485     * as the meta tag can be used to specify an alternate style sheet,
2486     * and is not guaranteed to come before the link tags.
2487     */

2488    class HeadAction extends BlockAction {
2489
2490        public void start(HTML.Tag JavaDoc t, MutableAttributeSet a) {
2491        inHead = true;
2492        // This check of the insertTag is put in to avoid considering
2493
// the implied-p that is generated for the head. This allows
2494
// inserts for HR to work correctly.
2495
if ((insertTag == null && !insertAfterImplied) ||
2496            (insertTag == HTML.Tag.HEAD) ||
2497            (insertAfterImplied &&
2498             (foundInsertTag || !a.isDefined(IMPLIED)))) {
2499            super.start(t, a);
2500        }
2501        }
2502
2503        public void end(HTML.Tag JavaDoc t) {
2504        inHead = inStyle = false;
2505        // See if there is a StyleSheet to link to.
2506
if (styles != null) {
2507            boolean isDefaultCSS = isStyleCSS;
2508            for (int counter = 0, maxCounter = styles.size();
2509             counter < maxCounter;) {
2510            Object JavaDoc value = styles.elementAt(counter);
2511            if (value == HTML.Tag.LINK) {
2512                handleLink((AttributeSet)styles.
2513                       elementAt(++counter));
2514                counter++;
2515            }
2516            else {
2517                // Rule.
2518
// First element gives type.
2519
String JavaDoc type = (String JavaDoc)styles.elementAt(++counter);
2520                boolean isCSS = (type == null) ? isDefaultCSS :
2521                            type.equals("text/css");
2522                while (++counter < maxCounter &&
2523                   (styles.elementAt(counter)
2524                    instanceof String JavaDoc)) {
2525                if (isCSS) {
2526                    addCSSRules((String JavaDoc)styles.elementAt
2527                        (counter));
2528                }
2529                }
2530            }
2531            }
2532        }
2533        if ((insertTag == null && !insertAfterImplied) ||
2534            insertTag == HTML.Tag.HEAD ||
2535            (insertAfterImplied && foundInsertTag)) {
2536            super.end(t);
2537        }
2538        }
2539
2540        boolean isEmpty(HTML.Tag JavaDoc t) {
2541        return false;
2542        }
2543
2544        private void handleLink(AttributeSet attr) {
2545        // Link.
2546
String JavaDoc type = (String JavaDoc)attr.getAttribute(HTML.Attribute.TYPE);
2547        if (type == null) {
2548            type = getDefaultStyleSheetType();
2549        }
2550        // Only choose if type==text/css
2551
// Select link if rel==stylesheet.
2552
// Otherwise if rel==alternate stylesheet and
2553
// title matches default style.
2554
if (type.equals("text/css")) {
2555            String JavaDoc rel = (String JavaDoc)attr.getAttribute(HTML.Attribute.REL);
2556            String JavaDoc title = (String JavaDoc)attr.getAttribute
2557                                   (HTML.Attribute.TITLE);
2558            String JavaDoc media = (String JavaDoc)attr.getAttribute
2559                                   (HTML.Attribute.MEDIA);
2560            if (media == null) {
2561            media = "all";
2562            }
2563            else {
2564            media = media.toLowerCase();
2565            }
2566            if (rel != null) {
2567            rel = rel.toLowerCase();
2568            if ((media.indexOf("all") != -1 ||
2569                 media.indexOf("screen") != -1) &&
2570                (rel.equals("stylesheet") ||
2571                 (rel.equals("alternate stylesheet") &&
2572                  title.equals(defaultStyle)))) {
2573                linkCSSStyleSheet((String JavaDoc)attr.getAttribute
2574                          (HTML.Attribute.HREF));
2575            }
2576            }
2577        }
2578        }
2579    }
2580
2581
2582    /**
2583     * A subclass to add the AttributeSet to styles if the
2584     * attributes contains an attribute for 'rel' with value
2585     * 'stylesheet' or 'alternate stylesheet'.
2586     */

2587    class LinkAction extends HiddenAction {
2588
2589        public void start(HTML.Tag JavaDoc t, MutableAttributeSet a) {
2590        String JavaDoc rel = (String JavaDoc)a.getAttribute(HTML.Attribute.REL);
2591        if (rel != null) {
2592            rel = rel.toLowerCase();
2593            if (rel.equals("stylesheet") ||
2594            rel.equals("alternate stylesheet")) {
2595            if (styles == null) {
2596                styles = new Vector(3);
2597            }
2598            styles.addElement(t);
2599            styles.addElement(a.copyAttributes());
2600            }
2601        }
2602        super.start(t, a);
2603        }
2604    }
2605
2606    class MapAction extends TagAction {
2607
2608        public void start(HTML.Tag JavaDoc t, MutableAttributeSet a) {
2609        lastMap = new Map JavaDoc((String JavaDoc)a.getAttribute(HTML.Attribute.NAME));
2610        addMap(lastMap);
2611        }
2612
2613        public void end(HTML.Tag JavaDoc t) {
2614        }
2615    }
2616
2617
2618    class AreaAction extends TagAction {
2619
2620        public void start(HTML.Tag JavaDoc t, MutableAttributeSet a) {
2621        if (lastMap != null) {
2622            lastMap.addArea(a.copyAttributes());
2623        }
2624        }
2625
2626        public void end(HTML.Tag JavaDoc t) {
2627        }
2628    }
2629
2630
2631    class StyleAction extends TagAction {
2632
2633        public void start(HTML.Tag JavaDoc t, MutableAttributeSet a) {
2634        if (inHead) {
2635            if (styles == null) {
2636            styles = new Vector(3);
2637            }
2638            styles.addElement(t);
2639            styles.addElement(a.getAttribute(HTML.Attribute.TYPE));
2640            inStyle = true;
2641        }
2642        }
2643
2644        public void end(HTML.Tag JavaDoc t) {
2645        inStyle = false;
2646        }
2647
2648        boolean isEmpty(HTML.Tag JavaDoc t) {
2649        return false;
2650        }
2651    }
2652        
2653
2654    public class PreAction extends BlockAction {
2655
2656        public void start(HTML.Tag JavaDoc t, MutableAttributeSet attr) {
2657        inPre = true;
2658        blockOpen(t, attr);
2659        attr.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
2660        blockOpen(HTML.Tag.IMPLIED, attr);
2661        }
2662
2663        public void end(HTML.Tag JavaDoc t) {
2664        blockClose(HTML.Tag.IMPLIED);
2665        // set inPre to false after closing, so that if a newline
2666
// is added it won't generate a blockOpen.
2667
inPre = false;
2668        blockClose(t);
2669        }
2670    }
2671
2672    public class CharacterAction extends TagAction {
2673
2674        public void start(HTML.Tag JavaDoc t, MutableAttributeSet attr) {
2675        pushCharacterStyle();
2676        if (!foundInsertTag) {
2677            // Note that the third argument should really be based off
2678
// inParagraph and impliedP. If we're wrong (that is
2679
// insertTagDepthDelta shouldn't be changed), we'll end up
2680
// removing an extra EndSpec, which won't matter anyway.
2681
boolean insert = canInsertTag(t, attr, false);
2682            if (foundInsertTag) {
2683            if (!inParagraph) {
2684                inParagraph = impliedP = true;
2685            }
2686            }
2687            if (!insert) {
2688            return;
2689            }
2690        }
2691        if (attr.isDefined(IMPLIED)) {
2692            attr.removeAttribute(IMPLIED);
2693        }
2694        charAttr.addAttribute(t, attr.copyAttributes());
2695        if (styleAttributes != null) {
2696            charAttr.addAttributes(styleAttributes);
2697        }
2698        }
2699    
2700        public void end(HTML.Tag JavaDoc t) {
2701        popCharacterStyle();
2702        }
2703    }
2704
2705    /**
2706     * Provides conversion of HTML tag/attribute
2707     * mappings that have a corresponding StyleConstants
2708     * and CSS mapping. The conversion is to CSS attributes.
2709     */

2710    class ConvertAction extends TagAction {
2711
2712        public void start(HTML.Tag JavaDoc t, MutableAttributeSet attr) {
2713        pushCharacterStyle();
2714        if (!foundInsertTag) {
2715            // Note that the third argument should really be based off
2716
// inParagraph and impliedP. If we're wrong (that is
2717
// insertTagDepthDelta shouldn't be changed), we'll end up
2718
// removing an extra EndSpec, which won't matter anyway.
2719
boolean insert = canInsertTag(t, attr, false);
2720            if (foundInsertTag) {
2721            if (!inParagraph) {
2722                inParagraph = impliedP = true;
2723            }
2724            }
2725            if (!insert) {
2726            return;
2727            }
2728        }
2729        if (attr.isDefined(IMPLIED)) {
2730            attr.removeAttribute(IMPLIED);
2731        }
2732        if (styleAttributes != null) {
2733            charAttr.addAttributes(styleAttributes);
2734        }
2735        // We also need to add attr, otherwise we lose custom
2736
// attributes, including class/id for style lookups, and
2737
// further confuse style lookup (doesn't have tag).
2738
charAttr.addAttribute(t, attr.copyAttributes());
2739        StyleSheet JavaDoc sheet = getStyleSheet();
2740        if (t == HTML.Tag.B) {
2741            sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_WEIGHT, "bold");
2742        } else if (t == HTML.Tag.I) {
2743            sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_STYLE, "italic");
2744        } else if (t == HTML.Tag.U) {
2745            Object JavaDoc v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
2746            String JavaDoc value = "underline";
2747            value = (v != null) ? value + "," + v.toString() : value;
2748            sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
2749        } else if (t == HTML.Tag.STRIKE) {
2750            Object JavaDoc v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
2751            String JavaDoc value = "line-through";
2752            value = (v != null) ? value + "," + v.toString() : value;
2753            sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
2754        } else if (t == HTML.Tag.SUP) {
2755            Object JavaDoc v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
2756            String JavaDoc value = "sup";
2757            value = (v != null) ? value + "," + v.toString() : value;
2758            sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
2759        } else if (t == HTML.Tag.SUB) {
2760            Object JavaDoc v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
2761            String JavaDoc value = "sub";
2762            value = (v != null) ? value + "," + v.toString() : value;
2763            sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
2764        } else if (t == HTML.Tag.FONT) {
2765            String JavaDoc color = (String JavaDoc) attr.getAttribute(HTML.Attribute.COLOR);
2766            if (color != null) {
2767            sheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color);
2768            }
2769            String JavaDoc face = (String JavaDoc) attr.getAttribute(HTML.Attribute.FACE);
2770            if (face != null) {
2771            sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY, face);
2772            }
2773            String JavaDoc size = (String JavaDoc) attr.getAttribute(HTML.Attribute.SIZE);
2774            if (size != null) {
2775            sheet.addCSSAttributeFromHTML(charAttr, CSS.Attribute.FONT_SIZE, size);
2776            }
2777        }
2778        }
2779    
2780        public void end(HTML.Tag JavaDoc t) {
2781        popCharacterStyle();
2782        }
2783        
2784    }
2785
2786    class AnchorAction extends CharacterAction {
2787
2788        public void start(HTML.Tag JavaDoc t, MutableAttributeSet attr) {
2789        // set flag to catch empty anchors
2790
emptyAnchor = true;
2791        super.start(t, attr);
2792        }
2793    
2794        public void end(HTML.Tag JavaDoc t) {
2795        if (emptyAnchor) {
2796            // if the anchor was empty it was probably a
2797
// named anchor point and we don't want to throw
2798
// it away.
2799
char[] one = new char[1];
2800            one[0] = '\n';
2801            addContent(one, 0, 1);
2802        }
2803        super.end(t);
2804        }
2805    }
2806
2807    class TitleAction extends HiddenAction {
2808
2809        public void start(HTML.Tag JavaDoc t, MutableAttributeSet attr) {
2810        inTitle = true;
2811        super.start(t, attr);
2812        }
2813    
2814        public void end(HTML.Tag JavaDoc t) {
2815        inTitle = false;
2816        super.end(t);
2817        }
2818
2819        boolean isEmpty(HTML.Tag JavaDoc t) {
2820        return false;
2821        }
2822    }
2823
2824
2825    class BaseAction extends TagAction {
2826
2827        public void start(HTML.Tag JavaDoc t, MutableAttributeSet attr) {
2828        String JavaDoc href = (String JavaDoc) attr.getAttribute(HTML.Attribute.HREF);
2829        if (href != null) {
2830            try {
2831            URL JavaDoc newBase = new URL JavaDoc(base, href);
2832            setBase(newBase);
2833            hasBaseTag = true;
2834            } catch (MalformedURLException JavaDoc ex) {
2835            }
2836        }
2837        baseTarget = (String JavaDoc) attr.getAttribute(HTML.Attribute.TARGET);
2838        }
2839    }
2840
2841    class ObjectAction extends SpecialAction {
2842
2843        public void start(HTML.Tag JavaDoc t, MutableAttributeSet a) {
2844        if (t == HTML.Tag.PARAM) {
2845            addParameter(a);
2846        } else {
2847            super.start(t, a);
2848        }
2849        }
2850
2851        public void end(HTML.Tag JavaDoc t) {
2852        if (t != HTML.Tag.PARAM) {
2853            super.end(t);
2854        }
2855        }
2856
2857        void addParameter(AttributeSet a) {
2858        String JavaDoc name = (String JavaDoc) a.getAttribute(HTML.Attribute.NAME);
2859        String JavaDoc value = (String JavaDoc) a.getAttribute(HTML.Attribute.VALUE);
2860        if ((name != null) && (value != null)) {
2861            ElementSpec objSpec = (ElementSpec) parseBuffer.lastElement();
2862            MutableAttributeSet objAttr = (MutableAttributeSet) objSpec.getAttributes();
2863            objAttr.addAttribute(name, value);
2864        }
2865        }
2866    }
2867
2868    /**
2869     * Action to support forms by building all of the elements
2870     * used to represent form controls. This will process
2871     * the &lt;INPUT&gt;, &lt;TEXTAREA&gt;, &lt;SELECT&gt;,
2872     * and &lt;OPTION&gt; tags. The element created by
2873     * this action is expected to have the attribute
2874     * <code>StyleConstants.ModelAttribute</code> set to
2875     * the model that holds the state for the form control.
2876     * This enables multiple views, and allows document to
2877     * be iterated over picking up the data of the form.
2878     * The following are the model assignments for the
2879     * various type of form elements.
2880     * <table summary="model assignments for the various types of form elements">
2881     * <tr>
2882     * <th>Element Type
2883     * <th>Model Type
2884     * <tr>
2885     * <td>input, type button
2886     * <td>{@link DefaultButtonModel}
2887     * <tr>
2888     * <td>input, type checkbox
2889     * <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
2890     * <tr>
2891     * <td>input, type image
2892     * <td>{@link DefaultButtonModel}
2893     * <tr>
2894     * <td>input, type password
2895     * <td>{@link PlainDocument}
2896     * <tr>
2897     * <td>input, type radio
2898     * <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
2899     * <tr>
2900     * <td>input, type reset
2901     * <td>{@link DefaultButtonModel}
2902     * <tr>
2903     * <td>input, type submit
2904     * <td>{@link DefaultButtonModel}
2905     * <tr>
2906     * <td>input, type text or type is null.
2907     * <td>{@link PlainDocument}
2908     * <tr>
2909     * <td>select
2910     * <td>{@link DefaultComboBoxModel} or an {@link DefaultListModel}, with an item type of Option
2911     * <tr>
2912     * <td>textarea
2913     * <td>{@link PlainDocument}
2914     * </table>
2915     *
2916     */

2917    public class FormAction extends SpecialAction {
2918
2919        public void start(HTML.Tag JavaDoc t, MutableAttributeSet attr) {
2920        if (t == HTML.Tag.INPUT) {
2921            String JavaDoc type = (String JavaDoc)
2922            attr.getAttribute(HTML.Attribute.TYPE);
2923            /*
2924             * if type is not defined teh default is
2925             * assumed to be text.
2926             */

2927            if (type == null) {
2928            type = "text";
2929            attr.addAttribute(HTML.Attribute.TYPE, "text");
2930            }
2931            setModel(type, attr);
2932        } else if (t == HTML.Tag.TEXTAREA) {
2933            inTextArea = true;
2934            textAreaDocument = new TextAreaDocument JavaDoc();
2935            attr.addAttribute(StyleConstants.ModelAttribute,
2936                      textAreaDocument);
2937        } else if (t == HTML.Tag.SELECT) {
2938            int size = HTML.getIntegerAttributeValue(attr,
2939                                 HTML.Attribute.SIZE,
2940                                 1);
2941            boolean multiple = ((String JavaDoc)attr.getAttribute(HTML.Attribute.MULTIPLE) != null);
2942            if ((size > 1) || multiple) {
2943            OptionListModel JavaDoc m = new OptionListModel JavaDoc();
2944            if (multiple) {
2945                m.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
2946            }
2947            selectModel = m;
2948            } else {
2949            selectModel = new OptionComboBoxModel JavaDoc();
2950            }
2951            attr.addAttribute(StyleConstants.ModelAttribute,
2952                      selectModel);
2953
2954        }
2955
2956        // build the element, unless this is an option.
2957
if (t == HTML.Tag.OPTION) {
2958            option = new Option JavaDoc(attr);
2959
2960            if (selectModel instanceof OptionListModel JavaDoc) {
2961            OptionListModel JavaDoc m = (OptionListModel JavaDoc)selectModel;
2962            m.addElement(option);
2963            if (option.isSelected()) {
2964                m.addSelectionInterval(optionCount, optionCount);
2965                m.setInitialSelection(optionCount);
2966            }
2967            } else if (selectModel instanceof OptionComboBoxModel JavaDoc) {
2968            OptionComboBoxModel JavaDoc m = (OptionComboBoxModel JavaDoc)selectModel;
2969            m.addElement(option);
2970            if (option.isSelected()) {
2971                m.setSelectedItem(option);
2972                m.setInitialSelection(option);
2973            }
2974            }
2975            optionCount++;
2976        } else {
2977            super.start(t, attr);
2978        }
2979        }
2980
2981        public void end(HTML.Tag JavaDoc t) {
2982        if (t == HTML.Tag.OPTION) {
2983            option = null;
2984        } else {
2985            if (t == HTML.Tag.SELECT) {
2986            selectModel = null;
2987            optionCount = 0;
2988            } else if (t == HTML.Tag.TEXTAREA) {
2989            inTextArea = false;
2990            
2991            /* Now that the textarea has ended,
2992             * store the entire initial text
2993             * of the text area. This will
2994             * enable us to restore the initial
2995             * state if a reset is requested.
2996             */

2997            textAreaDocument.storeInitialText();
2998            }
2999            super.end(t);
3000        }
3001        }
3002
3003        void setModel(String JavaDoc type, MutableAttributeSet attr) {
3004        if (type.equals("submit") ||
3005            type.equals("reset") ||
3006            type.equals("image")) {
3007
3008            // button model
3009
attr.addAttribute(StyleConstants.ModelAttribute,
3010                      new DefaultButtonModel());
3011        } else if (type.equals("text") ||
3012               type.equals("password")) {
3013            // plain text model
3014
int maxLength = HTML.getIntegerAttributeValue(
3015                                       attr, HTML.Attribute.MAXLENGTH, -1);
3016                    Document doc;
3017
3018                    if (maxLength > 0) {
3019                        doc = new FixedLengthDocument(maxLength);
3020                    }
3021                    else {
3022                        doc = new PlainDocument();
3023                    }
3024                    String JavaDoc value = (String JavaDoc)
3025                        attr.getAttribute(HTML.Attribute.VALUE);
3026                    try {
3027                        doc.insertString(0, value, null);
3028                    } catch (BadLocationException e) {
3029                    }
3030                    attr.addAttribute(StyleConstants.ModelAttribute, doc);
3031                } else if (type.equals("file")) {
3032            // plain text model
3033
attr.addAttribute(StyleConstants.ModelAttribute,
3034                      new PlainDocument());
3035        } else if (type.equals("checkbox") ||
3036               type.equals("radio")) {
3037            JToggleButton.ToggleButtonModel model = new JToggleButton.ToggleButtonModel();
3038            if (type.equals("radio")) {
3039            String JavaDoc name = (String JavaDoc) attr.getAttribute(HTML.Attribute.NAME);
3040                        if ( radioButtonGroupsMap == null ) { //fix for 4772743
3041
radioButtonGroupsMap = new HashMap();
3042                        }
3043            ButtonGroup radioButtonGroup = (ButtonGroup)radioButtonGroupsMap.get(name);
3044            if (radioButtonGroup == null) {
3045                radioButtonGroup = new ButtonGroup();
3046                radioButtonGroupsMap.put(name,radioButtonGroup);
3047            }
3048            model.setGroup(radioButtonGroup);
3049            }
3050                    boolean checked = (attr.getAttribute(HTML.Attribute.CHECKED) != null);
3051                    model.setSelected(checked);
3052            attr.addAttribute(StyleConstants.ModelAttribute, model);
3053        }
3054        }
3055
3056        /**
3057         * If a &lt;SELECT&gt; tag is being processed, this
3058         * model will be a reference to the model being filled
3059         * with the &lt;OPTION&gt; elements (which produce
3060         * objects of type <code>Option</code>.
3061         */

3062        Object JavaDoc selectModel;
3063        int optionCount;
3064    }
3065
3066
3067    // --- utility methods used by the reader ------------------
3068

3069    /**
3070     * Pushes the current character style on a stack in preparation
3071     * for forming a new nested character style.
3072     */

3073    protected void pushCharacterStyle() {
3074        charAttrStack.push(charAttr.copyAttributes());
3075    }
3076
3077    /**
3078     * Pops a previously pushed character style off the stack
3079     * to return to a previous style.
3080     */

3081    protected void popCharacterStyle() {
3082        if (!charAttrStack.empty()) {
3083        charAttr = (MutableAttributeSet) charAttrStack.peek();
3084        charAttrStack.pop();
3085        }
3086    }
3087
3088    /**
3089     * Adds the given content to the textarea document.
3090     * This method gets called when we are in a textarea
3091     * context. Therefore all text that is seen belongs
3092     * to the text area and is hence added to the
3093     * TextAreaDocument associated with the text area.
3094     */

3095    protected void textAreaContent(char[] data) {
3096        try {
3097        textAreaDocument.insertString(textAreaDocument.getLength(), new String JavaDoc(data), null);
3098        } catch (BadLocationException e) {
3099        // Should do something reasonable
3100
}
3101    }
3102    
3103    /**
3104     * Adds the given content that was encountered in a
3105     * PRE element. This synthesizes lines to hold the
3106     * runs of text, and makes calls to addContent to
3107     * actually add the text.
3108     */

3109    protected void preContent(char[] data) {
3110        int last = 0;
3111        for (int i = 0; i < data.length; i++) {
3112        if (data[i] == '\n') {
3113            addContent(data, last, i - last + 1);
3114            blockClose(HTML.Tag.IMPLIED);
3115            MutableAttributeSet a = new SimpleAttributeSet();
3116            a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
3117            blockOpen(HTML.Tag.IMPLIED, a);
3118            last = i + 1;
3119        }
3120        }
3121        if (last < data.length) {
3122        addContent(data, last, data.length - last);
3123        }
3124    }
3125
3126    /**
3127     * Adds an instruction to the parse buffer to create a
3128     * block element with the given attributes.
3129     */

3130    protected void blockOpen(HTML.Tag JavaDoc t, MutableAttributeSet attr) {
3131        if (impliedP) {
3132        blockClose(HTML.Tag.IMPLIED);
3133        }
3134        
3135        inBlock++;
3136
3137        if (!canInsertTag(t, attr, true)) {
3138        return;
3139        }
3140        if (attr.isDefined(IMPLIED)) {
3141        attr.removeAttribute(IMPLIED);
3142        }
3143        lastWasNewline = false;
3144        attr.addAttribute(StyleConstants.NameAttribute, t);
3145        ElementSpec es = new ElementSpec(
3146        attr.copyAttributes(), ElementSpec.StartTagType);
3147        parseBuffer.addElement(es);
3148    }
3149
3150    /**
3151     * Adds an instruction to the parse buffer to close out
3152     * a block element of the given type.
3153     */

3154    protected void blockClose(HTML.Tag JavaDoc t) {
3155        inBlock--;
3156        
3157        if (!foundInsertTag) {
3158        return;
3159        }
3160
3161        // Add a new line, if the last character wasn't one. This is
3162
// needed for proper positioning of the cursor. addContent
3163
// with true will force an implied paragraph to be generated if
3164
// there isn't one. This may result in a rather bogus structure
3165
// (perhaps a table with a child pargraph), but the paragraph
3166
// is needed for proper positioning and display.
3167
if(!lastWasNewline) {
3168                pushCharacterStyle();
3169                charAttr.addAttribute(IMPLIED_CR, Boolean.TRUE);
3170        addContent(NEWLINE, 0, 1, true);
3171                popCharacterStyle();
3172        lastWasNewline = true;
3173        }
3174
3175        if (impliedP) {
3176        impliedP = false;
3177        inParagraph = false;
3178                if (t != HTML.Tag.IMPLIED) {
3179                    blockClose(HTML.Tag.IMPLIED);
3180                }
3181        }
3182        // an open/close with no content will be removed, so we
3183
// add a space of content to keep the element being formed.
3184
ElementSpec prev = (parseBuffer.size() > 0) ?
3185        (ElementSpec) parseBuffer.lastElement() : null;
3186        if (prev != null && prev.getType() == ElementSpec.StartTagType) {
3187        char[] one = new char[1];
3188        one[0] = ' ';
3189        addContent(one, 0, 1);
3190        }
3191        ElementSpec es = new ElementSpec(
3192        null, ElementSpec.EndTagType);
3193        parseBuffer.addElement(es);
3194    }
3195
3196    /**
3197     * Adds some text with the current character attributes.
3198     *
3199     * @param embedded the attributes of an embedded object.
3200     */

3201    protected void addContent(char[] data, int offs, int length) {
3202        addContent(data, offs, length, true);
3203    }
3204
3205    /**
3206     * Adds some text with the current character attributes.
3207     *
3208     * @param embedded the attributes of an embedded object.
3209     */

3210    protected void addContent(char[] data, int offs, int length,
3211                  boolean generateImpliedPIfNecessary) {
3212        if (!foundInsertTag) {
3213        return;
3214        }
3215
3216        if (generateImpliedPIfNecessary && (! inParagraph) && (! inPre)) {
3217        blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
3218        inParagraph = true;
3219        impliedP = true;
3220        }
3221        emptyAnchor = false;
3222        charAttr.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
3223        AttributeSet a = charAttr.copyAttributes();
3224        ElementSpec es = new ElementSpec(
3225        a, ElementSpec.ContentType, data, offs, length);
3226        parseBuffer.addElement(es);
3227
3228        if (parseBuffer.size() > threshold) {
3229                if ( threshold <= MaxThreshold ) {
3230                    threshold *= StepThreshold;
3231                }
3232        try {
3233            flushBuffer(false);
3234        } catch (BadLocationException ble) {
3235        }
3236        }
3237        if(length > 0) {
3238        lastWasNewline = (data[offs + length - 1] == '\n');
3239        }
3240    }
3241
3242    /**
3243     * Adds content that is basically specified entirely
3244     * in the attribute set.
3245     */

3246    protected void addSpecialElement(HTML.Tag JavaDoc t, MutableAttributeSet a) {
3247        if ((t != HTML.Tag.FRAME) && (! inParagraph) && (! inPre)) {
3248        blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
3249        inParagraph = true;
3250        impliedP = true;
3251        }
3252        if (!canInsertTag(t, a, t.isBlock())) {
3253        return;
3254        }
3255        if (a.isDefined(IMPLIED)) {
3256        a.removeAttribute(IMPLIED);
3257        }
3258        emptyAnchor = false;
3259        a.addAttributes(charAttr);
3260        a.addAttribute(StyleConstants.NameAttribute, t);
3261        char[] one = new char[1];
3262        one[0] = ' ';
3263        ElementSpec es = new ElementSpec(
3264        a.copyAttributes(), ElementSpec.ContentType, one, 0, 1);
3265        parseBuffer.addElement(es);
3266        // Set this to avoid generating a newline for frames, frames
3267
// shouldn't have any content, and shouldn't need a newline.
3268
if (t == HTML.Tag.FRAME) {
3269        lastWasNewline = true;
3270        }
3271    }
3272
3273    /**
3274     * Flushes the current parse buffer into the document.
3275     * @param endOfStream true if there is no more content to parser
3276     */

3277    void flushBuffer(boolean endOfStream) throws BadLocationException {
3278        int oldLength = HTMLDocument.this.getLength();
3279        int size = parseBuffer.size();
3280        if (endOfStream && (insertTag != null || insertAfterImplied) &&
3281        size > 0) {
3282        adjustEndSpecsForPartialInsert();
3283        size = parseBuffer.size();
3284        }
3285        ElementSpec[] spec = new ElementSpec[size];
3286        parseBuffer.copyInto(spec);
3287
3288        if (oldLength == 0 && (insertTag == null && !insertAfterImplied)) {
3289        create(spec);
3290        } else {
3291        insert(offset, spec);
3292        }
3293        parseBuffer.removeAllElements();
3294        offset += HTMLDocument.this.getLength() - oldLength;
3295        flushCount++;
3296    }
3297
3298    /**
3299     * This will be invoked for the last flush, if <code>insertTag</code>
3300     * is non null.
3301     */

3302    private void adjustEndSpecsForPartialInsert() {
3303        int size = parseBuffer.size();
3304        if (insertTagDepthDelta < 0) {
3305        // When inserting via an insertTag, the depths (of the tree
3306
// being read in, and existing hiearchy) may not match up.
3307
// This attemps to clean it up.
3308
int removeCounter = insertTagDepthDelta;
3309        while (removeCounter < 0 && size >= 0 &&
3310               ((ElementSpec)parseBuffer.elementAt(size - 1)).
3311               getType() == ElementSpec.EndTagType) {
3312            parseBuffer.removeElementAt(--size);
3313            removeCounter++;
3314        }
3315        }
3316        if (flushCount == 0 && (!insertAfterImplied ||
3317                    !wantsTrailingNewline)) {
3318        // If this starts with content (or popDepth > 0 &&
3319
// pushDepth > 0) and ends with EndTagTypes, make sure
3320
// the last content isn't a \n, otherwise will end up with
3321
// an extra \n in the middle of content.
3322
int index = 0;
3323        if (pushDepth > 0) {
3324            if (((ElementSpec)parseBuffer.elementAt(0)).getType() ==
3325            ElementSpec.ContentType) {
3326            index++;
3327            }
3328        }
3329        index += (popDepth + pushDepth);
3330        int cCount = 0;
3331        int cStart = index;
3332        while (index < size && ((ElementSpec)parseBuffer.elementAt
3333                (index)).getType() == ElementSpec.ContentType) {
3334            index++;
3335            cCount++;
3336        }
3337        if (cCount > 1) {
3338            while (index < size && ((ElementSpec)parseBuffer.elementAt
3339                   (index)).getType() == ElementSpec.EndTagType) {
3340            index++;
3341            }
3342            if (index == size) {
3343            char[] lastText = ((ElementSpec)parseBuffer.elementAt
3344                       (cStart + cCount - 1)).getArray();
3345            if (lastText.length == 1 && lastText[0] == NEWLINE[0]){
3346                index = cStart + cCount - 1;
3347                while (size > index) {
3348                parseBuffer.removeElementAt(--size);
3349                }
3350            }
3351            }
3352        }
3353        }
3354            if (wantsTrailingNewline) {
3355                // Make sure there is in fact a newline
3356
for (int counter = parseBuffer.size() - 1; counter >= 0;
3357                                   counter--) {
3358                    ElementSpec spec = (ElementSpec)parseBuffer.
3359                                                    elementAt(counter);
3360                    if (spec.getType() == ElementSpec.ContentType) {
3361                        if (spec.getArray()[spec.getLength() - 1] != '\n') {
3362                            SimpleAttributeSet attrs =new SimpleAttributeSet();
3363
3364                            attrs.addAttribute(StyleConstants.NameAttribute,
3365                                               HTML.Tag.CONTENT);
3366                            parseBuffer.insertElementAt(new ElementSpec(
3367                                    attrs,
3368                    ElementSpec.ContentType, NEWLINE, 0, 1),
3369                                    counter + 1);
3370                        }
3371                        break;
3372                    }
3373                }
3374            }
3375    }
3376
3377    /**
3378     * Adds the CSS rules in <code>rules</code>.
3379     */

3380    void addCSSRules(String JavaDoc rules) {
3381        StyleSheet JavaDoc ss = getStyleSheet();
3382        ss.addRule(rules);
3383    }
3384
3385    /**
3386     * Adds the CSS stylesheet at <code>href</code> to the known list
3387     * of stylesheets.
3388     */

3389    void linkCSSStyleSheet(String JavaDoc href) {
3390        URL JavaDoc url = null;
3391        try {
3392        url = new URL JavaDoc(base, href);
3393        } catch (MalformedURLException JavaDoc mfe) {
3394        try {
3395            url = new URL JavaDoc(href);
3396        } catch (MalformedURLException JavaDoc mfe2) {
3397            url = null;
3398        }
3399        }
3400        if (url != null) {
3401        getStyleSheet().importStyleSheet(url);
3402        }
3403    }
3404
3405    /**
3406     * Returns true if can insert starting at <code>t</code>. This
3407     * will return false if the insert tag is set, and hasn't been found
3408     * yet.
3409     */

3410    private boolean canInsertTag(HTML.Tag JavaDoc t, AttributeSet attr,
3411                     boolean isBlockTag) {
3412        if (!foundInsertTag) {
3413        if ((insertTag != null && !isInsertTag(t)) ||
3414            (insertAfterImplied && (t == HTML.Tag.IMPLIED ||
3415                               (attr == null || attr.isDefined(IMPLIED))))) {
3416            return false;
3417        }
3418                // Allow the insert if t matches the insert tag, or
3419
// insertAfterImplied is true and the element is implied.
3420
foundInsertTag(isBlockTag);
3421        if (!insertInsertTag) {
3422            return false;
3423        }
3424        }
3425        return true;
3426    }
3427
3428    private boolean isInsertTag(HTML.Tag JavaDoc tag) {
3429        return (insertTag == tag);
3430    }
3431
3432    private void foundInsertTag(boolean isBlockTag) {
3433        foundInsertTag = true;
3434        if (!insertAfterImplied && (popDepth > 0 || pushDepth > 0)) {
3435        try {
3436            if (offset == 0 || !getText(offset - 1, 1).equals("\n")) {
3437            // Need to insert a newline.
3438
AttributeSet newAttrs = null;
3439            boolean joinP = true;
3440
3441            if (offset != 0) {
3442                // Determine if we can use JoinPrevious, we can't
3443
// if the Element has some attributes that are
3444
// not meant to be duplicated.
3445
Element charElement = getCharacterElement
3446                                    (offset - 1);
3447                AttributeSet attrs = charElement.getAttributes();
3448
3449                if (attrs.isDefined(StyleConstants.
3450                        ComposedTextAttribute)) {
3451                joinP = false;
3452                }
3453                else {
3454                Object JavaDoc name = attrs.getAttribute
3455                              (StyleConstants.NameAttribute);
3456                if (name instanceof HTML.Tag JavaDoc) {
3457                    HTML.Tag JavaDoc tag = (HTML.Tag JavaDoc)name;
3458                    if (tag == HTML.Tag.IMG ||
3459                    tag == HTML.Tag.HR ||
3460                    tag == HTML.Tag.COMMENT ||
3461                    (tag instanceof HTML.UnknownTag JavaDoc)) {
3462                    joinP = false;
3463                    }
3464                }
3465                }
3466            }
3467            if (!joinP) {
3468                // If not joining with the previous element, be
3469
// sure and set the name (otherwise it will be
3470
// inherited).
3471
newAttrs = new SimpleAttributeSet();
3472                ((SimpleAttributeSet)newAttrs).addAttribute
3473                              (StyleConstants.NameAttribute,
3474                           HTML.Tag.CONTENT);
3475            }
3476            ElementSpec es = new ElementSpec(newAttrs,
3477                     ElementSpec.ContentType, NEWLINE, 0,
3478                     NEWLINE.length);
3479            if (joinP) {
3480                es.setDirection(ElementSpec.
3481                        JoinPreviousDirection);
3482            }
3483            parseBuffer.addElement(es);
3484            }
3485        } catch (BadLocationException ble) {}
3486        }
3487        // pops
3488
for (int counter = 0; counter < popDepth; counter++) {
3489        parseBuffer.addElement(new ElementSpec(null, ElementSpec.
3490                               EndTagType));
3491        }
3492        // pushes
3493
for (int counter = 0; counter < pushDepth; counter++) {
3494        ElementSpec es = new ElementSpec(null, ElementSpec.
3495                         StartTagType);
3496        es.setDirection(ElementSpec.JoinNextDirection);
3497        parseBuffer.addElement(es);
3498        }
3499        insertTagDepthDelta = depthTo(Math.max(0, offset - 1)) -
3500                          popDepth + pushDepth - inBlock;
3501        if (isBlockTag) {
3502        // A start spec will be added (for this tag), so we account
3503
// for it here.
3504
insertTagDepthDelta++;
3505        }
3506        else {
3507        // An implied paragraph close (end spec) is going to be added,
3508
// so we account for it here.
3509
insertTagDepthDelta--;
3510                inParagraph = true;
3511                lastWasNewline = false;
3512        }
3513    }
3514
3515        /**
3516         * This is set to true when and end is invoked for <html>.
3517         */

3518        private boolean receivedEndHTML;
3519    /** Number of times <code>flushBuffer</code> has been invoked. */
3520    private int flushCount;
3521    /** If true, behavior is similiar to insertTag, but instead of
3522     * waiting for insertTag will wait for first Element without
3523     * an 'implied' attribute and begin inserting then. */

3524    private boolean insertAfterImplied;
3525    /** This is only used if insertAfterImplied is true. If false, only
3526     * inserting content, and there is a trailing newline it is removed. */

3527    private boolean wantsTrailingNewline;
3528    int threshold;
3529    int offset;
3530    boolean inParagraph = false;
3531    boolean impliedP = false;
3532    boolean inPre = false;
3533    boolean inTextArea = false;
3534    TextAreaDocument JavaDoc textAreaDocument = null;
3535    boolean inTitle = false;
3536    boolean lastWasNewline = true;
3537    boolean emptyAnchor;
3538    /** True if (!emptyDocument && insertTag == null), this is used so
3539     * much it is cached. */

3540    boolean midInsert;
3541    /** True when the body has been encountered. */
3542    boolean inBody;
3543    /** If non null, gives parent Tag that insert is to happen at. */
3544    HTML.Tag JavaDoc insertTag;
3545    /** If true, the insertTag is inserted, otherwise elements after
3546     * the insertTag is found are inserted. */

3547    boolean insertInsertTag;
3548    /** Set to true when insertTag has been found. */
3549    boolean foundInsertTag;
3550    /** When foundInsertTag is set to true, this will be updated to
3551     * reflect the delta between the two structures. That is, it
3552     * will be the depth the inserts are happening at minus the
3553     * depth of the tags being passed in. A value of 0 (the common
3554     * case) indicates the structures match, a value greater than 0 indicates
3555     * the insert is happening at a deeper depth than the stream is
3556     * parsing, and a value less than 0 indicates the insert is happening earlier
3557     * in the tree that the parser thinks and that we will need to remove
3558     * EndTagType specs in the flushBuffer method.
3559     */

3560    int insertTagDepthDelta;
3561    /** How many parents to ascend before insert new elements. */
3562    int popDepth;
3563    /** How many parents to descend (relative to popDepth) before
3564     * inserting. */

3565    int pushDepth;
3566    /** Last Map that was encountered. */
3567    Map JavaDoc lastMap;
3568    /** Set to true when a style element is encountered. */
3569    boolean inStyle = false;
3570    /** Name of style to use. Obtained from Meta tag. */
3571    String JavaDoc defaultStyle;
3572    /** Vector describing styles that should be include. Will consist
3573     * of a bunch of HTML.Tags, which will either be:
3574     * <p>LINK: in which case it is followed by an AttributeSet
3575     * <p>STYLE: in which case the following element is a String
3576     * indicating the type (may be null), and the elements following
3577     * it until the next HTML.Tag are the rules as Strings.
3578     */

3579    Vector styles;
3580    /** True if inside the head tag. */
3581    boolean inHead = false;
3582    /** Set to true if the style language is text/css. Since this is
3583     * used alot, it is cached. */

3584    boolean isStyleCSS;
3585    /** True if inserting into an empty document. */
3586    boolean emptyDocument;
3587    /** Attributes from a style Attribute. */
3588    AttributeSet styleAttributes;
3589
3590    /**
3591     * Current option, if in an option element (needed to
3592     * load the label.
3593     */

3594    Option JavaDoc option;
3595
3596    protected Vector<ElementSpec> parseBuffer = new Vector(); // Vector<ElementSpec>
3597
protected MutableAttributeSet charAttr = new TaggedAttributeSet();
3598    Stack charAttrStack = new Stack();
3599    Hashtable tagMap;
3600    int inBlock = 0;
3601    }
3602
3603
3604    /**
3605     * Used by StyleSheet to determine when to avoid removing HTML.Tags
3606     * matching StyleConstants.
3607     */

3608    static class TaggedAttributeSet extends SimpleAttributeSet {
3609        TaggedAttributeSet() {
3610            super();
3611        }
3612    }
3613
3614
3615    /**
3616     * An element that represents a chunk of text that has
3617     * a set of HTML character level attributes assigned to
3618     * it.
3619     */

3620    public class RunElement extends LeafElement {
3621
3622    /**
3623     * Constructs an element that represents content within the
3624     * document (has no children).
3625     *
3626     * @param parent the parent element
3627     * @param a the element attributes
3628     * @param offs0 the start offset (must be at least 0)
3629     * @param offs1 the end offset (must be at least offs0)
3630     */

3631    public RunElement(Element parent, AttributeSet a, int offs0, int offs1) {
3632        super(parent, a, offs0, offs1);
3633    }
3634
3635        /**
3636         * Gets the name of the element.
3637         *
3638         * @return the name, null if none
3639         */

3640        public String JavaDoc getName() {
3641        Object JavaDoc o = getAttribute(StyleConstants.NameAttribute);
3642        if (o != null) {
3643        return o.toString();
3644        }
3645        return super.getName();
3646    }
3647
3648    /**
3649         * Gets the resolving parent. HTML attributes are not inherited
3650     * at the model level so we override this to return null.
3651         *
3652         * @return null, there are none
3653     * @see AttributeSet#getResolveParent
3654     */

3655        public AttributeSet getResolveParent() {
3656        return null;
3657    }
3658    }
3659
3660    /**
3661     * An element that represents a structural <em>block</em> of
3662     * HTML.
3663     */

3664    public class BlockElement extends BranchElement {
3665
3666    /**
3667     * Constructs a composite element that initially contains
3668     * no children.
3669     *
3670     * @param parent the parent element
3671         * @param a the attributes for the element
3672     */

3673    public BlockElement(Element parent, AttributeSet a) {
3674        super(parent, a);
3675    }
3676
3677        /**
3678         * Gets the name of the element.
3679         *
3680         * @return the name, null if none
3681         */

3682        public String JavaDoc getName() {
3683        Object JavaDoc o = getAttribute(StyleConstants.NameAttribute);
3684        if (o != null) {
3685        return o.toString();
3686        }
3687        return super.getName();
3688    }
3689
3690    /**
3691         * Gets the resolving parent. HTML attributes are not inherited
3692     * at the model level so we override this to return null.
3693         *
3694         * @return null, there are none
3695     * @see AttributeSet#getResolveParent
3696     */

3697        public AttributeSet getResolveParent() {
3698        return null;
3699    }
3700
3701    }
3702
3703
3704    /**
3705     * Document that allows you to set the maximum length of the text.
3706     */

3707    private static class FixedLengthDocument extends PlainDocument {
3708        private int maxLength;
3709
3710        public FixedLengthDocument(int maxLength) {
3711            this.maxLength = maxLength;
3712        }
3713
3714        public void insertString(int offset, String JavaDoc str, AttributeSet a)
3715            throws BadLocationException {
3716            if (str != null && str.length() + getLength() <= maxLength) {
3717                super.insertString(offset, str, a);
3718            }
3719        }
3720    }
3721}
3722
Popular Tags