    rev 48858 : Work in progress for JDK-6303622
    Use generic in Swing components like JComboBox
    1. Added generic return type and parameter type for JComboBox.
    2. Modified JList related classes as well.
    3. Reverted the changes made to "getSelectedValues" as suggested by Joe.
    4. Modified files that were failing on Linux and Mac as well.

       1 /*
       2  * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
       3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       4  *
       5  * This code is free software; you can redistribute it and/or modify it
       6  * under the terms of the GNU General Public License version 2 only, as
       7  * published by the Free Software Foundation.  Oracle designates this
       8  * particular file as subject to the "Classpath" exception as provided
       9  * by Oracle in the LICENSE file that accompanied this code.
      10  *
      11  * This code is distributed in the hope that it will be useful, but WITHOUT
      12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14  * version 2 for more details (a copy is included in the LICENSE file that
      15  * accompanied this code).
      16  *
      17  * You should have received a copy of the GNU General Public License version
      18  * 2 along with this work; if not, write to the Free Software Foundation,
      19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20  *
      21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22  * or visit www.oracle.com if you need additional information or have any
      23  * questions.
      24  */
      25 package javax.swing.text.html;
      26
      27 import java.awt.font.TextAttribute;
      28 import java.util.*;
      29 import java.net.URL;
      30 import java.net.MalformedURLException;
      31 import java.io.*;
      32 import javax.swing.*;
      33 import javax.swing.event.*;
      34 import javax.swing.text.*;
      35 import javax.swing.undo.*;
      36 import sun.swing.SwingUtilities2;
      37 import static sun.swing.SwingUtilities2.IMPLIED_CR;
      38
      39 /**
      40  * A document that models HTML.  The purpose of this model is to
      41  * support both browsing and editing.  As a result, the structure
      42  * described by an HTML document is not exactly replicated by default.
      43  * The element structure that is modeled by default, is built by the
      44  * class <code>HTMLDocument.HTMLReader</code>, which implements the
      45  * <code>HTMLEditorKit.ParserCallback</code> protocol that the parser
      46  * expects.  To change the structure one can subclass
      47  * <code>HTMLReader</code>, and reimplement the method {@link
      48  * #getReader(int)} to return the new reader implementation.  The
      49  * documentation for <code>HTMLReader</code> should be consulted for
      50  * the details of the default structure created.  The intent is that
      51  * the document be non-lossy (although reproducing the HTML format may
      52  * result in a different format).
      53  *
      54  * <p>The document models only HTML, and makes no attempt to store
      55  * view attributes in it.  The elements are identified by the
      56  * <code>StyleContext.NameAttribute</code> attribute, which should
      57  * always have a value of type <code>HTML.Tag</code> that identifies
      58  * the kind of element.  Some of the elements (such as comments) are
      59  * synthesized.  The <code>HTMLFactory</code> uses this attribute to
      60  * determine what kind of view to build.</p>
      61  *
      62  * <p>This document supports incremental loading.  The
      63  * <code>TokenThreshold</code> property controls how much of the parse
      64  * is buffered before trying to update the element structure of the
      65  * document.  This property is set by the <code>EditorKit</code> so
      66  * that subclasses can disable it.</p>
      67  *
      68  * <p>The <code>Base</code> property determines the URL against which
      69  * relative URLs are resolved.  By default, this will be the
      70  * <code>Document.StreamDescriptionProperty</code> if the value of the
      71  * property is a URL.  If a &lt;BASE&gt; tag is encountered, the base
      72  * will become the URL specified by that tag.  Because the base URL is
      73  * a property, it can of course be set directly.</p>
      74  *
      75  * <p>The default content storage mechanism for this document is a gap
      76  * buffer (<code>GapContent</code>).  Alternatives can be supplied by
      77  * using the constructor that takes a <code>Content</code>
      78  * implementation.</p>
      79  *
      80  * <h2>Modifying HTMLDocument</h2>
      81  *
      82  * <p>In addition to the methods provided by Document and
      83  * StyledDocument for mutating an HTMLDocument, HTMLDocument provides
      84  * a number of convenience methods.  The following methods can be used
      85  * to insert HTML content into an existing document.</p>
      86  *
      87  * <ul>
      88  *   <li>{@link #setInnerHTML(Element, String)}</li>
      89  *   <li>{@link #setOuterHTML(Element, String)}</li>
      90  *   <li>{@link #insertBeforeStart(Element, String)}</li>
      91  *   <li>{@link #insertAfterStart(Element, String)}</li>
      92  *   <li>{@link #insertBeforeEnd(Element, String)}</li>
      93  *   <li>{@link #insertAfterEnd(Element, String)}</li>
      94  * </ul>
      95  *
      96  * <p>The following examples illustrate using these methods.  Each
      97  * example assumes the HTML document is initialized in the following
      98  * way:</p>
      99  *
     100  * <pre>
     101  * JEditorPane p = new JEditorPane();
     102  * p.setContentType("text/html");
     103  * p.setText("..."); // Document text is provided below.
     104  * HTMLDocument d = (HTMLDocument) p.getDocument();
     105  * </pre>
     106  *
     107  * <p>With the following HTML content:</p>
     108  *
     109  * <pre>
     110  * &lt;html&gt;
     111  *   &lt;head&gt;
     112  *     &lt;title&gt;An example HTMLDocument&lt;/title&gt;
     113  *     &lt;style type="text/css"&gt;
     114  *       div { background-color: silver; }
     115  *       ul { color: red; }
     116  *     &lt;/style&gt;
     117  *   &lt;/head&gt;
     118  *   &lt;body&gt;
     119  *     &lt;div id="BOX"&gt;
     120  *       &lt;p&gt;Paragraph 1&lt;/p&gt;
     121  *       &lt;p&gt;Paragraph 2&lt;/p&gt;
     122  *     &lt;/div&gt;
     123  *   &lt;/body&gt;
     124  * &lt;/html&gt;
     125  * </pre>
     126  *
     127  * <p>All the methods for modifying an HTML document require an {@link
     128  * Element}.  Elements can be obtained from an HTML document by using
     129  * the method {@link #getElement(Element e, Object attribute, Object
     130  * value)}.  It returns the first descendant element that contains the
     131  * specified attribute with the given value, in depth-first order.
     132  * For example, <code>d.getElement(d.getDefaultRootElement(),
     133  * StyleConstants.NameAttribute, HTML.Tag.P)</code> returns the first
     134  * paragraph element.</p>
     135  *
     136  * <p>A convenient shortcut for locating elements is the method {@link
     137  * #getElement(String)}; returns an element whose <code>ID</code>
     138  * attribute matches the specified value.  For example,
     139  * <code>d.getElement("BOX")</code> returns the <code>DIV</code>
     140  * element.</p>
     141  *
     142  * <p>The {@link #getIterator(HTML.Tag t)} method can also be used for
     143  * finding all occurrences of the specified HTML tag in the
     144  * document.</p>
     145  *
     146  * <h3>Inserting elements</h3>
     147  *
     148  * <p>Elements can be inserted before or after the existing children
     149  * of any non-leaf element by using the methods
     150  * <code>insertAfterStart</code> and <code>insertBeforeEnd</code>.
     151  * For example, if <code>e</code> is the <code>DIV</code> element,
     152  * <code>d.insertAfterStart(e, "&lt;ul&gt;&lt;li&gt;List
     153  * Item&lt;/li&gt;&lt;/ul&gt;")</code> inserts the list before the first
     154  * paragraph, and <code>d.insertBeforeEnd(e, "&lt;ul&gt;&lt;li&gt;List
     155  * Item&lt;/li&gt;&lt;/ul&gt;")</code> inserts the list after the last
     156  * paragraph.  The <code>DIV</code> block becomes the parent of the
     157  * newly inserted elements.</p>
     158  *
     159  * <p>Sibling elements can be inserted before or after any element by
     160  * using the methods <code>insertBeforeStart</code> and
     161  * <code>insertAfterEnd</code>.  For example, if <code>e</code> is the
     162  * <code>DIV</code> element, <code>d.insertBeforeStart(e,
     163  * "&lt;ul&gt;&lt;li&gt;List Item&lt;/li&gt;&lt;/ul&gt;")</code> inserts the list
     164  * before the <code>DIV</code> element, and <code>d.insertAfterEnd(e,
     165  * "&lt;ul&gt;&lt;li&gt;List Item&lt;/li&gt;&lt;/ul&gt;")</code> inserts the list
     166  * after the <code>DIV</code> element.  The newly inserted elements
     167  * become siblings of the <code>DIV</code> element.</p>
     168  *
     169  * <h3>Replacing elements</h3>
     170  *
     171  * <p>Elements and all their descendants can be replaced by using the
     172  * methods <code>setInnerHTML</code> and <code>setOuterHTML</code>.
     173  * For example, if <code>e</code> is the <code>DIV</code> element,
     174  * <code>d.setInnerHTML(e, "&lt;ul&gt;&lt;li&gt;List
     175  * Item&lt;/li&gt;&lt;/ul&gt;")</code> replaces all children paragraphs with
     176  * the list, and <code>d.setOuterHTML(e, "&lt;ul&gt;&lt;li&gt;List
     177  * Item&lt;/li&gt;&lt;/ul&gt;")</code> replaces the <code>DIV</code> element
     178  * itself.  In latter case the parent of the list is the
     179  * <code>BODY</code> element.
     180  *
     181  * <h3>Summary</h3>
     182  *
     183  * <p>The following table shows the example document and the results
     184  * of various methods described above.</p>
     185  *
     186  * <table class="plain">
     187  * <caption>HTML Content of example above</caption>
     188  *   <tr>
     189  *     <th>Example</th>
     190  *     <th><code>insertAfterStart</code></th>
     191  *     <th><code>insertBeforeEnd</code></th>
     192  *     <th><code>insertBeforeStart</code></th>
     193  *     <th><code>insertAfterEnd</code></th>
     194  *     <th><code>setInnerHTML</code></th>
     195  *     <th><code>setOuterHTML</code></th>
     196  *   </tr>
     197  *   <tr valign="top">
     198  *     <td style="white-space:nowrap">
     199  *       <div style="background-color: silver;">
     200  *         <p>Paragraph 1</p>
     201  *         <p>Paragraph 2</p>
     202  *       </div>
     203  *     </td>
     204  * <!--insertAfterStart-->
     205  *     <td style="white-space:nowrap">
     206  *       <div style="background-color: silver;">
     207  *         <ul style="color: red;">
     208  *           <li>List Item</li>
     209  *         </ul>
     210  *         <p>Paragraph 1</p>
     211  *         <p>Paragraph 2</p>
     212  *       </div>
     213  *     </td>
     214  * <!--insertBeforeEnd-->
     215  *     <td style="white-space:nowrap">
     216  *       <div style="background-color: silver;">
     217  *         <p>Paragraph 1</p>
     218  *         <p>Paragraph 2</p>
     219  *         <ul style="color: red;">
     220  *           <li>List Item</li>
     221  *         </ul>
     222  *       </div>
     223  *     </td>
     224  * <!--insertBeforeStart-->
     225  *     <td style="white-space:nowrap">
     226  *       <ul style="color: red;">
     227  *         <li>List Item</li>
     228  *       </ul>
     229  *       <div style="background-color: silver;">
     230  *         <p>Paragraph 1</p>
     231  *         <p>Paragraph 2</p>
     232  *       </div>
     233  *     </td>
     234  * <!--insertAfterEnd-->
     235  *     <td style="white-space:nowrap">
     236  *       <div style="background-color: silver;">
     237  *         <p>Paragraph 1</p>
     238  *         <p>Paragraph 2</p>
     239  *       </div>
     240  *       <ul style="color: red;">
     241  *         <li>List Item</li>
     242  *       </ul>
     243  *     </td>
     244  * <!--setInnerHTML-->
     245  *     <td style="white-space:nowrap">
     246  *       <div style="background-color: silver;">
     247  *         <ul style="color: red;">
     248  *           <li>List Item</li>
     249  *         </ul>
     250  *       </div>
     251  *     </td>
     252  * <!--setOuterHTML-->
     253  *     <td style="white-space:nowrap">
     254  *       <ul style="color: red;">
     255  *         <li>List Item</li>
     256  *       </ul>
     257  *     </td>
     258  *   </tr>
     259  * </table>
     260  *
     261  * <p><strong>Warning:</strong> Serialized objects of this class will
     262  * not be compatible with future Swing releases. The current
     263  * serialization support is appropriate for short term storage or RMI
     264  * between applications running the same version of Swing.  As of 1.4,
     265  * support for long term storage of all JavaBeans&trade;
     266  * has been added to the
     267  * <code>java.beans</code> package.  Please see {@link
     268  * java.beans.XMLEncoder}.</p>
     269  *
     270  * @author  Timothy Prinzing
     271  * @author  Scott Violet
     272  * @author  Sunita Mani
     273  */
     274 @SuppressWarnings("serial") // Same-version serialization only
     275 public class HTMLDocument extends DefaultStyledDocument {
     276     /**
     277      * Constructs an HTML document using the default buffer size
     278      * and a default <code>StyleSheet</code>.  This is a convenience
     279      * method for the constructor
     280      * <code>HTMLDocument(Content, StyleSheet)</code>.
     281      */
     282     public HTMLDocument() {
     283         this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet());
     284     }
     285
     286     /**
     287      * Constructs an HTML document with the default content
     288      * storage implementation and the specified style/attribute
     289      * storage mechanism.  This is a convenience method for the
     290      * constructor
     291      * <code>HTMLDocument(Content, StyleSheet)</code>.
     292      *
     293      * @param styles  the styles
     294      */
     295     public HTMLDocument(StyleSheet styles) {
     296         this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
     297     }
     298
     299     /**
     300      * Constructs an HTML document with the given content
     301      * storage implementation and the given style/attribute
     302      * storage mechanism.
     303      *
     304      * @param c  the container for the content
     305      * @param styles the styles
     306      */
     307     public HTMLDocument(Content c, StyleSheet styles) {
     308         super(c, styles);
     309     }
     310
     311     /**
     312      * Fetches the reader for the parser to use when loading the document
     313      * with HTML.  This is implemented to return an instance of
     314      * <code>HTMLDocument.HTMLReader</code>.
     315      * Subclasses can reimplement this
     316      * method to change how the document gets structured if desired.
     317      * (For example, to handle custom tags, or structurally represent character
     318      * style elements.)
     319      *
     320      * @param pos the starting position
     321      * @return the reader used by the parser to load the document
     322      */
     323     public HTMLEditorKit.ParserCallback getReader(int pos) {
     324         Object desc = getProperty(Document.StreamDescriptionProperty);
     325         if (desc instanceof URL) {
     326             setBase((URL)desc);
     327         }
     328         HTMLReader reader = new HTMLReader(pos);
     329         return reader;
     330     }
     331
     332     /**
     333      * Returns the reader for the parser to use to load the document
     334      * with HTML.  This is implemented to return an instance of
     335      * <code>HTMLDocument.HTMLReader</code>.
     336      * Subclasses can reimplement this
     337      * method to change how the document gets structured if desired.
     338      * (For example, to handle custom tags, or structurally represent character
     339      * style elements.)
     340      * <p>This is a convenience method for
     341      * <code>getReader(int, int, int, HTML.Tag, TRUE)</code>.
     342      *
     343      * @param pos the starting position
     344      * @param popDepth   the number of <code>ElementSpec.EndTagTypes</code>
     345      *          to generate before inserting
     346      * @param pushDepth  the number of <code>ElementSpec.StartTagTypes</code>
     347      *          with a direction of <code>ElementSpec.JoinNextDirection</code>
     348      *          that should be generated before inserting,
     349      *          but after the end tags have been generated
     350      * @param insertTag  the first tag to start inserting into document
     351      * @return the reader used by the parser to load the document
     352      */
     353     public HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
     354                                                   int pushDepth,
     355                                                   HTML.Tag insertTag) {
     356         return getReader(pos, popDepth, pushDepth, insertTag, true);
     357     }
     358
     359     /**
     360      * Fetches the reader for the parser to use to load the document
     361      * with HTML.  This is implemented to return an instance of
     362      * HTMLDocument.HTMLReader.  Subclasses can reimplement this
     363      * method to change how the document get structured if desired
     364      * (e.g. to handle custom tags, structurally represent character
     365      * style elements, etc.).
     366      *
     367      * @param popDepth   the number of <code>ElementSpec.EndTagTypes</code>
     368      *          to generate before inserting
     369      * @param pushDepth  the number of <code>ElementSpec.StartTagTypes</code>
     370      *          with a direction of <code>ElementSpec.JoinNextDirection</code>
     371      *          that should be generated before inserting,
     372      *          but after the end tags have been generated
     373      * @param insertTag  the first tag to start inserting into document
     374      * @param insertInsertTag  false if all the Elements after insertTag should
     375      *        be inserted; otherwise insertTag will be inserted
     376      * @return the reader used by the parser to load the document
     377      */
     378     HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
     379                                            int pushDepth,
     380                                            HTML.Tag insertTag,
     381                                            boolean insertInsertTag) {
     382         Object desc = getProperty(Document.StreamDescriptionProperty);
     383         if (desc instanceof URL) {
     384             setBase((URL)desc);
     385         }
     386         HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth,
     387                                            insertTag, insertInsertTag, false,
     388                                            true);
     389         return reader;
     390     }
     391
     392     /**
     393      * Returns the location to resolve relative URLs against.  By
     394      * default this will be the document's URL if the document
     395      * was loaded from a URL.  If a base tag is found and
     396      * can be parsed, it will be used as the base location.
     397      *
     398      * @return the base location
     399      */
     400     public URL getBase() {
     401         return base;
     402     }
     403
     404     /**
     405      * Sets the location to resolve relative URLs against.  By
     406      * default this will be the document's URL if the document
     407      * was loaded from a URL.  If a base tag is found and
     408      * can be parsed, it will be used as the base location.
     409      * <p>This also sets the base of the <code>StyleSheet</code>
     410      * to be <code>u</code> as well as the base of the document.
     411      *
     412      * @param u  the desired base URL
     413      */
     414     public void setBase(URL u) {
     415         base = u;
     416         getStyleSheet().setBase(u);
     417     }
     418
     419     /**
     420      * Inserts new elements in bulk.  This is how elements get created
     421      * in the document.  The parsing determines what structure is needed
     422      * and creates the specification as a set of tokens that describe the
     423      * edit while leaving the document free of a write-lock.  This method
     424      * can then be called in bursts by the reader to acquire a write-lock
     425      * for a shorter duration (i.e. while the document is actually being
     426      * altered).
     427      *
     428      * @param offset the starting offset
     429      * @param data the element data
     430      * @exception BadLocationException  if the given position does not
     431      *   represent a valid location in the associated document.
     432      */
     433     protected void insert(int offset, ElementSpec[] data) throws BadLocationException {
     434         super.insert(offset, data);
     435     }
     436
     437     /**
     438      * Updates document structure as a result of text insertion.  This
     439      * will happen within a write lock.  This implementation simply
     440      * parses the inserted content for line breaks and builds up a set
     441      * of instructions for the element buffer.
     442      *
     443      * @param chng a description of the document change
     444      * @param attr the attributes
     445      */
     446     protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
     447         if(attr == null) {
     448             attr = contentAttributeSet;
     449         }
     450
     451         // If this is the composed text element, merge the content attribute to it
     452         else if (attr.isDefined(StyleConstants.ComposedTextAttribute)) {
     453             ((MutableAttributeSet)attr).addAttributes(contentAttributeSet);
     454         }
     455
     456         if (attr.isDefined(IMPLIED_CR)) {
     457             ((MutableAttributeSet)attr).removeAttribute(IMPLIED_CR);
     458         }
     459
     460         super.insertUpdate(chng, attr);
     461     }
     462
     463     /**
     464      * Replaces the contents of the document with the given
     465      * element specifications.  This is called before insert if
     466      * the loading is done in bursts.  This is the only method called
     467      * if loading the document entirely in one burst.
     468      *
     469      * @param data  the new contents of the document
     470      */
     471     protected void create(ElementSpec[] data) {
     472         super.create(data);
     473     }
     474
     475     /**
     476      * Sets attributes for a paragraph.
     477      * <p>
     478      * This method is thread safe, although most Swing methods
     479      * are not. Please see
     480      * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
     481      * in Swing</A> for more information.
     482      *
     483      * @param offset the offset into the paragraph (must be at least 0)
     484      * @param length the number of characters affected (must be at least 0)
     485      * @param s the attributes
     486      * @param replace whether to replace existing attributes, or merge them
     487      */
     488     public void setParagraphAttributes(int offset, int length, AttributeSet s,
     489                                        boolean replace) {
     490         try {
     491             writeLock();
     492             // Make sure we send out a change for the length of the paragraph.
     493             int end = Math.min(offset + length, getLength());
     494             Element e = getParagraphElement(offset);
     495             offset = e.getStartOffset();
     496             e = getParagraphElement(end);
     497             length = Math.max(0, e.getEndOffset() - offset);
     498             DefaultDocumentEvent changes =
     499                 new DefaultDocumentEvent(offset, length,
     500                                          DocumentEvent.EventType.CHANGE);
     501             AttributeSet sCopy = s.copyAttributes();
     502             int lastEnd = Integer.MAX_VALUE;
     503             for (int pos = offset; pos <= end; pos = lastEnd) {
     504                 Element paragraph = getParagraphElement(pos);
     505                 if (lastEnd == paragraph.getEndOffset()) {
     506                     lastEnd++;
     507                 }
     508                 else {
     509                     lastEnd = paragraph.getEndOffset();
     510                 }
     511                 MutableAttributeSet attr =
     512                     (MutableAttributeSet) paragraph.getAttributes();
     513                 changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
     514                 if (replace) {
     515                     attr.removeAttributes(attr);
     516                 }
     517                 attr.addAttributes(s);
     518             }
     519             changes.end();
     520             fireChangedUpdate(changes);
     521             fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
     522         } finally {
     523             writeUnlock();
     524         }
     525     }
     526
     527     /**
     528      * Fetches the <code>StyleSheet</code> with the document-specific display
     529      * rules (CSS) that were specified in the HTML document itself.
     530      *
     531      * @return the <code>StyleSheet</code>
     532      */
     533     public StyleSheet getStyleSheet() {
     534         return (StyleSheet) getAttributeContext();
     535     }
     536
     537     /**
     538      * Fetches an iterator for the specified HTML tag.
     539      * This can be used for things like iterating over the
     540      * set of anchors contained, or iterating over the input
     541      * elements.
     542      *
     543      * @param t the requested <code>HTML.Tag</code>
     544      * @return the <code>Iterator</code> for the given HTML tag
     545      * @see javax.swing.text.html.HTML.Tag
     546      */
     547     public Iterator getIterator(HTML.Tag t) {
     548         if (t.isBlock()) {
     549             // TBD
     550             return null;
     551         }
     552         return new LeafIterator(t, this);
     553     }
     554
     555     /**
     556      * Creates a document leaf element that directly represents
     557      * text (doesn't have any children).  This is implemented
     558      * to return an element of type
     559      * <code>HTMLDocument.RunElement</code>.
     560      *
     561      * @param parent the parent element
     562      * @param a the attributes for the element
     563      * @param p0 the beginning of the range (must be at least 0)
     564      * @param p1 the end of the range (must be at least p0)
     565      * @return the new element
     566      */
     567     protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
     568         return new RunElement(parent, a, p0, p1);
     569     }
     570
     571     /**
     572      * Creates a document branch element, that can contain other elements.
     573      * This is implemented to return an element of type
     574      * <code>HTMLDocument.BlockElement</code>.
     575      *
     576      * @param parent the parent element
     577      * @param a the attributes
     578      * @return the element
     579      */
     580     protected Element createBranchElement(Element parent, AttributeSet a) {
     581         return new BlockElement(parent, a);
     582     }
     583
     584     /**
     585      * Creates the root element to be used to represent the
     586      * default document structure.
     587      *
     588      * @return the element base
     589      */
     590     protected AbstractElement createDefaultRoot() {
     591         // grabs a write-lock for this initialization and
     592         // abandon it during initialization so in normal
     593         // operation we can detect an illegitimate attempt
     594         // to mutate attributes.
     595         writeLock();
     596         MutableAttributeSet a = new SimpleAttributeSet();
     597         a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.HTML);
     598         BlockElement html = new BlockElement(null, a.copyAttributes());
     599         a.removeAttributes(a);
     600         a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.BODY);
     601         BlockElement body = new BlockElement(html, a.copyAttributes());
     602         a.removeAttributes(a);
     603         a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.P);
     604         getStyleSheet().addCSSAttributeFromHTML(a, CSS.Attribute.MARGIN_TOP, "0");
     605         BlockElement paragraph = new BlockElement(body, a.copyAttributes());
     606         a.removeAttributes(a);
     607         a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
     608         RunElement brk = new RunElement(paragraph, a, 0, 1);
     609         Element[] buff = new Element[1];
     610         buff[0] = brk;
     611         paragraph.replace(0, 0, buff);
     612         buff[0] = paragraph;
     613         body.replace(0, 0, buff);
     614         buff[0] = body;
     615         html.replace(0, 0, buff);
     616         writeUnlock();
     617         return html;
     618     }
     619
     620     /**
     621      * Sets the number of tokens to buffer before trying to update
     622      * the documents element structure.
     623      *
     624      * @param n  the number of tokens to buffer
     625      */
     626     public void setTokenThreshold(int n) {
     627         putProperty(TokenThreshold, n);
     628     }
     629
     630     /**
     631      * Gets the number of tokens to buffer before trying to update
     632      * the documents element structure.  The default value is
     633      * <code>Integer.MAX_VALUE</code>.
     634      *
     635      * @return the number of tokens to buffer
     636      */
     637     public int getTokenThreshold() {
     638         Integer i = (Integer) getProperty(TokenThreshold);
     639         if (i != null) {
     640             return i.intValue();
     641         }
     642         return Integer.MAX_VALUE;
     643     }
     644
     645     /**
     646      * Determines how unknown tags are handled by the parser.
     647      * If set to true, unknown
     648      * tags are put in the model, otherwise they are dropped.
     649      *
     650      * @param preservesTags  true if unknown tags should be
     651      *          saved in the model, otherwise tags are dropped
     652      * @see javax.swing.text.html.HTML.Tag
     653      */
     654     public void setPreservesUnknownTags(boolean preservesTags) {
     655         preservesUnknownTags = preservesTags;
     656     }
     657
     658     /**
     659      * Returns the behavior the parser observes when encountering
     660      * unknown tags.
     661      *
     662      * @see javax.swing.text.html.HTML.Tag
     663      * @return true if unknown tags are to be preserved when parsing
     664      */
     665     public boolean getPreservesUnknownTags() {
     666         return preservesUnknownTags;
     667     }
     668
     669     /**
     670      * Processes <code>HyperlinkEvents</code> that
     671      * are generated by documents in an HTML frame.
     672      * The <code>HyperlinkEvent</code> type, as the parameter suggests,
     673      * is <code>HTMLFrameHyperlinkEvent</code>.
     674      * In addition to the typical information contained in a
     675      * <code>HyperlinkEvent</code>,
     676      * this event contains the element that corresponds to the frame in
     677      * which the click happened (the source element) and the
     678      * target name.  The target name has 4 possible values:
     679      * <ul>
     680      * <li>  _self
     681      * <li>  _parent
     682      * <li>  _top
     683      * <li>  a named frame
     684      * </ul>
     685      *
     686      * If target is _self, the action is to change the value of the
     687      * <code>HTML.Attribute.SRC</code> attribute and fires a
     688      * <code>ChangedUpdate</code> event.
     689      *<p>
     690      * If the target is _parent, then it deletes the parent element,
     691      * which is a &lt;FRAMESET&gt; element, and inserts a new &lt;FRAME&gt;
     692      * element, and sets its <code>HTML.Attribute.SRC</code> attribute
     693      * to have a value equal to the destination URL and fire a
     694      * <code>RemovedUpdate</code> and <code>InsertUpdate</code>.
     695      *<p>
     696      * If the target is _top, this method does nothing. In the implementation
     697      * of the view for a frame, namely the <code>FrameView</code>,
     698      * the processing of _top is handled.  Given that _top implies
     699      * replacing the entire document, it made sense to handle this outside
     700      * of the document that it will replace.
     701      *<p>
     702      * If the target is a named frame, then the element hierarchy is searched
     703      * for an element with a name equal to the target, its
     704      * <code>HTML.Attribute.SRC</code> attribute is updated and a
     705      * <code>ChangedUpdate</code> event is fired.
     706      *
     707      * @param e the event
     708      */
     709     public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent e) {
     710         String frameName = e.getTarget();
     711         Element element = e.getSourceElement();
     712         String urlStr = e.getURL().toString();
     713
     714         if (frameName.equals("_self")) {
     715             /*
     716               The source and destination elements
     717               are the same.
     718             */
     719             updateFrame(element, urlStr);
     720         } else if (frameName.equals("_parent")) {
     721             /*
     722               The destination is the parent of the frame.
     723             */
     724             updateFrameSet(element.getParentElement(), urlStr);
     725         } else {
     726             /*
     727               locate a named frame
     728             */
     729             Element targetElement = findFrame(frameName);
     730             if (targetElement != null) {
     731                 updateFrame(targetElement, urlStr);
     732             }
     733         }
     734     }
     735
     736
     737     /**
     738      * Searches the element hierarchy for an FRAME element
     739      * that has its name attribute equal to the <code>frameName</code>.
     740      *
     741      * @param frameName
     742      * @return the element whose NAME attribute has a value of
     743      *          <code>frameName</code>; returns <code>null</code>
     744      *          if not found
     745      */
     746     private Element findFrame(String frameName) {
     747         ElementIterator it = new ElementIterator(this);
     748         Element next;
     749
     750         while ((next = it.next()) != null) {
     751             AttributeSet attr = next.getAttributes();
     752             if (matchNameAttribute(attr, HTML.Tag.FRAME)) {
     753                 String frameTarget = (String)attr.getAttribute(HTML.Attribute.NAME);
     754                 if (frameTarget != null && frameTarget.equals(frameName)) {
     755                     break;
     756                 }
     757             }
     758         }
     759         return next;
     760     }
     761
     762     /**
     763      * Returns true if <code>StyleConstants.NameAttribute</code> is
     764      * equal to the tag that is passed in as a parameter.
     765      *
     766      * @param attr the attributes to be matched
     767      * @param tag the value to be matched
     768      * @return true if there is a match, false otherwise
     769      * @see javax.swing.text.html.HTML.Attribute
     770      */
     771     static boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
     772         Object o = attr.getAttribute(StyleConstants.NameAttribute);
     773         if (o instanceof HTML.Tag) {
     774             HTML.Tag name = (HTML.Tag) o;
     775             if (name == tag) {
     776                 return true;
     777             }
     778         }
     779         return false;
     780     }
     781
     782     /**
     783      * Replaces a frameset branch Element with a frame leaf element.
     784      *
     785      * @param element the frameset element to remove
     786      * @param url     the value for the SRC attribute for the
     787      *                new frame that will replace the frameset
     788      */
     789     private void updateFrameSet(Element element, String url) {
     790         try {
     791             int startOffset = element.getStartOffset();
     792             int endOffset = Math.min(getLength(), element.getEndOffset());
     793             String html = "<frame";
     794             if (url != null) {
     795                 html += " src=\"" + url + "\"";
     796             }
     797             html += ">";
     798             installParserIfNecessary();
     799             setOuterHTML(element, html);
     800         } catch (BadLocationException e1) {
     801             // Should handle this better
     802         } catch (IOException ioe) {
     803             // Should handle this better
     804         }
     805     }
     806
     807
     808     /**
     809      * Updates the Frame elements <code>HTML.Attribute.SRC attribute</code>
     810      * and fires a <code>ChangedUpdate</code> event.
     811      *
     812      * @param element a FRAME element whose SRC attribute will be updated
     813      * @param url     a string specifying the new value for the SRC attribute
     814      */
     815     private void updateFrame(Element element, String url) {
     816
     817         try {
     818             writeLock();
     819             DefaultDocumentEvent changes = new DefaultDocumentEvent(element.getStartOffset(),
     820                                                                     1,
     821                                                                     DocumentEvent.EventType.CHANGE);
     822             AttributeSet sCopy = element.getAttributes().copyAttributes();
     823             MutableAttributeSet attr = (MutableAttributeSet) element.getAttributes();
     824             changes.addEdit(new AttributeUndoableEdit(element, sCopy, false));
     825             attr.removeAttribute(HTML.Attribute.SRC);
     826             attr.addAttribute(HTML.Attribute.SRC, url);
     827             changes.end();
     828             fireChangedUpdate(changes);
     829             fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
     830         } finally {
     831             writeUnlock();
     832         }
     833     }
     834
     835
     836     /**
     837      * Returns true if the document will be viewed in a frame.
     838      * @return true if document will be viewed in a frame, otherwise false
     839      */
     840     boolean isFrameDocument() {
     841         return frameDocument;
     842     }
     843
     844     /**
     845      * Sets a boolean state about whether the document will be
     846      * viewed in a frame.
     847      * @param frameDoc  true if the document will be viewed in a frame,
     848      *          otherwise false
     849      */
     850     void setFrameDocumentState(boolean frameDoc) {
     851         this.frameDocument = frameDoc;
     852     }
     853
     854     /**
     855      * Adds the specified map, this will remove a Map that has been
     856      * previously registered with the same name.
     857      *
     858      * @param map  the <code>Map</code> to be registered
     859      */
     860     void addMap(Map map) {
     861         String     name = map.getName();
     862
     863         if (name != null) {
     864             Object     maps = getProperty(MAP_PROPERTY);
     865
     866             if (maps == null) {
     867                 maps = new Hashtable<>(11);
     868                 putProperty(MAP_PROPERTY, maps);
     869             }
     870             if (maps instanceof Hashtable) {
     871                 @SuppressWarnings("unchecked")
     872                 Hashtable<Object, Object> tmp = (Hashtable)maps;
     873                 tmp.put("#" + name, map);
     874             }
     875         }
     876     }
     877
     878     /**
     879      * Removes a previously registered map.
     880      * @param map the <code>Map</code> to be removed
     881      */
     882     void removeMap(Map map) {
     883         String     name = map.getName();
     884
     885         if (name != null) {
     886             Object     maps = getProperty(MAP_PROPERTY);
     887
     888             if (maps instanceof Hashtable) {
     889                 ((Hashtable)maps).remove("#" + name);
     890             }
     891         }
     892     }
     893
     894     /**
     895      * Returns the Map associated with the given name.
     896      * @param name the name of the desired <code>Map</code>
     897      * @return the <code>Map</code> or <code>null</code> if it can't
     898      *          be found, or if <code>name</code> is <code>null</code>
     899      */
     900     Map getMap(String name) {
     901         if (name != null) {
     902             Object     maps = getProperty(MAP_PROPERTY);
     903
     904             if (maps != null && (maps instanceof Hashtable)) {
     905                 return (Map)((Hashtable)maps).get(name);
     906             }
     907         }
     908         return null;
     909     }
     910
     911     /**
     912      * Returns an <code>Enumeration</code> of the possible Maps.
     913      * @return the enumerated list of maps, or <code>null</code>
     914      *          if the maps are not an instance of <code>Hashtable</code>
     915      */
     916     Enumeration<Object> getMaps() {
     917         Object     maps = getProperty(MAP_PROPERTY);
     918
     919         if (maps instanceof Hashtable) {
     920             @SuppressWarnings("unchecked")
     921             Hashtable<Object, Object> tmp = (Hashtable) maps;
     922             return tmp.elements();
     923         }
     924         return null;
     925     }
     926
     927     /**
     928      * Sets the content type language used for style sheets that do not
     929      * explicitly specify the type. The default is text/css.
     930      * @param contentType  the content type language for the style sheets
     931      */
     932     /* public */
     933     void setDefaultStyleSheetType(String contentType) {
     934         putProperty(StyleType, contentType);
     935     }
     936
     937     /**
     938      * Returns the content type language used for style sheets. The default
     939      * is text/css.
     940      * @return the content type language used for the style sheets
     941      */
     942     /* public */
     943     String getDefaultStyleSheetType() {
     944         String retValue = (String)getProperty(StyleType);
     945         if (retValue == null) {
     946             return "text/css";
     947         }
     948         return retValue;
     949     }
     950
     951     /**
     952      * Sets the parser that is used by the methods that insert html
     953      * into the existing document, such as <code>setInnerHTML</code>,
     954      * and <code>setOuterHTML</code>.
     955      * <p>
     956      * <code>HTMLEditorKit.createDefaultDocument</code> will set the parser
     957      * for you. If you create an <code>HTMLDocument</code> by hand,
     958      * be sure and set the parser accordingly.
     959      * @param parser the parser to be used for text insertion
     960      *
     961      * @since 1.3
     962      */
     963     public void setParser(HTMLEditorKit.Parser parser) {
     964         this.parser = parser;
     965         putProperty("__PARSER__", null);
     966     }
     967
     968     /**
     969      * Returns the parser that is used when inserting HTML into the existing
     970      * document.
     971      * @return the parser used for text insertion
     972      *
     973      * @since 1.3
     974      */
     975     public HTMLEditorKit.Parser getParser() {
     976         Object p = getProperty("__PARSER__");
     977
     978         if (p instanceof HTMLEditorKit.Parser) {
     979             return (HTMLEditorKit.Parser)p;
     980         }
     981         return parser;
     982     }
     983
     984     /**
     985      * Replaces the children of the given element with the contents
     986      * specified as an HTML string.
     987      *
     988      * <p>This will be seen as at least two events, n inserts followed by
     989      * a remove.</p>
     990      *
     991      * <p>Consider the following structure (the <code>elem</code>
     992      * parameter is <b>in bold</b>).</p>
     993      *
     994      * <pre>
     995      *     &lt;body&gt;
     996      *       |
     997      *     <b>&lt;div&gt;</b>
     998      *      /  \
     999      *    &lt;p&gt;   &lt;p&gt;
    1000      * </pre>
    1001      *
    1002      * <p>Invoking <code>setInnerHTML(elem, "&lt;ul&gt;&lt;li&gt;")</code>
    1003      * results in the following structure (new elements are <span
    1004      * style="color: red;">in red</span>).</p>
    1005      *
    1006      * <pre>
    1007      *     &lt;body&gt;
    1008      *       |
    1009      *     <b>&lt;div&gt;</b>
    1010      *         \
    1011      *         <span style="color: red;">&lt;ul&gt;</span>
    1012      *           \
    1013      *           <span style="color: red;">&lt;li&gt;</span>
    1014      * </pre>
    1015      *
    1016      * <p>Parameter <code>elem</code> must not be a leaf element,
    1017      * otherwise an <code>IllegalArgumentException</code> is thrown.
    1018      * If either <code>elem</code> or <code>htmlText</code> parameter
    1019      * is <code>null</code>, no changes are made to the document.</p>
    1020      *
    1021      * <p>For this to work correctly, the document must have an
    1022      * <code>HTMLEditorKit.Parser</code> set. This will be the case
    1023      * if the document was created from an HTMLEditorKit via the
    1024      * <code>createDefaultDocument</code> method.</p>
    1025      *
    1026      * @param elem the branch element whose children will be replaced
    1027      * @param htmlText the string to be parsed and assigned to <code>elem</code>
    1028      * @throws IllegalArgumentException if <code>elem</code> is a leaf
    1029      * @throws IllegalStateException if an <code>HTMLEditorKit.Parser</code>
    1030      *         has not been defined
    1031      * @throws BadLocationException if replacement is impossible because of
    1032      *         a structural issue
    1033      * @throws IOException if an I/O exception occurs
    1034      * @since 1.3
    1035      */
    1036     public void setInnerHTML(Element elem, String htmlText) throws
    1037                              BadLocationException, IOException {
    1038         verifyParser();
    1039         if (elem != null && elem.isLeaf()) {
    1040             throw new IllegalArgumentException
    1041                 ("Can not set inner HTML of a leaf");
    1042         }
    1043         if (elem != null && htmlText != null) {
    1044             int oldCount = elem.getElementCount();
    1045             int insertPosition = elem.getStartOffset();
    1046             insertHTML(elem, elem.getStartOffset(), htmlText, true);
    1047             if (elem.getElementCount() > oldCount) {
    1048                 // Elements were inserted, do the cleanup.
    1049                 removeElements(elem, elem.getElementCount() - oldCount,
    1050                                oldCount);
    1051             }
    1052         }
    1053     }
    1054
    1055     /**
    1056      * Replaces the given element in the parent with the contents
    1057      * specified as an HTML string.
    1058      *
    1059      * <p>This will be seen as at least two events, n inserts followed by
    1060      * a remove.</p>
    1061      *
    1062      * <p>When replacing a leaf this will attempt to make sure there is
    1063      * a newline present if one is needed. This may result in an additional
    1064      * element being inserted. Consider, if you were to replace a character
    1065      * element that contained a newline with &lt;img&gt; this would create
    1066      * two elements, one for the image, and one for the newline.</p>
    1067      *
    1068      * <p>If you try to replace the element at length you will most
    1069      * likely end up with two elements, eg
    1070      * <code>setOuterHTML(getCharacterElement (getLength()),
    1071      * "blah")</code> will result in two leaf elements at the end, one
    1072      * representing 'blah', and the other representing the end
    1073      * element.</p>
    1074      *
    1075      * <p>Consider the following structure (the <code>elem</code>
    1076      * parameter is <b>in bold</b>).</p>
    1077      *
    1078      * <pre>
    1079      *     &lt;body&gt;
    1080      *       |
    1081      *     <b>&lt;div&gt;</b>
    1082      *      /  \
    1083      *    &lt;p&gt;   &lt;p&gt;
    1084      * </pre>
    1085      *
    1086      * <p>Invoking <code>setOuterHTML(elem, "&lt;ul&gt;&lt;li&gt;")</code>
    1087      * results in the following structure (new elements are <span
    1088      * style="color: red;">in red</span>).</p>
    1089      *
    1090      * <pre>
    1091      *    &lt;body&gt;
    1092      *      |
    1093      *     <span style="color: red;">&lt;ul&gt;</span>
    1094      *       \
    1095      *       <span style="color: red;">&lt;li&gt;</span>
    1096      * </pre>
    1097      *
    1098      * <p>If either <code>elem</code> or <code>htmlText</code>
    1099      * parameter is <code>null</code>, no changes are made to the
    1100      * document.</p>
    1101      *
    1102      * <p>For this to work correctly, the document must have an
    1103      * HTMLEditorKit.Parser set. This will be the case if the document
    1104      * was created from an HTMLEditorKit via the
    1105      * <code>createDefaultDocument</code> method.</p>
    1106      *
    1107      * @param elem the element to replace
    1108      * @param htmlText the string to be parsed and inserted in place of <code>elem</code>
    1109      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
    1110      *         been set
    1111      * @throws BadLocationException if replacement is impossible because of
    1112      *         a structural issue
    1113      * @throws IOException if an I/O exception occurs
    1114      * @since 1.3
    1115      */
    1116     public void setOuterHTML(Element elem, String htmlText) throws
    1117                             BadLocationException, IOException {
    1118         verifyParser();
    1119         if (elem != null && elem.getParentElement() != null &&
    1120             htmlText != null) {
    1121             int start = elem.getStartOffset();
    1122             int end = elem.getEndOffset();
    1123             int startLength = getLength();
    1124             // We don't want a newline if elem is a leaf, and doesn't contain
    1125             // a newline.
    1126             boolean wantsNewline = !elem.isLeaf();
    1127             if (!wantsNewline && (end > startLength ||
    1128                                  getText(end - 1, 1).charAt(0) == NEWLINE[0])){
    1129                 wantsNewline = true;
    1130             }
    1131             Element parent = elem.getParentElement();
    1132             int oldCount = parent.getElementCount();
    1133             insertHTML(parent, start, htmlText, wantsNewline);
    1134             // Remove old.
    1135             int newLength = getLength();
    1136             if (oldCount != parent.getElementCount()) {
    1137                 int removeIndex = parent.getElementIndex(start + newLength -
    1138                                                          startLength);
    1139                 removeElements(parent, removeIndex, 1);
    1140             }
    1141         }
    1142     }
    1143
    1144     /**
    1145      * Inserts the HTML specified as a string at the start
    1146      * of the element.
    1147      *
    1148      * <p>Consider the following structure (the <code>elem</code>
    1149      * parameter is <b>in bold</b>).</p>
    1150      *
    1151      * <pre>
    1152      *     &lt;body&gt;
    1153      *       |
    1154      *     <b>&lt;div&gt;</b>
    1155      *      /  \
    1156      *    &lt;p&gt;   &lt;p&gt;
    1157      * </pre>
    1158      *
    1159      * <p>Invoking <code>insertAfterStart(elem,
    1160      * "&lt;ul&gt;&lt;li&gt;")</code> results in the following structure
    1161      * (new elements are <span style="color: red;">in red</span>).</p>
    1162      *
    1163      * <pre>
    1164      *        &lt;body&gt;
    1165      *          |
    1166      *        <b>&lt;div&gt;</b>
    1167      *       /  |  \
    1168      *    <span style="color: red;">&lt;ul&gt;</span> &lt;p&gt; &lt;p&gt;
    1169      *     /
    1170      *  <span style="color: red;">&lt;li&gt;</span>
    1171      * </pre>
    1172      *
    1173      * <p>Unlike the <code>insertBeforeStart</code> method, new
    1174      *  elements become <em>children</em> of the specified element,
    1175      *  not siblings.</p>
    1176      *
    1177      * <p>Parameter <code>elem</code> must not be a leaf element,
    1178      * otherwise an <code>IllegalArgumentException</code> is thrown.
    1179      * If either <code>elem</code> or <code>htmlText</code> parameter
    1180      * is <code>null</code>, no changes are made to the document.</p>
    1181      *
    1182      * <p>For this to work correctly, the document must have an
    1183      * <code>HTMLEditorKit.Parser</code> set. This will be the case
    1184      * if the document was created from an HTMLEditorKit via the
    1185      * <code>createDefaultDocument</code> method.</p>
    1186      *
    1187      * @param elem the branch element to be the root for the new text
    1188      * @param htmlText the string to be parsed and assigned to <code>elem</code>
    1189      * @throws IllegalArgumentException if <code>elem</code> is a leaf
    1190      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
    1191      *         been set on the document
    1192      * @throws BadLocationException if insertion is impossible because of
    1193      *         a structural issue
    1194      * @throws IOException if an I/O exception occurs
    1195      * @since 1.3
    1196      */
    1197     public void insertAfterStart(Element elem, String htmlText) throws
    1198                                  BadLocationException, IOException {
    1199         verifyParser();
    1200
    1201         if (elem == null || htmlText == null) {
    1202             return;
    1203         }
    1204
    1205         if (elem.isLeaf()) {
    1206             throw new IllegalArgumentException
    1207                 ("Can not insert HTML after start of a leaf");
    1208         }
    1209         insertHTML(elem, elem.getStartOffset(), htmlText, false);
    1210     }
    1211
    1212     /**
    1213      * Inserts the HTML specified as a string at the end of
    1214      * the element.
    1215      *
    1216      * <p> If <code>elem</code>'s children are leaves, and the
    1217      * character at a <code>elem.getEndOffset() - 1</code> is a newline,
    1218      * this will insert before the newline so that there isn't text after
    1219      * the newline.</p>
    1220      *
    1221      * <p>Consider the following structure (the <code>elem</code>
    1222      * parameter is <b>in bold</b>).</p>
    1223      *
    1224      * <pre>
    1225      *     &lt;body&gt;
    1226      *       |
    1227      *     <b>&lt;div&gt;</b>
    1228      *      /  \
    1229      *    &lt;p&gt;   &lt;p&gt;
    1230      * </pre>
    1231      *
    1232      * <p>Invoking <code>insertBeforeEnd(elem, "&lt;ul&gt;&lt;li&gt;")</code>
    1233      * results in the following structure (new elements are <span
    1234      * style="color: red;">in red</span>).</p>
    1235      *
    1236      * <pre>
    1237      *        &lt;body&gt;
    1238      *          |
    1239      *        <b>&lt;div&gt;</b>
    1240      *       /  |  \
    1241      *     &lt;p&gt; &lt;p&gt; <span style="color: red;">&lt;ul&gt;</span>
    1242      *               \
    1243      *               <span style="color: red;">&lt;li&gt;</span>
    1244      * </pre>
    1245      *
    1246      * <p>Unlike the <code>insertAfterEnd</code> method, new elements
    1247      * become <em>children</em> of the specified element, not
    1248      * siblings.</p>
    1249      *
    1250      * <p>Parameter <code>elem</code> must not be a leaf element,
    1251      * otherwise an <code>IllegalArgumentException</code> is thrown.
    1252      * If either <code>elem</code> or <code>htmlText</code> parameter
    1253      * is <code>null</code>, no changes are made to the document.</p>
    1254      *
    1255      * <p>For this to work correctly, the document must have an
    1256      * <code>HTMLEditorKit.Parser</code> set. This will be the case
    1257      * if the document was created from an HTMLEditorKit via the
    1258      * <code>createDefaultDocument</code> method.</p>
    1259      *
    1260      * @param elem the element to be the root for the new text
    1261      * @param htmlText the string to be parsed and assigned to <code>elem</code>
    1262      * @throws IllegalArgumentException if <code>elem</code> is a leaf
    1263      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
    1264      *         been set on the document
    1265      * @throws BadLocationException if insertion is impossible because of
    1266      *         a structural issue
    1267      * @throws IOException if an I/O exception occurs
    1268      * @since 1.3
    1269      */
    1270     public void insertBeforeEnd(Element elem, String htmlText) throws
    1271                                 BadLocationException, IOException {
    1272         verifyParser();
    1273         if (elem != null && elem.isLeaf()) {
    1274             throw new IllegalArgumentException
    1275                 ("Can not set inner HTML before end of leaf");
    1276         }
    1277         if (elem != null) {
    1278             int offset = elem.getEndOffset();
    1279             if (elem.getElement(elem.getElementIndex(offset - 1)).isLeaf() &&
    1280                 getText(offset - 1, 1).charAt(0) == NEWLINE[0]) {
    1281                 offset--;
    1282             }
    1283             insertHTML(elem, offset, htmlText, false);
    1284         }
    1285     }
    1286
    1287     /**
    1288      * Inserts the HTML specified as a string before the start of
    1289      * the given element.
    1290      *
    1291      * <p>Consider the following structure (the <code>elem</code>
    1292      * parameter is <b>in bold</b>).</p>
    1293      *
    1294      * <pre>
    1295      *     &lt;body&gt;
    1296      *       |
    1297      *     <b>&lt;div&gt;</b>
    1298      *      /  \
    1299      *    &lt;p&gt;   &lt;p&gt;
    1300      * </pre>
    1301      *
    1302      * <p>Invoking <code>insertBeforeStart(elem,
    1303      * "&lt;ul&gt;&lt;li&gt;")</code> results in the following structure
    1304      * (new elements are <span style="color: red;">in red</span>).</p>
    1305      *
    1306      * <pre>
    1307      *        &lt;body&gt;
    1308      *         /  \
    1309      *      <span style="color: red;">&lt;ul&gt;</span> <b>&lt;div&gt;</b>
    1310      *       /    /  \
    1311      *     <span style="color: red;">&lt;li&gt;</span> &lt;p&gt;  &lt;p&gt;
    1312      * </pre>
    1313      *
    1314      * <p>Unlike the <code>insertAfterStart</code> method, new
    1315      * elements become <em>siblings</em> of the specified element, not
    1316      * children.</p>
    1317      *
    1318      * <p>If either <code>elem</code> or <code>htmlText</code>
    1319      * parameter is <code>null</code>, no changes are made to the
    1320      * document.</p>
    1321      *
    1322      * <p>For this to work correctly, the document must have an
    1323      * <code>HTMLEditorKit.Parser</code> set. This will be the case
    1324      * if the document was created from an HTMLEditorKit via the
    1325      * <code>createDefaultDocument</code> method.</p>
    1326      *
    1327      * @param elem the element the content is inserted before
    1328      * @param htmlText the string to be parsed and inserted before <code>elem</code>
    1329      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
    1330      *         been set on the document
    1331      * @throws BadLocationException if insertion is impossible because of
    1332      *         a structural issue
    1333      * @throws IOException if an I/O exception occurs
    1334      * @since 1.3
    1335      */
    1336     public void insertBeforeStart(Element elem, String htmlText) throws
    1337                                   BadLocationException, IOException {
    1338         verifyParser();
    1339         if (elem != null) {
    1340             Element parent = elem.getParentElement();
    1341
    1342             if (parent != null) {
    1343                 insertHTML(parent, elem.getStartOffset(), htmlText, false);
    1344             }
    1345         }
    1346     }
    1347
    1348     /**
    1349      * Inserts the HTML specified as a string after the end of the
    1350      * given element.
    1351      *
    1352      * <p>Consider the following structure (the <code>elem</code>
    1353      * parameter is <b>in bold</b>).</p>
    1354      *
    1355      * <pre>
    1356      *     &lt;body&gt;
    1357      *       |
    1358      *     <b>&lt;div&gt;</b>
    1359      *      /  \
    1360      *    &lt;p&gt;   &lt;p&gt;
    1361      * </pre>
    1362      *
    1363      * <p>Invoking <code>insertAfterEnd(elem, "&lt;ul&gt;&lt;li&gt;")</code>
    1364      * results in the following structure (new elements are <span
    1365      * style="color: red;">in red</span>).</p>
    1366      *
    1367      * <pre>
    1368      *        &lt;body&gt;
    1369      *         /  \
    1370      *      <b>&lt;div&gt;</b> <span style="color: red;">&lt;ul&gt;</span>
    1371      *       / \    \
    1372      *     &lt;p&gt; &lt;p&gt;  <span style="color: red;">&lt;li&gt;</span>
    1373      * </pre>
    1374      *
    1375      * <p>Unlike the <code>insertBeforeEnd</code> method, new elements
    1376      * become <em>siblings</em> of the specified element, not
    1377      * children.</p>
    1378      *
    1379      * <p>If either <code>elem</code> or <code>htmlText</code>
    1380      * parameter is <code>null</code>, no changes are made to the
    1381      * document.</p>
    1382      *
    1383      * <p>For this to work correctly, the document must have an
    1384      * <code>HTMLEditorKit.Parser</code> set. This will be the case
    1385      * if the document was created from an HTMLEditorKit via the
    1386      * <code>createDefaultDocument</code> method.</p>
    1387      *
    1388      * @param elem the element the content is inserted after
    1389      * @param htmlText the string to be parsed and inserted after <code>elem</code>
    1390      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
    1391      *         been set on the document
    1392      * @throws BadLocationException if insertion is impossible because of
    1393      *         a structural issue
    1394      * @throws IOException if an I/O exception occurs
    1395      * @since 1.3
    1396      */
    1397     public void insertAfterEnd(Element elem, String htmlText) throws
    1398                                BadLocationException, IOException {
    1399         verifyParser();
    1400         if (elem != null) {
    1401             Element parent = elem.getParentElement();
    1402
    1403             if (parent != null) {
    1404                 // If we are going to insert the string into the body
    1405                 // section, it is necessary to set the corrsponding flag.
    1406                 if (HTML.Tag.BODY.name.equals(parent.getName())) {
    1407                     insertInBody = true;
    1408                 }
    1409                 int offset = elem.getEndOffset();
    1410                 if (offset > (getLength() + 1)) {
    1411                     offset--;
    1412                 }
    1413                 else if (elem.isLeaf() && getText(offset - 1, 1).
    1414                     charAt(0) == NEWLINE[0]) {
    1415                     offset--;
    1416                 }
    1417                 insertHTML(parent, offset, htmlText, false);
    1418                 // Cleanup the flag, if any.
    1419                 if (insertInBody) {
    1420                     insertInBody = false;
    1421                 }
    1422             }
    1423         }
    1424     }
    1425
    1426     /**
    1427      * Returns the element that has the given id <code>Attribute</code>.
    1428      * If the element can't be found, <code>null</code> is returned.
    1429      * Note that this method works on an <code>Attribute</code>,
    1430      * <i>not</i> a character tag.  In the following HTML snippet:
    1431      * <code>&lt;a id="HelloThere"&gt;</code> the attribute is
    1432      * 'id' and the character tag is 'a'.
    1433      * This is a convenience method for
    1434      * <code>getElement(RootElement, HTML.Attribute.id, id)</code>.
    1435      * This is not thread-safe.
    1436      *
    1437      * @param id  the string representing the desired <code>Attribute</code>
    1438      * @return the element with the specified <code>Attribute</code>
    1439      *          or <code>null</code> if it can't be found,
    1440      *          or <code>null</code> if <code>id</code> is <code>null</code>
    1441      * @see javax.swing.text.html.HTML.Attribute
    1442      * @since 1.3
    1443      */
    1444     public Element getElement(String id) {
    1445         if (id == null) {
    1446             return null;
    1447         }
    1448         return getElement(getDefaultRootElement(), HTML.Attribute.ID, id,
    1449                           true);
    1450     }
    1451
    1452     /**
    1453      * Returns the child element of <code>e</code> that contains the
    1454      * attribute, <code>attribute</code> with value <code>value</code>, or
    1455      * <code>null</code> if one isn't found. This is not thread-safe.
    1456      *
    1457      * @param e the root element where the search begins
    1458      * @param attribute the desired <code>Attribute</code>
    1459      * @param value the values for the specified <code>Attribute</code>
    1460      * @return the element with the specified <code>Attribute</code>
    1461      *          and the specified <code>value</code>, or <code>null</code>
    1462      *          if it can't be found
    1463      * @see javax.swing.text.html.HTML.Attribute
    1464      * @since 1.3
    1465      */
    1466     public Element getElement(Element e, Object attribute, Object value) {
    1467         return getElement(e, attribute, value, true);
    1468     }
    1469
    1470     /**
    1471      * Returns the child element of <code>e</code> that contains the
    1472      * attribute, <code>attribute</code> with value <code>value</code>, or
    1473      * <code>null</code> if one isn't found. This is not thread-safe.
    1474      * <p>
    1475      * If <code>searchLeafAttributes</code> is true, and <code>e</code> is
    1476      * a leaf, any attributes that are instances of <code>HTML.Tag</code>
    1477      * with a value that is an <code>AttributeSet</code> will also be checked.
    1478      *
    1479      * @param e the root element where the search begins
    1480      * @param attribute the desired <code>Attribute</code>
    1481      * @param value the values for the specified <code>Attribute</code>
    1482      * @return the element with the specified <code>Attribute</code>
    1483      *          and the specified <code>value</code>, or <code>null</code>
    1484      *          if it can't be found
    1485      * @see javax.swing.text.html.HTML.Attribute
    1486      */
    1487     private Element getElement(Element e, Object attribute, Object value,
    1488                                boolean searchLeafAttributes) {
    1489         AttributeSet attr = e.getAttributes();
    1490
    1491         if (attr != null && attr.isDefined(attribute)) {
    1492             if (value.equals(attr.getAttribute(attribute))) {
    1493                 return e;
    1494             }
    1495         }
    1496         if (!e.isLeaf()) {
    1497             for (int counter = 0, maxCounter = e.getElementCount();
    1498                  counter < maxCounter; counter++) {
    1499                 Element retValue = getElement(e.getElement(counter), attribute,
    1500                                               value, searchLeafAttributes);
    1501
    1502                 if (retValue != null) {
    1503                     return retValue;
    1504                 }
    1505             }
    1506         }
    1507         else if (searchLeafAttributes && attr != null) {
    1508             // For some leaf elements we store the actual attributes inside
    1509             // the AttributeSet of the Element (such as anchors).
    1510             Enumeration<?> names = attr.getAttributeNames();
    1511             if (names != null) {
    1512                 while (names.hasMoreElements()) {
    1513                     Object name = names.nextElement();
    1514                     if ((name instanceof HTML.Tag) &&
    1515                         (attr.getAttribute(name) instanceof AttributeSet)) {
    1516
    1517                         AttributeSet check = (AttributeSet)attr.
    1518                                              getAttribute(name);
    1519                         if (check.isDefined(attribute) &&
    1520                             value.equals(check.getAttribute(attribute))) {
    1521                             return e;
    1522                         }
    1523                     }
    1524                 }
    1525             }
    1526         }
    1527         return null;
    1528     }
    1529
    1530     /**
    1531      * Verifies the document has an <code>HTMLEditorKit.Parser</code> set.
    1532      * If <code>getParser</code> returns <code>null</code>, this will throw an
    1533      * IllegalStateException.
    1534      *
    1535      * @throws IllegalStateException if the document does not have a Parser
    1536      */
    1537     private void verifyParser() {
    1538         if (getParser() == null) {
    1539             throw new IllegalStateException("No HTMLEditorKit.Parser");
    1540         }
    1541     }
    1542
    1543     /**
    1544      * Installs a default Parser if one has not been installed yet.
    1545      */
    1546     private void installParserIfNecessary() {
    1547         if (getParser() == null) {
    1548             setParser(new HTMLEditorKit().getParser());
    1549         }
    1550     }
    1551
    1552     /**
    1553      * Inserts a string of HTML into the document at the given position.
    1554      * <code>parent</code> is used to identify the location to insert the
    1555      * <code>html</code>. If <code>parent</code> is a leaf this can have
    1556      * unexpected results.
    1557      */
    1558     private void insertHTML(Element parent, int offset, String html,
    1559                             boolean wantsTrailingNewline)
    1560                  throws BadLocationException, IOException {
    1561         if (parent != null && html != null) {
    1562             HTMLEditorKit.Parser parser = getParser();
    1563             if (parser != null) {
    1564                 int lastOffset = Math.max(0, offset - 1);
    1565                 Element charElement = getCharacterElement(lastOffset);
    1566                 Element commonParent = parent;
    1567                 int pop = 0;
    1568                 int push = 0;
    1569
    1570                 if (parent.getStartOffset() > lastOffset) {
    1571                     while (commonParent != null &&
    1572                            commonParent.getStartOffset() > lastOffset) {
    1573                         commonParent = commonParent.getParentElement();
    1574                         push++;
    1575                     }
    1576                     if (commonParent == null) {
    1577                         throw new BadLocationException("No common parent",
    1578                                                        offset);
    1579                     }
    1580                 }
    1581                 while (charElement != null && charElement != commonParent) {
    1582                     pop++;
    1583                     charElement = charElement.getParentElement();
    1584                 }
    1585                 if (charElement != null) {
    1586                     // Found it, do the insert.
    1587                     HTMLReader reader = new HTMLReader(offset, pop - 1, push,
    1588                                                        null, false, true,
    1589                                                        wantsTrailingNewline);
    1590
    1591                     parser.parse(new StringReader(html), reader, true);
    1592                     reader.flush();
    1593                 }
    1594             }
    1595         }
    1596     }
    1597
    1598     /**
    1599      * Removes child Elements of the passed in Element <code>e</code>. This
    1600      * will do the necessary cleanup to ensure the element representing the
    1601      * end character is correctly created.
    1602      * <p>This is not a general purpose method, it assumes that <code>e</code>
    1603      * will still have at least one child after the remove, and it assumes
    1604      * the character at <code>e.getStartOffset() - 1</code> is a newline and
    1605      * is of length 1.
    1606      */
    1607     private void removeElements(Element e, int index, int count) throws BadLocationException {
    1608         writeLock();
    1609         try {
    1610             int start = e.getElement(index).getStartOffset();
    1611             int end = e.getElement(index + count - 1).getEndOffset();
    1612             if (end > getLength()) {
    1613                 removeElementsAtEnd(e, index, count, start, end);
    1614             }
    1615             else {
    1616                 removeElements(e, index, count, start, end);
    1617             }
    1618         } finally {
    1619             writeUnlock();
    1620         }
    1621     }
    1622
    1623     /**
    1624      * Called to remove child elements of <code>e</code> when one of the
    1625      * elements to remove is representing the end character.
    1626      * <p>Since the Content will not allow a removal to the end character
    1627      * this will do a remove from <code>start - 1</code> to <code>end</code>.
    1628      * The end Element(s) will be removed, and the element representing
    1629      * <code>start - 1</code> to <code>start</code> will be recreated. This
    1630      * Element has to be recreated as after the content removal its offsets
    1631      * become <code>start - 1</code> to <code>start - 1</code>.
    1632      */
    1633     private void removeElementsAtEnd(Element e, int index, int count,
    1634                          int start, int end) throws BadLocationException {
    1635         // index must be > 0 otherwise no insert would have happened.
    1636         boolean isLeaf = (e.getElement(index - 1).isLeaf());
    1637         DefaultDocumentEvent dde = new DefaultDocumentEvent(
    1638                        start - 1, end - start + 1, DocumentEvent.
    1639                        EventType.REMOVE);
    1640
    1641         if (isLeaf) {
    1642             Element endE = getCharacterElement(getLength());
    1643             // e.getElement(index - 1) should represent the newline.
    1644             index--;
    1645             if (endE.getParentElement() != e) {
    1646                 // The hiearchies don't match, we'll have to manually
    1647                 // recreate the leaf at e.getElement(index - 1)
    1648                 replace(dde, e, index, ++count, start, end, true, true);
    1649             }
    1650             else {
    1651                 // The hierarchies for the end Element and
    1652                 // e.getElement(index - 1), match, we can safely remove
    1653                 // the Elements and the end content will be aligned
    1654                 // appropriately.
    1655                 replace(dde, e, index, count, start, end, true, false);
    1656             }
    1657         }
    1658         else {
    1659             // Not a leaf, descend until we find the leaf representing
    1660             // start - 1 and remove it.
    1661             Element newLineE = e.getElement(index - 1);
    1662             while (!newLineE.isLeaf()) {
    1663                 newLineE = newLineE.getElement(newLineE.getElementCount() - 1);
    1664             }
    1665             newLineE = newLineE.getParentElement();
    1666             replace(dde, e, index, count, start, end, false, false);
    1667             replace(dde, newLineE, newLineE.getElementCount() - 1, 1, start,
    1668                     end, true, true);
    1669         }
    1670         postRemoveUpdate(dde);
    1671         dde.end();
    1672         fireRemoveUpdate(dde);
    1673         fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
    1674     }
    1675
    1676     /**
    1677      * This is used by <code>removeElementsAtEnd</code>, it removes
    1678      * <code>count</code> elements starting at <code>start</code> from
    1679      * <code>e</code>.  If <code>remove</code> is true text of length
    1680      * <code>start - 1</code> to <code>end - 1</code> is removed.  If
    1681      * <code>create</code> is true a new leaf is created of length 1.
    1682      */
    1683     private void replace(DefaultDocumentEvent dde, Element e, int index,
    1684                          int count, int start, int end, boolean remove,
    1685                          boolean create) throws BadLocationException {
    1686         Element[] added;
    1687         AttributeSet attrs = e.getElement(index).getAttributes();
    1688         Element[] removed = new Element[count];
    1689
    1690         for (int counter = 0; counter < count; counter++) {
    1691             removed[counter] = e.getElement(counter + index);
    1692         }
    1693         if (remove) {
    1694             UndoableEdit u = getContent().remove(start - 1, end - start);
    1695             if (u != null) {
    1696                 dde.addEdit(u);
    1697             }
    1698         }
    1699         if (create) {
    1700             added = new Element[1];
    1701             added[0] = createLeafElement(e, attrs, start - 1, start);
    1702         }
    1703         else {
    1704             added = new Element[0];
    1705         }
    1706         dde.addEdit(new ElementEdit(e, index, removed, added));
    1707         ((AbstractDocument.BranchElement)e).replace(
    1708                                              index, removed.length, added);
    1709     }
    1710
    1711     /**
    1712      * Called to remove child Elements when the end is not touched.
    1713      */
    1714     private void removeElements(Element e, int index, int count,
    1715                              int start, int end) throws BadLocationException {
    1716         Element[] removed = new Element[count];
    1717         Element[] added = new Element[0];
    1718         for (int counter = 0; counter < count; counter++) {
    1719             removed[counter] = e.getElement(counter + index);
    1720         }
    1721         DefaultDocumentEvent dde = new DefaultDocumentEvent
    1722                 (start, end - start, DocumentEvent.EventType.REMOVE);
    1723         ((AbstractDocument.BranchElement)e).replace(index, removed.length,
    1724                                                     added);
    1725         dde.addEdit(new ElementEdit(e, index, removed, added));
    1726         UndoableEdit u = getContent().remove(start, end - start);
    1727         if (u != null) {
    1728             dde.addEdit(u);
    1729         }
    1730         postRemoveUpdate(dde);
    1731         dde.end();
    1732         fireRemoveUpdate(dde);
    1733         if (u != null) {
    1734             fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
    1735         }
    1736     }
    1737
    1738
    1739     // These two are provided for inner class access. The are named different
    1740     // than the super class as the super class implementations are final.
    1741     void obtainLock() {
    1742         writeLock();
    1743     }
    1744
    1745     void releaseLock() {
    1746         writeUnlock();
    1747     }
    1748
    1749     //
    1750     // Provided for inner class access.
    1751     //
    1752
    1753     /**
    1754      * Notifies all listeners that have registered interest for
    1755      * notification on this event type.  The event instance
    1756      * is lazily created using the parameters passed into
    1757      * the fire method.
    1758      *
    1759      * @param e the event
    1760      * @see EventListenerList
    1761      */
    1762     protected void fireChangedUpdate(DocumentEvent e) {
    1763         super.fireChangedUpdate(e);
    1764     }
    1765
    1766     /**
    1767      * Notifies all listeners that have registered interest for
    1768      * notification on this event type.  The event instance
    1769      * is lazily created using the parameters passed into
    1770      * the fire method.
    1771      *
    1772      * @param e the event
    1773      * @see EventListenerList
    1774      */
    1775     protected void fireUndoableEditUpdate(UndoableEditEvent e) {
    1776         super.fireUndoableEditUpdate(e);
    1777     }
    1778
    1779     boolean hasBaseTag() {
    1780         return hasBaseTag;
    1781     }
    1782
    1783     String getBaseTarget() {
    1784         return baseTarget;
    1785     }
    1786
    1787     /*
    1788      * state defines whether the document is a frame document
    1789      * or not.
    1790      */
    1791     private boolean frameDocument = false;
    1792     private boolean preservesUnknownTags = true;
    1793
    1794     /*
    1795      * Used to store button groups for radio buttons in
    1796      * a form.
    1797      */
    1798     private HashMap<String, ButtonGroup> radioButtonGroupsMap;
    1799
    1800     /**
    1801      * Document property for the number of tokens to buffer
    1802      * before building an element subtree to represent them.
    1803      */
    1804     static final String TokenThreshold = "token threshold";
    1805
    1806     private static final int MaxThreshold = 10000;
    1807
    1808     private static final int StepThreshold = 5;
    1809
    1810
    1811     /**
    1812      * Document property key value. The value for the key will be a Vector
    1813      * of Strings that are comments not found in the body.
    1814      */
    1815     public static final String AdditionalComments = "AdditionalComments";
    1816
    1817     /**
    1818      * Document property key value. The value for the key will be a
    1819      * String indicating the default type of stylesheet links.
    1820      */
    1821     /* public */ static final String StyleType = "StyleType";
    1822
    1823     /**
    1824      * The location to resolve relative URLs against.  By
    1825      * default this will be the document's URL if the document
    1826      * was loaded from a URL.  If a base tag is found and
    1827      * can be parsed, it will be used as the base location.
    1828      */
    1829     URL base;
    1830
    1831     /**
    1832      * does the document have base tag
    1833      */
    1834     boolean hasBaseTag = false;
    1835
    1836     /**
    1837      * BASE tag's TARGET attribute value
    1838      */
    1839     private String baseTarget = null;
    1840
    1841     /**
    1842      * The parser that is used when inserting html into the existing
    1843      * document.
    1844      */
    1845     private HTMLEditorKit.Parser parser;
    1846
    1847     /**
    1848      * Used for inserts when a null AttributeSet is supplied.
    1849      */
    1850     private static AttributeSet contentAttributeSet;
    1851
    1852     /**
    1853      * Property Maps are registered under, will be a Hashtable.
    1854      */
    1855     static String MAP_PROPERTY = "__MAP__";
    1856
    1857     private static char[] NEWLINE;
    1858
    1859     /**
    1860      * Indicates that direct insertion to body section takes place.
    1861      */
    1862     private boolean insertInBody = false;
    1863
    1864     /**
    1865      * I18N property key.
    1866      *
    1867      * @see AbstractDocument#I18NProperty
    1868      */
    1869     private static final String I18NProperty = "i18n";
    1870
    1871     static {
    1872         contentAttributeSet = new SimpleAttributeSet();
    1873         ((MutableAttributeSet)contentAttributeSet).
    1874                         addAttribute(StyleConstants.NameAttribute,
    1875                                      HTML.Tag.CONTENT);
    1876         NEWLINE = new char[1];
    1877         NEWLINE[0] = '\n';
    1878     }
    1879
    1880
    1881     /**
    1882      * An iterator to iterate over a particular type of
    1883      * tag.  The iterator is not thread safe.  If reliable
    1884      * access to the document is not already ensured by
    1885      * the context under which the iterator is being used,
    1886      * its use should be performed under the protection of
    1887      * Document.render.
    1888      */
    1889     public abstract static class Iterator {
    1890
    1891         /**
    1892          * Return the attributes for this tag.
    1893          * @return the <code>AttributeSet</code> for this tag, or
    1894          *      <code>null</code> if none can be found
    1895          */
    1896         public abstract AttributeSet getAttributes();
    1897
    1898         /**
    1899          * Returns the start of the range for which the current occurrence of
    1900          * the tag is defined and has the same attributes.
    1901          *
    1902          * @return the start of the range, or -1 if it can't be found
    1903          */
    1904         public abstract int getStartOffset();
    1905
    1906         /**
    1907          * Returns the end of the range for which the current occurrence of
    1908          * the tag is defined and has the same attributes.
    1909          *
    1910          * @return the end of the range
    1911          */
    1912         public abstract int getEndOffset();
    1913
    1914         /**
    1915          * Move the iterator forward to the next occurrence
    1916          * of the tag it represents.
    1917          */
    1918         public abstract void next();
    1919
    1920         /**
    1921          * Indicates if the iterator is currently
    1922          * representing an occurrence of a tag.  If
    1923          * false there are no more tags for this iterator.
    1924          * @return true if the iterator is currently representing an
    1925          *              occurrence of a tag, otherwise returns false
    1926          */
    1927         public abstract boolean isValid();
    1928
    1929         /**
    1930          * Type of tag this iterator represents.
    1931          * @return the tag
    1932          */
    1933         public abstract HTML.Tag getTag();
    1934     }
    1935
    1936     /**
    1937      * An iterator to iterate over a particular type of tag.
    1938      */
    1939     static class LeafIterator extends Iterator {
    1940
    1941         LeafIterator(HTML.Tag t, Document doc) {
    1942             tag = t;
    1943             pos = new ElementIterator(doc);
    1944             endOffset = 0;
    1945             next();
    1946         }
    1947
    1948         /**
    1949          * Returns the attributes for this tag.
    1950          * @return the <code>AttributeSet</code> for this tag,
    1951          *              or <code>null</code> if none can be found
    1952          */
    1953         public AttributeSet getAttributes() {
    1954             Element elem = pos.current();
    1955             if (elem != null) {
    1956                 AttributeSet a = (AttributeSet)
    1957                     elem.getAttributes().getAttribute(tag);
    1958                 if (a == null) {
    1959                     a = elem.getAttributes();
    1960                 }
    1961                 return a;
    1962             }
    1963             return null;
    1964         }
    1965
    1966         /**
    1967          * Returns the start of the range for which the current occurrence of
    1968          * the tag is defined and has the same attributes.
    1969          *
    1970          * @return the start of the range, or -1 if it can't be found
    1971          */
    1972         public int getStartOffset() {
    1973             Element elem = pos.current();
    1974             if (elem != null) {
    1975                 return elem.getStartOffset();
    1976             }
    1977             return -1;
    1978         }
    1979
    1980         /**
    1981          * Returns the end of the range for which the current occurrence of
    1982          * the tag is defined and has the same attributes.
    1983          *
    1984          * @return the end of the range
    1985          */
    1986         public int getEndOffset() {
    1987             return endOffset;
    1988         }
    1989
    1990         /**
    1991          * Moves the iterator forward to the next occurrence
    1992          * of the tag it represents.
    1993          */
    1994         public void next() {
    1995             for (nextLeaf(pos); isValid(); nextLeaf(pos)) {
    1996                 Element elem = pos.current();
    1997                 if (elem.getStartOffset() >= endOffset) {
    1998                     AttributeSet a = pos.current().getAttributes();
    1999
    2000                     if (a.isDefined(tag) ||
    2001                         a.getAttribute(StyleConstants.NameAttribute) == tag) {
    2002
    2003                         // we found the next one
    2004                         setEndOffset();
    2005                         break;
    2006                     }
    2007                 }
    2008             }
    2009         }
    2010
    2011         /**
    2012          * Returns the type of tag this iterator represents.
    2013          *
    2014          * @return the <code>HTML.Tag</code> that this iterator represents.
    2015          * @see javax.swing.text.html.HTML.Tag
    2016          */
    2017         public HTML.Tag getTag() {
    2018             return tag;
    2019         }
    2020
    2021         /**
    2022          * Returns true if the current position is not <code>null</code>.
    2023          * @return true if current position is not <code>null</code>,
    2024          *              otherwise returns false
    2025          */
    2026         public boolean isValid() {
    2027             return (pos.current() != null);
    2028         }
    2029
    2030         /**
    2031          * Moves the given iterator to the next leaf element.
    2032          * @param iter  the iterator to be scanned
    2033          */
    2034         void nextLeaf(ElementIterator iter) {
    2035             for (iter.next(); iter.current() != null; iter.next()) {
    2036                 Element e = iter.current();
    2037                 if (e.isLeaf()) {
    2038                     break;
    2039                 }
    2040             }
    2041         }
    2042
    2043         /**
    2044          * Marches a cloned iterator forward to locate the end
    2045          * of the run.  This sets the value of <code>endOffset</code>.
    2046          */
    2047         void setEndOffset() {
    2048             AttributeSet a0 = getAttributes();
    2049             endOffset = pos.current().getEndOffset();
    2050             ElementIterator fwd = (ElementIterator) pos.clone();
    2051             for (nextLeaf(fwd); fwd.current() != null; nextLeaf(fwd)) {
    2052                 Element e = fwd.current();
    2053                 AttributeSet a1 = (AttributeSet) e.getAttributes().getAttribute(tag);
    2054                 if ((a1 == null) || (! a1.equals(a0))) {
    2055                     break;
    2056                 }
    2057                 endOffset = e.getEndOffset();
    2058             }
    2059         }
    2060
    2061         private int endOffset;
    2062         private HTML.Tag tag;
    2063         private ElementIterator pos;
    2064
    2065     }
    2066
    2067     /**
    2068      * An HTML reader to load an HTML document with an HTML
    2069      * element structure.  This is a set of callbacks from
    2070      * the parser, implemented to create a set of elements
    2071      * tagged with attributes.  The parse builds up tokens
    2072      * (ElementSpec) that describe the element subtree desired,
    2073      * and burst it into the document under the protection of
    2074      * a write lock using the insert method on the document
    2075      * outer class.
    2076      * <p>
    2077      * The reader can be configured by registering actions
    2078      * (of type <code>HTMLDocument.HTMLReader.TagAction</code>)
    2079      * that describe how to handle the action.  The idea behind
    2080      * the actions provided is that the most natural text editing
    2081      * operations can be provided if the element structure boils
    2082      * down to paragraphs with runs of some kind of style
    2083      * in them.  Some things are more naturally specified
    2084      * structurally, so arbitrary structure should be allowed
    2085      * above the paragraphs, but will need to be edited with structural
    2086      * actions.  The implication of this is that some of the
    2087      * HTML elements specified in the stream being parsed will
    2088      * be collapsed into attributes, and in some cases paragraphs
    2089      * will be synthesized.  When HTML elements have been
    2090      * converted to attributes, the attribute key will be of
    2091      * type HTML.Tag, and the value will be of type AttributeSet
    2092      * so that no information is lost.  This enables many of the
    2093      * existing actions to work so that the user can type input,
    2094      * hit the return key, backspace, delete, etc and have a
    2095      * reasonable result.  Selections can be created, and attributes
    2096      * applied or removed, etc.  With this in mind, the work done
    2097      * by the reader can be categorized into the following kinds
    2098      * of tasks:
    2099      * <dl>
    2100      * <dt>Block
    2101      * <dd>Build the structure like it's specified in the stream.
    2102      * This produces elements that contain other elements.
    2103      * <dt>Paragraph
    2104      * <dd>Like block except that it's expected that the element
    2105      * will be used with a paragraph view so a paragraph element
    2106      * won't need to be synthesized.
    2107      * <dt>Character
    2108      * <dd>Contribute the element as an attribute that will start
    2109      * and stop at arbitrary text locations.  This will ultimately
    2110      * be mixed into a run of text, with all of the currently
    2111      * flattened HTML character elements.
    2112      * <dt>Special
    2113      * <dd>Produce an embedded graphical element.
    2114      * <dt>Form
    2115      * <dd>Produce an element that is like the embedded graphical
    2116      * element, except that it also has a component model associated
    2117      * with it.
    2118      * <dt>Hidden
    2119      * <dd>Create an element that is hidden from view when the
    2120      * document is being viewed read-only, and visible when the
    2121      * document is being edited.  This is useful to keep the
    2122      * model from losing information, and used to store things
    2123      * like comments and unrecognized tags.
    2124      *
    2125      * </dl>
    2126      * <p>
    2127      * Currently, &lt;APPLET&gt;, &lt;PARAM&gt;, &lt;MAP&gt;, &lt;AREA&gt;, &lt;LINK&gt;,
    2128      * &lt;SCRIPT&gt; and &lt;STYLE&gt; are unsupported.
    2129      *
    2130      * <p>
    2131      * The assignment of the actions described is shown in the
    2132      * following table for the tags defined in <code>HTML.Tag</code>.
    2133      *
    2134      * <table class="striped">
    2135      * <caption>HTML tags and assigned actions</caption>
    2136      * <thead>
    2137      *   <tr>
    2138      *     <th scope="col">Tag
    2139      *     <th scope="col">Action
    2140      * </thead>
    2141      * <tbody>
    2142      *   <tr>
    2143      *     <th scope="row">{@code HTML.Tag.A}
    2144      *     <td>CharacterAction
    2145      *   <tr>
    2146      *     <th scope="row">{@code HTML.Tag.ADDRESS}
    2147      *     <td>CharacterAction
    2148      *   <tr>
    2149      *     <th scope="row">{@code HTML.Tag.APPLET}
    2150      *     <td>HiddenAction
    2151      *   <tr>
    2152      *     <th scope="row">{@code HTML.Tag.AREA}
    2153      *     <td>AreaAction
    2154      *   <tr>
    2155      *     <th scope="row">{@code HTML.Tag.B}
    2156      *     <td>CharacterAction
    2157      *   <tr>
    2158      *     <th scope="row">{@code HTML.Tag.BASE}
    2159      *     <td>BaseAction
    2160      *   <tr>
    2161      *     <th scope="row">{@code HTML.Tag.BASEFONT}
    2162      *     <td>CharacterAction
    2163      *   <tr>
    2164      *     <th scope="row">{@code HTML.Tag.BIG}
    2165      *     <td>CharacterAction
    2166      *   <tr>
    2167      *     <th scope="row">{@code HTML.Tag.BLOCKQUOTE}
    2168      *     <td>BlockAction
    2169      *   <tr>
    2170      *     <th scope="row">{@code HTML.Tag.BODY}
    2171      *     <td>BlockAction
    2172      *   <tr>
    2173      *     <th scope="row">{@code HTML.Tag.BR}
    2174      *     <td>SpecialAction
    2175      *   <tr>
    2176      *     <th scope="row">{@code HTML.Tag.CAPTION}
    2177      *     <td>BlockAction
    2178      *   <tr>
    2179      *     <th scope="row">{@code HTML.Tag.CENTER}
    2180      *     <td>BlockAction
    2181      *   <tr>
    2182      *     <th scope="row">{@code HTML.Tag.CITE}
    2183      *     <td>CharacterAction
    2184      *   <tr>
    2185      *     <th scope="row">{@code HTML.Tag.CODE}
    2186      *     <td>CharacterAction
    2187      *   <tr>
    2188      *     <th scope="row">{@code HTML.Tag.DD}
    2189      *     <td>BlockAction
    2190      *   <tr>
    2191      *     <th scope="row">{@code HTML.Tag.DFN}
    2192      *     <td>CharacterAction
    2193      *   <tr>
    2194      *     <th scope="row">{@code HTML.Tag.DIR}
    2195      *     <td>BlockAction
    2196      *   <tr>
    2197      *     <th scope="row">{@code HTML.Tag.DIV}
    2198      *     <td>BlockAction
    2199      *   <tr>
    2200      *     <th scope="row">{@code HTML.Tag.DL}
    2201      *     <td>BlockAction
    2202      *   <tr>
    2203      *     <th scope="row">{@code HTML.Tag.DT}
    2204      *     <td>ParagraphAction
    2205      *   <tr>
    2206      *     <th scope="row">{@code HTML.Tag.EM}
    2207      *     <td>CharacterAction
    2208      *   <tr>
    2209      *     <th scope="row">{@code HTML.Tag.FONT}
    2210      *     <td>CharacterAction
    2211      *   <tr>
    2212      *     <th scope="row">{@code HTML.Tag.FORM}
    2213      *     <td>As of 1.4 a BlockAction
    2214      *   <tr>
    2215      *     <th scope="row">{@code HTML.Tag.FRAME}
    2216      *     <td>SpecialAction
    2217      *   <tr>
    2218      *     <th scope="row">{@code HTML.Tag.FRAMESET}
    2219      *     <td>BlockAction
    2220      *   <tr>
    2221      *     <th scope="row">{@code HTML.Tag.H1}
    2222      *     <td>ParagraphAction
    2223      *   <tr>
    2224      *     <th scope="row">{@code HTML.Tag.H2}
    2225      *     <td>ParagraphAction
    2226      *   <tr>
    2227      *     <th scope="row">{@code HTML.Tag.H3}
    2228      *     <td>ParagraphAction
    2229      *   <tr>
    2230      *     <th scope="row">{@code HTML.Tag.H4}
    2231      *     <td>ParagraphAction
    2232      *   <tr>
    2233      *     <th scope="row">{@code HTML.Tag.H5}
    2234      *     <td>ParagraphAction
    2235      *   <tr>
    2236      *     <th scope="row">{@code HTML.Tag.H6}
    2237      *     <td>ParagraphAction
    2238      *   <tr>
    2239      *     <th scope="row">{@code HTML.Tag.HEAD}
    2240      *     <td>HeadAction
    2241      *   <tr>
    2242      *     <th scope="row">{@code HTML.Tag.HR}
    2243      *     <td>SpecialAction
    2244      *   <tr>
    2245      *     <th scope="row">{@code HTML.Tag.HTML}
    2246      *     <td>BlockAction
    2247      *   <tr>
    2248      *     <th scope="row">{@code HTML.Tag.I}
    2249      *     <td>CharacterAction
    2250      *   <tr>
    2251      *     <th scope="row">{@code HTML.Tag.IMG}
    2252      *     <td>SpecialAction
    2253      *   <tr>
    2254      *     <th scope="row">{@code HTML.Tag.INPUT}
    2255      *     <td>FormAction
    2256      *   <tr>
    2257      *     <th scope="row">{@code HTML.Tag.ISINDEX}
    2258      *     <td>IsndexAction
    2259      *   <tr>
    2260      *     <th scope="row">{@code HTML.Tag.KBD}
    2261      *     <td>CharacterAction
    2262      *   <tr>
    2263      *     <th scope="row">{@code HTML.Tag.LI}
    2264      *     <td>BlockAction
    2265      *   <tr>
    2266      *     <th scope="row">{@code HTML.Tag.LINK}
    2267      *     <td>LinkAction
    2268      *   <tr>
    2269      *     <th scope="row">{@code HTML.Tag.MAP}
    2270      *     <td>MapAction
    2271      *   <tr>
    2272      *     <th scope="row">{@code HTML.Tag.MENU}
    2273      *     <td>BlockAction
    2274      *   <tr>
    2275      *     <th scope="row">{@code HTML.Tag.META}
    2276      *     <td>MetaAction
    2277      *   <tr>
    2278      *     <th scope="row">{@code HTML.Tag.NOFRAMES}
    2279      *     <td>BlockAction
    2280      *   <tr>
    2281      *     <th scope="row">{@code HTML.Tag.OBJECT}
    2282      *     <td>SpecialAction
    2283      *   <tr>
    2284      *     <th scope="row">{@code HTML.Tag.OL}
    2285      *     <td>BlockAction
    2286      *   <tr>
    2287      *     <th scope="row">{@code HTML.Tag.OPTION}
    2288      *     <td>FormAction
    2289      *   <tr>
    2290      *     <th scope="row">{@code HTML.Tag.P}
    2291      *     <td>ParagraphAction
    2292      *   <tr>
    2293      *     <th scope="row">{@code HTML.Tag.PARAM}
    2294      *     <td>HiddenAction
    2295      *   <tr>
    2296      *     <th scope="row">{@code HTML.Tag.PRE}
    2297      *     <td>PreAction
    2298      *   <tr>
    2299      *     <th scope="row">{@code HTML.Tag.SAMP}
    2300      *     <td>CharacterAction
    2301      *   <tr>
    2302      *     <th scope="row">{@code HTML.Tag.SCRIPT}
    2303      *     <td>HiddenAction
    2304      *   <tr>
    2305      *     <th scope="row">{@code HTML.Tag.SELECT}
    2306      *     <td>FormAction
    2307      *   <tr>
    2308      *     <th scope="row">{@code HTML.Tag.SMALL}
    2309      *     <td>CharacterAction
    2310      *   <tr>
    2311      *     <th scope="row">{@code HTML.Tag.STRIKE}
    2312      *     <td>CharacterAction
    2313      *   <tr>
    2314      *     <th scope="row">{@code HTML.Tag.S}
    2315      *     <td>CharacterAction
    2316      *   <tr>
    2317      *     <th scope="row">{@code HTML.Tag.STRONG}
    2318      *     <td>CharacterAction
    2319      *   <tr>
    2320      *     <th scope="row">{@code HTML.Tag.STYLE}
    2321      *     <td>StyleAction
    2322      *   <tr>
    2323      *     <th scope="row">{@code HTML.Tag.SUB}
    2324      *     <td>CharacterAction
    2325      *   <tr>
    2326      *     <th scope="row">{@code HTML.Tag.SUP}
    2327      *     <td>CharacterAction
    2328      *   <tr>
    2329      *     <th scope="row">{@code HTML.Tag.TABLE}
    2330      *     <td>BlockAction
    2331      *   <tr>
    2332      *     <th scope="row">{@code HTML.Tag.TD}
    2333      *     <td>BlockAction
    2334      *   <tr>
    2335      *     <th scope="row">{@code HTML.Tag.TEXTAREA}
    2336      *     <td>FormAction
    2337      *   <tr>
    2338      *     <th scope="row">{@code HTML.Tag.TH}
    2339      *     <td>BlockAction
    2340      *   <tr>
    2341      *     <th scope="row">{@code HTML.Tag.TITLE}
    2342      *     <td>TitleAction
    2343      *   <tr>
    2344      *     <th scope="row">{@code HTML.Tag.TR}
    2345      *     <td>BlockAction
    2346      *   <tr>
    2347      *     <th scope="row">{@code HTML.Tag.TT}
    2348      *     <td>CharacterAction
    2349      *   <tr>
    2350      *     <th scope="row">{@code HTML.Tag.U}
    2351      *     <td>CharacterAction
    2352      *   <tr>
    2353      *     <th scope="row">{@code HTML.Tag.UL}
    2354      *     <td>BlockAction
    2355      *   <tr>
    2356      *     <th scope="row">{@code HTML.Tag.VAR}
    2357      *     <td>CharacterAction
    2358      * </tbody>
    2359      * </table>
    2360      * <p>
    2361      * Once &lt;/html&gt; is encountered, the Actions are no longer notified.
    2362      */
    2363     public class HTMLReader extends HTMLEditorKit.ParserCallback {
    2364
    2365         /**
    2366          * Constructs an HTMLReader using default pop and push depth and no tag to insert.
    2367          *
    2368          * @param offset the starting offset
    2369          */
    2370         public HTMLReader(int offset) {
    2371             this(offset, 0, 0, null);
    2372         }
    2373
    2374         /**
    2375          * Constructs an HTMLReader.
    2376          *
    2377          * @param offset the starting offset
    2378          * @param popDepth how many parents to ascend before insert new element
    2379          * @param pushDepth how many parents to descend (relative to popDepth) before
    2380          *                  inserting
    2381          * @param insertTag a tag to insert (may be null)
    2382          */
    2383         public HTMLReader(int offset, int popDepth, int pushDepth,
    2384                           HTML.Tag insertTag) {
    2385             this(offset, popDepth, pushDepth, insertTag, true, false, true);
    2386         }
    2387
    2388         /**
    2389          * Generates a RuntimeException (will eventually generate
    2390          * a BadLocationException when API changes are alloced) if inserting
    2391          * into non empty document, <code>insertTag</code> is
    2392          * non-<code>null</code>, and <code>offset</code> is not in the body.
    2393          */
    2394         // PENDING(sky): Add throws BadLocationException and remove
    2395         // RuntimeException
    2396         HTMLReader(int offset, int popDepth, int pushDepth,
    2397                    HTML.Tag insertTag, boolean insertInsertTag,
    2398                    boolean insertAfterImplied, boolean wantsTrailingNewline) {
    2399             emptyDocument = (getLength() == 0);
    2400             isStyleCSS = "text/css".equals(getDefaultStyleSheetType());
    2401             this.offset = offset;
    2402             threshold = HTMLDocument.this.getTokenThreshold();
    2403             tagMap = new Hashtable<HTML.Tag, TagAction>(57);
    2404             TagAction na = new TagAction();
    2405             TagAction ba = new BlockAction();
    2406             TagAction pa = new ParagraphAction();
    2407             TagAction ca = new CharacterAction();
    2408             TagAction sa = new SpecialAction();
    2409             TagAction fa = new FormAction();
    2410             TagAction ha = new HiddenAction();
    2411             TagAction conv = new ConvertAction();
    2412
    2413             // register handlers for the well known tags
    2414             tagMap.put(HTML.Tag.A, new AnchorAction());
    2415             tagMap.put(HTML.Tag.ADDRESS, ca);
    2416             tagMap.put(HTML.Tag.APPLET, ha);
    2417             tagMap.put(HTML.Tag.AREA, new AreaAction());
    2418             tagMap.put(HTML.Tag.B, conv);
    2419             tagMap.put(HTML.Tag.BASE, new BaseAction());
    2420             tagMap.put(HTML.Tag.BASEFONT, ca);
    2421             tagMap.put(HTML.Tag.BIG, ca);
    2422             tagMap.put(HTML.Tag.BLOCKQUOTE, ba);
    2423             tagMap.put(HTML.Tag.BODY, ba);
    2424             tagMap.put(HTML.Tag.BR, sa);
    2425             tagMap.put(HTML.Tag.CAPTION, ba);
    2426             tagMap.put(HTML.Tag.CENTER, ba);
    2427             tagMap.put(HTML.Tag.CITE, ca);
    2428             tagMap.put(HTML.Tag.CODE, ca);
    2429             tagMap.put(HTML.Tag.DD, ba);
    2430             tagMap.put(HTML.Tag.DFN, ca);
    2431             tagMap.put(HTML.Tag.DIR, ba);
    2432             tagMap.put(HTML.Tag.DIV, ba);
    2433             tagMap.put(HTML.Tag.DL, ba);
    2434             tagMap.put(HTML.Tag.DT, pa);
    2435             tagMap.put(HTML.Tag.EM, ca);
    2436             tagMap.put(HTML.Tag.FONT, conv);
    2437             tagMap.put(HTML.Tag.FORM, new FormTagAction());
    2438             tagMap.put(HTML.Tag.FRAME, sa);
    2439             tagMap.put(HTML.Tag.FRAMESET, ba);
    2440             tagMap.put(HTML.Tag.H1, pa);
    2441             tagMap.put(HTML.Tag.H2, pa);
    2442             tagMap.put(HTML.Tag.H3, pa);
    2443             tagMap.put(HTML.Tag.H4, pa);
    2444             tagMap.put(HTML.Tag.H5, pa);
    2445             tagMap.put(HTML.Tag.H6, pa);
    2446             tagMap.put(HTML.Tag.HEAD, new HeadAction());
    2447             tagMap.put(HTML.Tag.HR, sa);
    2448             tagMap.put(HTML.Tag.HTML, ba);
    2449             tagMap.put(HTML.Tag.I, conv);
    2450             tagMap.put(HTML.Tag.IMG, sa);
    2451             tagMap.put(HTML.Tag.INPUT, fa);
    2452             tagMap.put(HTML.Tag.ISINDEX, new IsindexAction());
    2453             tagMap.put(HTML.Tag.KBD, ca);
    2454             tagMap.put(HTML.Tag.LI, ba);
    2455             tagMap.put(HTML.Tag.LINK, new LinkAction());
    2456             tagMap.put(HTML.Tag.MAP, new MapAction());
    2457             tagMap.put(HTML.Tag.MENU, ba);
    2458             tagMap.put(HTML.Tag.META, new MetaAction());
    2459             tagMap.put(HTML.Tag.NOBR, ca);
    2460             tagMap.put(HTML.Tag.NOFRAMES, ba);
    2461             tagMap.put(HTML.Tag.OBJECT, sa);
    2462             tagMap.put(HTML.Tag.OL, ba);
    2463             tagMap.put(HTML.Tag.OPTION, fa);
    2464             tagMap.put(HTML.Tag.P, pa);
    2465             tagMap.put(HTML.Tag.PARAM, new ObjectAction());
    2466             tagMap.put(HTML.Tag.PRE, new PreAction());
    2467             tagMap.put(HTML.Tag.SAMP, ca);
    2468             tagMap.put(HTML.Tag.SCRIPT, ha);
    2469             tagMap.put(HTML.Tag.SELECT, fa);
    2470             tagMap.put(HTML.Tag.SMALL, ca);
    2471             tagMap.put(HTML.Tag.SPAN, ca);
    2472             tagMap.put(HTML.Tag.STRIKE, conv);
    2473             tagMap.put(HTML.Tag.S, ca);
    2474             tagMap.put(HTML.Tag.STRONG, ca);
    2475             tagMap.put(HTML.Tag.STYLE, new StyleAction());
    2476             tagMap.put(HTML.Tag.SUB, conv);
    2477             tagMap.put(HTML.Tag.SUP, conv);
    2478             tagMap.put(HTML.Tag.TABLE, ba);
    2479             tagMap.put(HTML.Tag.TD, ba);
    2480             tagMap.put(HTML.Tag.TEXTAREA, fa);
    2481             tagMap.put(HTML.Tag.TH, ba);
    2482             tagMap.put(HTML.Tag.TITLE, new TitleAction());
    2483             tagMap.put(HTML.Tag.TR, ba);
    2484             tagMap.put(HTML.Tag.TT, ca);
    2485             tagMap.put(HTML.Tag.U, conv);
    2486             tagMap.put(HTML.Tag.UL, ba);
    2487             tagMap.put(HTML.Tag.VAR, ca);
    2488
    2489             if (insertTag != null) {
    2490                 this.insertTag = insertTag;
    2491                 this.popDepth = popDepth;
    2492                 this.pushDepth = pushDepth;
    2493                 this.insertInsertTag = insertInsertTag;
    2494                 foundInsertTag = false;
    2495             }
    2496             else {
    2497                 foundInsertTag = true;
    2498             }
    2499             if (insertAfterImplied) {
    2500                 this.popDepth = popDepth;
    2501                 this.pushDepth = pushDepth;
    2502                 this.insertAfterImplied = true;
    2503                 foundInsertTag = false;
    2504                 midInsert = false;
    2505                 this.insertInsertTag = true;
    2506                 this.wantsTrailingNewline = wantsTrailingNewline;
    2507             }
    2508             else {
    2509                 midInsert = (!emptyDocument && insertTag == null);
    2510                 if (midInsert) {
    2511                     generateEndsSpecsForMidInsert();
    2512                 }
    2513             }
    2514
    2515             /**
    2516              * This block initializes the <code>inParagraph</code> flag.
    2517              * It is left in <code>false</code> value automatically
    2518              * if the target document is empty or future inserts
    2519              * were positioned into the 'body' tag.
    2520              */
    2521             if (!emptyDocument && !midInsert) {
    2522                 int targetOffset = Math.max(this.offset - 1, 0);
    2523                 Element elem =
    2524                         HTMLDocument.this.getCharacterElement(targetOffset);
    2525                 /* Going up by the left document structure path */
    2526                 for (int i = 0; i <= this.popDepth; i++) {
    2527                     elem = elem.getParentElement();
    2528                 }
    2529                 /* Going down by the right document structure path */
    2530                 for (int i = 0; i < this.pushDepth; i++) {
    2531                     int index = elem.getElementIndex(this.offset);
    2532                     elem = elem.getElement(index);
    2533                 }
    2534                 AttributeSet attrs = elem.getAttributes();
    2535                 if (attrs != null) {
    2536                     HTML.Tag tagToInsertInto =
    2537                             (HTML.Tag) attrs.getAttribute(StyleConstants.NameAttribute);
    2538                     if (tagToInsertInto != null) {
    2539                         this.inParagraph = tagToInsertInto.isParagraph();
    2540                     }
    2541                 }
    2542             }
    2543         }
    2544
    2545         /**
    2546          * Generates an initial batch of end <code>ElementSpecs</code>
    2547          * in parseBuffer to position future inserts into the body.
    2548          */
    2549         private void generateEndsSpecsForMidInsert() {
    2550             int           count = heightToElementWithName(HTML.Tag.BODY,
    2551                                                    Math.max(0, offset - 1));
    2552             boolean       joinNext = false;
    2553
    2554             if (count == -1 && offset > 0) {
    2555                 count = heightToElementWithName(HTML.Tag.BODY, offset);
    2556                 if (count != -1) {
    2557                     // Previous isn't in body, but current is. Have to
    2558                     // do some end specs, followed by join next.
    2559                     count = depthTo(offset - 1) - 1;
    2560                     joinNext = true;
    2561                 }
    2562             }
    2563             if (count == -1) {
    2564                 throw new RuntimeException("Must insert new content into body element-");
    2565             }
    2566             if (count != -1) {
    2567                 // Insert a newline, if necessary.
    2568                 try {
    2569                     if (!joinNext && offset > 0 &&
    2570                         !getText(offset - 1, 1).equals("\n")) {
    2571                         SimpleAttributeSet newAttrs = new SimpleAttributeSet();
    2572                         newAttrs.addAttribute(StyleConstants.NameAttribute,
    2573                                               HTML.Tag.CONTENT);
    2574                         ElementSpec spec = new ElementSpec(newAttrs,
    2575                                     ElementSpec.ContentType, NEWLINE, 0, 1);
    2576                         parseBuffer.addElement(spec);
    2577                     }
    2578                     // Should never throw, but will catch anyway.
    2579                 } catch (BadLocationException ble) {}
    2580                 while (count-- > 0) {
    2581                     parseBuffer.addElement(new ElementSpec
    2582                                            (null, ElementSpec.EndTagType));
    2583                 }
    2584                 if (joinNext) {
    2585                     ElementSpec spec = new ElementSpec(null, ElementSpec.
    2586                                                        StartTagType);
    2587
    2588                     spec.setDirection(ElementSpec.JoinNextDirection);
    2589                     parseBuffer.addElement(spec);
    2590                 }
    2591             }
    2592             // We should probably throw an exception if (count == -1)
    2593             // Or look for the body and reset the offset.
    2594         }
    2595
    2596         /**
    2597          * @return number of parents to reach the child at offset.
    2598          */
    2599         private int depthTo(int offset) {
    2600             Element       e = getDefaultRootElement();
    2601             int           count = 0;
    2602
    2603             while (!e.isLeaf()) {
    2604                 count++;
    2605                 e = e.getElement(e.getElementIndex(offset));
    2606             }
    2607             return count;
    2608         }
    2609
    2610         /**
    2611          * @return number of parents of the leaf at <code>offset</code>
    2612          *         until a parent with name, <code>name</code> has been
    2613          *         found. -1 indicates no matching parent with
    2614          *         <code>name</code>.
    2615          */
    2616         private int heightToElementWithName(Object name, int offset) {
    2617             Element       e = getCharacterElement(offset).getParentElement();
    2618             int           count = 0;
    2619
    2620             while (e != null && e.getAttributes().getAttribute
    2621                    (StyleConstants.NameAttribute) != name) {
    2622                 count++;
    2623                 e = e.getParentElement();
    2624             }
    2625             return (e == null) ? -1 : count;
    2626         }
    2627
    2628         /**
    2629          * This will make sure there aren't two BODYs (the second is
    2630          * typically created when you do a remove all, and then an insert).
    2631          */
    2632         private void adjustEndElement() {
    2633             int length = getLength();
    2634             if (length == 0) {
    2635                 return;
    2636             }
    2637             obtainLock();
    2638             try {
    2639                 Element[] pPath = getPathTo(length - 1);
    2640                 int pLength = pPath.length;
    2641                 if (pLength > 1 && pPath[1].getAttributes().getAttribute
    2642                          (StyleConstants.NameAttribute) == HTML.Tag.BODY &&
    2643                          pPath[1].getEndOffset() == length) {
    2644                     String lastText = getText(length - 1, 1);
    2645                     DefaultDocumentEvent event;
    2646                     Element[] added;
    2647                     Element[] removed;
    2648                     int index;
    2649                     // Remove the fake second body.
    2650                     added = new Element[0];
    2651                     removed = new Element[1];
    2652                     index = pPath[0].getElementIndex(length);
    2653                     removed[0] = pPath[0].getElement(index);
    2654                     ((BranchElement)pPath[0]).replace(index, 1, added);
    2655                     ElementEdit firstEdit = new ElementEdit(pPath[0], index,
    2656                                                             removed, added);
    2657
    2658                     // Insert a new element to represent the end that the
    2659                     // second body was representing.
    2660                     SimpleAttributeSet sas = new SimpleAttributeSet();
    2661                     sas.addAttribute(StyleConstants.NameAttribute,
    2662                                          HTML.Tag.CONTENT);
    2663                     sas.addAttribute(IMPLIED_CR, Boolean.TRUE);
    2664                     added = new Element[1];
    2665                     added[0] = createLeafElement(pPath[pLength - 1],
    2666                                                      sas, length, length + 1);
    2667                     index = pPath[pLength - 1].getElementCount();
    2668                     ((BranchElement)pPath[pLength - 1]).replace(index, 0,
    2669                                                                 added);
    2670                     event = new DefaultDocumentEvent(length, 1,
    2671                                             DocumentEvent.EventType.CHANGE);
    2672                     event.addEdit(new ElementEdit(pPath[pLength - 1],
    2673                                          index, new Element[0], added));
    2674                     event.addEdit(firstEdit);
    2675                     event.end();
    2676                     fireChangedUpdate(event);
    2677                     fireUndoableEditUpdate(new UndoableEditEvent(this, event));
    2678
    2679                     if (lastText.equals("\n")) {
    2680                         // We now have two \n's, one part of the Document.
    2681                         // We need to remove one
    2682                         event = new DefaultDocumentEvent(length - 1, 1,
    2683                                            DocumentEvent.EventType.REMOVE);
    2684                         removeUpdate(event);
    2685                         UndoableEdit u = getContent().remove(length - 1, 1);
    2686                         if (u != null) {
    2687                             event.addEdit(u);
    2688                         }
    2689                         postRemoveUpdate(event);
    2690                         // Mark the edit as done.
    2691                         event.end();
    2692                         fireRemoveUpdate(event);
    2693                         fireUndoableEditUpdate(new UndoableEditEvent(
    2694                                                this, event));
    2695                     }
    2696                 }
    2697             }
    2698             catch (BadLocationException ble) {
    2699             }
    2700             finally {
    2701                 releaseLock();
    2702             }
    2703         }
    2704
    2705         private Element[] getPathTo(int offset) {
    2706             Stack<Element> elements = new Stack<Element>();
    2707             Element e = getDefaultRootElement();
    2708             int index;
    2709             while (!e.isLeaf()) {
    2710                 elements.push(e);
    2711                 e = e.getElement(e.getElementIndex(offset));
    2712             }
    2713             Element[] retValue = new Element[elements.size()];
    2714             elements.copyInto(retValue);
    2715             return retValue;
    2716         }
    2717
    2718         // -- HTMLEditorKit.ParserCallback methods --------------------
    2719
    2720         /**
    2721          * The last method called on the reader.  It allows
    2722          * any pending changes to be flushed into the document.
    2723          * Since this is currently loading synchronously, the entire
    2724          * set of changes are pushed in at this point.
    2725          */
    2726         public void flush() throws BadLocationException {
    2727             if (emptyDocument && !insertAfterImplied) {
    2728                 if (HTMLDocument.this.getLength() > 0 ||
    2729                                       parseBuffer.size() > 0) {
    2730                     flushBuffer(true);
    2731                     adjustEndElement();
    2732                 }
    2733                 // We won't insert when
    2734             }
    2735             else {
    2736                 flushBuffer(true);
    2737             }
    2738         }
    2739
    2740         /**
    2741          * Called by the parser to indicate a block of text was
    2742          * encountered.
    2743          */
    2744         public void handleText(char[] data, int pos) {
    2745             if (receivedEndHTML || (midInsert && !inBody)) {
    2746                 return;
    2747             }
    2748
    2749             // see if complex glyph layout support is needed
    2750             if(HTMLDocument.this.getProperty(I18NProperty).equals( Boolean.FALSE ) ) {
    2751                 // if a default direction of right-to-left has been specified,
    2752                 // we want complex layout even if the text is all left to right.
    2753                 Object d = getProperty(TextAttribute.RUN_DIRECTION);
    2754                 if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
    2755                     HTMLDocument.this.putProperty( I18NProperty, Boolean.TRUE);
    2756                 } else {
    2757                     if (SwingUtilities2.isComplexLayout(data, 0, data.length)) {
    2758                         HTMLDocument.this.putProperty( I18NProperty, Boolean.TRUE);
    2759                     }
    2760                 }
    2761             }
    2762
    2763             if (inTextArea) {
    2764                 textAreaContent(data);
    2765             } else if (inPre) {
    2766                 preContent(data);
    2767             } else if (inTitle) {
    2768                 putProperty(Document.TitleProperty, new String(data));
    2769             } else if (option != null) {
    2770                 option.setLabel(new String(data));
    2771             } else if (inStyle) {
    2772                 if (styles != null) {
    2773                     styles.addElement(new String(data));
    2774                 }
    2775             } else if (inBlock > 0) {
    2776                 if (!foundInsertTag && insertAfterImplied) {
    2777                     // Assume content should be added.
    2778                     foundInsertTag(false);
    2779                     foundInsertTag = true;
    2780                     // If content is added directly to the body, it should
    2781                     // be wrapped by p-implied.
    2782                     inParagraph = impliedP = !insertInBody;
    2783                 }
    2784                 if (data.length >= 1) {
    2785                     addContent(data, 0, data.length);
    2786                 }
    2787             }
    2788         }
    2789
    2790         /**
    2791          * Callback from the parser.  Route to the appropriate
    2792          * handler for the tag.
    2793          */
    2794         public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
    2795             if (receivedEndHTML) {
    2796                 return;
    2797             }
    2798             if (midInsert && !inBody) {
    2799                 if (t == HTML.Tag.BODY) {
    2800                     inBody = true;
    2801                     // Increment inBlock since we know we are in the body,
    2802                     // this is needed incase an implied-p is needed. If
    2803                     // inBlock isn't incremented, and an implied-p is
    2804                     // encountered, addContent won't be called!
    2805                     inBlock++;
    2806                 }
    2807                 return;
    2808             }
    2809             if (!inBody && t == HTML.Tag.BODY) {
    2810                 inBody = true;
    2811             }
    2812             if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
    2813                 // Map the style attributes.
    2814                 String decl = (String)a.getAttribute(HTML.Attribute.STYLE);
    2815                 a.removeAttribute(HTML.Attribute.STYLE);
    2816                 styleAttributes = getStyleSheet().getDeclaration(decl);
    2817                 a.addAttributes(styleAttributes);
    2818             }
    2819             else {
    2820                 styleAttributes = null;
    2821             }
    2822             TagAction action = tagMap.get(t);
    2823
    2824             if (action != null) {
    2825                 action.start(t, a);
    2826             }
    2827         }
    2828
    2829         public void handleComment(char[] data, int pos) {
    2830             if (receivedEndHTML) {
    2831                 addExternalComment(new String(data));
    2832                 return;
    2833             }
    2834             if (inStyle) {
    2835                 if (styles != null) {
    2836                     styles.addElement(new String(data));
    2837                 }
    2838             }
    2839             else if (getPreservesUnknownTags()) {
    2840                 if (inBlock == 0 && (foundInsertTag ||
    2841                                      insertTag != HTML.Tag.COMMENT)) {
    2842                     // Comment outside of body, will not be able to show it,
    2843                     // but can add it as a property on the Document.
    2844                     addExternalComment(new String(data));
    2845                     return;
    2846                 }
    2847                 SimpleAttributeSet sas = new SimpleAttributeSet();
    2848                 sas.addAttribute(HTML.Attribute.COMMENT, new String(data));
    2849                 addSpecialElement(HTML.Tag.COMMENT, sas);
    2850             }
    2851
    2852             TagAction action = tagMap.get(HTML.Tag.COMMENT);
    2853             if (action != null) {
    2854                 action.start(HTML.Tag.COMMENT, new SimpleAttributeSet());
    2855                 action.end(HTML.Tag.COMMENT);
    2856             }
    2857         }
    2858
    2859         /**
    2860          * Adds the comment <code>comment</code> to the set of comments
    2861          * maintained outside of the scope of elements.
    2862          */
    2863         private void addExternalComment(String comment) {
    2864             Object comments = getProperty(AdditionalComments);
    2865             if (comments != null && !(comments instanceof Vector)) {
    2866                 // No place to put comment.
    2867                 return;
    2868             }
    2869             if (comments == null) {
    2870                 comments = new Vector<>();
    2871                 putProperty(AdditionalComments, comments);
    2872             }
    2873             @SuppressWarnings("unchecked")
    2874             Vector<Object> v = (Vector<Object>)comments;
    2875             v.addElement(comment);
    2876         }
    2877
    2878         /**
    2879          * Callback from the parser.  Route to the appropriate
    2880          * handler for the tag.
    2881          */
    2882         public void handleEndTag(HTML.Tag t, int pos) {
    2883             if (receivedEndHTML || (midInsert && !inBody)) {
    2884                 return;
    2885             }
    2886             if (t == HTML.Tag.HTML) {
    2887                 receivedEndHTML = true;
    2888             }
    2889             if (t == HTML.Tag.BODY) {
    2890                 inBody = false;
    2891                 if (midInsert) {
    2892                     inBlock--;
    2893                 }
    2894             }
    2895             TagAction action = tagMap.get(t);
    2896             if (action != null) {
    2897                 action.end(t);
    2898             }
    2899         }
    2900
    2901         /**
    2902          * Callback from the parser.  Route to the appropriate
    2903          * handler for the tag.
    2904          */
    2905         public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
    2906             if (receivedEndHTML || (midInsert && !inBody)) {
    2907                 return;
    2908             }
    2909
    2910             if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
    2911                 // Map the style attributes.
    2912                 String decl = (String)a.getAttribute(HTML.Attribute.STYLE);
    2913                 a.removeAttribute(HTML.Attribute.STYLE);
    2914                 styleAttributes = getStyleSheet().getDeclaration(decl);
    2915                 a.addAttributes(styleAttributes);
    2916             }
    2917             else {
    2918                 styleAttributes = null;
    2919             }
    2920
    2921             TagAction action = tagMap.get(t);
    2922             if (action != null) {
    2923                 action.start(t, a);
    2924                 action.end(t);
    2925             }
    2926             else if (getPreservesUnknownTags()) {
    2927                 // unknown tag, only add if should preserve it.
    2928                 addSpecialElement(t, a);
    2929             }
    2930         }
    2931
    2932         /**
    2933          * This is invoked after the stream has been parsed, but before
    2934          * <code>flush</code>. <code>eol</code> will be one of \n, \r
    2935          * or \r\n, which ever is encountered the most in parsing the
    2936          * stream.
    2937          *
    2938          * @since 1.3
    2939          */
    2940         public void handleEndOfLineString(String eol) {
    2941             if (emptyDocument && eol != null) {
    2942                 putProperty(DefaultEditorKit.EndOfLineStringProperty,
    2943                             eol);
    2944             }
    2945         }
    2946
    2947         // ---- tag handling support ------------------------------
    2948
    2949         /**
    2950          * Registers a handler for the given tag.  By default
    2951          * all of the well-known tags will have been registered.
    2952          * This can be used to change the handling of a particular
    2953          * tag or to add support for custom tags.
    2954          *
    2955          * @param t an HTML tag
    2956          * @param a tag action handler
    2957          */
    2958         protected void registerTag(HTML.Tag t, TagAction a) {
    2959             tagMap.put(t, a);
    2960         }
    2961
    2962         /**
    2963          * An action to be performed in response
    2964          * to parsing a tag.  This allows customization
    2965          * of how each tag is handled and avoids a large
    2966          * switch statement.
    2967          */
    2968         public class TagAction {
    2969
    2970             /**
    2971              * Called when a start tag is seen for the
    2972              * type of tag this action was registered
    2973              * to.  The tag argument indicates the actual
    2974              * tag for those actions that are shared across
    2975              * many tags.  By default this does nothing and
    2976              * completely ignores the tag.
    2977              *
    2978              * @param t the HTML tag
    2979              * @param a the attributes
    2980              */
    2981             public void start(HTML.Tag t, MutableAttributeSet a) {
    2982             }
    2983
    2984             /**
    2985              * Called when an end tag is seen for the
    2986              * type of tag this action was registered
    2987              * to.  The tag argument indicates the actual
    2988              * tag for those actions that are shared across
    2989              * many tags.  By default this does nothing and
    2990              * completely ignores the tag.
    2991              *
    2992              * @param t the HTML tag
    2993              */
    2994             public void end(HTML.Tag t) {
    2995             }
    2996
    2997         }
    2998
    2999         /**
    3000          * Action assigned by default to handle the Block task of the reader.
    3001          */
    3002         public class BlockAction extends TagAction {
    3003
    3004             public void start(HTML.Tag t, MutableAttributeSet attr) {
    3005                 blockOpen(t, attr);
    3006             }
    3007
    3008             public void end(HTML.Tag t) {
    3009                 blockClose(t);
    3010             }
    3011         }
    3012
    3013
    3014         /**
    3015          * Action used for the actual element form tag. This is named such
    3016          * as there was already a public class named FormAction.
    3017          */
    3018         private class FormTagAction extends BlockAction {
    3019             public void start(HTML.Tag t, MutableAttributeSet attr) {
    3020                 super.start(t, attr);
    3021                 // initialize a ButtonGroupsMap when
    3022                 // FORM tag is encountered.  This will
    3023                 // be used for any radio buttons that
    3024                 // might be defined in the FORM.
    3025                 // for new group new ButtonGroup will be created (fix for 4529702)
    3026                 // group name is a key in radioButtonGroupsMap
    3027                 radioButtonGroupsMap = new HashMap<String, ButtonGroup>();
    3028             }
    3029
    3030             public void end(HTML.Tag t) {
    3031                 super.end(t);
    3032                 // reset the button group to null since
    3033                 // the form has ended.
    3034                 radioButtonGroupsMap = null;
    3035             }
    3036         }
    3037
    3038
    3039         /**
    3040          * Action assigned by default to handle the Paragraph task of the reader.
    3041          */
    3042         public class ParagraphAction extends BlockAction {
    3043
    3044             public void start(HTML.Tag t, MutableAttributeSet a) {
    3045                 super.start(t, a);
    3046                 inParagraph = true;
    3047             }
    3048
    3049             public void end(HTML.Tag t) {
    3050                 super.end(t);
    3051                 inParagraph = false;
    3052             }
    3053         }
    3054
    3055         /**
    3056          * Action assigned by default to handle the Special task of the reader.
    3057          */
    3058         public class SpecialAction extends TagAction {
    3059
    3060             public void start(HTML.Tag t, MutableAttributeSet a) {
    3061                 addSpecialElement(t, a);
    3062             }
    3063
    3064         }
    3065
    3066         /**
    3067          * Action assigned by default to handle the Isindex task of the reader.
    3068          */
    3069         public class IsindexAction extends TagAction {
    3070
    3071             public void start(HTML.Tag t, MutableAttributeSet a) {
    3072                 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
    3073                 addSpecialElement(t, a);
    3074                 blockClose(HTML.Tag.IMPLIED);
    3075             }
    3076
    3077         }
    3078
    3079
    3080         /**
    3081          * Action assigned by default to handle the Hidden task of the reader.
    3082          */
    3083         public class HiddenAction extends TagAction {
    3084
    3085             public void start(HTML.Tag t, MutableAttributeSet a) {
    3086                 addSpecialElement(t, a);
    3087             }
    3088
    3089             public void end(HTML.Tag t) {
    3090                 if (!isEmpty(t)) {
    3091                     MutableAttributeSet a = new SimpleAttributeSet();
    3092                     a.addAttribute(HTML.Attribute.ENDTAG, "true");
    3093                     addSpecialElement(t, a);
    3094                 }
    3095             }
    3096
    3097             boolean isEmpty(HTML.Tag t) {
    3098                 if (t == HTML.Tag.APPLET ||
    3099                     t == HTML.Tag.SCRIPT) {
    3100                     return false;
    3101                 }
    3102                 return true;
    3103             }
    3104         }
    3105
    3106
    3107         /**
    3108          * Subclass of HiddenAction to set the content type for style sheets,
    3109          * and to set the name of the default style sheet.
    3110          */
    3111         class MetaAction extends HiddenAction {
    3112
    3113             public void start(HTML.Tag t, MutableAttributeSet a) {
    3114                 Object equiv = a.getAttribute(HTML.Attribute.HTTPEQUIV);
    3115                 if (equiv != null) {
    3116                     equiv = ((String)equiv).toLowerCase();
    3117                     if (equiv.equals("content-style-type")) {
    3118                         String value = (String)a.getAttribute
    3119                                        (HTML.Attribute.CONTENT);
    3120                         setDefaultStyleSheetType(value);
    3121                         isStyleCSS = "text/css".equals
    3122                                       (getDefaultStyleSheetType());
    3123                     }
    3124                     else if (equiv.equals("default-style")) {
    3125                         defaultStyle = (String)a.getAttribute
    3126                                        (HTML.Attribute.CONTENT);
    3127                     }
    3128                 }
    3129                 super.start(t, a);
    3130             }
    3131
    3132             boolean isEmpty(HTML.Tag t) {
    3133                 return true;
    3134             }
    3135         }
    3136
    3137
    3138         /**
    3139          * End if overridden to create the necessary stylesheets that
    3140          * are referenced via the link tag. It is done in this manner
    3141          * as the meta tag can be used to specify an alternate style sheet,
    3142          * and is not guaranteed to come before the link tags.
    3143          */
    3144         class HeadAction extends BlockAction {
    3145
    3146             public void start(HTML.Tag t, MutableAttributeSet a) {
    3147                 inHead = true;
    3148                 // This check of the insertTag is put in to avoid considering
    3149                 // the implied-p that is generated for the head. This allows
    3150                 // inserts for HR to work correctly.
    3151                 if ((insertTag == null && !insertAfterImplied) ||
    3152                     (insertTag == HTML.Tag.HEAD) ||
    3153                     (insertAfterImplied &&
    3154                      (foundInsertTag || !a.isDefined(IMPLIED)))) {
    3155                     super.start(t, a);
    3156                 }
    3157             }
    3158
    3159             public void end(HTML.Tag t) {
    3160                 inHead = inStyle = false;
    3161                 // See if there is a StyleSheet to link to.
    3162                 if (styles != null) {
    3163                     boolean isDefaultCSS = isStyleCSS;
    3164                     for (int counter = 0, maxCounter = styles.size();
    3165                          counter < maxCounter;) {
    3166                         Object value = styles.elementAt(counter);
    3167                         if (value == HTML.Tag.LINK) {
    3168                             handleLink((AttributeSet)styles.
    3169                                        elementAt(++counter));
    3170                             counter++;
    3171                         }
    3172                         else {
    3173                             // Rule.
    3174                             // First element gives type.
    3175                             String type = (String)styles.elementAt(++counter);
    3176                             boolean isCSS = (type == null) ? isDefaultCSS :
    3177                                             type.equals("text/css");
    3178                             while (++counter < maxCounter &&
    3179                                    (styles.elementAt(counter)
    3180                                     instanceof String)) {
    3181                                 if (isCSS) {
    3182                                     addCSSRules((String)styles.elementAt
    3183                                                 (counter));
    3184                                 }
    3185                             }
    3186                         }
    3187                     }
    3188                 }
    3189                 if ((insertTag == null && !insertAfterImplied) ||
    3190                     insertTag == HTML.Tag.HEAD ||
    3191                     (insertAfterImplied && foundInsertTag)) {
    3192                     super.end(t);
    3193                 }
    3194             }
    3195
    3196             boolean isEmpty(HTML.Tag t) {
    3197                 return false;
    3198             }
    3199
    3200             private void handleLink(AttributeSet attr) {
    3201                 // Link.
    3202                 String type = (String)attr.getAttribute(HTML.Attribute.TYPE);
    3203                 if (type == null) {
    3204                     type = getDefaultStyleSheetType();
    3205                 }
    3206                 // Only choose if type==text/css
    3207                 // Select link if rel==stylesheet.
    3208                 // Otherwise if rel==alternate stylesheet and
    3209                 //   title matches default style.
    3210                 if (type.equals("text/css")) {
    3211                     String rel = (String)attr.getAttribute(HTML.Attribute.REL);
    3212                     String title = (String)attr.getAttribute
    3213                                                (HTML.Attribute.TITLE);
    3214                     String media = (String)attr.getAttribute
    3215                                                    (HTML.Attribute.MEDIA);
    3216                     if (media == null) {
    3217                         media = "all";
    3218                     }
    3219                     else {
    3220                         media = media.toLowerCase();
    3221                     }
    3222                     if (rel != null) {
    3223                         rel = rel.toLowerCase();
    3224                         if ((media.indexOf("all") != -1 ||
    3225                              media.indexOf("screen") != -1) &&
    3226                             (rel.equals("stylesheet") ||
    3227                              (rel.equals("alternate stylesheet") &&
    3228                               title.equals(defaultStyle)))) {
    3229                             linkCSSStyleSheet((String)attr.getAttribute
    3230                                               (HTML.Attribute.HREF));
    3231                         }
    3232                     }
    3233                 }
    3234             }
    3235         }
    3236
    3237
    3238         /**
    3239          * A subclass to add the AttributeSet to styles if the
    3240          * attributes contains an attribute for 'rel' with value
    3241          * 'stylesheet' or 'alternate stylesheet'.
    3242          */
    3243         class LinkAction extends HiddenAction {
    3244
    3245             public void start(HTML.Tag t, MutableAttributeSet a) {
    3246                 String rel = (String)a.getAttribute(HTML.Attribute.REL);
    3247                 if (rel != null) {
    3248                     rel = rel.toLowerCase();
    3249                     if (rel.equals("stylesheet") ||
    3250                         rel.equals("alternate stylesheet")) {
    3251                         if (styles == null) {
    3252                             styles = new Vector<Object>(3);
    3253                         }
    3254                         styles.addElement(t);
    3255                         styles.addElement(a.copyAttributes());
    3256                     }
    3257                 }
    3258                 super.start(t, a);
    3259             }
    3260         }
    3261
    3262         class MapAction extends TagAction {
    3263
    3264             public void start(HTML.Tag t, MutableAttributeSet a) {
    3265                 lastMap = new Map((String)a.getAttribute(HTML.Attribute.NAME));
    3266                 addMap(lastMap);
    3267             }
    3268
    3269             public void end(HTML.Tag t) {
    3270             }
    3271         }
    3272
    3273
    3274         class AreaAction extends TagAction {
    3275
    3276             public void start(HTML.Tag t, MutableAttributeSet a) {
    3277                 if (lastMap != null) {
    3278                     lastMap.addArea(a.copyAttributes());
    3279                 }
    3280             }
    3281
    3282             public void end(HTML.Tag t) {
    3283             }
    3284         }
    3285
    3286
    3287         class StyleAction extends TagAction {
    3288
    3289             public void start(HTML.Tag t, MutableAttributeSet a) {
    3290                 if (inHead) {
    3291                     if (styles == null) {
    3292                         styles = new Vector<Object>(3);
    3293                     }
    3294                     styles.addElement(t);
    3295                     styles.addElement(a.getAttribute(HTML.Attribute.TYPE));
    3296                     inStyle = true;
    3297                 }
    3298             }
    3299
    3300             public void end(HTML.Tag t) {
    3301                 inStyle = false;
    3302             }
    3303
    3304             boolean isEmpty(HTML.Tag t) {
    3305                 return false;
    3306             }
    3307         }
    3308
    3309         /**
    3310          * Action assigned by default to handle the Pre block task of the reader.
    3311          */
    3312         public class PreAction extends BlockAction {
    3313
    3314             public void start(HTML.Tag t, MutableAttributeSet attr) {
    3315                 inPre = true;
    3316                 blockOpen(t, attr);
    3317                 attr.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
    3318                 blockOpen(HTML.Tag.IMPLIED, attr);
    3319             }
    3320
    3321             public void end(HTML.Tag t) {
    3322                 blockClose(HTML.Tag.IMPLIED);
    3323                 // set inPre to false after closing, so that if a newline
    3324                 // is added it won't generate a blockOpen.
    3325                 inPre = false;
    3326                 blockClose(t);
    3327             }
    3328         }
    3329
    3330         /**
    3331          * Action assigned by default to handle the Character task of the reader.
    3332          */
    3333         public class CharacterAction extends TagAction {
    3334
    3335             public void start(HTML.Tag t, MutableAttributeSet attr) {
    3336                 pushCharacterStyle();
    3337                 if (!foundInsertTag) {
    3338                     // Note that the third argument should really be based off
    3339                     // inParagraph and impliedP. If we're wrong (that is
    3340                     // insertTagDepthDelta shouldn't be changed), we'll end up
    3341                     // removing an extra EndSpec, which won't matter anyway.
    3342                     boolean insert = canInsertTag(t, attr, false);
    3343                     if (foundInsertTag) {
    3344                         if (!inParagraph) {
    3345                             inParagraph = impliedP = true;
    3346                         }
    3347                     }
    3348                     if (!insert) {
    3349                         return;
    3350                     }
    3351                 }
    3352                 if (attr.isDefined(IMPLIED)) {
    3353                     attr.removeAttribute(IMPLIED);
    3354                 }
    3355                 charAttr.addAttribute(t, attr.copyAttributes());
    3356                 if (styleAttributes != null) {
    3357                     charAttr.addAttributes(styleAttributes);
    3358                 }
    3359             }
    3360
    3361             public void end(HTML.Tag t) {
    3362                 popCharacterStyle();
    3363             }
    3364         }
    3365
    3366         /**
    3367          * Provides conversion of HTML tag/attribute
    3368          * mappings that have a corresponding StyleConstants
    3369          * and CSS mapping.  The conversion is to CSS attributes.
    3370          */
    3371         class ConvertAction extends TagAction {
    3372
    3373             public void start(HTML.Tag t, MutableAttributeSet attr) {
    3374                 pushCharacterStyle();
    3375                 if (!foundInsertTag) {
    3376                     // Note that the third argument should really be based off
    3377                     // inParagraph and impliedP. If we're wrong (that is
    3378                     // insertTagDepthDelta shouldn't be changed), we'll end up
    3379                     // removing an extra EndSpec, which won't matter anyway.
    3380                     boolean insert = canInsertTag(t, attr, false);
    3381                     if (foundInsertTag) {
    3382                         if (!inParagraph) {
    3383                             inParagraph = impliedP = true;
    3384                         }
    3385                     }
    3386                     if (!insert) {
    3387                         return;
    3388                     }
    3389                 }
    3390                 if (attr.isDefined(IMPLIED)) {
    3391                     attr.removeAttribute(IMPLIED);
    3392                 }
    3393                 if (styleAttributes != null) {
    3394                     charAttr.addAttributes(styleAttributes);
    3395                 }
    3396                 // We also need to add attr, otherwise we lose custom
    3397                 // attributes, including class/id for style lookups, and
    3398                 // further confuse style lookup (doesn't have tag).
    3399                 charAttr.addAttribute(t, attr.copyAttributes());
    3400                 StyleSheet sheet = getStyleSheet();
    3401                 if (t == HTML.Tag.B) {
    3402                     sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_WEIGHT, "bold");
    3403                 } else if (t == HTML.Tag.I) {
    3404                     sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_STYLE, "italic");
    3405                 } else if (t == HTML.Tag.U) {
    3406                     Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
    3407                     String value = "underline";
    3408                     value = (v != null) ? value + "," + v.toString() : value;
    3409                     sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
    3410                 } else if (t == HTML.Tag.STRIKE) {
    3411                     Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
    3412                     String value = "line-through";
    3413                     value = (v != null) ? value + "," + v.toString() : value;
    3414                     sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
    3415                 } else if (t == HTML.Tag.SUP) {
    3416                     Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
    3417                     String value = "sup";
    3418                     value = (v != null) ? value + "," + v.toString() : value;
    3419                     sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
    3420                 } else if (t == HTML.Tag.SUB) {
    3421                     Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
    3422                     String value = "sub";
    3423                     value = (v != null) ? value + "," + v.toString() : value;
    3424                     sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
    3425                 } else if (t == HTML.Tag.FONT) {
    3426                     String color = (String) attr.getAttribute(HTML.Attribute.COLOR);
    3427                     if (color != null) {
    3428                         sheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color);
    3429                     }
    3430                     String face = (String) attr.getAttribute(HTML.Attribute.FACE);
    3431                     if (face != null) {
    3432                         sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY, face);
    3433                     }
    3434                     String size = (String) attr.getAttribute(HTML.Attribute.SIZE);
    3435                     if (size != null) {
    3436                         sheet.addCSSAttributeFromHTML(charAttr, CSS.Attribute.FONT_SIZE, size);
    3437                     }
    3438                 }
    3439             }
    3440
    3441             public void end(HTML.Tag t) {
    3442                 popCharacterStyle();
    3443             }
    3444
    3445         }
    3446
    3447         class AnchorAction extends CharacterAction {
    3448
    3449             public void start(HTML.Tag t, MutableAttributeSet attr) {
    3450                 // set flag to catch empty anchors
    3451                 emptyAnchor = true;
    3452                 super.start(t, attr);
    3453             }
    3454
    3455             public void end(HTML.Tag t) {
    3456                 if (emptyAnchor) {
    3457                     // if the anchor was empty it was probably a
    3458                     // named anchor point and we don't want to throw
    3459                     // it away.
    3460                     char[] one = new char[1];
    3461                     one[0] = '\n';
    3462                     addContent(one, 0, 1);
    3463                 }
    3464                 super.end(t);
    3465             }
    3466         }
    3467
    3468         class TitleAction extends HiddenAction {
    3469
    3470             public void start(HTML.Tag t, MutableAttributeSet attr) {
    3471                 inTitle = true;
    3472                 super.start(t, attr);
    3473             }
    3474
    3475             public void end(HTML.Tag t) {
    3476                 inTitle = false;
    3477                 super.end(t);
    3478             }
    3479
    3480             boolean isEmpty(HTML.Tag t) {
    3481                 return false;
    3482             }
    3483         }
    3484
    3485
    3486         class BaseAction extends TagAction {
    3487
    3488             public void start(HTML.Tag t, MutableAttributeSet attr) {
    3489                 String href = (String) attr.getAttribute(HTML.Attribute.HREF);
    3490                 if (href != null) {
    3491                     try {
    3492                         URL newBase = new URL(base, href);
    3493                         setBase(newBase);
    3494                         hasBaseTag = true;
    3495                     } catch (MalformedURLException ex) {
    3496                     }
    3497                 }
    3498                 baseTarget = (String) attr.getAttribute(HTML.Attribute.TARGET);
    3499             }
    3500         }
    3501
    3502         class ObjectAction extends SpecialAction {
    3503
    3504             public void start(HTML.Tag t, MutableAttributeSet a) {
    3505                 if (t == HTML.Tag.PARAM) {
    3506                     addParameter(a);
    3507                 } else {
    3508                     super.start(t, a);
    3509                 }
    3510             }
    3511
    3512             public void end(HTML.Tag t) {
    3513                 if (t != HTML.Tag.PARAM) {
    3514                     super.end(t);
    3515                 }
    3516             }
    3517
    3518             void addParameter(AttributeSet a) {
    3519                 String name = (String) a.getAttribute(HTML.Attribute.NAME);
    3520                 String value = (String) a.getAttribute(HTML.Attribute.VALUE);
    3521                 if ((name != null) && (value != null)) {
    3522                     ElementSpec objSpec = parseBuffer.lastElement();
    3523                     MutableAttributeSet objAttr = (MutableAttributeSet) objSpec.getAttributes();
    3524                     objAttr.addAttribute(name, value);
    3525                 }
    3526             }
    3527         }
    3528
    3529         /**
    3530          * Action to support forms by building all of the elements
    3531          * used to represent form controls.  This will process
    3532          * the &lt;INPUT&gt;, &lt;TEXTAREA&gt;, &lt;SELECT&gt;,
    3533          * and &lt;OPTION&gt; tags.  The element created by
    3534          * this action is expected to have the attribute
    3535          * <code>StyleConstants.ModelAttribute</code> set to
    3536          * the model that holds the state for the form control.
    3537          * This enables multiple views, and allows document to
    3538          * be iterated over picking up the data of the form.
    3539          * The following are the model assignments for the
    3540          * various type of form elements.
    3541          *
    3542          * <table class="striped">
    3543          * <caption>Model assignments for the various types of form elements
    3544          * </caption>
    3545          * <thead>
    3546          *   <tr>
    3547          *     <th scope="col">Element Type
    3548          *     <th scope="col">Model Type
    3549          * </thead>
    3550          * <tbody>
    3551          *   <tr>
    3552          *     <th scope="row">input, type button
    3553          *     <td>{@link DefaultButtonModel}
    3554          *   <tr>
    3555          *     <th scope="row">input, type checkbox
    3556          *     <td>{@link JToggleButton.ToggleButtonModel}
    3557          *   <tr>
    3558          *     <th scope="row">input, type image
    3559          *     <td>{@link DefaultButtonModel}
    3560          *   <tr>
    3561          *     <th scope="row">input, type password
    3562          *     <td>{@link PlainDocument}
    3563          *   <tr>
    3564          *     <th scope="row">input, type radio
    3565          *     <td>{@link JToggleButton.ToggleButtonModel}
    3566          *   <tr>
    3567          *     <th scope="row">input, type reset
    3568          *     <td>{@link DefaultButtonModel}
    3569          *   <tr>
    3570          *     <th scope="row">input, type submit
    3571          *     <td>{@link DefaultButtonModel}
    3572          *   <tr>
    3573          *     <th scope="row">input, type text or type is null.
    3574          *     <td>{@link PlainDocument}
    3575          *   <tr>
    3576          *     <th scope="row">select
    3577          *     <td>{@link DefaultComboBoxModel} or an {@link DefaultListModel},
    3578          *     with an item type of Option
    3579          *   <tr>
    3580          *     <td>textarea
    3581          *     <td>{@link PlainDocument}
    3582          * </tbody>
    3583          * </table>
    3584          */
    3585         public class FormAction extends SpecialAction {
    3586
    3587             public void start(HTML.Tag t, MutableAttributeSet attr) {
    3588                 if (t == HTML.Tag.INPUT) {
    3589                     String type = (String)
    3590                         attr.getAttribute(HTML.Attribute.TYPE);
    3591                     /*
    3592                      * if type is not defined the default is
    3593                      * assumed to be text.
    3594                      */
    3595                     if (type == null) {
    3596                         type = "text";
    3597                         attr.addAttribute(HTML.Attribute.TYPE, "text");
    3598                     }
    3599                     setModel(type, attr);
    3600                 } else if (t == HTML.Tag.TEXTAREA) {
    3601                     inTextArea = true;
    3602                     textAreaDocument = new TextAreaDocument();
    3603                     attr.addAttribute(StyleConstants.ModelAttribute,
    3604                                       textAreaDocument);
    3605                 } else if (t == HTML.Tag.SELECT) {
    3606                     int size = HTML.getIntegerAttributeValue(attr,
    3607                                                              HTML.Attribute.SIZE,
    3608                                                              1);
    3609                     boolean multiple = attr.getAttribute(HTML.Attribute.MULTIPLE) != null;
    3610                     if ((size > 1) || multiple) {
    3611                         OptionListModel<Option> m = new OptionListModel<Option>();
    3612                         if (multiple) {
    3613                             m.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    3614                         }
    3615                         selectModel = m;
    3616                     } else {
    3617                         selectModel = new OptionComboBoxModel<Option>();
    3618                     }
    3619                     attr.addAttribute(StyleConstants.ModelAttribute,
    3620                                       selectModel);
    3621
    3622                 }
    3623
    3624                 // build the element, unless this is an option.
    3625                 if (t == HTML.Tag.OPTION) {
    3626                     option = new Option(attr);
    3627
    3628                     if (selectModel instanceof OptionListModel) {
    3629                         @SuppressWarnings("unchecked")
    3630                         OptionListModel<Option> m = (OptionListModel<Option>) selectModel;
    3631                         m.addElement(option);
    3632                         if (option.isSelected()) {
    3633                             m.addSelectionInterval(optionCount, optionCount);
    3634                             m.setInitialSelection(optionCount);
    3635                         }
    3636                     } else if (selectModel instanceof OptionComboBoxModel) {
    3637                         @SuppressWarnings("unchecked")
    3638                         OptionComboBoxModel<Option> m = (OptionComboBoxModel<Option>) selectModel;
    3639                         m.addElement(option);
    3640                         if (option.isSelected()) {
    3641                             m.setSelectedItem(option);
    3642                             m.setInitialSelection(option);
    3643                         }
    3644                     }
    3645                     optionCount++;
    3646                 } else {
    3647                     super.start(t, attr);
    3648                 }
    3649             }
    3650
    3651             public void end(HTML.Tag t) {
    3652                 if (t == HTML.Tag.OPTION) {
    3653                     option = null;
    3654                 } else {
    3655                     if (t == HTML.Tag.SELECT) {
    3656                         selectModel = null;
    3657                         optionCount = 0;
    3658                     } else if (t == HTML.Tag.TEXTAREA) {
    3659                         inTextArea = false;
    3660
    3661                         /* Now that the textarea has ended,
    3662                          * store the entire initial text
    3663                          * of the text area.  This will
    3664                          * enable us to restore the initial
    3665                          * state if a reset is requested.
    3666                          */
    3667                         textAreaDocument.storeInitialText();
    3668                     }
    3669                     super.end(t);
    3670                 }
    3671             }
    3672
    3673             void setModel(String type, MutableAttributeSet attr) {
    3674                 if (type.equals("submit") ||
    3675                     type.equals("reset") ||
    3676                     type.equals("image")) {
    3677
    3678                     // button model
    3679                     attr.addAttribute(StyleConstants.ModelAttribute,
    3680                                       new DefaultButtonModel());
    3681                 } else if (type.equals("text") ||
    3682                            type.equals("password")) {
    3683                     // plain text model
    3684                     int maxLength = HTML.getIntegerAttributeValue(
    3685                                        attr, HTML.Attribute.MAXLENGTH, -1);
    3686                     Document doc;
    3687
    3688                     if (maxLength > 0) {
    3689                         doc = new FixedLengthDocument(maxLength);
    3690                     }
    3691                     else {
    3692                         doc = new PlainDocument();
    3693                     }
    3694                     String value = (String)
    3695                         attr.getAttribute(HTML.Attribute.VALUE);
    3696                     try {
    3697                         doc.insertString(0, value, null);
    3698                     } catch (BadLocationException e) {
    3699                     }
    3700                     attr.addAttribute(StyleConstants.ModelAttribute, doc);
    3701                 } else if (type.equals("file")) {
    3702                     // plain text model
    3703                     attr.addAttribute(StyleConstants.ModelAttribute,
    3704                                       new PlainDocument());
    3705                 } else if (type.equals("checkbox") ||
    3706                            type.equals("radio")) {
    3707                     JToggleButton.ToggleButtonModel model = new JToggleButton.ToggleButtonModel();
    3708                     if (type.equals("radio")) {
    3709                         String name = (String) attr.getAttribute(HTML.Attribute.NAME);
    3710                         if ( radioButtonGroupsMap == null ) { //fix for 4772743
    3711                            radioButtonGroupsMap = new HashMap<String, ButtonGroup>();
    3712                         }
    3713                         ButtonGroup radioButtonGroup = radioButtonGroupsMap.get(name);
    3714                         if (radioButtonGroup == null) {
    3715                             radioButtonGroup = new ButtonGroup();
    3716                             radioButtonGroupsMap.put(name,radioButtonGroup);
    3717                         }
    3718                         model.setGroup(radioButtonGroup);
    3719                     }
    3720                     boolean checked = (attr.getAttribute(HTML.Attribute.CHECKED) != null);
    3721                     model.setSelected(checked);
    3722                     attr.addAttribute(StyleConstants.ModelAttribute, model);
    3723                 }
    3724             }
    3725
    3726             /**
    3727              * If a &lt;SELECT&gt; tag is being processed, this
    3728              * model will be a reference to the model being filled
    3729              * with the &lt;OPTION&gt; elements (which produce
    3730              * objects of type <code>Option</code>.
    3731              */
    3732             Object selectModel;
    3733             int optionCount;
    3734         }
    3735
    3736
    3737         // --- utility methods used by the reader ------------------
    3738
    3739         /**
    3740          * Pushes the current character style on a stack in preparation
    3741          * for forming a new nested character style.
    3742          */
    3743         protected void pushCharacterStyle() {
    3744             charAttrStack.push(charAttr.copyAttributes());
    3745         }
    3746
    3747         /**
    3748          * Pops a previously pushed character style off the stack
    3749          * to return to a previous style.
    3750          */
    3751         protected void popCharacterStyle() {
    3752             if (!charAttrStack.empty()) {
    3753                 charAttr = (MutableAttributeSet) charAttrStack.peek();
    3754                 charAttrStack.pop();
    3755             }
    3756         }
    3757
    3758         /**
    3759          * Adds the given content to the textarea document.
    3760          * This method gets called when we are in a textarea
    3761          * context.  Therefore all text that is seen belongs
    3762          * to the text area and is hence added to the
    3763          * TextAreaDocument associated with the text area.
    3764          *
    3765          * @param data the given content
    3766          */
    3767         protected void textAreaContent(char[] data) {
    3768             try {
    3769                 textAreaDocument.insertString(textAreaDocument.getLength(), new String(data), null);
    3770             } catch (BadLocationException e) {
    3771                 // Should do something reasonable
    3772             }
    3773         }
    3774
    3775         /**
    3776          * Adds the given content that was encountered in a
    3777          * PRE element.  This synthesizes lines to hold the
    3778          * runs of text, and makes calls to addContent to
    3779          * actually add the text.
    3780          *
    3781          * @param data the given content
    3782          */
    3783         protected void preContent(char[] data) {
    3784             int last = 0;
    3785             for (int i = 0; i < data.length; i++) {
    3786                 if (data[i] == '\n') {
    3787                     addContent(data, last, i - last + 1);
    3788                     blockClose(HTML.Tag.IMPLIED);
    3789                     MutableAttributeSet a = new SimpleAttributeSet();
    3790                     a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
    3791                     blockOpen(HTML.Tag.IMPLIED, a);
    3792                     last = i + 1;
    3793                 }
    3794             }
    3795             if (last < data.length) {
    3796                 addContent(data, last, data.length - last);
    3797             }
    3798         }
    3799
    3800         /**
    3801          * Adds an instruction to the parse buffer to create a
    3802          * block element with the given attributes.
    3803          *
    3804          * @param t an HTML tag
    3805          * @param attr the attribute set
    3806          */
    3807         protected void blockOpen(HTML.Tag t, MutableAttributeSet attr) {
    3808             if (impliedP) {
    3809                 blockClose(HTML.Tag.IMPLIED);
    3810             }
    3811
    3812             inBlock++;
    3813
    3814             if (!canInsertTag(t, attr, true)) {
    3815                 return;
    3816             }
    3817             if (attr.isDefined(IMPLIED)) {
    3818                 attr.removeAttribute(IMPLIED);
    3819             }
    3820             lastWasNewline = false;
    3821             attr.addAttribute(StyleConstants.NameAttribute, t);
    3822             ElementSpec es = new ElementSpec(
    3823                 attr.copyAttributes(), ElementSpec.StartTagType);
    3824             parseBuffer.addElement(es);
    3825         }
    3826
    3827         /**
    3828          * Adds an instruction to the parse buffer to close out
    3829          * a block element of the given type.
    3830          *
    3831          * @param t the HTML tag
    3832          */
    3833         protected void blockClose(HTML.Tag t) {
    3834             inBlock--;
    3835
    3836             if (!foundInsertTag) {
    3837                 return;
    3838             }
    3839
    3840             // Add a new line, if the last character wasn't one. This is
    3841             // needed for proper positioning of the cursor. addContent
    3842             // with true will force an implied paragraph to be generated if
    3843             // there isn't one. This may result in a rather bogus structure
    3844             // (perhaps a table with a child pargraph), but the paragraph
    3845             // is needed for proper positioning and display.
    3846             if(!lastWasNewline) {
    3847                 pushCharacterStyle();
    3848                 charAttr.addAttribute(IMPLIED_CR, Boolean.TRUE);
    3849                 addContent(NEWLINE, 0, 1, true);
    3850                 popCharacterStyle();
    3851                 lastWasNewline = true;
    3852             }
    3853
    3854             if (impliedP) {
    3855                 impliedP = false;
    3856                 inParagraph = false;
    3857                 if (t != HTML.Tag.IMPLIED) {
    3858                     blockClose(HTML.Tag.IMPLIED);
    3859                 }
    3860             }
    3861             // an open/close with no content will be removed, so we
    3862             // add a space of content to keep the element being formed.
    3863             ElementSpec prev = (parseBuffer.size() > 0) ?
    3864                 parseBuffer.lastElement() : null;
    3865             if (prev != null && prev.getType() == ElementSpec.StartTagType) {
    3866                 char[] one = new char[1];
    3867                 one[0] = ' ';
    3868                 addContent(one, 0, 1);
    3869             }
    3870             ElementSpec es = new ElementSpec(
    3871                 null, ElementSpec.EndTagType);
    3872             parseBuffer.addElement(es);
    3873         }
    3874
    3875         /**
    3876          * Adds some text with the current character attributes.
    3877          *
    3878          * @param data the content to add
    3879          * @param offs the initial offset
    3880          * @param length the length
    3881          */
    3882         protected void addContent(char[] data, int offs, int length) {
    3883             addContent(data, offs, length, true);
    3884         }
    3885
    3886         /**
    3887          * Adds some text with the current character attributes.
    3888          *
    3889          * @param data the content to add
    3890          * @param offs the initial offset
    3891          * @param length the length
    3892          * @param generateImpliedPIfNecessary whether to generate implied
    3893          * paragraphs
    3894          */
    3895         protected void addContent(char[] data, int offs, int length,
    3896                                   boolean generateImpliedPIfNecessary) {
    3897             if (!foundInsertTag) {
    3898                 return;
    3899             }
    3900
    3901             if (generateImpliedPIfNecessary && (! inParagraph) && (! inPre)) {
    3902                 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
    3903                 inParagraph = true;
    3904                 impliedP = true;
    3905             }
    3906             emptyAnchor = false;
    3907             charAttr.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
    3908             AttributeSet a = charAttr.copyAttributes();
    3909             ElementSpec es = new ElementSpec(
    3910                 a, ElementSpec.ContentType, data, offs, length);
    3911             parseBuffer.addElement(es);
    3912
    3913             if (parseBuffer.size() > threshold) {
    3914                 if ( threshold <= MaxThreshold ) {
    3915                     threshold *= StepThreshold;
    3916                 }
    3917                 try {
    3918                     flushBuffer(false);
    3919                 } catch (BadLocationException ble) {
    3920                 }
    3921             }
    3922             if(length > 0) {
    3923                 lastWasNewline = (data[offs + length - 1] == '\n');
    3924             }
    3925         }
    3926
    3927         /**
    3928          * Adds content that is basically specified entirely
    3929          * in the attribute set.
    3930          *
    3931          * @param t an HTML tag
    3932          * @param a the attribute set
    3933          */
    3934         protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a) {
    3935             if ((t != HTML.Tag.FRAME) && (! inParagraph) && (! inPre)) {
    3936                 nextTagAfterPImplied = t;
    3937                 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
    3938                 nextTagAfterPImplied = null;
    3939                 inParagraph = true;
    3940                 impliedP = true;
    3941             }
    3942             if (!canInsertTag(t, a, t.isBlock())) {
    3943                 return;
    3944             }
    3945             if (a.isDefined(IMPLIED)) {
    3946                 a.removeAttribute(IMPLIED);
    3947             }
    3948             emptyAnchor = false;
    3949             a.addAttributes(charAttr);
    3950             a.addAttribute(StyleConstants.NameAttribute, t);
    3951             char[] one = new char[1];
    3952             one[0] = ' ';
    3953             ElementSpec es = new ElementSpec(
    3954                 a.copyAttributes(), ElementSpec.ContentType, one, 0, 1);
    3955             parseBuffer.addElement(es);
    3956             // Set this to avoid generating a newline for frames, frames
    3957             // shouldn't have any content, and shouldn't need a newline.
    3958             if (t == HTML.Tag.FRAME) {
    3959                 lastWasNewline = true;
    3960             }
    3961         }
    3962
    3963         /**
    3964          * Flushes the current parse buffer into the document.
    3965          * @param endOfStream true if there is no more content to parser
    3966          */
    3967         void flushBuffer(boolean endOfStream) throws BadLocationException {
    3968             int oldLength = HTMLDocument.this.getLength();
    3969             int size = parseBuffer.size();
    3970             if (endOfStream && (insertTag != null || insertAfterImplied) &&
    3971                 size > 0) {
    3972                 adjustEndSpecsForPartialInsert();
    3973                 size = parseBuffer.size();
    3974             }
    3975             ElementSpec[] spec = new ElementSpec[size];
    3976             parseBuffer.copyInto(spec);
    3977
    3978             if (oldLength == 0 && (insertTag == null && !insertAfterImplied)) {
    3979                 create(spec);
    3980             } else {
    3981                 insert(offset, spec);
    3982             }
    3983             parseBuffer.removeAllElements();
    3984             offset += HTMLDocument.this.getLength() - oldLength;
    3985             flushCount++;
    3986         }
    3987
    3988         /**
    3989          * This will be invoked for the last flush, if <code>insertTag</code>
    3990          * is non null.
    3991          */
    3992         private void adjustEndSpecsForPartialInsert() {
    3993             int size = parseBuffer.size();
    3994             if (insertTagDepthDelta < 0) {
    3995                 // When inserting via an insertTag, the depths (of the tree
    3996                 // being read in, and existing hierarchy) may not match up.
    3997                 // This attemps to clean it up.
    3998                 int removeCounter = insertTagDepthDelta;
    3999                 while (removeCounter < 0 && size >= 0 &&
    4000                         parseBuffer.elementAt(size - 1).
    4001                        getType() == ElementSpec.EndTagType) {
    4002                     parseBuffer.removeElementAt(--size);
    4003                     removeCounter++;
    4004                 }
    4005             }
    4006             if (flushCount == 0 && (!insertAfterImplied ||
    4007                                     !wantsTrailingNewline)) {
    4008                 // If this starts with content (or popDepth > 0 &&
    4009                 // pushDepth > 0) and ends with EndTagTypes, make sure
    4010                 // the last content isn't a \n, otherwise will end up with
    4011                 // an extra \n in the middle of content.
    4012                 int index = 0;
    4013                 if (pushDepth > 0) {
    4014                     if (parseBuffer.elementAt(0).getType() ==
    4015                         ElementSpec.ContentType) {
    4016                         index++;
    4017                     }
    4018                 }
    4019                 index += (popDepth + pushDepth);
    4020                 int cCount = 0;
    4021                 int cStart = index;
    4022                 while (index < size && parseBuffer.elementAt
    4023                         (index).getType() == ElementSpec.ContentType) {
    4024                     index++;
    4025                     cCount++;
    4026                 }
    4027                 if (cCount > 1) {
    4028                     while (index < size && parseBuffer.elementAt
    4029                             (index).getType() == ElementSpec.EndTagType) {
    4030                         index++;
    4031                     }
    4032                     if (index == size) {
    4033                         char[] lastText = parseBuffer.elementAt
    4034                                 (cStart + cCount - 1).getArray();
    4035                         if (lastText.length == 1 && lastText[0] == NEWLINE[0]){
    4036                             index = cStart + cCount - 1;
    4037                             while (size > index) {
    4038                                 parseBuffer.removeElementAt(--size);
    4039                             }
    4040                         }
    4041                     }
    4042                 }
    4043             }
    4044             if (wantsTrailingNewline) {
    4045                 // Make sure there is in fact a newline
    4046                 for (int counter = parseBuffer.size() - 1; counter >= 0;
    4047                                    counter--) {
    4048                     ElementSpec spec = parseBuffer.elementAt(counter);
    4049                     if (spec.getType() == ElementSpec.ContentType) {
    4050                         if (spec.getArray()[spec.getLength() - 1] != '\n') {
    4051                             SimpleAttributeSet attrs =new SimpleAttributeSet();
    4052
    4053                             attrs.addAttribute(StyleConstants.NameAttribute,
    4054                                                HTML.Tag.CONTENT);
    4055                             parseBuffer.insertElementAt(new ElementSpec(
    4056                                     attrs,
    4057                                     ElementSpec.ContentType, NEWLINE, 0, 1),
    4058                                     counter + 1);
    4059                         }
    4060                         break;
    4061                     }
    4062                 }
    4063             }
    4064         }
    4065
    4066         /**
    4067          * Adds the CSS rules in <code>rules</code>.
    4068          */
    4069         void addCSSRules(String rules) {
    4070             StyleSheet ss = getStyleSheet();
    4071             ss.addRule(rules);
    4072         }
    4073
    4074         /**
    4075          * Adds the CSS stylesheet at <code>href</code> to the known list
    4076          * of stylesheets.
    4077          */
    4078         void linkCSSStyleSheet(String href) {
    4079             URL url;
    4080             try {
    4081                 url = new URL(base, href);
    4082             } catch (MalformedURLException mfe) {
    4083                 try {
    4084                     url = new URL(href);
    4085                 } catch (MalformedURLException mfe2) {
    4086                     url = null;
    4087                 }
    4088             }
    4089             if (url != null) {
    4090                 getStyleSheet().importStyleSheet(url);
    4091             }
    4092         }
    4093
    4094         /**
    4095          * Returns true if can insert starting at <code>t</code>. This
    4096          * will return false if the insert tag is set, and hasn't been found
    4097          * yet.
    4098          */
    4099         private boolean canInsertTag(HTML.Tag t, AttributeSet attr,
    4100                                      boolean isBlockTag) {
    4101             if (!foundInsertTag) {
    4102                 boolean needPImplied = ((t == HTML.Tag.IMPLIED)
    4103                                                           && (!inParagraph)
    4104                                                           && (!inPre));
    4105                 if (needPImplied && (nextTagAfterPImplied != null)) {
    4106
    4107                     /*
    4108                      * If insertTag == null then just proceed to
    4109                      * foundInsertTag() call below and return true.
    4110                      */
    4111                     if (insertTag != null) {
    4112                         boolean nextTagIsInsertTag =
    4113                                 isInsertTag(nextTagAfterPImplied);
    4114                         if ( (! nextTagIsInsertTag) || (! insertInsertTag) ) {
    4115                             return false;
    4116                         }
    4117                     }
    4118                     /*
    4119                      *  Proceed to foundInsertTag() call...
    4120                      */
    4121                  } else if ((insertTag != null && !isInsertTag(t))
    4122                                || (insertAfterImplied
    4123                                     && (attr == null
    4124                                         || attr.isDefined(IMPLIED)
    4125                                         || t == HTML.Tag.IMPLIED
    4126                                        )
    4127                                    )
    4128                            ) {
    4129                     return false;
    4130                 }
    4131
    4132                 // Allow the insert if t matches the insert tag, or
    4133                 // insertAfterImplied is true and the element is implied.
    4134                 foundInsertTag(isBlockTag);
    4135                 if (!insertInsertTag) {
    4136                     return false;
    4137                 }
    4138             }
    4139             return true;
    4140         }
    4141
    4142         private boolean isInsertTag(HTML.Tag tag) {
    4143             return (insertTag == tag);
    4144         }
    4145
    4146         private void foundInsertTag(boolean isBlockTag) {
    4147             foundInsertTag = true;
    4148             if (!insertAfterImplied && (popDepth > 0 || pushDepth > 0)) {
    4149                 try {
    4150                     if (offset == 0 || !getText(offset - 1, 1).equals("\n")) {
    4151                         // Need to insert a newline.
    4152                         AttributeSet newAttrs = null;
    4153                         boolean joinP = true;
    4154
    4155                         if (offset != 0) {
    4156                             // Determine if we can use JoinPrevious, we can't
    4157                             // if the Element has some attributes that are
    4158                             // not meant to be duplicated.
    4159                             Element charElement = getCharacterElement
    4160                                                     (offset - 1);
    4161                             AttributeSet attrs = charElement.getAttributes();
    4162
    4163                             if (attrs.isDefined(StyleConstants.
    4164                                                 ComposedTextAttribute)) {
    4165                                 joinP = false;
    4166                             }
    4167                             else {
    4168                                 Object name = attrs.getAttribute
    4169                                               (StyleConstants.NameAttribute);
    4170                                 if (name instanceof HTML.Tag) {
    4171                                     HTML.Tag tag = (HTML.Tag)name;
    4172                                     if (tag == HTML.Tag.IMG ||
    4173                                         tag == HTML.Tag.HR ||
    4174                                         tag == HTML.Tag.COMMENT ||
    4175                                         (tag instanceof HTML.UnknownTag)) {
    4176                                         joinP = false;
    4177                                     }
    4178                                 }
    4179                             }
    4180                         }
    4181                         if (!joinP) {
    4182                             // If not joining with the previous element, be
    4183                             // sure and set the name (otherwise it will be
    4184                             // inherited).
    4185                             newAttrs = new SimpleAttributeSet();
    4186                             ((SimpleAttributeSet)newAttrs).addAttribute
    4187                                               (StyleConstants.NameAttribute,
    4188                                                HTML.Tag.CONTENT);
    4189                         }
    4190                         ElementSpec es = new ElementSpec(newAttrs,
    4191                                      ElementSpec.ContentType, NEWLINE, 0,
    4192                                      NEWLINE.length);
    4193                         if (joinP) {
    4194                             es.setDirection(ElementSpec.
    4195                                             JoinPreviousDirection);
    4196                         }
    4197                         parseBuffer.addElement(es);
    4198                     }
    4199                 } catch (BadLocationException ble) {}
    4200             }
    4201             // pops
    4202             for (int counter = 0; counter < popDepth; counter++) {
    4203                 parseBuffer.addElement(new ElementSpec(null, ElementSpec.
    4204                                                        EndTagType));
    4205             }
    4206             // pushes
    4207             for (int counter = 0; counter < pushDepth; counter++) {
    4208                 ElementSpec es = new ElementSpec(null, ElementSpec.
    4209                                                  StartTagType);
    4210                 es.setDirection(ElementSpec.JoinNextDirection);
    4211                 parseBuffer.addElement(es);
    4212             }
    4213             insertTagDepthDelta = depthTo(Math.max(0, offset - 1)) -
    4214                                   popDepth + pushDepth - inBlock;
    4215             if (isBlockTag) {
    4216                 // A start spec will be added (for this tag), so we account
    4217                 // for it here.
    4218                 insertTagDepthDelta++;
    4219             }
    4220             else {
    4221                 // An implied paragraph close (end spec) is going to be added,
    4222                 // so we account for it here.
    4223                 insertTagDepthDelta--;
    4224                 inParagraph = true;
    4225                 lastWasNewline = false;
    4226             }
    4227         }
    4228
    4229         /**
    4230          * This is set to true when and end is invoked for {@literal <html>}.
    4231          */
    4232         private boolean receivedEndHTML;
    4233         /** Number of times <code>flushBuffer</code> has been invoked. */
    4234         private int flushCount;
    4235         /** If true, behavior is similar to insertTag, but instead of
    4236          * waiting for insertTag will wait for first Element without
    4237          * an 'implied' attribute and begin inserting then. */
    4238         private boolean insertAfterImplied;
    4239         /** This is only used if insertAfterImplied is true. If false, only
    4240          * inserting content, and there is a trailing newline it is removed. */
    4241         private boolean wantsTrailingNewline;
    4242         int threshold;
    4243         int offset;
    4244         boolean inParagraph = false;
    4245         boolean impliedP = false;
    4246         boolean inPre = false;
    4247         boolean inTextArea = false;
    4248         TextAreaDocument textAreaDocument = null;
    4249         boolean inTitle = false;
    4250         boolean lastWasNewline = true;
    4251         boolean emptyAnchor;
    4252         /** True if (!emptyDocument &amp;&amp; insertTag == null), this is used so
    4253          * much it is cached. */
    4254         boolean midInsert;
    4255         /** True when the body has been encountered. */
    4256         boolean inBody;
    4257         /** If non null, gives parent Tag that insert is to happen at. */
    4258         HTML.Tag insertTag;
    4259         /** If true, the insertTag is inserted, otherwise elements after
    4260          * the insertTag is found are inserted. */
    4261         boolean insertInsertTag;
    4262         /** Set to true when insertTag has been found. */
    4263         boolean foundInsertTag;
    4264         /** When foundInsertTag is set to true, this will be updated to
    4265          * reflect the delta between the two structures. That is, it
    4266          * will be the depth the inserts are happening at minus the
    4267          * depth of the tags being passed in. A value of 0 (the common
    4268          * case) indicates the structures match, a value greater than 0 indicates
    4269          * the insert is happening at a deeper depth than the stream is
    4270          * parsing, and a value less than 0 indicates the insert is happening earlier
    4271          * in the tree that the parser thinks and that we will need to remove
    4272          * EndTagType specs in the flushBuffer method.
    4273          */
    4274         int insertTagDepthDelta;
    4275         /** How many parents to ascend before insert new elements. */
    4276         int popDepth;
    4277         /** How many parents to descend (relative to popDepth) before
    4278          * inserting. */
    4279         int pushDepth;
    4280         /** Last Map that was encountered. */
    4281         Map lastMap;
    4282         /** Set to true when a style element is encountered. */
    4283         boolean inStyle = false;
    4284         /** Name of style to use. Obtained from Meta tag. */
    4285         String defaultStyle;
    4286         /** Vector describing styles that should be include. Will consist
    4287          * of a bunch of HTML.Tags, which will either be:
    4288          * <p>LINK: in which case it is followed by an AttributeSet
    4289          * <p>STYLE: in which case the following element is a String
    4290          * indicating the type (may be null), and the elements following
    4291          * it until the next HTML.Tag are the rules as Strings.
    4292          */
    4293         Vector<Object> styles;
    4294         /** True if inside the head tag. */
    4295         boolean inHead = false;
    4296         /** Set to true if the style language is text/css. Since this is
    4297          * used alot, it is cached. */
    4298         boolean isStyleCSS;
    4299         /** True if inserting into an empty document. */
    4300         boolean emptyDocument;
    4301         /** Attributes from a style Attribute. */
    4302         AttributeSet styleAttributes;
    4303
    4304         /**
    4305          * Current option, if in an option element (needed to
    4306          * load the label.
    4307          */
    4308         Option option;
    4309
    4310         /**
    4311          * Buffer to keep building elements.
    4312          */
    4313         protected Vector<ElementSpec> parseBuffer = new Vector<ElementSpec>();
    4314         /**
    4315          * Current character attribute set.
    4316          */
    4317         protected MutableAttributeSet charAttr = new TaggedAttributeSet();
    4318         Stack<AttributeSet> charAttrStack = new Stack<AttributeSet>();
    4319         Hashtable<HTML.Tag, TagAction> tagMap;
    4320         int inBlock = 0;
    4321
    4322         /**
    4323          * This attribute is sometimes used to refer to next tag
    4324          * to be handled after p-implied when the latter is
    4325          * the current tag which is being handled.
    4326          */
    4327         private HTML.Tag nextTagAfterPImplied = null;
    4328     }
    4329
    4330
    4331     /**
    4332      * Used by StyleSheet to determine when to avoid removing HTML.Tags
    4333      * matching StyleConstants.
    4334      */
    4335     static class TaggedAttributeSet extends SimpleAttributeSet {
    4336         TaggedAttributeSet() {
    4337             super();
    4338         }
    4339     }
    4340
    4341
    4342     /**
    4343      * An element that represents a chunk of text that has
    4344      * a set of HTML character level attributes assigned to
    4345      * it.
    4346      */
    4347     public class RunElement extends LeafElement {
    4348
    4349         /**
    4350          * Constructs an element that represents content within the
    4351          * document (has no children).
    4352          *
    4353          * @param parent  the parent element
    4354          * @param a       the element attributes
    4355          * @param offs0   the start offset (must be at least 0)
    4356          * @param offs1   the end offset (must be at least offs0)
    4357          * @since 1.4
    4358          */
    4359         public RunElement(Element parent, AttributeSet a, int offs0, int offs1) {
    4360             super(parent, a, offs0, offs1);
    4361         }
    4362
    4363         /**
    4364          * Gets the name of the element.
    4365          *
    4366          * @return the name, null if none
    4367          */
    4368         public String getName() {
    4369             Object o = getAttribute(StyleConstants.NameAttribute);
    4370             if (o != null) {
    4371                 return o.toString();
    4372             }
    4373             return super.getName();
    4374         }
    4375
    4376         /**
    4377          * Gets the resolving parent.  HTML attributes are not inherited
    4378          * at the model level so we override this to return null.
    4379          *
    4380          * @return null, there are none
    4381          * @see AttributeSet#getResolveParent
    4382          */
    4383         public AttributeSet getResolveParent() {
    4384             return null;
    4385         }
    4386     }
    4387
    4388     /**
    4389      * An element that represents a structural <em>block</em> of
    4390      * HTML.
    4391      */
    4392     public class BlockElement extends BranchElement {
    4393
    4394         /**
    4395          * Constructs a composite element that initially contains
    4396          * no children.
    4397          *
    4398          * @param parent  the parent element
    4399          * @param a       the attributes for the element
    4400          * @since 1.4
    4401          */
    4402         public BlockElement(Element parent, AttributeSet a) {
    4403             super(parent, a);
    4404         }
    4405
    4406         /**
    4407          * Gets the name of the element.
    4408          *
    4409          * @return the name, null if none
    4410          */
    4411         public String getName() {
    4412             Object o = getAttribute(StyleConstants.NameAttribute);
    4413             if (o != null) {
    4414                 return o.toString();
    4415             }
    4416             return super.getName();
    4417         }
    4418
    4419         /**
    4420          * Gets the resolving parent.  HTML attributes are not inherited
    4421          * at the model level so we override this to return null.
    4422          *
    4423          * @return null, there are none
    4424          * @see AttributeSet#getResolveParent
    4425          */
    4426         public AttributeSet getResolveParent() {
    4427             return null;
    4428         }
    4429
    4430     }
    4431
    4432
    4433     /**
    4434      * Document that allows you to set the maximum length of the text.
    4435      */
    4436     private static class FixedLengthDocument extends PlainDocument {
    4437         private int maxLength;
    4438
    4439         public FixedLengthDocument(int maxLength) {
    4440             this.maxLength = maxLength;
    4441         }
    4442
    4443         public void insertString(int offset, String str, AttributeSet a)
    4444             throws BadLocationException {
    4445             if (str != null && str.length() + getLength() <= maxLength) {
    4446                 super.insertString(offset, str, a);
    4447             }
    4448         }
    4449     }
    4450 }
    --- EOF ---