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