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