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