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