       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 m = new OptionListModel();
    3612                         if (multiple) {
    3613                             m.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    3614                         }
    3615                         selectModel = m;
    3616                     } else {
    3617                         selectModel = new OptionComboBoxModel();
    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                         OptionListModel m = (OptionListModel) selectModel;
    3630                         m.addElement(option);
    3631                         if (option.isSelected()) {
    3632                             m.addSelectionInterval(optionCount, optionCount);
    3633                             m.setInitialSelection(optionCount);
    3634                         }
    3635                     } else if (selectModel instanceof OptionComboBoxModel) {
    3636                         OptionComboBoxModel m = (OptionComboBoxModel) selectModel;
    3637                         m.addElement(option);
    3638                         if (option.isSelected()) {
    3639                             m.setSelectedItem(option);
    3640                             m.setInitialSelection(option);
    3641                         }
    3642                     }
    3643                     optionCount++;
    3644                 } else {
    3645                     super.start(t, attr);
    3646                 }
    3647             }
    3648
    3649             public void end(HTML.Tag t) {
    3650                 if (t == HTML.Tag.OPTION) {
    3651                     option = null;
    3652                 } else {
    3653                     if (t == HTML.Tag.SELECT) {
    3654                         selectModel = null;
    3655                         optionCount = 0;
    3656                     } else if (t == HTML.Tag.TEXTAREA) {
    3657                         inTextArea = false;
    3658
    3659                         /* Now that the textarea has ended,
    3660                          * store the entire initial text
    3661                          * of the text area.  This will
    3662                          * enable us to restore the initial
    3663                          * state if a reset is requested.
    3664                          */
    3665                         textAreaDocument.storeInitialText();
    3666                     }
    3667                     super.end(t);
    3668                 }
    3669             }
    3670
    3671             void setModel(String type, MutableAttributeSet attr) {
    3672                 if (type.equals("submit") ||
    3673                     type.equals("reset") ||
    3674                     type.equals("image")) {
    3675
    3676                     // button model
    3677                     attr.addAttribute(StyleConstants.ModelAttribute,
    3678                                       new DefaultButtonModel());
    3679                 } else if (type.equals("text") ||
    3680                            type.equals("password")) {
    3681                     // plain text model
    3682                     int maxLength = HTML.getIntegerAttributeValue(
    3683                                        attr, HTML.Attribute.MAXLENGTH, -1);
    3684                     Document doc;
    3685
    3686                     if (maxLength > 0) {
    3687                         doc = new FixedLengthDocument(maxLength);
    3688                     }
    3689                     else {
    3690                         doc = new PlainDocument();
    3691                     }
    3692                     String value = (String)
    3693                         attr.getAttribute(HTML.Attribute.VALUE);
    3694                     try {
    3695                         doc.insertString(0, value, null);
    3696                     } catch (BadLocationException e) {
    3697                     }
    3698                     attr.addAttribute(StyleConstants.ModelAttribute, doc);
    3699                 } else if (type.equals("file")) {
    3700                     // plain text model
    3701                     attr.addAttribute(StyleConstants.ModelAttribute,
    3702                                       new PlainDocument());
    3703                 } else if (type.equals("checkbox") ||
    3704                            type.equals("radio")) {
    3705                     JToggleButton.ToggleButtonModel model = new JToggleButton.ToggleButtonModel();
    3706                     if (type.equals("radio")) {
    3707                         String name = (String) attr.getAttribute(HTML.Attribute.NAME);
    3708                         if ( radioButtonGroupsMap == null ) { //fix for 4772743
    3709                            radioButtonGroupsMap = new HashMap<String, ButtonGroup>();
    3710                         }
    3711                         ButtonGroup radioButtonGroup = radioButtonGroupsMap.get(name);
    3712                         if (radioButtonGroup == null) {
    3713                             radioButtonGroup = new ButtonGroup();
    3714                             radioButtonGroupsMap.put(name,radioButtonGroup);
    3715                         }
    3716                         model.setGroup(radioButtonGroup);
    3717                     }
    3718                     boolean checked = (attr.getAttribute(HTML.Attribute.CHECKED) != null);
    3719                     model.setSelected(checked);
    3720                     attr.addAttribute(StyleConstants.ModelAttribute, model);
    3721                 }
    3722             }
    3723
    3724             /**
    3725              * If a &lt;SELECT&gt; tag is being processed, this
    3726              * model will be a reference to the model being filled
    3727              * with the &lt;OPTION&gt; elements (which produce
    3728              * objects of type <code>Option</code>.
    3729              */
    3730             Object selectModel;
    3731             int optionCount;
    3732         }
    3733
    3734
    3735         // --- utility methods used by the reader ------------------
    3736
    3737         /**
    3738          * Pushes the current character style on a stack in preparation
    3739          * for forming a new nested character style.
    3740          */
    3741         protected void pushCharacterStyle() {
    3742             charAttrStack.push(charAttr.copyAttributes());
    3743         }
    3744
    3745         /**
    3746          * Pops a previously pushed character style off the stack
    3747          * to return to a previous style.
    3748          */
    3749         protected void popCharacterStyle() {
    3750             if (!charAttrStack.empty()) {
    3751                 charAttr = (MutableAttributeSet) charAttrStack.peek();
    3752                 charAttrStack.pop();
    3753             }
    3754         }
    3755
    3756         /**
    3757          * Adds the given content to the textarea document.
    3758          * This method gets called when we are in a textarea
    3759          * context.  Therefore all text that is seen belongs
    3760          * to the text area and is hence added to the
    3761          * TextAreaDocument associated with the text area.
    3762          *
    3763          * @param data the given content
    3764          */
    3765         protected void textAreaContent(char[] data) {
    3766             try {
    3767                 textAreaDocument.insertString(textAreaDocument.getLength(), new String(data), null);
    3768             } catch (BadLocationException e) {
    3769                 // Should do something reasonable
    3770             }
    3771         }
    3772
    3773         /**
    3774          * Adds the given content that was encountered in a
    3775          * PRE element.  This synthesizes lines to hold the
    3776          * runs of text, and makes calls to addContent to
    3777          * actually add the text.
    3778          *
    3779          * @param data the given content
    3780          */
    3781         protected void preContent(char[] data) {
    3782             int last = 0;
    3783             for (int i = 0; i < data.length; i++) {
    3784                 if (data[i] == '\n') {
    3785                     addContent(data, last, i - last + 1);
    3786                     blockClose(HTML.Tag.IMPLIED);
    3787                     MutableAttributeSet a = new SimpleAttributeSet();
    3788                     a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
    3789                     blockOpen(HTML.Tag.IMPLIED, a);
    3790                     last = i + 1;
    3791                 }
    3792             }
    3793             if (last < data.length) {
    3794                 addContent(data, last, data.length - last);
    3795             }
    3796         }
    3797
    3798         /**
    3799          * Adds an instruction to the parse buffer to create a
    3800          * block element with the given attributes.
    3801          *
    3802          * @param t an HTML tag
    3803          * @param attr the attribute set
    3804          */
    3805         protected void blockOpen(HTML.Tag t, MutableAttributeSet attr) {
    3806             if (impliedP) {
    3807                 blockClose(HTML.Tag.IMPLIED);
    3808             }
    3809
    3810             inBlock++;
    3811
    3812             if (!canInsertTag(t, attr, true)) {
    3813                 return;
    3814             }
    3815             if (attr.isDefined(IMPLIED)) {
    3816                 attr.removeAttribute(IMPLIED);
    3817             }
    3818             lastWasNewline = false;
    3819             attr.addAttribute(StyleConstants.NameAttribute, t);
    3820             ElementSpec es = new ElementSpec(
    3821                 attr.copyAttributes(), ElementSpec.StartTagType);
    3822             parseBuffer.addElement(es);
    3823         }
    3824
    3825         /**
    3826          * Adds an instruction to the parse buffer to close out
    3827          * a block element of the given type.
    3828          *
    3829          * @param t the HTML tag
    3830          */
    3831         protected void blockClose(HTML.Tag t) {
    3832             inBlock--;
    3833
    3834             if (!foundInsertTag) {
    3835                 return;
    3836             }
    3837
    3838             // Add a new line, if the last character wasn't one. This is
    3839             // needed for proper positioning of the cursor. addContent
    3840             // with true will force an implied paragraph to be generated if
    3841             // there isn't one. This may result in a rather bogus structure
    3842             // (perhaps a table with a child pargraph), but the paragraph
    3843             // is needed for proper positioning and display.
    3844             if(!lastWasNewline) {
    3845                 pushCharacterStyle();
    3846                 charAttr.addAttribute(IMPLIED_CR, Boolean.TRUE);
    3847                 addContent(NEWLINE, 0, 1, true);
    3848                 popCharacterStyle();
    3849                 lastWasNewline = true;
    3850             }
    3851
    3852             if (impliedP) {
    3853                 impliedP = false;
    3854                 inParagraph = false;
    3855                 if (t != HTML.Tag.IMPLIED) {
    3856                     blockClose(HTML.Tag.IMPLIED);
    3857                 }
    3858             }
    3859             // an open/close with no content will be removed, so we
    3860             // add a space of content to keep the element being formed.
    3861             ElementSpec prev = (parseBuffer.size() > 0) ?
    3862                 parseBuffer.lastElement() : null;
    3863             if (prev != null && prev.getType() == ElementSpec.StartTagType) {
    3864                 char[] one = new char[1];
    3865                 one[0] = ' ';
    3866                 addContent(one, 0, 1);
    3867             }
    3868             ElementSpec es = new ElementSpec(
    3869                 null, ElementSpec.EndTagType);
    3870             parseBuffer.addElement(es);
    3871         }
    3872
    3873         /**
    3874          * Adds some text with the current character attributes.
    3875          *
    3876          * @param data the content to add
    3877          * @param offs the initial offset
    3878          * @param length the length
    3879          */
    3880         protected void addContent(char[] data, int offs, int length) {
    3881             addContent(data, offs, length, true);
    3882         }
    3883
    3884         /**
    3885          * Adds some text with the current character attributes.
    3886          *
    3887          * @param data the content to add
    3888          * @param offs the initial offset
    3889          * @param length the length
    3890          * @param generateImpliedPIfNecessary whether to generate implied
    3891          * paragraphs
    3892          */
    3893         protected void addContent(char[] data, int offs, int length,
    3894                                   boolean generateImpliedPIfNecessary) {
    3895             if (!foundInsertTag) {
    3896                 return;
    3897             }
    3898
    3899             if (generateImpliedPIfNecessary && (! inParagraph) && (! inPre)) {
    3900                 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
    3901                 inParagraph = true;
    3902                 impliedP = true;
    3903             }
    3904             emptyAnchor = false;
    3905             charAttr.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
    3906             AttributeSet a = charAttr.copyAttributes();
    3907             ElementSpec es = new ElementSpec(
    3908                 a, ElementSpec.ContentType, data, offs, length);
    3909             parseBuffer.addElement(es);
    3910
    3911             if (parseBuffer.size() > threshold) {
    3912                 if ( threshold <= MaxThreshold ) {
    3913                     threshold *= StepThreshold;
    3914                 }
    3915                 try {
    3916                     flushBuffer(false);
    3917                 } catch (BadLocationException ble) {
    3918                 }
    3919             }
    3920             if(length > 0) {
    3921                 lastWasNewline = (data[offs + length - 1] == '\n');
    3922             }
    3923         }
    3924
    3925         /**
    3926          * Adds content that is basically specified entirely
    3927          * in the attribute set.
    3928          *
    3929          * @param t an HTML tag
    3930          * @param a the attribute set
    3931          */
    3932         protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a) {
    3933             if ((t != HTML.Tag.FRAME) && (! inParagraph) && (! inPre)) {
    3934                 nextTagAfterPImplied = t;
    3935                 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
    3936                 nextTagAfterPImplied = null;
    3937                 inParagraph = true;
    3938                 impliedP = true;
    3939             }
    3940             if (!canInsertTag(t, a, t.isBlock())) {
    3941                 return;
    3942             }
    3943             if (a.isDefined(IMPLIED)) {
    3944                 a.removeAttribute(IMPLIED);
    3945             }
    3946             emptyAnchor = false;
    3947             a.addAttributes(charAttr);
    3948             a.addAttribute(StyleConstants.NameAttribute, t);
    3949             char[] one = new char[1];
    3950             one[0] = ' ';
    3951             ElementSpec es = new ElementSpec(
    3952                 a.copyAttributes(), ElementSpec.ContentType, one, 0, 1);
    3953             parseBuffer.addElement(es);
    3954             // Set this to avoid generating a newline for frames, frames
    3955             // shouldn't have any content, and shouldn't need a newline.
    3956             if (t == HTML.Tag.FRAME) {
    3957                 lastWasNewline = true;
    3958             }
    3959         }
    3960
    3961         /**
    3962          * Flushes the current parse buffer into the document.
    3963          * @param endOfStream true if there is no more content to parser
    3964          */
    3965         void flushBuffer(boolean endOfStream) throws BadLocationException {
    3966             int oldLength = HTMLDocument.this.getLength();
    3967             int size = parseBuffer.size();
    3968             if (endOfStream && (insertTag != null || insertAfterImplied) &&
    3969                 size > 0) {
    3970                 adjustEndSpecsForPartialInsert();
    3971                 size = parseBuffer.size();
    3972             }
    3973             ElementSpec[] spec = new ElementSpec[size];
    3974             parseBuffer.copyInto(spec);
    3975
    3976             if (oldLength == 0 && (insertTag == null && !insertAfterImplied)) {
    3977                 create(spec);
    3978             } else {
    3979                 insert(offset, spec);
    3980             }
    3981             parseBuffer.removeAllElements();
    3982             offset += HTMLDocument.this.getLength() - oldLength;
    3983             flushCount++;
    3984         }
    3985
    3986         /**
    3987          * This will be invoked for the last flush, if <code>insertTag</code>
    3988          * is non null.
    3989          */
    3990         private void adjustEndSpecsForPartialInsert() {
    3991             int size = parseBuffer.size();
    3992             if (insertTagDepthDelta < 0) {
    3993                 // When inserting via an insertTag, the depths (of the tree
    3994                 // being read in, and existing hierarchy) may not match up.
    3995                 // This attemps to clean it up.
    3996                 int removeCounter = insertTagDepthDelta;
    3997                 while (removeCounter < 0 && size >= 0 &&
    3998                         parseBuffer.elementAt(size - 1).
    3999                        getType() == ElementSpec.EndTagType) {
    4000                     parseBuffer.removeElementAt(--size);
    4001                     removeCounter++;
    4002                 }
    4003             }
    4004             if (flushCount == 0 && (!insertAfterImplied ||
    4005                                     !wantsTrailingNewline)) {
    4006                 // If this starts with content (or popDepth > 0 &&
    4007                 // pushDepth > 0) and ends with EndTagTypes, make sure
    4008                 // the last content isn't a \n, otherwise will end up with
    4009                 // an extra \n in the middle of content.
    4010                 int index = 0;
    4011                 if (pushDepth > 0) {
    4012                     if (parseBuffer.elementAt(0).getType() ==
    4013                         ElementSpec.ContentType) {
    4014                         index++;
    4015                     }
    4016                 }
    4017                 index += (popDepth + pushDepth);
    4018                 int cCount = 0;
    4019                 int cStart = index;
    4020                 while (index < size && parseBuffer.elementAt
    4021                         (index).getType() == ElementSpec.ContentType) {
    4022                     index++;
    4023                     cCount++;
    4024                 }
    4025                 if (cCount > 1) {
    4026                     while (index < size && parseBuffer.elementAt
    4027                             (index).getType() == ElementSpec.EndTagType) {
    4028                         index++;
    4029                     }
    4030                     if (index == size) {
    4031                         char[] lastText = parseBuffer.elementAt
    4032                                 (cStart + cCount - 1).getArray();
    4033                         if (lastText.length == 1 && lastText[0] == NEWLINE[0]){
    4034                             index = cStart + cCount - 1;
    4035                             while (size > index) {
    4036                                 parseBuffer.removeElementAt(--size);
    4037                             }
    4038                         }
    4039                     }
    4040                 }
    4041             }
    4042             if (wantsTrailingNewline) {
    4043                 // Make sure there is in fact a newline
    4044                 for (int counter = parseBuffer.size() - 1; counter >= 0;
    4045                                    counter--) {
    4046                     ElementSpec spec = parseBuffer.elementAt(counter);
    4047                     if (spec.getType() == ElementSpec.ContentType) {
    4048                         if (spec.getArray()[spec.getLength() - 1] != '\n') {
    4049                             SimpleAttributeSet attrs =new SimpleAttributeSet();
    4050
    4051                             attrs.addAttribute(StyleConstants.NameAttribute,
    4052                                                HTML.Tag.CONTENT);
    4053                             parseBuffer.insertElementAt(new ElementSpec(
    4054                                     attrs,
    4055                                     ElementSpec.ContentType, NEWLINE, 0, 1),
    4056                                     counter + 1);
    4057                         }
    4058                         break;
    4059                     }
    4060                 }
    4061             }
    4062         }
    4063
    4064         /**
    4065          * Adds the CSS rules in <code>rules</code>.
    4066          */
    4067         void addCSSRules(String rules) {
    4068             StyleSheet ss = getStyleSheet();
    4069             ss.addRule(rules);
    4070         }
    4071
    4072         /**
    4073          * Adds the CSS stylesheet at <code>href</code> to the known list
    4074          * of stylesheets.
    4075          */
    4076         void linkCSSStyleSheet(String href) {
    4077             URL url;
    4078             try {
    4079                 url = new URL(base, href);
    4080             } catch (MalformedURLException mfe) {
    4081                 try {
    4082                     url = new URL(href);
    4083                 } catch (MalformedURLException mfe2) {
    4084                     url = null;
    4085                 }
    4086             }
    4087             if (url != null) {
    4088                 getStyleSheet().importStyleSheet(url);
    4089             }
    4090         }
    4091
    4092         /**
    4093          * Returns true if can insert starting at <code>t</code>. This
    4094          * will return false if the insert tag is set, and hasn't been found
    4095          * yet.
    4096          */
    4097         private boolean canInsertTag(HTML.Tag t, AttributeSet attr,
    4098                                      boolean isBlockTag) {
    4099             if (!foundInsertTag) {
    4100                 boolean needPImplied = ((t == HTML.Tag.IMPLIED)
    4101                                                           && (!inParagraph)
    4102                                                           && (!inPre));
    4103                 if (needPImplied && (nextTagAfterPImplied != null)) {
    4104
    4105                     /*
    4106                      * If insertTag == null then just proceed to
    4107                      * foundInsertTag() call below and return true.
    4108                      */
    4109                     if (insertTag != null) {
    4110                         boolean nextTagIsInsertTag =
    4111                                 isInsertTag(nextTagAfterPImplied);
    4112                         if ( (! nextTagIsInsertTag) || (! insertInsertTag) ) {
    4113                             return false;
    4114                         }
    4115                     }
    4116                     /*
    4117                      *  Proceed to foundInsertTag() call...
    4118                      */
    4119                  } else if ((insertTag != null && !isInsertTag(t))
    4120                                || (insertAfterImplied
    4121                                     && (attr == null
    4122                                         || attr.isDefined(IMPLIED)
    4123                                         || t == HTML.Tag.IMPLIED
    4124                                        )
    4125                                    )
    4126                            ) {
    4127                     return false;
    4128                 }
    4129
    4130                 // Allow the insert if t matches the insert tag, or
    4131                 // insertAfterImplied is true and the element is implied.
    4132                 foundInsertTag(isBlockTag);
    4133                 if (!insertInsertTag) {
    4134                     return false;
    4135                 }
    4136             }
    4137             return true;
    4138         }
    4139
    4140         private boolean isInsertTag(HTML.Tag tag) {
    4141             return (insertTag == tag);
    4142         }
    4143
    4144         private void foundInsertTag(boolean isBlockTag) {
    4145             foundInsertTag = true;
    4146             if (!insertAfterImplied && (popDepth > 0 || pushDepth > 0)) {
    4147                 try {
    4148                     if (offset == 0 || !getText(offset - 1, 1).equals("\n")) {
    4149                         // Need to insert a newline.
    4150                         AttributeSet newAttrs = null;
    4151                         boolean joinP = true;
    4152
    4153                         if (offset != 0) {
    4154                             // Determine if we can use JoinPrevious, we can't
    4155                             // if the Element has some attributes that are
    4156                             // not meant to be duplicated.
    4157                             Element charElement = getCharacterElement
    4158                                                     (offset - 1);
    4159                             AttributeSet attrs = charElement.getAttributes();
    4160
    4161                             if (attrs.isDefined(StyleConstants.
    4162                                                 ComposedTextAttribute)) {
    4163                                 joinP = false;
    4164                             }
    4165                             else {
    4166                                 Object name = attrs.getAttribute
    4167                                               (StyleConstants.NameAttribute);
    4168                                 if (name instanceof HTML.Tag) {
    4169                                     HTML.Tag tag = (HTML.Tag)name;
    4170                                     if (tag == HTML.Tag.IMG ||
    4171                                         tag == HTML.Tag.HR ||
    4172                                         tag == HTML.Tag.COMMENT ||
    4173                                         (tag instanceof HTML.UnknownTag)) {
    4174                                         joinP = false;
    4175                                     }
    4176                                 }
    4177                             }
    4178                         }
    4179                         if (!joinP) {
    4180                             // If not joining with the previous element, be
    4181                             // sure and set the name (otherwise it will be
    4182                             // inherited).
    4183                             newAttrs = new SimpleAttributeSet();
    4184                             ((SimpleAttributeSet)newAttrs).addAttribute
    4185                                               (StyleConstants.NameAttribute,
    4186                                                HTML.Tag.CONTENT);
    4187                         }
    4188                         ElementSpec es = new ElementSpec(newAttrs,
    4189                                      ElementSpec.ContentType, NEWLINE, 0,
    4190                                      NEWLINE.length);
    4191                         if (joinP) {
    4192                             es.setDirection(ElementSpec.
    4193                                             JoinPreviousDirection);
    4194                         }
    4195                         parseBuffer.addElement(es);
    4196                     }
    4197                 } catch (BadLocationException ble) {}
    4198             }
    4199             // pops
    4200             for (int counter = 0; counter < popDepth; counter++) {
    4201                 parseBuffer.addElement(new ElementSpec(null, ElementSpec.
    4202                                                        EndTagType));
    4203             }
    4204             // pushes
    4205             for (int counter = 0; counter < pushDepth; counter++) {
    4206                 ElementSpec es = new ElementSpec(null, ElementSpec.
    4207                                                  StartTagType);
    4208                 es.setDirection(ElementSpec.JoinNextDirection);
    4209                 parseBuffer.addElement(es);
    4210             }
    4211             insertTagDepthDelta = depthTo(Math.max(0, offset - 1)) -
    4212                                   popDepth + pushDepth - inBlock;
    4213             if (isBlockTag) {
    4214                 // A start spec will be added (for this tag), so we account
    4215                 // for it here.
    4216                 insertTagDepthDelta++;
    4217             }
    4218             else {
    4219                 // An implied paragraph close (end spec) is going to be added,
    4220                 // so we account for it here.
    4221                 insertTagDepthDelta--;
    4222                 inParagraph = true;
    4223                 lastWasNewline = false;
    4224             }
    4225         }
    4226
    4227         /**
    4228          * This is set to true when and end is invoked for {@literal <html>}.
    4229          */
    4230         private boolean receivedEndHTML;
    4231         /** Number of times <code>flushBuffer</code> has been invoked. */
    4232         private int flushCount;
    4233         /** If true, behavior is similar to insertTag, but instead of
    4234          * waiting for insertTag will wait for first Element without
    4235          * an 'implied' attribute and begin inserting then. */
    4236         private boolean insertAfterImplied;
    4237         /** This is only used if insertAfterImplied is true. If false, only
    4238          * inserting content, and there is a trailing newline it is removed. */
    4239         private boolean wantsTrailingNewline;
    4240         int threshold;
    4241         int offset;
    4242         boolean inParagraph = false;
    4243         boolean impliedP = false;
    4244         boolean inPre = false;
    4245         boolean inTextArea = false;
    4246         TextAreaDocument textAreaDocument = null;
    4247         boolean inTitle = false;
    4248         boolean lastWasNewline = true;
    4249         boolean emptyAnchor;
    4250         /** True if (!emptyDocument &amp;&amp; insertTag == null), this is used so
    4251          * much it is cached. */
    4252         boolean midInsert;
    4253         /** True when the body has been encountered. */
    4254         boolean inBody;
    4255         /** If non null, gives parent Tag that insert is to happen at. */
    4256         HTML.Tag insertTag;
    4257         /** If true, the insertTag is inserted, otherwise elements after
    4258          * the insertTag is found are inserted. */
    4259         boolean insertInsertTag;
    4260         /** Set to true when insertTag has been found. */
    4261         boolean foundInsertTag;
    4262         /** When foundInsertTag is set to true, this will be updated to
    4263          * reflect the delta between the two structures. That is, it
    4264          * will be the depth the inserts are happening at minus the
    4265          * depth of the tags being passed in. A value of 0 (the common
    4266          * case) indicates the structures match, a value greater than 0 indicates
    4267          * the insert is happening at a deeper depth than the stream is
    4268          * parsing, and a value less than 0 indicates the insert is happening earlier
    4269          * in the tree that the parser thinks and that we will need to remove
    4270          * EndTagType specs in the flushBuffer method.
    4271          */
    4272         int insertTagDepthDelta;
    4273         /** How many parents to ascend before insert new elements. */
    4274         int popDepth;
    4275         /** How many parents to descend (relative to popDepth) before
    4276          * inserting. */
    4277         int pushDepth;
    4278         /** Last Map that was encountered. */
    4279         Map lastMap;
    4280         /** Set to true when a style element is encountered. */
    4281         boolean inStyle = false;
    4282         /** Name of style to use. Obtained from Meta tag. */
    4283         String defaultStyle;
    4284         /** Vector describing styles that should be include. Will consist
    4285          * of a bunch of HTML.Tags, which will either be:
    4286          * <p>LINK: in which case it is followed by an AttributeSet
    4287          * <p>STYLE: in which case the following element is a String
    4288          * indicating the type (may be null), and the elements following
    4289          * it until the next HTML.Tag are the rules as Strings.
    4290          */
    4291         Vector<Object> styles;
    4292         /** True if inside the head tag. */
    4293         boolean inHead = false;
    4294         /** Set to true if the style language is text/css. Since this is
    4295          * used alot, it is cached. */
    4296         boolean isStyleCSS;
    4297         /** True if inserting into an empty document. */
    4298         boolean emptyDocument;
    4299         /** Attributes from a style Attribute. */
    4300         AttributeSet styleAttributes;
    4301
    4302         /**
    4303          * Current option, if in an option element (needed to
    4304          * load the label.
    4305          */
    4306         Option option;
    4307
    4308         /**
    4309          * Buffer to keep building elements.
    4310          */
    4311         protected Vector<ElementSpec> parseBuffer = new Vector<ElementSpec>();
    4312         /**
    4313          * Current character attribute set.
    4314          */
    4315         protected MutableAttributeSet charAttr = new TaggedAttributeSet();
    4316         Stack<AttributeSet> charAttrStack = new Stack<AttributeSet>();
    4317         Hashtable<HTML.Tag, TagAction> tagMap;
    4318         int inBlock = 0;
    4319
    4320         /**
    4321          * This attribute is sometimes used to refer to next tag
    4322          * to be handled after p-implied when the latter is
    4323          * the current tag which is being handled.
    4324          */
    4325         private HTML.Tag nextTagAfterPImplied = null;
    4326     }
    4327
    4328
    4329     /**
    4330      * Used by StyleSheet to determine when to avoid removing HTML.Tags
    4331      * matching StyleConstants.
    4332      */
    4333     static class TaggedAttributeSet extends SimpleAttributeSet {
    4334         TaggedAttributeSet() {
    4335             super();
    4336         }
    4337     }
    4338
    4339
    4340     /**
    4341      * An element that represents a chunk of text that has
    4342      * a set of HTML character level attributes assigned to
    4343      * it.
    4344      */
    4345     public class RunElement extends LeafElement {
    4346
    4347         /**
    4348          * Constructs an element that represents content within the
    4349          * document (has no children).
    4350          *
    4351          * @param parent  the parent element
    4352          * @param a       the element attributes
    4353          * @param offs0   the start offset (must be at least 0)
    4354          * @param offs1   the end offset (must be at least offs0)
    4355          * @since 1.4
    4356          */
    4357         public RunElement(Element parent, AttributeSet a, int offs0, int offs1) {
    4358             super(parent, a, offs0, offs1);
    4359         }
    4360
    4361         /**
    4362          * Gets the name of the element.
    4363          *
    4364          * @return the name, null if none
    4365          */
    4366         public String getName() {
    4367             Object o = getAttribute(StyleConstants.NameAttribute);
    4368             if (o != null) {
    4369                 return o.toString();
    4370             }
    4371             return super.getName();
    4372         }
    4373
    4374         /**
    4375          * Gets the resolving parent.  HTML attributes are not inherited
    4376          * at the model level so we override this to return null.
    4377          *
    4378          * @return null, there are none
    4379          * @see AttributeSet#getResolveParent
    4380          */
    4381         public AttributeSet getResolveParent() {
    4382             return null;
    4383         }
    4384     }
    4385
    4386     /**
    4387      * An element that represents a structural <em>block</em> of
    4388      * HTML.
    4389      */
    4390     public class BlockElement extends BranchElement {
    4391
    4392         /**
    4393          * Constructs a composite element that initially contains
    4394          * no children.
    4395          *
    4396          * @param parent  the parent element
    4397          * @param a       the attributes for the element
    4398          * @since 1.4
    4399          */
    4400         public BlockElement(Element parent, AttributeSet a) {
    4401             super(parent, a);
    4402         }
    4403
    4404         /**
    4405          * Gets the name of the element.
    4406          *
    4407          * @return the name, null if none
    4408          */
    4409         public String getName() {
    4410             Object o = getAttribute(StyleConstants.NameAttribute);
    4411             if (o != null) {
    4412                 return o.toString();
    4413             }
    4414             return super.getName();
    4415         }
    4416
    4417         /**
    4418          * Gets the resolving parent.  HTML attributes are not inherited
    4419          * at the model level so we override this to return null.
    4420          *
    4421          * @return null, there are none
    4422          * @see AttributeSet#getResolveParent
    4423          */
    4424         public AttributeSet getResolveParent() {
    4425             return null;
    4426         }
    4427
    4428     }
    4429
    4430
    4431     /**
    4432      * Document that allows you to set the maximum length of the text.
    4433      */
    4434     private static class FixedLengthDocument extends PlainDocument {
    4435         private int maxLength;
    4436
    4437         public FixedLengthDocument(int maxLength) {
    4438             this.maxLength = maxLength;
    4439         }
    4440
    4441         public void insertString(int offset, String str, AttributeSet a)
    4442             throws BadLocationException {
    4443             if (str != null && str.length() + getLength() <= maxLength) {
    4444                 super.insertString(offset, str, a);
    4445             }
    4446         }
    4447     }
    4448 }