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