< prev index next >

src/java.desktop/share/classes/javax/swing/text/html/HTMLDocument.java

Print this page




  24  */
  25 package javax.swing.text.html;
  26 
  27 import java.awt.font.TextAttribute;
  28 import java.util.*;
  29 import java.net.URL;
  30 import java.net.MalformedURLException;
  31 import java.io.*;
  32 import javax.swing.*;
  33 import javax.swing.event.*;
  34 import javax.swing.text.*;
  35 import javax.swing.undo.*;
  36 import sun.swing.SwingUtilities2;
  37 import static sun.swing.SwingUtilities2.IMPLIED_CR;
  38 
  39 /**
  40  * A document that models HTML.  The purpose of this model is to
  41  * support both browsing and editing.  As a result, the structure
  42  * described by an HTML document is not exactly replicated by default.
  43  * The element structure that is modeled by default, is built by the
  44  * class <code>HTMLDocument.HTMLReader</code>, which implements the
  45  * <code>HTMLEditorKit.ParserCallback</code> protocol that the parser
  46  * expects.  To change the structure one can subclass
  47  * <code>HTMLReader</code>, and reimplement the method {@link
  48  * #getReader(int)} to return the new reader implementation.  The
  49  * documentation for <code>HTMLReader</code> should be consulted for
  50  * the details of the default structure created.  The intent is that
  51  * the document be non-lossy (although reproducing the HTML format may
  52  * result in a different format).
  53  *
  54  * <p>The document models only HTML, and makes no attempt to store
  55  * view attributes in it.  The elements are identified by the
  56  * <code>StyleContext.NameAttribute</code> attribute, which should
  57  * always have a value of type <code>HTML.Tag</code> that identifies
  58  * the kind of element.  Some of the elements (such as comments) are
  59  * synthesized.  The <code>HTMLFactory</code> uses this attribute to
  60  * determine what kind of view to build.</p>
  61  *
  62  * <p>This document supports incremental loading.  The
  63  * <code>TokenThreshold</code> property controls how much of the parse
  64  * is buffered before trying to update the element structure of the
  65  * document.  This property is set by the <code>EditorKit</code> so
  66  * that subclasses can disable it.</p>
  67  *
  68  * <p>The <code>Base</code> property determines the URL against which
  69  * relative URLs are resolved.  By default, this will be the
  70  * <code>Document.StreamDescriptionProperty</code> if the value of the
  71  * property is a URL.  If a &lt;BASE&gt; tag is encountered, the base
  72  * will become the URL specified by that tag.  Because the base URL is
  73  * a property, it can of course be set directly.</p>
  74  *
  75  * <p>The default content storage mechanism for this document is a gap
  76  * buffer (<code>GapContent</code>).  Alternatives can be supplied by
  77  * using the constructor that takes a <code>Content</code>
  78  * implementation.</p>
  79  *
  80  * <h2>Modifying HTMLDocument</h2>
  81  *
  82  * <p>In addition to the methods provided by Document and
  83  * StyledDocument for mutating an HTMLDocument, HTMLDocument provides
  84  * a number of convenience methods.  The following methods can be used
  85  * to insert HTML content into an existing document.</p>
  86  *
  87  * <ul>
  88  *   <li>{@link #setInnerHTML(Element, String)}</li>
  89  *   <li>{@link #setOuterHTML(Element, String)}</li>
  90  *   <li>{@link #insertBeforeStart(Element, String)}</li>
  91  *   <li>{@link #insertAfterStart(Element, String)}</li>
  92  *   <li>{@link #insertBeforeEnd(Element, String)}</li>
  93  *   <li>{@link #insertAfterEnd(Element, String)}</li>
  94  * </ul>
  95  *
  96  * <p>The following examples illustrate using these methods.  Each
  97  * example assumes the HTML document is initialized in the following
  98  * way:</p>
  99  *
 100  * <pre>
 101  * JEditorPane p = new JEditorPane();
 102  * p.setContentType("text/html");
 103  * p.setText("..."); // Document text is provided below.
 104  * HTMLDocument d = (HTMLDocument) p.getDocument();
 105  * </pre>
 106  *
 107  * <p>With the following HTML content:</p>
 108  *
 109  * <pre>
 110  * &lt;html&gt;
 111  *   &lt;head&gt;
 112  *     &lt;title&gt;An example HTMLDocument&lt;/title&gt;
 113  *     &lt;style type="text/css"&gt;
 114  *       div { background-color: silver; }
 115  *       ul { color: red; }
 116  *     &lt;/style&gt;
 117  *   &lt;/head&gt;
 118  *   &lt;body&gt;
 119  *     &lt;div id="BOX"&gt;
 120  *       &lt;p&gt;Paragraph 1&lt;/p&gt;
 121  *       &lt;p&gt;Paragraph 2&lt;/p&gt;
 122  *     &lt;/div&gt;
 123  *   &lt;/body&gt;
 124  * &lt;/html&gt;
 125  * </pre>
 126  *
 127  * <p>All the methods for modifying an HTML document require an {@link
 128  * Element}.  Elements can be obtained from an HTML document by using
 129  * the method {@link #getElement(Element e, Object attribute, Object
 130  * value)}.  It returns the first descendant element that contains the
 131  * specified attribute with the given value, in depth-first order.
 132  * For example, <code>d.getElement(d.getDefaultRootElement(),
 133  * StyleConstants.NameAttribute, HTML.Tag.P)</code> returns the first
 134  * paragraph element.</p>
 135  *
 136  * <p>A convenient shortcut for locating elements is the method {@link
 137  * #getElement(String)}; returns an element whose <code>ID</code>
 138  * attribute matches the specified value.  For example,
 139  * <code>d.getElement("BOX")</code> returns the <code>DIV</code>
 140  * element.</p>
 141  *
 142  * <p>The {@link #getIterator(HTML.Tag t)} method can also be used for
 143  * finding all occurrences of the specified HTML tag in the
 144  * document.</p>
 145  *
 146  * <h3>Inserting elements</h3>
 147  *
 148  * <p>Elements can be inserted before or after the existing children
 149  * of any non-leaf element by using the methods
 150  * <code>insertAfterStart</code> and <code>insertBeforeEnd</code>.
 151  * For example, if <code>e</code> is the <code>DIV</code> element,
 152  * <code>d.insertAfterStart(e, "&lt;ul&gt;&lt;li&gt;List
 153  * Item&lt;/li&gt;&lt;/ul&gt;")</code> inserts the list before the first
 154  * paragraph, and <code>d.insertBeforeEnd(e, "&lt;ul&gt;&lt;li&gt;List
 155  * Item&lt;/li&gt;&lt;/ul&gt;")</code> inserts the list after the last
 156  * paragraph.  The <code>DIV</code> block becomes the parent of the

 157  * newly inserted elements.</p>
 158  *
 159  * <p>Sibling elements can be inserted before or after any element by
 160  * using the methods <code>insertBeforeStart</code> and
 161  * <code>insertAfterEnd</code>.  For example, if <code>e</code> is the
 162  * <code>DIV</code> element, <code>d.insertBeforeStart(e,
 163  * "&lt;ul&gt;&lt;li&gt;List Item&lt;/li&gt;&lt;/ul&gt;")</code> inserts the list
 164  * before the <code>DIV</code> element, and <code>d.insertAfterEnd(e,
 165  * "&lt;ul&gt;&lt;li&gt;List Item&lt;/li&gt;&lt;/ul&gt;")</code> inserts the list
 166  * after the <code>DIV</code> element.  The newly inserted elements
 167  * become siblings of the <code>DIV</code> element.</p>

 168  *
 169  * <h3>Replacing elements</h3>
 170  *
 171  * <p>Elements and all their descendants can be replaced by using the
 172  * methods <code>setInnerHTML</code> and <code>setOuterHTML</code>.
 173  * For example, if <code>e</code> is the <code>DIV</code> element,
 174  * <code>d.setInnerHTML(e, "&lt;ul&gt;&lt;li&gt;List
 175  * Item&lt;/li&gt;&lt;/ul&gt;")</code> replaces all children paragraphs with
 176  * the list, and <code>d.setOuterHTML(e, "&lt;ul&gt;&lt;li&gt;List
 177  * Item&lt;/li&gt;&lt;/ul&gt;")</code> replaces the <code>DIV</code> element

 178  * itself.  In latter case the parent of the list is the
 179  * <code>BODY</code> element.
 180  *
 181  * <h3>Summary</h3>
 182  *
 183  * <p>The following table shows the example document and the results
 184  * of various methods described above.</p>
 185  *
 186  * <table border=1 cellspacing=0 summary="HTML Content of example above">
 187  *   <tr>
 188  *     <th>Example</th>
 189  *     <th><code>insertAfterStart</code></th>
 190  *     <th><code>insertBeforeEnd</code></th>
 191  *     <th><code>insertBeforeStart</code></th>
 192  *     <th><code>insertAfterEnd</code></th>
 193  *     <th><code>setInnerHTML</code></th>
 194  *     <th><code>setOuterHTML</code></th>
 195  *   </tr>
 196  *   <tr valign="top">
 197  *     <td style="white-space:nowrap">
 198  *       <div style="background-color: silver;">
 199  *         <p>Paragraph 1</p>
 200  *         <p>Paragraph 2</p>
 201  *       </div>
 202  *     </td>
 203  * <!--insertAfterStart-->
 204  *     <td style="white-space:nowrap">
 205  *       <div style="background-color: silver;">
 206  *         <ul style="color: red;">
 207  *           <li>List Item</li>
 208  *         </ul>
 209  *         <p>Paragraph 1</p>
 210  *         <p>Paragraph 2</p>
 211  *       </div>
 212  *     </td>
 213  * <!--insertBeforeEnd-->
 214  *     <td style="white-space:nowrap">


 246  *         <ul style="color: red;">
 247  *           <li>List Item</li>
 248  *         </ul>
 249  *       </div>
 250  *     </td>
 251  * <!--setOuterHTML-->
 252  *     <td style="white-space:nowrap">
 253  *       <ul style="color: red;">
 254  *         <li>List Item</li>
 255  *       </ul>
 256  *     </td>
 257  *   </tr>
 258  * </table>
 259  *
 260  * <p><strong>Warning:</strong> Serialized objects of this class will
 261  * not be compatible with future Swing releases. The current
 262  * serialization support is appropriate for short term storage or RMI
 263  * between applications running the same version of Swing.  As of 1.4,
 264  * support for long term storage of all JavaBeans&trade;
 265  * has been added to the
 266  * <code>java.beans</code> package.  Please see {@link
 267  * java.beans.XMLEncoder}.</p>
 268  *
 269  * @author  Timothy Prinzing
 270  * @author  Scott Violet
 271  * @author  Sunita Mani
 272  */
 273 @SuppressWarnings("serial") // Same-version serialization only
 274 public class HTMLDocument extends DefaultStyledDocument {
 275     /**
 276      * Constructs an HTML document using the default buffer size
 277      * and a default <code>StyleSheet</code>.  This is a convenience
 278      * method for the constructor
 279      * <code>HTMLDocument(Content, StyleSheet)</code>.
 280      */
 281     public HTMLDocument() {
 282         this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet());
 283     }
 284 
 285     /**
 286      * Constructs an HTML document with the default content
 287      * storage implementation and the specified style/attribute
 288      * storage mechanism.  This is a convenience method for the
 289      * constructor
 290      * <code>HTMLDocument(Content, StyleSheet)</code>.
 291      *
 292      * @param styles  the styles
 293      */
 294     public HTMLDocument(StyleSheet styles) {
 295         this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
 296     }
 297 
 298     /**
 299      * Constructs an HTML document with the given content
 300      * storage implementation and the given style/attribute
 301      * storage mechanism.
 302      *
 303      * @param c  the container for the content
 304      * @param styles the styles
 305      */
 306     public HTMLDocument(Content c, StyleSheet styles) {
 307         super(c, styles);
 308     }
 309 
 310     /**
 311      * Fetches the reader for the parser to use when loading the document
 312      * with HTML.  This is implemented to return an instance of
 313      * <code>HTMLDocument.HTMLReader</code>.
 314      * Subclasses can reimplement this
 315      * method to change how the document gets structured if desired.
 316      * (For example, to handle custom tags, or structurally represent character
 317      * style elements.)
 318      *
 319      * @param pos the starting position
 320      * @return the reader used by the parser to load the document
 321      */
 322     public HTMLEditorKit.ParserCallback getReader(int pos) {
 323         Object desc = getProperty(Document.StreamDescriptionProperty);
 324         if (desc instanceof URL) {
 325             setBase((URL)desc);
 326         }
 327         HTMLReader reader = new HTMLReader(pos);
 328         return reader;
 329     }
 330 
 331     /**
 332      * Returns the reader for the parser to use to load the document
 333      * with HTML.  This is implemented to return an instance of
 334      * <code>HTMLDocument.HTMLReader</code>.
 335      * Subclasses can reimplement this
 336      * method to change how the document gets structured if desired.
 337      * (For example, to handle custom tags, or structurally represent character
 338      * style elements.)
 339      * <p>This is a convenience method for
 340      * <code>getReader(int, int, int, HTML.Tag, TRUE)</code>.
 341      *
 342      * @param pos the starting position
 343      * @param popDepth   the number of <code>ElementSpec.EndTagTypes</code>
 344      *          to generate before inserting
 345      * @param pushDepth  the number of <code>ElementSpec.StartTagTypes</code>
 346      *          with a direction of <code>ElementSpec.JoinNextDirection</code>
 347      *          that should be generated before inserting,
 348      *          but after the end tags have been generated
 349      * @param insertTag  the first tag to start inserting into document
 350      * @return the reader used by the parser to load the document
 351      */
 352     public HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
 353                                                   int pushDepth,
 354                                                   HTML.Tag insertTag) {
 355         return getReader(pos, popDepth, pushDepth, insertTag, true);
 356     }
 357 
 358     /**
 359      * Fetches the reader for the parser to use to load the document
 360      * with HTML.  This is implemented to return an instance of
 361      * HTMLDocument.HTMLReader.  Subclasses can reimplement this
 362      * method to change how the document get structured if desired
 363      * (e.g. to handle custom tags, structurally represent character
 364      * style elements, etc.).
 365      *
 366      * @param popDepth   the number of <code>ElementSpec.EndTagTypes</code>
 367      *          to generate before inserting
 368      * @param pushDepth  the number of <code>ElementSpec.StartTagTypes</code>
 369      *          with a direction of <code>ElementSpec.JoinNextDirection</code>
 370      *          that should be generated before inserting,
 371      *          but after the end tags have been generated
 372      * @param insertTag  the first tag to start inserting into document
 373      * @param insertInsertTag  false if all the Elements after insertTag should
 374      *        be inserted; otherwise insertTag will be inserted
 375      * @return the reader used by the parser to load the document
 376      */
 377     HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
 378                                            int pushDepth,
 379                                            HTML.Tag insertTag,
 380                                            boolean insertInsertTag) {
 381         Object desc = getProperty(Document.StreamDescriptionProperty);
 382         if (desc instanceof URL) {
 383             setBase((URL)desc);
 384         }
 385         HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth,
 386                                            insertTag, insertInsertTag, false,
 387                                            true);
 388         return reader;
 389     }
 390 
 391     /**
 392      * Returns the location to resolve relative URLs against.  By
 393      * default this will be the document's URL if the document
 394      * was loaded from a URL.  If a base tag is found and
 395      * can be parsed, it will be used as the base location.
 396      *
 397      * @return the base location
 398      */
 399     public URL getBase() {
 400         return base;
 401     }
 402 
 403     /**
 404      * Sets the location to resolve relative URLs against.  By
 405      * default this will be the document's URL if the document
 406      * was loaded from a URL.  If a base tag is found and
 407      * can be parsed, it will be used as the base location.
 408      * <p>This also sets the base of the <code>StyleSheet</code>
 409      * to be <code>u</code> as well as the base of the document.
 410      *
 411      * @param u  the desired base URL
 412      */
 413     public void setBase(URL u) {
 414         base = u;
 415         getStyleSheet().setBase(u);
 416     }
 417 
 418     /**
 419      * Inserts new elements in bulk.  This is how elements get created
 420      * in the document.  The parsing determines what structure is needed
 421      * and creates the specification as a set of tokens that describe the
 422      * edit while leaving the document free of a write-lock.  This method
 423      * can then be called in bursts by the reader to acquire a write-lock
 424      * for a shorter duration (i.e. while the document is actually being
 425      * altered).
 426      *
 427      * @param offset the starting offset
 428      * @param data the element data
 429      * @exception BadLocationException  if the given position does not


 507                 else {
 508                     lastEnd = paragraph.getEndOffset();
 509                 }
 510                 MutableAttributeSet attr =
 511                     (MutableAttributeSet) paragraph.getAttributes();
 512                 changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
 513                 if (replace) {
 514                     attr.removeAttributes(attr);
 515                 }
 516                 attr.addAttributes(s);
 517             }
 518             changes.end();
 519             fireChangedUpdate(changes);
 520             fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
 521         } finally {
 522             writeUnlock();
 523         }
 524     }
 525 
 526     /**
 527      * Fetches the <code>StyleSheet</code> with the document-specific display
 528      * rules (CSS) that were specified in the HTML document itself.
 529      *
 530      * @return the <code>StyleSheet</code>
 531      */
 532     public StyleSheet getStyleSheet() {
 533         return (StyleSheet) getAttributeContext();
 534     }
 535 
 536     /**
 537      * Fetches an iterator for the specified HTML tag.
 538      * This can be used for things like iterating over the
 539      * set of anchors contained, or iterating over the input
 540      * elements.
 541      *
 542      * @param t the requested <code>HTML.Tag</code>
 543      * @return the <code>Iterator</code> for the given HTML tag
 544      * @see javax.swing.text.html.HTML.Tag
 545      */
 546     public Iterator getIterator(HTML.Tag t) {
 547         if (t.isBlock()) {
 548             // TBD
 549             return null;
 550         }
 551         return new LeafIterator(t, this);
 552     }
 553 
 554     /**
 555      * Creates a document leaf element that directly represents
 556      * text (doesn't have any children).  This is implemented
 557      * to return an element of type
 558      * <code>HTMLDocument.RunElement</code>.
 559      *
 560      * @param parent the parent element
 561      * @param a the attributes for the element
 562      * @param p0 the beginning of the range (must be at least 0)
 563      * @param p1 the end of the range (must be at least p0)
 564      * @return the new element
 565      */
 566     protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
 567         return new RunElement(parent, a, p0, p1);
 568     }
 569 
 570     /**
 571      * Creates a document branch element, that can contain other elements.
 572      * This is implemented to return an element of type
 573      * <code>HTMLDocument.BlockElement</code>.
 574      *
 575      * @param parent the parent element
 576      * @param a the attributes
 577      * @return the element
 578      */
 579     protected Element createBranchElement(Element parent, AttributeSet a) {
 580         return new BlockElement(parent, a);
 581     }
 582 
 583     /**
 584      * Creates the root element to be used to represent the
 585      * default document structure.
 586      *
 587      * @return the element base
 588      */
 589     protected AbstractElement createDefaultRoot() {
 590         // grabs a write-lock for this initialization and
 591         // abandon it during initialization so in normal
 592         // operation we can detect an illegitimate attempt
 593         // to mutate attributes.


 612         body.replace(0, 0, buff);
 613         buff[0] = body;
 614         html.replace(0, 0, buff);
 615         writeUnlock();
 616         return html;
 617     }
 618 
 619     /**
 620      * Sets the number of tokens to buffer before trying to update
 621      * the documents element structure.
 622      *
 623      * @param n  the number of tokens to buffer
 624      */
 625     public void setTokenThreshold(int n) {
 626         putProperty(TokenThreshold, n);
 627     }
 628 
 629     /**
 630      * Gets the number of tokens to buffer before trying to update
 631      * the documents element structure.  The default value is
 632      * <code>Integer.MAX_VALUE</code>.
 633      *
 634      * @return the number of tokens to buffer
 635      */
 636     public int getTokenThreshold() {
 637         Integer i = (Integer) getProperty(TokenThreshold);
 638         if (i != null) {
 639             return i.intValue();
 640         }
 641         return Integer.MAX_VALUE;
 642     }
 643 
 644     /**
 645      * Determines how unknown tags are handled by the parser.
 646      * If set to true, unknown
 647      * tags are put in the model, otherwise they are dropped.
 648      *
 649      * @param preservesTags  true if unknown tags should be
 650      *          saved in the model, otherwise tags are dropped
 651      * @see javax.swing.text.html.HTML.Tag
 652      */
 653     public void setPreservesUnknownTags(boolean preservesTags) {
 654         preservesUnknownTags = preservesTags;
 655     }
 656 
 657     /**
 658      * Returns the behavior the parser observes when encountering
 659      * unknown tags.
 660      *
 661      * @see javax.swing.text.html.HTML.Tag
 662      * @return true if unknown tags are to be preserved when parsing
 663      */
 664     public boolean getPreservesUnknownTags() {
 665         return preservesUnknownTags;
 666     }
 667 
 668     /**
 669      * Processes <code>HyperlinkEvents</code> that
 670      * are generated by documents in an HTML frame.
 671      * The <code>HyperlinkEvent</code> type, as the parameter suggests,
 672      * is <code>HTMLFrameHyperlinkEvent</code>.
 673      * In addition to the typical information contained in a
 674      * <code>HyperlinkEvent</code>,
 675      * this event contains the element that corresponds to the frame in
 676      * which the click happened (the source element) and the
 677      * target name.  The target name has 4 possible values:
 678      * <ul>
 679      * <li>  _self
 680      * <li>  _parent
 681      * <li>  _top
 682      * <li>  a named frame
 683      * </ul>
 684      *
 685      * If target is _self, the action is to change the value of the
 686      * <code>HTML.Attribute.SRC</code> attribute and fires a
 687      * <code>ChangedUpdate</code> event.
 688      *<p>
 689      * If the target is _parent, then it deletes the parent element,
 690      * which is a &lt;FRAMESET&gt; element, and inserts a new &lt;FRAME&gt;
 691      * element, and sets its <code>HTML.Attribute.SRC</code> attribute
 692      * to have a value equal to the destination URL and fire a
 693      * <code>RemovedUpdate</code> and <code>InsertUpdate</code>.
 694      *<p>
 695      * If the target is _top, this method does nothing. In the implementation
 696      * of the view for a frame, namely the <code>FrameView</code>,
 697      * the processing of _top is handled.  Given that _top implies
 698      * replacing the entire document, it made sense to handle this outside
 699      * of the document that it will replace.
 700      *<p>
 701      * If the target is a named frame, then the element hierarchy is searched
 702      * for an element with a name equal to the target, its
 703      * <code>HTML.Attribute.SRC</code> attribute is updated and a
 704      * <code>ChangedUpdate</code> event is fired.
 705      *
 706      * @param e the event
 707      */
 708     public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent e) {
 709         String frameName = e.getTarget();
 710         Element element = e.getSourceElement();
 711         String urlStr = e.getURL().toString();
 712 
 713         if (frameName.equals("_self")) {
 714             /*
 715               The source and destination elements
 716               are the same.
 717             */
 718             updateFrame(element, urlStr);
 719         } else if (frameName.equals("_parent")) {
 720             /*
 721               The destination is the parent of the frame.
 722             */
 723             updateFrameSet(element.getParentElement(), urlStr);
 724         } else {
 725             /*
 726               locate a named frame
 727             */
 728             Element targetElement = findFrame(frameName);
 729             if (targetElement != null) {
 730                 updateFrame(targetElement, urlStr);
 731             }
 732         }
 733     }
 734 
 735 
 736     /**
 737      * Searches the element hierarchy for an FRAME element
 738      * that has its name attribute equal to the <code>frameName</code>.
 739      *
 740      * @param frameName
 741      * @return the element whose NAME attribute has a value of
 742      *          <code>frameName</code>; returns <code>null</code>
 743      *          if not found
 744      */
 745     private Element findFrame(String frameName) {
 746         ElementIterator it = new ElementIterator(this);
 747         Element next;
 748 
 749         while ((next = it.next()) != null) {
 750             AttributeSet attr = next.getAttributes();
 751             if (matchNameAttribute(attr, HTML.Tag.FRAME)) {
 752                 String frameTarget = (String)attr.getAttribute(HTML.Attribute.NAME);
 753                 if (frameTarget != null && frameTarget.equals(frameName)) {
 754                     break;
 755                 }
 756             }
 757         }
 758         return next;
 759     }
 760 
 761     /**
 762      * Returns true if <code>StyleConstants.NameAttribute</code> is
 763      * equal to the tag that is passed in as a parameter.
 764      *
 765      * @param attr the attributes to be matched
 766      * @param tag the value to be matched
 767      * @return true if there is a match, false otherwise
 768      * @see javax.swing.text.html.HTML.Attribute
 769      */
 770     static boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
 771         Object o = attr.getAttribute(StyleConstants.NameAttribute);
 772         if (o instanceof HTML.Tag) {
 773             HTML.Tag name = (HTML.Tag) o;
 774             if (name == tag) {
 775                 return true;
 776             }
 777         }
 778         return false;
 779     }
 780 
 781     /**
 782      * Replaces a frameset branch Element with a frame leaf element.


 788     private void updateFrameSet(Element element, String url) {
 789         try {
 790             int startOffset = element.getStartOffset();
 791             int endOffset = Math.min(getLength(), element.getEndOffset());
 792             String html = "<frame";
 793             if (url != null) {
 794                 html += " src=\"" + url + "\"";
 795             }
 796             html += ">";
 797             installParserIfNecessary();
 798             setOuterHTML(element, html);
 799         } catch (BadLocationException e1) {
 800             // Should handle this better
 801         } catch (IOException ioe) {
 802             // Should handle this better
 803         }
 804     }
 805 
 806 
 807     /**
 808      * Updates the Frame elements <code>HTML.Attribute.SRC attribute</code>
 809      * and fires a <code>ChangedUpdate</code> event.
 810      *
 811      * @param element a FRAME element whose SRC attribute will be updated
 812      * @param url     a string specifying the new value for the SRC attribute
 813      */
 814     private void updateFrame(Element element, String url) {
 815 
 816         try {
 817             writeLock();
 818             DefaultDocumentEvent changes = new DefaultDocumentEvent(element.getStartOffset(),
 819                                                                     1,
 820                                                                     DocumentEvent.EventType.CHANGE);
 821             AttributeSet sCopy = element.getAttributes().copyAttributes();
 822             MutableAttributeSet attr = (MutableAttributeSet) element.getAttributes();
 823             changes.addEdit(new AttributeUndoableEdit(element, sCopy, false));
 824             attr.removeAttribute(HTML.Attribute.SRC);
 825             attr.addAttribute(HTML.Attribute.SRC, url);
 826             changes.end();
 827             fireChangedUpdate(changes);
 828             fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
 829         } finally {


 837      * @return true if document will be viewed in a frame, otherwise false
 838      */
 839     boolean isFrameDocument() {
 840         return frameDocument;
 841     }
 842 
 843     /**
 844      * Sets a boolean state about whether the document will be
 845      * viewed in a frame.
 846      * @param frameDoc  true if the document will be viewed in a frame,
 847      *          otherwise false
 848      */
 849     void setFrameDocumentState(boolean frameDoc) {
 850         this.frameDocument = frameDoc;
 851     }
 852 
 853     /**
 854      * Adds the specified map, this will remove a Map that has been
 855      * previously registered with the same name.
 856      *
 857      * @param map  the <code>Map</code> to be registered
 858      */
 859     void addMap(Map map) {
 860         String     name = map.getName();
 861 
 862         if (name != null) {
 863             Object     maps = getProperty(MAP_PROPERTY);
 864 
 865             if (maps == null) {
 866                 maps = new Hashtable<>(11);
 867                 putProperty(MAP_PROPERTY, maps);
 868             }
 869             if (maps instanceof Hashtable) {
 870                 @SuppressWarnings("unchecked")
 871                 Hashtable<Object, Object> tmp = (Hashtable)maps;
 872                 tmp.put("#" + name, map);
 873             }
 874         }
 875     }
 876 
 877     /**
 878      * Removes a previously registered map.
 879      * @param map the <code>Map</code> to be removed
 880      */
 881     void removeMap(Map map) {
 882         String     name = map.getName();
 883 
 884         if (name != null) {
 885             Object     maps = getProperty(MAP_PROPERTY);
 886 
 887             if (maps instanceof Hashtable) {
 888                 ((Hashtable)maps).remove("#" + name);
 889             }
 890         }
 891     }
 892 
 893     /**
 894      * Returns the Map associated with the given name.
 895      * @param name the name of the desired <code>Map</code>
 896      * @return the <code>Map</code> or <code>null</code> if it can't
 897      *          be found, or if <code>name</code> is <code>null</code>
 898      */
 899     Map getMap(String name) {
 900         if (name != null) {
 901             Object     maps = getProperty(MAP_PROPERTY);
 902 
 903             if (maps != null && (maps instanceof Hashtable)) {
 904                 return (Map)((Hashtable)maps).get(name);
 905             }
 906         }
 907         return null;
 908     }
 909 
 910     /**
 911      * Returns an <code>Enumeration</code> of the possible Maps.
 912      * @return the enumerated list of maps, or <code>null</code>
 913      *          if the maps are not an instance of <code>Hashtable</code>
 914      */
 915     Enumeration<Object> getMaps() {
 916         Object     maps = getProperty(MAP_PROPERTY);
 917 
 918         if (maps instanceof Hashtable) {
 919             @SuppressWarnings("unchecked")
 920             Hashtable<Object, Object> tmp = (Hashtable) maps;
 921             return tmp.elements();
 922         }
 923         return null;
 924     }
 925 
 926     /**
 927      * Sets the content type language used for style sheets that do not
 928      * explicitly specify the type. The default is text/css.
 929      * @param contentType  the content type language for the style sheets
 930      */
 931     /* public */
 932     void setDefaultStyleSheetType(String contentType) {
 933         putProperty(StyleType, contentType);
 934     }
 935 
 936     /**
 937      * Returns the content type language used for style sheets. The default
 938      * is text/css.
 939      * @return the content type language used for the style sheets
 940      */
 941     /* public */
 942     String getDefaultStyleSheetType() {
 943         String retValue = (String)getProperty(StyleType);
 944         if (retValue == null) {
 945             return "text/css";
 946         }
 947         return retValue;
 948     }
 949 
 950     /**
 951      * Sets the parser that is used by the methods that insert html
 952      * into the existing document, such as <code>setInnerHTML</code>,
 953      * and <code>setOuterHTML</code>.
 954      * <p>
 955      * <code>HTMLEditorKit.createDefaultDocument</code> will set the parser
 956      * for you. If you create an <code>HTMLDocument</code> by hand,
 957      * be sure and set the parser accordingly.
 958      * @param parser the parser to be used for text insertion
 959      *
 960      * @since 1.3
 961      */
 962     public void setParser(HTMLEditorKit.Parser parser) {
 963         this.parser = parser;
 964         putProperty("__PARSER__", null);
 965     }
 966 
 967     /**
 968      * Returns the parser that is used when inserting HTML into the existing
 969      * document.
 970      * @return the parser used for text insertion
 971      *
 972      * @since 1.3
 973      */
 974     public HTMLEditorKit.Parser getParser() {
 975         Object p = getProperty("__PARSER__");
 976 
 977         if (p instanceof HTMLEditorKit.Parser) {
 978             return (HTMLEditorKit.Parser)p;
 979         }
 980         return parser;
 981     }
 982 
 983     /**
 984      * Replaces the children of the given element with the contents
 985      * specified as an HTML string.
 986      *
 987      * <p>This will be seen as at least two events, n inserts followed by
 988      * a remove.</p>
 989      *
 990      * <p>Consider the following structure (the <code>elem</code>
 991      * parameter is <b>in bold</b>).</p>
 992      *
 993      * <pre>
 994      *     &lt;body&gt;
 995      *       |
 996      *     <b>&lt;div&gt;</b>
 997      *      /  \
 998      *    &lt;p&gt;   &lt;p&gt;
 999      * </pre>
1000      *
1001      * <p>Invoking <code>setInnerHTML(elem, "&lt;ul&gt;&lt;li&gt;")</code>
1002      * results in the following structure (new elements are <font
1003      * style="color: red;">in red</font>).</p>
1004      *
1005      * <pre>
1006      *     &lt;body&gt;
1007      *       |
1008      *     <b>&lt;div&gt;</b>
1009      *         \
1010      *         <font style="color: red;">&lt;ul&gt;</font>
1011      *           \
1012      *           <font style="color: red;">&lt;li&gt;</font>
1013      * </pre>
1014      *
1015      * <p>Parameter <code>elem</code> must not be a leaf element,
1016      * otherwise an <code>IllegalArgumentException</code> is thrown.
1017      * If either <code>elem</code> or <code>htmlText</code> parameter
1018      * is <code>null</code>, no changes are made to the document.</p>
1019      *
1020      * <p>For this to work correctly, the document must have an
1021      * <code>HTMLEditorKit.Parser</code> set. This will be the case
1022      * if the document was created from an HTMLEditorKit via the
1023      * <code>createDefaultDocument</code> method.</p>
1024      *
1025      * @param elem the branch element whose children will be replaced
1026      * @param htmlText the string to be parsed and assigned to <code>elem</code>
1027      * @throws IllegalArgumentException if <code>elem</code> is a leaf
1028      * @throws IllegalStateException if an <code>HTMLEditorKit.Parser</code>
1029      *         has not been defined
1030      * @throws BadLocationException if replacement is impossible because of
1031      *         a structural issue
1032      * @throws IOException if an I/O exception occurs
1033      * @since 1.3
1034      */
1035     public void setInnerHTML(Element elem, String htmlText) throws
1036                              BadLocationException, IOException {
1037         verifyParser();
1038         if (elem != null && elem.isLeaf()) {
1039             throw new IllegalArgumentException
1040                 ("Can not set inner HTML of a leaf");
1041         }
1042         if (elem != null && htmlText != null) {
1043             int oldCount = elem.getElementCount();
1044             int insertPosition = elem.getStartOffset();
1045             insertHTML(elem, elem.getStartOffset(), htmlText, true);
1046             if (elem.getElementCount() > oldCount) {
1047                 // Elements were inserted, do the cleanup.
1048                 removeElements(elem, elem.getElementCount() - oldCount,
1049                                oldCount);
1050             }
1051         }
1052     }
1053 
1054     /**
1055      * Replaces the given element in the parent with the contents
1056      * specified as an HTML string.
1057      *
1058      * <p>This will be seen as at least two events, n inserts followed by
1059      * a remove.</p>
1060      *
1061      * <p>When replacing a leaf this will attempt to make sure there is
1062      * a newline present if one is needed. This may result in an additional
1063      * element being inserted. Consider, if you were to replace a character
1064      * element that contained a newline with &lt;img&gt; this would create
1065      * two elements, one for the image, and one for the newline.</p>
1066      *
1067      * <p>If you try to replace the element at length you will most
1068      * likely end up with two elements, eg
1069      * <code>setOuterHTML(getCharacterElement (getLength()),
1070      * "blah")</code> will result in two leaf elements at the end, one
1071      * representing 'blah', and the other representing the end
1072      * element.</p>
1073      *
1074      * <p>Consider the following structure (the <code>elem</code>
1075      * parameter is <b>in bold</b>).</p>
1076      *
1077      * <pre>
1078      *     &lt;body&gt;
1079      *       |
1080      *     <b>&lt;div&gt;</b>
1081      *      /  \
1082      *    &lt;p&gt;   &lt;p&gt;
1083      * </pre>
1084      *
1085      * <p>Invoking <code>setOuterHTML(elem, "&lt;ul&gt;&lt;li&gt;")</code>
1086      * results in the following structure (new elements are <font
1087      * style="color: red;">in red</font>).</p>
1088      *
1089      * <pre>
1090      *    &lt;body&gt;
1091      *      |
1092      *     <font style="color: red;">&lt;ul&gt;</font>
1093      *       \
1094      *       <font style="color: red;">&lt;li&gt;</font>
1095      * </pre>
1096      *
1097      * <p>If either <code>elem</code> or <code>htmlText</code>
1098      * parameter is <code>null</code>, no changes are made to the
1099      * document.</p>
1100      *
1101      * <p>For this to work correctly, the document must have an
1102      * HTMLEditorKit.Parser set. This will be the case if the document
1103      * was created from an HTMLEditorKit via the
1104      * <code>createDefaultDocument</code> method.</p>
1105      *
1106      * @param elem the element to replace
1107      * @param htmlText the string to be parsed and inserted in place of <code>elem</code>
1108      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1109      *         been set
1110      * @throws BadLocationException if replacement is impossible because of
1111      *         a structural issue
1112      * @throws IOException if an I/O exception occurs
1113      * @since 1.3
1114      */
1115     public void setOuterHTML(Element elem, String htmlText) throws
1116                             BadLocationException, IOException {
1117         verifyParser();
1118         if (elem != null && elem.getParentElement() != null &&
1119             htmlText != null) {
1120             int start = elem.getStartOffset();
1121             int end = elem.getEndOffset();
1122             int startLength = getLength();
1123             // We don't want a newline if elem is a leaf, and doesn't contain
1124             // a newline.
1125             boolean wantsNewline = !elem.isLeaf();
1126             if (!wantsNewline && (end > startLength ||
1127                                  getText(end - 1, 1).charAt(0) == NEWLINE[0])){
1128                 wantsNewline = true;
1129             }
1130             Element parent = elem.getParentElement();
1131             int oldCount = parent.getElementCount();
1132             insertHTML(parent, start, htmlText, wantsNewline);
1133             // Remove old.
1134             int newLength = getLength();
1135             if (oldCount != parent.getElementCount()) {
1136                 int removeIndex = parent.getElementIndex(start + newLength -
1137                                                          startLength);
1138                 removeElements(parent, removeIndex, 1);
1139             }
1140         }
1141     }
1142 
1143     /**
1144      * Inserts the HTML specified as a string at the start
1145      * of the element.
1146      *
1147      * <p>Consider the following structure (the <code>elem</code>
1148      * parameter is <b>in bold</b>).</p>
1149      *
1150      * <pre>
1151      *     &lt;body&gt;
1152      *       |
1153      *     <b>&lt;div&gt;</b>
1154      *      /  \
1155      *    &lt;p&gt;   &lt;p&gt;
1156      * </pre>
1157      *
1158      * <p>Invoking <code>insertAfterStart(elem,
1159      * "&lt;ul&gt;&lt;li&gt;")</code> results in the following structure

1160      * (new elements are <font style="color: red;">in red</font>).</p>
1161      *
1162      * <pre>
1163      *        &lt;body&gt;
1164      *          |
1165      *        <b>&lt;div&gt;</b>
1166      *       /  |  \
1167      *    <font style="color: red;">&lt;ul&gt;</font> &lt;p&gt; &lt;p&gt;
1168      *     /
1169      *  <font style="color: red;">&lt;li&gt;</font>
1170      * </pre>
1171      *
1172      * <p>Unlike the <code>insertBeforeStart</code> method, new
1173      *  elements become <em>children</em> of the specified element,
1174      *  not siblings.</p>
1175      *
1176      * <p>Parameter <code>elem</code> must not be a leaf element,
1177      * otherwise an <code>IllegalArgumentException</code> is thrown.
1178      * If either <code>elem</code> or <code>htmlText</code> parameter
1179      * is <code>null</code>, no changes are made to the document.</p>
1180      *
1181      * <p>For this to work correctly, the document must have an
1182      * <code>HTMLEditorKit.Parser</code> set. This will be the case
1183      * if the document was created from an HTMLEditorKit via the
1184      * <code>createDefaultDocument</code> method.</p>
1185      *
1186      * @param elem the branch element to be the root for the new text
1187      * @param htmlText the string to be parsed and assigned to <code>elem</code>
1188      * @throws IllegalArgumentException if <code>elem</code> is a leaf
1189      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1190      *         been set on the document
1191      * @throws BadLocationException if insertion is impossible because of
1192      *         a structural issue
1193      * @throws IOException if an I/O exception occurs
1194      * @since 1.3
1195      */
1196     public void insertAfterStart(Element elem, String htmlText) throws
1197                                  BadLocationException, IOException {
1198         verifyParser();
1199 
1200         if (elem == null || htmlText == null) {
1201             return;
1202         }
1203 
1204         if (elem.isLeaf()) {
1205             throw new IllegalArgumentException
1206                 ("Can not insert HTML after start of a leaf");
1207         }
1208         insertHTML(elem, elem.getStartOffset(), htmlText, false);
1209     }
1210 
1211     /**
1212      * Inserts the HTML specified as a string at the end of
1213      * the element.
1214      *
1215      * <p> If <code>elem</code>'s children are leaves, and the
1216      * character at a <code>elem.getEndOffset() - 1</code> is a newline,
1217      * this will insert before the newline so that there isn't text after
1218      * the newline.</p>
1219      *
1220      * <p>Consider the following structure (the <code>elem</code>
1221      * parameter is <b>in bold</b>).</p>
1222      *
1223      * <pre>
1224      *     &lt;body&gt;
1225      *       |
1226      *     <b>&lt;div&gt;</b>
1227      *      /  \
1228      *    &lt;p&gt;   &lt;p&gt;
1229      * </pre>
1230      *
1231      * <p>Invoking <code>insertBeforeEnd(elem, "&lt;ul&gt;&lt;li&gt;")</code>
1232      * results in the following structure (new elements are <font
1233      * style="color: red;">in red</font>).</p>
1234      *
1235      * <pre>
1236      *        &lt;body&gt;
1237      *          |
1238      *        <b>&lt;div&gt;</b>
1239      *       /  |  \
1240      *     &lt;p&gt; &lt;p&gt; <font style="color: red;">&lt;ul&gt;</font>
1241      *               \
1242      *               <font style="color: red;">&lt;li&gt;</font>
1243      * </pre>
1244      *
1245      * <p>Unlike the <code>insertAfterEnd</code> method, new elements
1246      * become <em>children</em> of the specified element, not
1247      * siblings.</p>
1248      *
1249      * <p>Parameter <code>elem</code> must not be a leaf element,
1250      * otherwise an <code>IllegalArgumentException</code> is thrown.
1251      * If either <code>elem</code> or <code>htmlText</code> parameter
1252      * is <code>null</code>, no changes are made to the document.</p>
1253      *
1254      * <p>For this to work correctly, the document must have an
1255      * <code>HTMLEditorKit.Parser</code> set. This will be the case
1256      * if the document was created from an HTMLEditorKit via the
1257      * <code>createDefaultDocument</code> method.</p>
1258      *
1259      * @param elem the element to be the root for the new text
1260      * @param htmlText the string to be parsed and assigned to <code>elem</code>
1261      * @throws IllegalArgumentException if <code>elem</code> is a leaf
1262      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1263      *         been set on the document
1264      * @throws BadLocationException if insertion is impossible because of
1265      *         a structural issue
1266      * @throws IOException if an I/O exception occurs
1267      * @since 1.3
1268      */
1269     public void insertBeforeEnd(Element elem, String htmlText) throws
1270                                 BadLocationException, IOException {
1271         verifyParser();
1272         if (elem != null && elem.isLeaf()) {
1273             throw new IllegalArgumentException
1274                 ("Can not set inner HTML before end of leaf");
1275         }
1276         if (elem != null) {
1277             int offset = elem.getEndOffset();
1278             if (elem.getElement(elem.getElementIndex(offset - 1)).isLeaf() &&
1279                 getText(offset - 1, 1).charAt(0) == NEWLINE[0]) {
1280                 offset--;
1281             }
1282             insertHTML(elem, offset, htmlText, false);
1283         }
1284     }
1285 
1286     /**
1287      * Inserts the HTML specified as a string before the start of
1288      * the given element.
1289      *
1290      * <p>Consider the following structure (the <code>elem</code>
1291      * parameter is <b>in bold</b>).</p>
1292      *
1293      * <pre>
1294      *     &lt;body&gt;
1295      *       |
1296      *     <b>&lt;div&gt;</b>
1297      *      /  \
1298      *    &lt;p&gt;   &lt;p&gt;
1299      * </pre>
1300      *
1301      * <p>Invoking <code>insertBeforeStart(elem,
1302      * "&lt;ul&gt;&lt;li&gt;")</code> results in the following structure

1303      * (new elements are <font style="color: red;">in red</font>).</p>
1304      *
1305      * <pre>
1306      *        &lt;body&gt;
1307      *         /  \
1308      *      <font style="color: red;">&lt;ul&gt;</font> <b>&lt;div&gt;</b>
1309      *       /    /  \
1310      *     <font style="color: red;">&lt;li&gt;</font> &lt;p&gt;  &lt;p&gt;
1311      * </pre>
1312      *
1313      * <p>Unlike the <code>insertAfterStart</code> method, new
1314      * elements become <em>siblings</em> of the specified element, not
1315      * children.</p>
1316      *
1317      * <p>If either <code>elem</code> or <code>htmlText</code>
1318      * parameter is <code>null</code>, no changes are made to the
1319      * document.</p>
1320      *
1321      * <p>For this to work correctly, the document must have an
1322      * <code>HTMLEditorKit.Parser</code> set. This will be the case
1323      * if the document was created from an HTMLEditorKit via the
1324      * <code>createDefaultDocument</code> method.</p>
1325      *
1326      * @param elem the element the content is inserted before
1327      * @param htmlText the string to be parsed and inserted before <code>elem</code>
1328      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1329      *         been set on the document
1330      * @throws BadLocationException if insertion is impossible because of
1331      *         a structural issue
1332      * @throws IOException if an I/O exception occurs
1333      * @since 1.3
1334      */
1335     public void insertBeforeStart(Element elem, String htmlText) throws
1336                                   BadLocationException, IOException {
1337         verifyParser();
1338         if (elem != null) {
1339             Element parent = elem.getParentElement();
1340 
1341             if (parent != null) {
1342                 insertHTML(parent, elem.getStartOffset(), htmlText, false);
1343             }
1344         }
1345     }
1346 
1347     /**
1348      * Inserts the HTML specified as a string after the end of the
1349      * given element.
1350      *
1351      * <p>Consider the following structure (the <code>elem</code>
1352      * parameter is <b>in bold</b>).</p>
1353      *
1354      * <pre>
1355      *     &lt;body&gt;
1356      *       |
1357      *     <b>&lt;div&gt;</b>
1358      *      /  \
1359      *    &lt;p&gt;   &lt;p&gt;
1360      * </pre>
1361      *
1362      * <p>Invoking <code>insertAfterEnd(elem, "&lt;ul&gt;&lt;li&gt;")</code>
1363      * results in the following structure (new elements are <font
1364      * style="color: red;">in red</font>).</p>
1365      *
1366      * <pre>
1367      *        &lt;body&gt;
1368      *         /  \
1369      *      <b>&lt;div&gt;</b> <font style="color: red;">&lt;ul&gt;</font>
1370      *       / \    \
1371      *     &lt;p&gt; &lt;p&gt;  <font style="color: red;">&lt;li&gt;</font>
1372      * </pre>
1373      *
1374      * <p>Unlike the <code>insertBeforeEnd</code> method, new elements
1375      * become <em>siblings</em> of the specified element, not
1376      * children.</p>
1377      *
1378      * <p>If either <code>elem</code> or <code>htmlText</code>
1379      * parameter is <code>null</code>, no changes are made to the
1380      * document.</p>
1381      *
1382      * <p>For this to work correctly, the document must have an
1383      * <code>HTMLEditorKit.Parser</code> set. This will be the case
1384      * if the document was created from an HTMLEditorKit via the
1385      * <code>createDefaultDocument</code> method.</p>
1386      *
1387      * @param elem the element the content is inserted after
1388      * @param htmlText the string to be parsed and inserted after <code>elem</code>
1389      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1390      *         been set on the document
1391      * @throws BadLocationException if insertion is impossible because of
1392      *         a structural issue
1393      * @throws IOException if an I/O exception occurs
1394      * @since 1.3
1395      */
1396     public void insertAfterEnd(Element elem, String htmlText) throws
1397                                BadLocationException, IOException {
1398         verifyParser();
1399         if (elem != null) {
1400             Element parent = elem.getParentElement();
1401 
1402             if (parent != null) {
1403                 // If we are going to insert the string into the body
1404                 // section, it is necessary to set the corrsponding flag.
1405                 if (HTML.Tag.BODY.name.equals(parent.getName())) {
1406                     insertInBody = true;
1407                 }
1408                 int offset = elem.getEndOffset();
1409                 if (offset > (getLength() + 1)) {
1410                     offset--;
1411                 }
1412                 else if (elem.isLeaf() && getText(offset - 1, 1).
1413                     charAt(0) == NEWLINE[0]) {
1414                     offset--;
1415                 }
1416                 insertHTML(parent, offset, htmlText, false);
1417                 // Cleanup the flag, if any.
1418                 if (insertInBody) {
1419                     insertInBody = false;
1420                 }
1421             }
1422         }
1423     }
1424 
1425     /**
1426      * Returns the element that has the given id <code>Attribute</code>.
1427      * If the element can't be found, <code>null</code> is returned.
1428      * Note that this method works on an <code>Attribute</code>,
1429      * <i>not</i> a character tag.  In the following HTML snippet:
1430      * <code>&lt;a id="HelloThere"&gt;</code> the attribute is
1431      * 'id' and the character tag is 'a'.
1432      * This is a convenience method for
1433      * <code>getElement(RootElement, HTML.Attribute.id, id)</code>.
1434      * This is not thread-safe.
1435      *
1436      * @param id  the string representing the desired <code>Attribute</code>
1437      * @return the element with the specified <code>Attribute</code>
1438      *          or <code>null</code> if it can't be found,
1439      *          or <code>null</code> if <code>id</code> is <code>null</code>
1440      * @see javax.swing.text.html.HTML.Attribute
1441      * @since 1.3
1442      */
1443     public Element getElement(String id) {
1444         if (id == null) {
1445             return null;
1446         }
1447         return getElement(getDefaultRootElement(), HTML.Attribute.ID, id,
1448                           true);
1449     }
1450 
1451     /**
1452      * Returns the child element of <code>e</code> that contains the
1453      * attribute, <code>attribute</code> with value <code>value</code>, or
1454      * <code>null</code> if one isn't found. This is not thread-safe.
1455      *
1456      * @param e the root element where the search begins
1457      * @param attribute the desired <code>Attribute</code>
1458      * @param value the values for the specified <code>Attribute</code>
1459      * @return the element with the specified <code>Attribute</code>
1460      *          and the specified <code>value</code>, or <code>null</code>
1461      *          if it can't be found
1462      * @see javax.swing.text.html.HTML.Attribute
1463      * @since 1.3
1464      */
1465     public Element getElement(Element e, Object attribute, Object value) {
1466         return getElement(e, attribute, value, true);
1467     }
1468 
1469     /**
1470      * Returns the child element of <code>e</code> that contains the
1471      * attribute, <code>attribute</code> with value <code>value</code>, or
1472      * <code>null</code> if one isn't found. This is not thread-safe.
1473      * <p>
1474      * If <code>searchLeafAttributes</code> is true, and <code>e</code> is
1475      * a leaf, any attributes that are instances of <code>HTML.Tag</code>
1476      * with a value that is an <code>AttributeSet</code> will also be checked.
1477      *
1478      * @param e the root element where the search begins
1479      * @param attribute the desired <code>Attribute</code>
1480      * @param value the values for the specified <code>Attribute</code>
1481      * @return the element with the specified <code>Attribute</code>
1482      *          and the specified <code>value</code>, or <code>null</code>
1483      *          if it can't be found
1484      * @see javax.swing.text.html.HTML.Attribute
1485      */
1486     private Element getElement(Element e, Object attribute, Object value,
1487                                boolean searchLeafAttributes) {
1488         AttributeSet attr = e.getAttributes();
1489 
1490         if (attr != null && attr.isDefined(attribute)) {
1491             if (value.equals(attr.getAttribute(attribute))) {
1492                 return e;
1493             }
1494         }
1495         if (!e.isLeaf()) {
1496             for (int counter = 0, maxCounter = e.getElementCount();
1497                  counter < maxCounter; counter++) {
1498                 Element retValue = getElement(e.getElement(counter), attribute,
1499                                               value, searchLeafAttributes);
1500 
1501                 if (retValue != null) {
1502                     return retValue;


1510             if (names != null) {
1511                 while (names.hasMoreElements()) {
1512                     Object name = names.nextElement();
1513                     if ((name instanceof HTML.Tag) &&
1514                         (attr.getAttribute(name) instanceof AttributeSet)) {
1515 
1516                         AttributeSet check = (AttributeSet)attr.
1517                                              getAttribute(name);
1518                         if (check.isDefined(attribute) &&
1519                             value.equals(check.getAttribute(attribute))) {
1520                             return e;
1521                         }
1522                     }
1523                 }
1524             }
1525         }
1526         return null;
1527     }
1528 
1529     /**
1530      * Verifies the document has an <code>HTMLEditorKit.Parser</code> set.
1531      * If <code>getParser</code> returns <code>null</code>, this will throw an
1532      * IllegalStateException.
1533      *
1534      * @throws IllegalStateException if the document does not have a Parser
1535      */
1536     private void verifyParser() {
1537         if (getParser() == null) {
1538             throw new IllegalStateException("No HTMLEditorKit.Parser");
1539         }
1540     }
1541 
1542     /**
1543      * Installs a default Parser if one has not been installed yet.
1544      */
1545     private void installParserIfNecessary() {
1546         if (getParser() == null) {
1547             setParser(new HTMLEditorKit().getParser());
1548         }
1549     }
1550 
1551     /**
1552      * Inserts a string of HTML into the document at the given position.
1553      * <code>parent</code> is used to identify the location to insert the
1554      * <code>html</code>. If <code>parent</code> is a leaf this can have
1555      * unexpected results.
1556      */
1557     private void insertHTML(Element parent, int offset, String html,
1558                             boolean wantsTrailingNewline)
1559                  throws BadLocationException, IOException {
1560         if (parent != null && html != null) {
1561             HTMLEditorKit.Parser parser = getParser();
1562             if (parser != null) {
1563                 int lastOffset = Math.max(0, offset - 1);
1564                 Element charElement = getCharacterElement(lastOffset);
1565                 Element commonParent = parent;
1566                 int pop = 0;
1567                 int push = 0;
1568 
1569                 if (parent.getStartOffset() > lastOffset) {
1570                     while (commonParent != null &&
1571                            commonParent.getStartOffset() > lastOffset) {
1572                         commonParent = commonParent.getParentElement();
1573                         push++;
1574                     }


1578                     }
1579                 }
1580                 while (charElement != null && charElement != commonParent) {
1581                     pop++;
1582                     charElement = charElement.getParentElement();
1583                 }
1584                 if (charElement != null) {
1585                     // Found it, do the insert.
1586                     HTMLReader reader = new HTMLReader(offset, pop - 1, push,
1587                                                        null, false, true,
1588                                                        wantsTrailingNewline);
1589 
1590                     parser.parse(new StringReader(html), reader, true);
1591                     reader.flush();
1592                 }
1593             }
1594         }
1595     }
1596 
1597     /**
1598      * Removes child Elements of the passed in Element <code>e</code>. This
1599      * will do the necessary cleanup to ensure the element representing the
1600      * end character is correctly created.
1601      * <p>This is not a general purpose method, it assumes that <code>e</code>
1602      * will still have at least one child after the remove, and it assumes
1603      * the character at <code>e.getStartOffset() - 1</code> is a newline and
1604      * is of length 1.
1605      */
1606     private void removeElements(Element e, int index, int count) throws BadLocationException {
1607         writeLock();
1608         try {
1609             int start = e.getElement(index).getStartOffset();
1610             int end = e.getElement(index + count - 1).getEndOffset();
1611             if (end > getLength()) {
1612                 removeElementsAtEnd(e, index, count, start, end);
1613             }
1614             else {
1615                 removeElements(e, index, count, start, end);
1616             }
1617         } finally {
1618             writeUnlock();
1619         }
1620     }
1621 
1622     /**
1623      * Called to remove child elements of <code>e</code> when one of the
1624      * elements to remove is representing the end character.
1625      * <p>Since the Content will not allow a removal to the end character
1626      * this will do a remove from <code>start - 1</code> to <code>end</code>.
1627      * The end Element(s) will be removed, and the element representing
1628      * <code>start - 1</code> to <code>start</code> will be recreated. This
1629      * Element has to be recreated as after the content removal its offsets
1630      * become <code>start - 1</code> to <code>start - 1</code>.
1631      */
1632     private void removeElementsAtEnd(Element e, int index, int count,
1633                          int start, int end) throws BadLocationException {
1634         // index must be > 0 otherwise no insert would have happened.
1635         boolean isLeaf = (e.getElement(index - 1).isLeaf());
1636         DefaultDocumentEvent dde = new DefaultDocumentEvent(
1637                        start - 1, end - start + 1, DocumentEvent.
1638                        EventType.REMOVE);
1639 
1640         if (isLeaf) {
1641             Element endE = getCharacterElement(getLength());
1642             // e.getElement(index - 1) should represent the newline.
1643             index--;
1644             if (endE.getParentElement() != e) {
1645                 // The hiearchies don't match, we'll have to manually
1646                 // recreate the leaf at e.getElement(index - 1)
1647                 replace(dde, e, index, ++count, start, end, true, true);
1648             }
1649             else {
1650                 // The hierarchies for the end Element and


1656         }
1657         else {
1658             // Not a leaf, descend until we find the leaf representing
1659             // start - 1 and remove it.
1660             Element newLineE = e.getElement(index - 1);
1661             while (!newLineE.isLeaf()) {
1662                 newLineE = newLineE.getElement(newLineE.getElementCount() - 1);
1663             }
1664             newLineE = newLineE.getParentElement();
1665             replace(dde, e, index, count, start, end, false, false);
1666             replace(dde, newLineE, newLineE.getElementCount() - 1, 1, start,
1667                     end, true, true);
1668         }
1669         postRemoveUpdate(dde);
1670         dde.end();
1671         fireRemoveUpdate(dde);
1672         fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
1673     }
1674 
1675     /**
1676      * This is used by <code>removeElementsAtEnd</code>, it removes
1677      * <code>count</code> elements starting at <code>start</code> from
1678      * <code>e</code>.  If <code>remove</code> is true text of length
1679      * <code>start - 1</code> to <code>end - 1</code> is removed.  If
1680      * <code>create</code> is true a new leaf is created of length 1.
1681      */
1682     private void replace(DefaultDocumentEvent dde, Element e, int index,
1683                          int count, int start, int end, boolean remove,
1684                          boolean create) throws BadLocationException {
1685         Element[] added;
1686         AttributeSet attrs = e.getElement(index).getAttributes();
1687         Element[] removed = new Element[count];
1688 
1689         for (int counter = 0; counter < count; counter++) {
1690             removed[counter] = e.getElement(counter + index);
1691         }
1692         if (remove) {
1693             UndoableEdit u = getContent().remove(start - 1, end - start);
1694             if (u != null) {
1695                 dde.addEdit(u);
1696             }
1697         }
1698         if (create) {
1699             added = new Element[1];
1700             added[0] = createLeafElement(e, attrs, start - 1, start);


1872         ((MutableAttributeSet)contentAttributeSet).
1873                         addAttribute(StyleConstants.NameAttribute,
1874                                      HTML.Tag.CONTENT);
1875         NEWLINE = new char[1];
1876         NEWLINE[0] = '\n';
1877     }
1878 
1879 
1880     /**
1881      * An iterator to iterate over a particular type of
1882      * tag.  The iterator is not thread safe.  If reliable
1883      * access to the document is not already ensured by
1884      * the context under which the iterator is being used,
1885      * its use should be performed under the protection of
1886      * Document.render.
1887      */
1888     public abstract static class Iterator {
1889 
1890         /**
1891          * Return the attributes for this tag.
1892          * @return the <code>AttributeSet</code> for this tag, or
1893          *      <code>null</code> if none can be found
1894          */
1895         public abstract AttributeSet getAttributes();
1896 
1897         /**
1898          * Returns the start of the range for which the current occurrence of
1899          * the tag is defined and has the same attributes.
1900          *
1901          * @return the start of the range, or -1 if it can't be found
1902          */
1903         public abstract int getStartOffset();
1904 
1905         /**
1906          * Returns the end of the range for which the current occurrence of
1907          * the tag is defined and has the same attributes.
1908          *
1909          * @return the end of the range
1910          */
1911         public abstract int getEndOffset();
1912 
1913         /**


1929          * Type of tag this iterator represents.
1930          * @return the tag
1931          */
1932         public abstract HTML.Tag getTag();
1933     }
1934 
1935     /**
1936      * An iterator to iterate over a particular type of tag.
1937      */
1938     static class LeafIterator extends Iterator {
1939 
1940         LeafIterator(HTML.Tag t, Document doc) {
1941             tag = t;
1942             pos = new ElementIterator(doc);
1943             endOffset = 0;
1944             next();
1945         }
1946 
1947         /**
1948          * Returns the attributes for this tag.
1949          * @return the <code>AttributeSet</code> for this tag,
1950          *              or <code>null</code> if none can be found
1951          */
1952         public AttributeSet getAttributes() {
1953             Element elem = pos.current();
1954             if (elem != null) {
1955                 AttributeSet a = (AttributeSet)
1956                     elem.getAttributes().getAttribute(tag);
1957                 if (a == null) {
1958                     a = elem.getAttributes();
1959                 }
1960                 return a;
1961             }
1962             return null;
1963         }
1964 
1965         /**
1966          * Returns the start of the range for which the current occurrence of
1967          * the tag is defined and has the same attributes.
1968          *
1969          * @return the start of the range, or -1 if it can't be found
1970          */


1993         public void next() {
1994             for (nextLeaf(pos); isValid(); nextLeaf(pos)) {
1995                 Element elem = pos.current();
1996                 if (elem.getStartOffset() >= endOffset) {
1997                     AttributeSet a = pos.current().getAttributes();
1998 
1999                     if (a.isDefined(tag) ||
2000                         a.getAttribute(StyleConstants.NameAttribute) == tag) {
2001 
2002                         // we found the next one
2003                         setEndOffset();
2004                         break;
2005                     }
2006                 }
2007             }
2008         }
2009 
2010         /**
2011          * Returns the type of tag this iterator represents.
2012          *
2013          * @return the <code>HTML.Tag</code> that this iterator represents.
2014          * @see javax.swing.text.html.HTML.Tag
2015          */
2016         public HTML.Tag getTag() {
2017             return tag;
2018         }
2019 
2020         /**
2021          * Returns true if the current position is not <code>null</code>.
2022          * @return true if current position is not <code>null</code>,
2023          *              otherwise returns false
2024          */
2025         public boolean isValid() {
2026             return (pos.current() != null);
2027         }
2028 
2029         /**
2030          * Moves the given iterator to the next leaf element.
2031          * @param iter  the iterator to be scanned
2032          */
2033         void nextLeaf(ElementIterator iter) {
2034             for (iter.next(); iter.current() != null; iter.next()) {
2035                 Element e = iter.current();
2036                 if (e.isLeaf()) {
2037                     break;
2038                 }
2039             }
2040         }
2041 
2042         /**
2043          * Marches a cloned iterator forward to locate the end
2044          * of the run.  This sets the value of <code>endOffset</code>.
2045          */
2046         void setEndOffset() {
2047             AttributeSet a0 = getAttributes();
2048             endOffset = pos.current().getEndOffset();
2049             ElementIterator fwd = (ElementIterator) pos.clone();
2050             for (nextLeaf(fwd); fwd.current() != null; nextLeaf(fwd)) {
2051                 Element e = fwd.current();
2052                 AttributeSet a1 = (AttributeSet) e.getAttributes().getAttribute(tag);
2053                 if ((a1 == null) || (! a1.equals(a0))) {
2054                     break;
2055                 }
2056                 endOffset = e.getEndOffset();
2057             }
2058         }
2059 
2060         private int endOffset;
2061         private HTML.Tag tag;
2062         private ElementIterator pos;
2063 
2064     }
2065 
2066     /**
2067      * An HTML reader to load an HTML document with an HTML
2068      * element structure.  This is a set of callbacks from
2069      * the parser, implemented to create a set of elements
2070      * tagged with attributes.  The parse builds up tokens
2071      * (ElementSpec) that describe the element subtree desired,
2072      * and burst it into the document under the protection of
2073      * a write lock using the insert method on the document
2074      * outer class.
2075      * <p>
2076      * The reader can be configured by registering actions
2077      * (of type <code>HTMLDocument.HTMLReader.TagAction</code>)
2078      * that describe how to handle the action.  The idea behind
2079      * the actions provided is that the most natural text editing
2080      * operations can be provided if the element structure boils
2081      * down to paragraphs with runs of some kind of style
2082      * in them.  Some things are more naturally specified
2083      * structurally, so arbitrary structure should be allowed
2084      * above the paragraphs, but will need to be edited with structural
2085      * actions.  The implication of this is that some of the
2086      * HTML elements specified in the stream being parsed will
2087      * be collapsed into attributes, and in some cases paragraphs
2088      * will be synthesized.  When HTML elements have been
2089      * converted to attributes, the attribute key will be of
2090      * type HTML.Tag, and the value will be of type AttributeSet
2091      * so that no information is lost.  This enables many of the
2092      * existing actions to work so that the user can type input,
2093      * hit the return key, backspace, delete, etc and have a
2094      * reasonable result.  Selections can be created, and attributes
2095      * applied or removed, etc.  With this in mind, the work done
2096      * by the reader can be categorized into the following kinds
2097      * of tasks:


2111      * <dt>Special
2112      * <dd>Produce an embedded graphical element.
2113      * <dt>Form
2114      * <dd>Produce an element that is like the embedded graphical
2115      * element, except that it also has a component model associated
2116      * with it.
2117      * <dt>Hidden
2118      * <dd>Create an element that is hidden from view when the
2119      * document is being viewed read-only, and visible when the
2120      * document is being edited.  This is useful to keep the
2121      * model from losing information, and used to store things
2122      * like comments and unrecognized tags.
2123      *
2124      * </dl>
2125      * <p>
2126      * Currently, &lt;APPLET&gt;, &lt;PARAM&gt;, &lt;MAP&gt;, &lt;AREA&gt;, &lt;LINK&gt;,
2127      * &lt;SCRIPT&gt; and &lt;STYLE&gt; are unsupported.
2128      *
2129      * <p>
2130      * The assignment of the actions described is shown in the
2131      * following table for the tags defined in <code>HTML.Tag</code>.
2132      * <table border=1 summary="HTML tags and assigned actions">
2133      * <tr><th>Tag</th><th>Action</th></tr>
2134      * <tr><td><code>HTML.Tag.A</code>         <td>CharacterAction
2135      * <tr><td><code>HTML.Tag.ADDRESS</code>   <td>CharacterAction
2136      * <tr><td><code>HTML.Tag.APPLET</code>    <td>HiddenAction
2137      * <tr><td><code>HTML.Tag.AREA</code>      <td>AreaAction
2138      * <tr><td><code>HTML.Tag.B</code>         <td>CharacterAction
2139      * <tr><td><code>HTML.Tag.BASE</code>      <td>BaseAction
2140      * <tr><td><code>HTML.Tag.BASEFONT</code>  <td>CharacterAction
2141      * <tr><td><code>HTML.Tag.BIG</code>       <td>CharacterAction
2142      * <tr><td><code>HTML.Tag.BLOCKQUOTE</code><td>BlockAction
2143      * <tr><td><code>HTML.Tag.BODY</code>      <td>BlockAction
2144      * <tr><td><code>HTML.Tag.BR</code>        <td>SpecialAction
2145      * <tr><td><code>HTML.Tag.CAPTION</code>   <td>BlockAction
2146      * <tr><td><code>HTML.Tag.CENTER</code>    <td>BlockAction
2147      * <tr><td><code>HTML.Tag.CITE</code>      <td>CharacterAction
2148      * <tr><td><code>HTML.Tag.CODE</code>      <td>CharacterAction
2149      * <tr><td><code>HTML.Tag.DD</code>        <td>BlockAction
2150      * <tr><td><code>HTML.Tag.DFN</code>       <td>CharacterAction
2151      * <tr><td><code>HTML.Tag.DIR</code>       <td>BlockAction
2152      * <tr><td><code>HTML.Tag.DIV</code>       <td>BlockAction
2153      * <tr><td><code>HTML.Tag.DL</code>        <td>BlockAction
2154      * <tr><td><code>HTML.Tag.DT</code>        <td>ParagraphAction
2155      * <tr><td><code>HTML.Tag.EM</code>        <td>CharacterAction
2156      * <tr><td><code>HTML.Tag.FONT</code>      <td>CharacterAction
2157      * <tr><td><code>HTML.Tag.FORM</code>      <td>As of 1.4 a BlockAction
2158      * <tr><td><code>HTML.Tag.FRAME</code>     <td>SpecialAction
2159      * <tr><td><code>HTML.Tag.FRAMESET</code>  <td>BlockAction
2160      * <tr><td><code>HTML.Tag.H1</code>        <td>ParagraphAction
2161      * <tr><td><code>HTML.Tag.H2</code>        <td>ParagraphAction
2162      * <tr><td><code>HTML.Tag.H3</code>        <td>ParagraphAction
2163      * <tr><td><code>HTML.Tag.H4</code>        <td>ParagraphAction
2164      * <tr><td><code>HTML.Tag.H5</code>        <td>ParagraphAction
2165      * <tr><td><code>HTML.Tag.H6</code>        <td>ParagraphAction
2166      * <tr><td><code>HTML.Tag.HEAD</code>      <td>HeadAction
2167      * <tr><td><code>HTML.Tag.HR</code>        <td>SpecialAction
2168      * <tr><td><code>HTML.Tag.HTML</code>      <td>BlockAction
2169      * <tr><td><code>HTML.Tag.I</code>         <td>CharacterAction
2170      * <tr><td><code>HTML.Tag.IMG</code>       <td>SpecialAction
2171      * <tr><td><code>HTML.Tag.INPUT</code>     <td>FormAction
2172      * <tr><td><code>HTML.Tag.ISINDEX</code>   <td>IsndexAction
2173      * <tr><td><code>HTML.Tag.KBD</code>       <td>CharacterAction
2174      * <tr><td><code>HTML.Tag.LI</code>        <td>BlockAction
2175      * <tr><td><code>HTML.Tag.LINK</code>      <td>LinkAction
2176      * <tr><td><code>HTML.Tag.MAP</code>       <td>MapAction
2177      * <tr><td><code>HTML.Tag.MENU</code>      <td>BlockAction
2178      * <tr><td><code>HTML.Tag.META</code>      <td>MetaAction
2179      * <tr><td><code>HTML.Tag.NOFRAMES</code>  <td>BlockAction
2180      * <tr><td><code>HTML.Tag.OBJECT</code>    <td>SpecialAction
2181      * <tr><td><code>HTML.Tag.OL</code>        <td>BlockAction
2182      * <tr><td><code>HTML.Tag.OPTION</code>    <td>FormAction
2183      * <tr><td><code>HTML.Tag.P</code>         <td>ParagraphAction
2184      * <tr><td><code>HTML.Tag.PARAM</code>     <td>HiddenAction
2185      * <tr><td><code>HTML.Tag.PRE</code>       <td>PreAction
2186      * <tr><td><code>HTML.Tag.SAMP</code>      <td>CharacterAction
2187      * <tr><td><code>HTML.Tag.SCRIPT</code>    <td>HiddenAction
2188      * <tr><td><code>HTML.Tag.SELECT</code>    <td>FormAction
2189      * <tr><td><code>HTML.Tag.SMALL</code>     <td>CharacterAction
2190      * <tr><td><code>HTML.Tag.STRIKE</code>    <td>CharacterAction
2191      * <tr><td><code>HTML.Tag.S</code>         <td>CharacterAction
2192      * <tr><td><code>HTML.Tag.STRONG</code>    <td>CharacterAction
2193      * <tr><td><code>HTML.Tag.STYLE</code>     <td>StyleAction
2194      * <tr><td><code>HTML.Tag.SUB</code>       <td>CharacterAction
2195      * <tr><td><code>HTML.Tag.SUP</code>       <td>CharacterAction
2196      * <tr><td><code>HTML.Tag.TABLE</code>     <td>BlockAction
2197      * <tr><td><code>HTML.Tag.TD</code>        <td>BlockAction
2198      * <tr><td><code>HTML.Tag.TEXTAREA</code>  <td>FormAction
2199      * <tr><td><code>HTML.Tag.TH</code>        <td>BlockAction
2200      * <tr><td><code>HTML.Tag.TITLE</code>     <td>TitleAction
2201      * <tr><td><code>HTML.Tag.TR</code>        <td>BlockAction
2202      * <tr><td><code>HTML.Tag.TT</code>        <td>CharacterAction
2203      * <tr><td><code>HTML.Tag.U</code>         <td>CharacterAction
2204      * <tr><td><code>HTML.Tag.UL</code>        <td>BlockAction
2205      * <tr><td><code>HTML.Tag.VAR</code>       <td>CharacterAction
2206      * </table>
2207      * <p>
2208      * Once &lt;/html&gt; is encountered, the Actions are no longer notified.
2209      */
2210     public class HTMLReader extends HTMLEditorKit.ParserCallback {
2211 
2212         /**
2213          * Constructs an HTMLReader using default pop and push depth and no tag to insert.
2214          *
2215          * @param offset the starting offset
2216          */
2217         public HTMLReader(int offset) {
2218             this(offset, 0, 0, null);
2219         }
2220 
2221         /**
2222          * Constructs an HTMLReader.
2223          *
2224          * @param offset the starting offset
2225          * @param popDepth how many parents to ascend before insert new element
2226          * @param pushDepth how many parents to descend (relative to popDepth) before
2227          *                  inserting
2228          * @param insertTag a tag to insert (may be null)
2229          */
2230         public HTMLReader(int offset, int popDepth, int pushDepth,
2231                           HTML.Tag insertTag) {
2232             this(offset, popDepth, pushDepth, insertTag, true, false, true);
2233         }
2234 
2235         /**
2236          * Generates a RuntimeException (will eventually generate
2237          * a BadLocationException when API changes are alloced) if inserting
2238          * into non empty document, <code>insertTag</code> is
2239          * non-<code>null</code>, and <code>offset</code> is not in the body.
2240          */
2241         // PENDING(sky): Add throws BadLocationException and remove
2242         // RuntimeException
2243         HTMLReader(int offset, int popDepth, int pushDepth,
2244                    HTML.Tag insertTag, boolean insertInsertTag,
2245                    boolean insertAfterImplied, boolean wantsTrailingNewline) {
2246             emptyDocument = (getLength() == 0);
2247             isStyleCSS = "text/css".equals(getDefaultStyleSheetType());
2248             this.offset = offset;
2249             threshold = HTMLDocument.this.getTokenThreshold();
2250             tagMap = new Hashtable<HTML.Tag, TagAction>(57);
2251             TagAction na = new TagAction();
2252             TagAction ba = new BlockAction();
2253             TagAction pa = new ParagraphAction();
2254             TagAction ca = new CharacterAction();
2255             TagAction sa = new SpecialAction();
2256             TagAction fa = new FormAction();
2257             TagAction ha = new HiddenAction();
2258             TagAction conv = new ConvertAction();
2259 


2343             else {
2344                 foundInsertTag = true;
2345             }
2346             if (insertAfterImplied) {
2347                 this.popDepth = popDepth;
2348                 this.pushDepth = pushDepth;
2349                 this.insertAfterImplied = true;
2350                 foundInsertTag = false;
2351                 midInsert = false;
2352                 this.insertInsertTag = true;
2353                 this.wantsTrailingNewline = wantsTrailingNewline;
2354             }
2355             else {
2356                 midInsert = (!emptyDocument && insertTag == null);
2357                 if (midInsert) {
2358                     generateEndsSpecsForMidInsert();
2359                 }
2360             }
2361 
2362             /**
2363              * This block initializes the <code>inParagraph</code> flag.
2364              * It is left in <code>false</code> value automatically
2365              * if the target document is empty or future inserts
2366              * were positioned into the 'body' tag.
2367              */
2368             if (!emptyDocument && !midInsert) {
2369                 int targetOffset = Math.max(this.offset - 1, 0);
2370                 Element elem =
2371                         HTMLDocument.this.getCharacterElement(targetOffset);
2372                 /* Going up by the left document structure path */
2373                 for (int i = 0; i <= this.popDepth; i++) {
2374                     elem = elem.getParentElement();
2375                 }
2376                 /* Going down by the right document structure path */
2377                 for (int i = 0; i < this.pushDepth; i++) {
2378                     int index = elem.getElementIndex(this.offset);
2379                     elem = elem.getElement(index);
2380                 }
2381                 AttributeSet attrs = elem.getAttributes();
2382                 if (attrs != null) {
2383                     HTML.Tag tagToInsertInto =
2384                             (HTML.Tag) attrs.getAttribute(StyleConstants.NameAttribute);
2385                     if (tagToInsertInto != null) {
2386                         this.inParagraph = tagToInsertInto.isParagraph();
2387                     }
2388                 }
2389             }
2390         }
2391 
2392         /**
2393          * Generates an initial batch of end <code>ElementSpecs</code>
2394          * in parseBuffer to position future inserts into the body.
2395          */
2396         private void generateEndsSpecsForMidInsert() {
2397             int           count = heightToElementWithName(HTML.Tag.BODY,
2398                                                    Math.max(0, offset - 1));
2399             boolean       joinNext = false;
2400 
2401             if (count == -1 && offset > 0) {
2402                 count = heightToElementWithName(HTML.Tag.BODY, offset);
2403                 if (count != -1) {
2404                     // Previous isn't in body, but current is. Have to
2405                     // do some end specs, followed by join next.
2406                     count = depthTo(offset - 1) - 1;
2407                     joinNext = true;
2408                 }
2409             }
2410             if (count == -1) {
2411                 throw new RuntimeException("Must insert new content into body element-");
2412             }
2413             if (count != -1) {


2438             }
2439             // We should probably throw an exception if (count == -1)
2440             // Or look for the body and reset the offset.
2441         }
2442 
2443         /**
2444          * @return number of parents to reach the child at offset.
2445          */
2446         private int depthTo(int offset) {
2447             Element       e = getDefaultRootElement();
2448             int           count = 0;
2449 
2450             while (!e.isLeaf()) {
2451                 count++;
2452                 e = e.getElement(e.getElementIndex(offset));
2453             }
2454             return count;
2455         }
2456 
2457         /**
2458          * @return number of parents of the leaf at <code>offset</code>
2459          *         until a parent with name, <code>name</code> has been
2460          *         found. -1 indicates no matching parent with
2461          *         <code>name</code>.
2462          */
2463         private int heightToElementWithName(Object name, int offset) {
2464             Element       e = getCharacterElement(offset).getParentElement();
2465             int           count = 0;
2466 
2467             while (e != null && e.getAttributes().getAttribute
2468                    (StyleConstants.NameAttribute) != name) {
2469                 count++;
2470                 e = e.getParentElement();
2471             }
2472             return (e == null) ? -1 : count;
2473         }
2474 
2475         /**
2476          * This will make sure there aren't two BODYs (the second is
2477          * typically created when you do a remove all, and then an insert).
2478          */
2479         private void adjustEndElement() {
2480             int length = getLength();
2481             if (length == 0) {


2687                 if (inBlock == 0 && (foundInsertTag ||
2688                                      insertTag != HTML.Tag.COMMENT)) {
2689                     // Comment outside of body, will not be able to show it,
2690                     // but can add it as a property on the Document.
2691                     addExternalComment(new String(data));
2692                     return;
2693                 }
2694                 SimpleAttributeSet sas = new SimpleAttributeSet();
2695                 sas.addAttribute(HTML.Attribute.COMMENT, new String(data));
2696                 addSpecialElement(HTML.Tag.COMMENT, sas);
2697             }
2698 
2699             TagAction action = tagMap.get(HTML.Tag.COMMENT);
2700             if (action != null) {
2701                 action.start(HTML.Tag.COMMENT, new SimpleAttributeSet());
2702                 action.end(HTML.Tag.COMMENT);
2703             }
2704         }
2705 
2706         /**
2707          * Adds the comment <code>comment</code> to the set of comments
2708          * maintained outside of the scope of elements.
2709          */
2710         private void addExternalComment(String comment) {
2711             Object comments = getProperty(AdditionalComments);
2712             if (comments != null && !(comments instanceof Vector)) {
2713                 // No place to put comment.
2714                 return;
2715             }
2716             if (comments == null) {
2717                 comments = new Vector<>();
2718                 putProperty(AdditionalComments, comments);
2719             }
2720             @SuppressWarnings("unchecked")
2721             Vector<Object> v = (Vector<Object>)comments;
2722             v.addElement(comment);
2723         }
2724 
2725         /**
2726          * Callback from the parser.  Route to the appropriate
2727          * handler for the tag.


2761                 styleAttributes = getStyleSheet().getDeclaration(decl);
2762                 a.addAttributes(styleAttributes);
2763             }
2764             else {
2765                 styleAttributes = null;
2766             }
2767 
2768             TagAction action = tagMap.get(t);
2769             if (action != null) {
2770                 action.start(t, a);
2771                 action.end(t);
2772             }
2773             else if (getPreservesUnknownTags()) {
2774                 // unknown tag, only add if should preserve it.
2775                 addSpecialElement(t, a);
2776             }
2777         }
2778 
2779         /**
2780          * This is invoked after the stream has been parsed, but before
2781          * <code>flush</code>. <code>eol</code> will be one of \n, \r
2782          * or \r\n, which ever is encountered the most in parsing the
2783          * stream.
2784          *
2785          * @since 1.3
2786          */
2787         public void handleEndOfLineString(String eol) {
2788             if (emptyDocument && eol != null) {
2789                 putProperty(DefaultEditorKit.EndOfLineStringProperty,
2790                             eol);
2791             }
2792         }
2793 
2794         // ---- tag handling support ------------------------------
2795 
2796         /**
2797          * Registers a handler for the given tag.  By default
2798          * all of the well-known tags will have been registered.
2799          * This can be used to change the handling of a particular
2800          * tag or to add support for custom tags.
2801          *


3362                 }
3363             }
3364 
3365             void addParameter(AttributeSet a) {
3366                 String name = (String) a.getAttribute(HTML.Attribute.NAME);
3367                 String value = (String) a.getAttribute(HTML.Attribute.VALUE);
3368                 if ((name != null) && (value != null)) {
3369                     ElementSpec objSpec = parseBuffer.lastElement();
3370                     MutableAttributeSet objAttr = (MutableAttributeSet) objSpec.getAttributes();
3371                     objAttr.addAttribute(name, value);
3372                 }
3373             }
3374         }
3375 
3376         /**
3377          * Action to support forms by building all of the elements
3378          * used to represent form controls.  This will process
3379          * the &lt;INPUT&gt;, &lt;TEXTAREA&gt;, &lt;SELECT&gt;,
3380          * and &lt;OPTION&gt; tags.  The element created by
3381          * this action is expected to have the attribute
3382          * <code>StyleConstants.ModelAttribute</code> set to
3383          * the model that holds the state for the form control.
3384          * This enables multiple views, and allows document to
3385          * be iterated over picking up the data of the form.
3386          * The following are the model assignments for the
3387          * various type of form elements.
3388          * <table summary="model assignments for the various types of form elements">
3389          * <tr>
3390          *   <th>Element Type
3391          *   <th>Model Type
3392          * <tr>
3393          *   <td>input, type button
3394          *   <td>{@link DefaultButtonModel}
3395          * <tr>
3396          *   <td>input, type checkbox
3397          *   <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
3398          * <tr>
3399          *   <td>input, type image
3400          *   <td>{@link DefaultButtonModel}
3401          * <tr>
3402          *   <td>input, type password


3550                         if ( radioButtonGroupsMap == null ) { //fix for 4772743
3551                            radioButtonGroupsMap = new HashMap<String, ButtonGroup>();
3552                         }
3553                         ButtonGroup radioButtonGroup = radioButtonGroupsMap.get(name);
3554                         if (radioButtonGroup == null) {
3555                             radioButtonGroup = new ButtonGroup();
3556                             radioButtonGroupsMap.put(name,radioButtonGroup);
3557                         }
3558                         model.setGroup(radioButtonGroup);
3559                     }
3560                     boolean checked = (attr.getAttribute(HTML.Attribute.CHECKED) != null);
3561                     model.setSelected(checked);
3562                     attr.addAttribute(StyleConstants.ModelAttribute, model);
3563                 }
3564             }
3565 
3566             /**
3567              * If a &lt;SELECT&gt; tag is being processed, this
3568              * model will be a reference to the model being filled
3569              * with the &lt;OPTION&gt; elements (which produce
3570              * objects of type <code>Option</code>.
3571              */
3572             Object selectModel;
3573             int optionCount;
3574         }
3575 
3576 
3577         // --- utility methods used by the reader ------------------
3578 
3579         /**
3580          * Pushes the current character style on a stack in preparation
3581          * for forming a new nested character style.
3582          */
3583         protected void pushCharacterStyle() {
3584             charAttrStack.push(charAttr.copyAttributes());
3585         }
3586 
3587         /**
3588          * Pops a previously pushed character style off the stack
3589          * to return to a previous style.
3590          */


3809             int size = parseBuffer.size();
3810             if (endOfStream && (insertTag != null || insertAfterImplied) &&
3811                 size > 0) {
3812                 adjustEndSpecsForPartialInsert();
3813                 size = parseBuffer.size();
3814             }
3815             ElementSpec[] spec = new ElementSpec[size];
3816             parseBuffer.copyInto(spec);
3817 
3818             if (oldLength == 0 && (insertTag == null && !insertAfterImplied)) {
3819                 create(spec);
3820             } else {
3821                 insert(offset, spec);
3822             }
3823             parseBuffer.removeAllElements();
3824             offset += HTMLDocument.this.getLength() - oldLength;
3825             flushCount++;
3826         }
3827 
3828         /**
3829          * This will be invoked for the last flush, if <code>insertTag</code>
3830          * is non null.
3831          */
3832         private void adjustEndSpecsForPartialInsert() {
3833             int size = parseBuffer.size();
3834             if (insertTagDepthDelta < 0) {
3835                 // When inserting via an insertTag, the depths (of the tree
3836                 // being read in, and existing hierarchy) may not match up.
3837                 // This attemps to clean it up.
3838                 int removeCounter = insertTagDepthDelta;
3839                 while (removeCounter < 0 && size >= 0 &&
3840                         parseBuffer.elementAt(size - 1).
3841                        getType() == ElementSpec.EndTagType) {
3842                     parseBuffer.removeElementAt(--size);
3843                     removeCounter++;
3844                 }
3845             }
3846             if (flushCount == 0 && (!insertAfterImplied ||
3847                                     !wantsTrailingNewline)) {
3848                 // If this starts with content (or popDepth > 0 &&
3849                 // pushDepth > 0) and ends with EndTagTypes, make sure


3887                                    counter--) {
3888                     ElementSpec spec = parseBuffer.elementAt(counter);
3889                     if (spec.getType() == ElementSpec.ContentType) {
3890                         if (spec.getArray()[spec.getLength() - 1] != '\n') {
3891                             SimpleAttributeSet attrs =new SimpleAttributeSet();
3892 
3893                             attrs.addAttribute(StyleConstants.NameAttribute,
3894                                                HTML.Tag.CONTENT);
3895                             parseBuffer.insertElementAt(new ElementSpec(
3896                                     attrs,
3897                                     ElementSpec.ContentType, NEWLINE, 0, 1),
3898                                     counter + 1);
3899                         }
3900                         break;
3901                     }
3902                 }
3903             }
3904         }
3905 
3906         /**
3907          * Adds the CSS rules in <code>rules</code>.
3908          */
3909         void addCSSRules(String rules) {
3910             StyleSheet ss = getStyleSheet();
3911             ss.addRule(rules);
3912         }
3913 
3914         /**
3915          * Adds the CSS stylesheet at <code>href</code> to the known list
3916          * of stylesheets.
3917          */
3918         void linkCSSStyleSheet(String href) {
3919             URL url;
3920             try {
3921                 url = new URL(base, href);
3922             } catch (MalformedURLException mfe) {
3923                 try {
3924                     url = new URL(href);
3925                 } catch (MalformedURLException mfe2) {
3926                     url = null;
3927                 }
3928             }
3929             if (url != null) {
3930                 getStyleSheet().importStyleSheet(url);
3931             }
3932         }
3933 
3934         /**
3935          * Returns true if can insert starting at <code>t</code>. This
3936          * will return false if the insert tag is set, and hasn't been found
3937          * yet.
3938          */
3939         private boolean canInsertTag(HTML.Tag t, AttributeSet attr,
3940                                      boolean isBlockTag) {
3941             if (!foundInsertTag) {
3942                 boolean needPImplied = ((t == HTML.Tag.IMPLIED)
3943                                                           && (!inParagraph)
3944                                                           && (!inPre));
3945                 if (needPImplied && (nextTagAfterPImplied != null)) {
3946 
3947                     /*
3948                      * If insertTag == null then just proceed to
3949                      * foundInsertTag() call below and return true.
3950                      */
3951                     if (insertTag != null) {
3952                         boolean nextTagIsInsertTag =
3953                                 isInsertTag(nextTagAfterPImplied);
3954                         if ( (! nextTagIsInsertTag) || (! insertInsertTag) ) {
3955                             return false;


4053             insertTagDepthDelta = depthTo(Math.max(0, offset - 1)) -
4054                                   popDepth + pushDepth - inBlock;
4055             if (isBlockTag) {
4056                 // A start spec will be added (for this tag), so we account
4057                 // for it here.
4058                 insertTagDepthDelta++;
4059             }
4060             else {
4061                 // An implied paragraph close (end spec) is going to be added,
4062                 // so we account for it here.
4063                 insertTagDepthDelta--;
4064                 inParagraph = true;
4065                 lastWasNewline = false;
4066             }
4067         }
4068 
4069         /**
4070          * This is set to true when and end is invoked for {@literal <html>}.
4071          */
4072         private boolean receivedEndHTML;
4073         /** Number of times <code>flushBuffer</code> has been invoked. */
4074         private int flushCount;
4075         /** If true, behavior is similar to insertTag, but instead of
4076          * waiting for insertTag will wait for first Element without
4077          * an 'implied' attribute and begin inserting then. */
4078         private boolean insertAfterImplied;
4079         /** This is only used if insertAfterImplied is true. If false, only
4080          * inserting content, and there is a trailing newline it is removed. */
4081         private boolean wantsTrailingNewline;
4082         int threshold;
4083         int offset;
4084         boolean inParagraph = false;
4085         boolean impliedP = false;
4086         boolean inPre = false;
4087         boolean inTextArea = false;
4088         TextAreaDocument textAreaDocument = null;
4089         boolean inTitle = false;
4090         boolean lastWasNewline = true;
4091         boolean emptyAnchor;
4092         /** True if (!emptyDocument &amp;&amp; insertTag == null), this is used so
4093          * much it is cached. */




  24  */
  25 package javax.swing.text.html;
  26 
  27 import java.awt.font.TextAttribute;
  28 import java.util.*;
  29 import java.net.URL;
  30 import java.net.MalformedURLException;
  31 import java.io.*;
  32 import javax.swing.*;
  33 import javax.swing.event.*;
  34 import javax.swing.text.*;
  35 import javax.swing.undo.*;
  36 import sun.swing.SwingUtilities2;
  37 import static sun.swing.SwingUtilities2.IMPLIED_CR;
  38 
  39 /**
  40  * A document that models HTML.  The purpose of this model is to
  41  * support both browsing and editing.  As a result, the structure
  42  * described by an HTML document is not exactly replicated by default.
  43  * The element structure that is modeled by default, is built by the
  44  * class {@code HTMLDocument.HTMLReader}, which implements the
  45  * {@code HTMLEditorKit.ParserCallback} protocol that the parser
  46  * expects.  To change the structure one can subclass
  47  * {@code HTMLReader}, and reimplement the method {@link
  48  * #getReader(int)} to return the new reader implementation.  The
  49  * documentation for {@code HTMLReader} should be consulted for
  50  * the details of the default structure created.  The intent is that
  51  * the document be non-lossy (although reproducing the HTML format may
  52  * result in a different format).
  53  *
  54  * <p>The document models only HTML, and makes no attempt to store
  55  * view attributes in it.  The elements are identified by the
  56  * {@code StyleContext.NameAttribute} attribute, which should
  57  * always have a value of type {@code HTML.Tag} that identifies
  58  * the kind of element.  Some of the elements (such as comments) are
  59  * synthesized.  The {@code HTMLFactory} uses this attribute to
  60  * determine what kind of view to build.</p>
  61  *
  62  * <p>This document supports incremental loading.  The
  63  * {@code TokenThreshold} property controls how much of the parse
  64  * is buffered before trying to update the element structure of the
  65  * document.  This property is set by the {@code EditorKit} so
  66  * that subclasses can disable it.</p>
  67  *
  68  * <p>The {@code Base} property determines the URL against which
  69  * relative URLs are resolved.  By default, this will be the
  70  * {@code Document.StreamDescriptionProperty} if the value of the
  71  * property is a URL.  If a &lt;BASE&gt; tag is encountered, the base
  72  * will become the URL specified by that tag.  Because the base URL is
  73  * a property, it can of course be set directly.</p>
  74  *
  75  * <p>The default content storage mechanism for this document is a gap
  76  * buffer ({@code GapContent}).  Alternatives can be supplied by
  77  * using the constructor that takes a {@code Content}
  78  * implementation.</p>
  79  *
  80  * <h2>Modifying HTMLDocument</h2>
  81  *
  82  * <p>In addition to the methods provided by Document and
  83  * StyledDocument for mutating an HTMLDocument, HTMLDocument provides
  84  * a number of convenience methods.  The following methods can be used
  85  * to insert HTML content into an existing document.</p>
  86  *
  87  * <ul>
  88  *   <li>{@link #setInnerHTML(Element, String)}</li>
  89  *   <li>{@link #setOuterHTML(Element, String)}</li>
  90  *   <li>{@link #insertBeforeStart(Element, String)}</li>
  91  *   <li>{@link #insertAfterStart(Element, String)}</li>
  92  *   <li>{@link #insertBeforeEnd(Element, String)}</li>
  93  *   <li>{@link #insertAfterEnd(Element, String)}</li>
  94  * </ul>
  95  *
  96  * <p>The following examples illustrate using these methods.  Each
  97  * example assumes the HTML document is initialized in the following
  98  * way:</p>
  99  *
 100  * <pre>
 101  * JEditorPane p = new JEditorPane();
 102  * p.setContentType("text/html");
 103  * p.setText("..."); // Document text is provided below.
 104  * HTMLDocument d = (HTMLDocument) p.getDocument();
 105  * </pre>
 106  *
 107  * <p>With the following HTML content:</p>
 108  *
 109  * <pre>{@code
 110  * <html>
 111  *   <head>
 112  *     <title>An example HTMLDocument</title>
 113  *     <style type="text/css">
 114  *       div { background-color: silver; }
 115  *       ul { color: red; }
 116  *     </style>
 117  *   </head>
 118  *   <body>
 119  *     <div id="BOX">
 120  *       <p>Paragraph 1</p>
 121  *       <p>Paragraph 2</p>
 122  *     </div>
 123  *   </body>
 124  * </html>
 125  * }</pre>
 126  *
 127  * <p>All the methods for modifying an HTML document require an {@link
 128  * Element}.  Elements can be obtained from an HTML document by using
 129  * the method {@link #getElement(Element e, Object attribute, Object
 130  * value)}.  It returns the first descendant element that contains the
 131  * specified attribute with the given value, in depth-first order.
 132  * For example, {@code d.getElement(d.getDefaultRootElement(),
 133  * StyleConstants.NameAttribute, HTML.Tag.P)} returns the first
 134  * paragraph element.</p>
 135  *
 136  * <p>A convenient shortcut for locating elements is the method {@link
 137  * #getElement(String)}; returns an element whose {@code ID}
 138  * attribute matches the specified value.  For example,
 139  * {@code d.getElement("BOX")} returns the {@code DIV}
 140  * element.</p>
 141  *
 142  * <p>The {@link #getIterator(HTML.Tag t)} method can also be used for
 143  * finding all occurrences of the specified HTML tag in the
 144  * document.</p>
 145  *
 146  * <h3>Inserting elements</h3>
 147  *
 148  * <p>Elements can be inserted before or after the existing children
 149  * of any non-leaf element by using the methods
 150  * {@code insertAfterStart} and {@code insertBeforeEnd}.
 151  * For example, if {@code e} is the {@code DIV} element,
 152  * {@code d.insertAfterStart(e, "<ul><li>List Item</li></ul>")}
 153  * inserts the list before the first
 154  * paragraph, and 
 155  * {@code d.insertBeforeEnd(e, "<ul><li>List Item</li></ul>")}
 156  * inserts the list after the last
 157  * paragraph.  The {@code DIV} block becomes the parent of the
 158  * newly inserted elements.</p>
 159  *
 160  * <p>Sibling elements can be inserted before or after any element by
 161  * using the methods {@code insertBeforeStart} and
 162  * {@code insertAfterEnd}.  For example, if {@code e} is the
 163  * {@code DIV} element,
 164  * {@code d.insertBeforeStart(e, "<ul><li>List Item</li></ul>")}
 165  * inserts the list before the {@code DIV} element, and
 166  * {@code d.insertAfterEnd(e, "<ul><li>List Item</li></ul>")}
 167  * inserts the list after the {@code DIV} element.
 168  * The newly inserted elements
 169  * become siblings of the {@code DIV} element.</p>
 170  *
 171  * <h3>Replacing elements</h3>
 172  *
 173  * <p>Elements and all their descendants can be replaced by using the
 174  * methods {@code setInnerHTML} and {@code setOuterHTML}.
 175  * For example, if {@code e} is the {@code DIV} element,
 176  * {@code d.setInnerHTML(e, "<ul><li>List Item</li></ul>")}
 177  * replaces all children paragraphs with
 178  * the list, and
 179  * {@code d.setOuterHTML(e, "<ul><li>List Item</li></ul>")}
 180  * replaces the {@code DIV} element
 181  * itself.  In latter case the parent of the list is the
 182  * {@code BODY} element.
 183  *
 184  * <h3>Summary</h3>
 185  *
 186  * <p>The following table shows the example document and the results
 187  * of various methods described above.</p>
 188  *
 189  * <table border=1 cellspacing=0 summary="HTML Content of example above">
 190  *   <tr>
 191  *     <th>Example</th>
 192  *     <th>{@code insertAfterStart}</th>
 193  *     <th>{@code insertBeforeEnd}</th>
 194  *     <th>{@code insertBeforeStart}</th>
 195  *     <th>{@code insertAfterEnd}</th>
 196  *     <th>{@code setInnerHTML}</th>
 197  *     <th>{@code setOuterHTML}</th>
 198  *   </tr>
 199  *   <tr valign="top">
 200  *     <td style="white-space:nowrap">
 201  *       <div style="background-color: silver;">
 202  *         <p>Paragraph 1</p>
 203  *         <p>Paragraph 2</p>
 204  *       </div>
 205  *     </td>
 206  * <!--insertAfterStart-->
 207  *     <td style="white-space:nowrap">
 208  *       <div style="background-color: silver;">
 209  *         <ul style="color: red;">
 210  *           <li>List Item</li>
 211  *         </ul>
 212  *         <p>Paragraph 1</p>
 213  *         <p>Paragraph 2</p>
 214  *       </div>
 215  *     </td>
 216  * <!--insertBeforeEnd-->
 217  *     <td style="white-space:nowrap">


 249  *         <ul style="color: red;">
 250  *           <li>List Item</li>
 251  *         </ul>
 252  *       </div>
 253  *     </td>
 254  * <!--setOuterHTML-->
 255  *     <td style="white-space:nowrap">
 256  *       <ul style="color: red;">
 257  *         <li>List Item</li>
 258  *       </ul>
 259  *     </td>
 260  *   </tr>
 261  * </table>
 262  *
 263  * <p><strong>Warning:</strong> Serialized objects of this class will
 264  * not be compatible with future Swing releases. The current
 265  * serialization support is appropriate for short term storage or RMI
 266  * between applications running the same version of Swing.  As of 1.4,
 267  * support for long term storage of all JavaBeans&trade;
 268  * has been added to the
 269  * {@code java.beans} package.  Please see {@link
 270  * java.beans.XMLEncoder}.</p>
 271  *
 272  * @author  Timothy Prinzing
 273  * @author  Scott Violet
 274  * @author  Sunita Mani
 275  */
 276 @SuppressWarnings("serial") // Same-version serialization only
 277 public class HTMLDocument extends DefaultStyledDocument {
 278     /**
 279      * Constructs an HTML document using the default buffer size
 280      * and a default {@code StyleSheet}.  This is a convenience
 281      * method for the constructor
 282      * {@code HTMLDocument(Content, StyleSheet)}.
 283      */
 284     public HTMLDocument() {
 285         this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet());
 286     }
 287 
 288     /**
 289      * Constructs an HTML document with the default content
 290      * storage implementation and the specified style/attribute
 291      * storage mechanism.  This is a convenience method for the
 292      * constructor
 293      * {@code HTMLDocument(Content, StyleSheet)}.
 294      *
 295      * @param styles  the styles
 296      */
 297     public HTMLDocument(StyleSheet styles) {
 298         this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
 299     }
 300 
 301     /**
 302      * Constructs an HTML document with the given content
 303      * storage implementation and the given style/attribute
 304      * storage mechanism.
 305      *
 306      * @param c  the container for the content
 307      * @param styles the styles
 308      */
 309     public HTMLDocument(Content c, StyleSheet styles) {
 310         super(c, styles);
 311     }
 312 
 313     /**
 314      * Fetches the reader for the parser to use when loading the document
 315      * with HTML.  This is implemented to return an instance of
 316      * {@code HTMLDocument.HTMLReader}.
 317      * Subclasses can reimplement this
 318      * method to change how the document gets structured if desired.
 319      * (For example, to handle custom tags, or structurally represent character
 320      * style elements.)
 321      *
 322      * @param pos the starting position
 323      * @return the reader used by the parser to load the document
 324      */
 325     public HTMLEditorKit.ParserCallback getReader(int pos) {
 326         Object desc = getProperty(Document.StreamDescriptionProperty);
 327         if (desc instanceof URL) {
 328             setBase((URL)desc);
 329         }
 330         HTMLReader reader = new HTMLReader(pos);
 331         return reader;
 332     }
 333 
 334     /**
 335      * Returns the reader for the parser to use to load the document
 336      * with HTML.  This is implemented to return an instance of
 337      * {@code HTMLDocument.HTMLReader}.
 338      * Subclasses can reimplement this
 339      * method to change how the document gets structured if desired.
 340      * (For example, to handle custom tags, or structurally represent character
 341      * style elements.)
 342      * <p>This is a convenience method for
 343      * {@code getReader(int, int, int, HTML.Tag, TRUE)}.
 344      *
 345      * @param pos the starting position
 346      * @param popDepth   the number of {@code ElementSpec.EndTagTypes}
 347      *          to generate before inserting
 348      * @param pushDepth  the number of {@code ElementSpec.StartTagTypes}
 349      *          with a direction of {@code ElementSpec.JoinNextDirection}
 350      *          that should be generated before inserting,
 351      *          but after the end tags have been generated
 352      * @param insertTag  the first tag to start inserting into document
 353      * @return the reader used by the parser to load the document
 354      */
 355     public HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
 356                                                   int pushDepth,
 357                                                   HTML.Tag insertTag) {
 358         return getReader(pos, popDepth, pushDepth, insertTag, true);
 359     }
 360 
 361     /**
 362      * Fetches the reader for the parser to use to load the document
 363      * with HTML.  This is implemented to return an instance of
 364      * HTMLDocument.HTMLReader.  Subclasses can reimplement this
 365      * method to change how the document get structured if desired
 366      * (e.g. to handle custom tags, structurally represent character
 367      * style elements, etc.).
 368      *
 369      * @param popDepth   the number of {@code ElementSpec.EndTagTypes}
 370      *          to generate before inserting
 371      * @param pushDepth  the number of {@code ElementSpec.StartTagTypes}
 372      *          with a direction of {@code ElementSpec.JoinNextDirection}
 373      *          that should be generated before inserting,
 374      *          but after the end tags have been generated
 375      * @param insertTag  the first tag to start inserting into document
 376      * @param insertInsertTag  false if all the Elements after insertTag should
 377      *        be inserted; otherwise insertTag will be inserted
 378      * @return the reader used by the parser to load the document
 379      */
 380     HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
 381                                            int pushDepth,
 382                                            HTML.Tag insertTag,
 383                                            boolean insertInsertTag) {
 384         Object desc = getProperty(Document.StreamDescriptionProperty);
 385         if (desc instanceof URL) {
 386             setBase((URL)desc);
 387         }
 388         HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth,
 389                                            insertTag, insertInsertTag, false,
 390                                            true);
 391         return reader;
 392     }
 393 
 394     /**
 395      * Returns the location to resolve relative URLs against.  By
 396      * default this will be the document's URL if the document
 397      * was loaded from a URL.  If a base tag is found and
 398      * can be parsed, it will be used as the base location.
 399      *
 400      * @return the base location
 401      */
 402     public URL getBase() {
 403         return base;
 404     }
 405 
 406     /**
 407      * Sets the location to resolve relative URLs against.  By
 408      * default this will be the document's URL if the document
 409      * was loaded from a URL.  If a base tag is found and
 410      * can be parsed, it will be used as the base location.
 411      * <p>This also sets the base of the {@code StyleSheet}
 412      * to be {@code u} as well as the base of the document.
 413      *
 414      * @param u  the desired base URL
 415      */
 416     public void setBase(URL u) {
 417         base = u;
 418         getStyleSheet().setBase(u);
 419     }
 420 
 421     /**
 422      * Inserts new elements in bulk.  This is how elements get created
 423      * in the document.  The parsing determines what structure is needed
 424      * and creates the specification as a set of tokens that describe the
 425      * edit while leaving the document free of a write-lock.  This method
 426      * can then be called in bursts by the reader to acquire a write-lock
 427      * for a shorter duration (i.e. while the document is actually being
 428      * altered).
 429      *
 430      * @param offset the starting offset
 431      * @param data the element data
 432      * @exception BadLocationException  if the given position does not


 510                 else {
 511                     lastEnd = paragraph.getEndOffset();
 512                 }
 513                 MutableAttributeSet attr =
 514                     (MutableAttributeSet) paragraph.getAttributes();
 515                 changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
 516                 if (replace) {
 517                     attr.removeAttributes(attr);
 518                 }
 519                 attr.addAttributes(s);
 520             }
 521             changes.end();
 522             fireChangedUpdate(changes);
 523             fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
 524         } finally {
 525             writeUnlock();
 526         }
 527     }
 528 
 529     /**
 530      * Fetches the {@code StyleSheet} with the document-specific display
 531      * rules (CSS) that were specified in the HTML document itself.
 532      *
 533      * @return the {@code StyleSheet}
 534      */
 535     public StyleSheet getStyleSheet() {
 536         return (StyleSheet) getAttributeContext();
 537     }
 538 
 539     /**
 540      * Fetches an iterator for the specified HTML tag.
 541      * This can be used for things like iterating over the
 542      * set of anchors contained, or iterating over the input
 543      * elements.
 544      *
 545      * @param t the requested {@code HTML.Tag}
 546      * @return the {@code Iterator} for the given HTML tag
 547      * @see javax.swing.text.html.HTML.Tag
 548      */
 549     public Iterator getIterator(HTML.Tag t) {
 550         if (t.isBlock()) {
 551             // TBD
 552             return null;
 553         }
 554         return new LeafIterator(t, this);
 555     }
 556 
 557     /**
 558      * Creates a document leaf element that directly represents
 559      * text (doesn't have any children).  This is implemented
 560      * to return an element of type
 561      * {@code HTMLDocument.RunElement}.
 562      *
 563      * @param parent the parent element
 564      * @param a the attributes for the element
 565      * @param p0 the beginning of the range (must be at least 0)
 566      * @param p1 the end of the range (must be at least p0)
 567      * @return the new element
 568      */
 569     protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
 570         return new RunElement(parent, a, p0, p1);
 571     }
 572 
 573     /**
 574      * Creates a document branch element, that can contain other elements.
 575      * This is implemented to return an element of type
 576      * {@code HTMLDocument.BlockElement}.
 577      *
 578      * @param parent the parent element
 579      * @param a the attributes
 580      * @return the element
 581      */
 582     protected Element createBranchElement(Element parent, AttributeSet a) {
 583         return new BlockElement(parent, a);
 584     }
 585 
 586     /**
 587      * Creates the root element to be used to represent the
 588      * default document structure.
 589      *
 590      * @return the element base
 591      */
 592     protected AbstractElement createDefaultRoot() {
 593         // grabs a write-lock for this initialization and
 594         // abandon it during initialization so in normal
 595         // operation we can detect an illegitimate attempt
 596         // to mutate attributes.


 615         body.replace(0, 0, buff);
 616         buff[0] = body;
 617         html.replace(0, 0, buff);
 618         writeUnlock();
 619         return html;
 620     }
 621 
 622     /**
 623      * Sets the number of tokens to buffer before trying to update
 624      * the documents element structure.
 625      *
 626      * @param n  the number of tokens to buffer
 627      */
 628     public void setTokenThreshold(int n) {
 629         putProperty(TokenThreshold, n);
 630     }
 631 
 632     /**
 633      * Gets the number of tokens to buffer before trying to update
 634      * the documents element structure.  The default value is
 635      * {@code Integer.MAX_VALUE}.
 636      *
 637      * @return the number of tokens to buffer
 638      */
 639     public int getTokenThreshold() {
 640         Integer i = (Integer) getProperty(TokenThreshold);
 641         if (i != null) {
 642             return i.intValue();
 643         }
 644         return Integer.MAX_VALUE;
 645     }
 646 
 647     /**
 648      * Determines how unknown tags are handled by the parser.
 649      * If set to true, unknown
 650      * tags are put in the model, otherwise they are dropped.
 651      *
 652      * @param preservesTags  true if unknown tags should be
 653      *          saved in the model, otherwise tags are dropped
 654      * @see javax.swing.text.html.HTML.Tag
 655      */
 656     public void setPreservesUnknownTags(boolean preservesTags) {
 657         preservesUnknownTags = preservesTags;
 658     }
 659 
 660     /**
 661      * Returns the behavior the parser observes when encountering
 662      * unknown tags.
 663      *
 664      * @see javax.swing.text.html.HTML.Tag
 665      * @return true if unknown tags are to be preserved when parsing
 666      */
 667     public boolean getPreservesUnknownTags() {
 668         return preservesUnknownTags;
 669     }
 670 
 671     /**
 672      * Processes {@code HyperlinkEvents} that
 673      * are generated by documents in an HTML frame.
 674      * The {@code HyperlinkEvent} type, as the parameter suggests,
 675      * is {@code HTMLFrameHyperlinkEvent}.
 676      * In addition to the typical information contained in a
 677      * {@code HyperlinkEvent},
 678      * this event contains the element that corresponds to the frame in
 679      * which the click happened (the source element) and the
 680      * target name.  The target name has 4 possible values:
 681      * <ul>
 682      * <li>  _self
 683      * <li>  _parent
 684      * <li>  _top
 685      * <li>  a named frame
 686      * </ul>
 687      *
 688      * If target is _self, the action is to change the value of the
 689      * {@code HTML.Attribute.SRC} attribute and fires a
 690      * {@code ChangedUpdate} event.
 691      *<p>
 692      * If the target is _parent, then it deletes the parent element,
 693      * which is a &lt;FRAMESET&gt; element, and inserts a new &lt;FRAME&gt;
 694      * element, and sets its {@code HTML.Attribute.SRC} attribute
 695      * to have a value equal to the destination URL and fire a
 696      * {@code RemovedUpdate} and {@code InsertUpdate}.
 697      *<p>
 698      * If the target is _top, this method does nothing. In the implementation
 699      * of the view for a frame, namely the {@code FrameView},
 700      * the processing of _top is handled.  Given that _top implies
 701      * replacing the entire document, it made sense to handle this outside
 702      * of the document that it will replace.
 703      *<p>
 704      * If the target is a named frame, then the element hierarchy is searched
 705      * for an element with a name equal to the target, its
 706      * {@code HTML.Attribute.SRC} attribute is updated and a
 707      * {@code ChangedUpdate} event is fired.
 708      *
 709      * @param e the event
 710      */
 711     public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent e) {
 712         String frameName = e.getTarget();
 713         Element element = e.getSourceElement();
 714         String urlStr = e.getURL().toString();
 715 
 716         if (frameName.equals("_self")) {
 717             /*
 718               The source and destination elements
 719               are the same.
 720             */
 721             updateFrame(element, urlStr);
 722         } else if (frameName.equals("_parent")) {
 723             /*
 724               The destination is the parent of the frame.
 725             */
 726             updateFrameSet(element.getParentElement(), urlStr);
 727         } else {
 728             /*
 729               locate a named frame
 730             */
 731             Element targetElement = findFrame(frameName);
 732             if (targetElement != null) {
 733                 updateFrame(targetElement, urlStr);
 734             }
 735         }
 736     }
 737 
 738 
 739     /**
 740      * Searches the element hierarchy for an FRAME element
 741      * that has its name attribute equal to the {@code frameName}.
 742      *
 743      * @param frameName
 744      * @return the element whose NAME attribute has a value of
 745      *          {@code frameName}; returns {@code null}
 746      *          if not found
 747      */
 748     private Element findFrame(String frameName) {
 749         ElementIterator it = new ElementIterator(this);
 750         Element next;
 751 
 752         while ((next = it.next()) != null) {
 753             AttributeSet attr = next.getAttributes();
 754             if (matchNameAttribute(attr, HTML.Tag.FRAME)) {
 755                 String frameTarget = (String)attr.getAttribute(HTML.Attribute.NAME);
 756                 if (frameTarget != null && frameTarget.equals(frameName)) {
 757                     break;
 758                 }
 759             }
 760         }
 761         return next;
 762     }
 763 
 764     /**
 765      * Returns true if {@code StyleConstants.NameAttribute} is
 766      * equal to the tag that is passed in as a parameter.
 767      *
 768      * @param attr the attributes to be matched
 769      * @param tag the value to be matched
 770      * @return true if there is a match, false otherwise
 771      * @see javax.swing.text.html.HTML.Attribute
 772      */
 773     static boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
 774         Object o = attr.getAttribute(StyleConstants.NameAttribute);
 775         if (o instanceof HTML.Tag) {
 776             HTML.Tag name = (HTML.Tag) o;
 777             if (name == tag) {
 778                 return true;
 779             }
 780         }
 781         return false;
 782     }
 783 
 784     /**
 785      * Replaces a frameset branch Element with a frame leaf element.


 791     private void updateFrameSet(Element element, String url) {
 792         try {
 793             int startOffset = element.getStartOffset();
 794             int endOffset = Math.min(getLength(), element.getEndOffset());
 795             String html = "<frame";
 796             if (url != null) {
 797                 html += " src=\"" + url + "\"";
 798             }
 799             html += ">";
 800             installParserIfNecessary();
 801             setOuterHTML(element, html);
 802         } catch (BadLocationException e1) {
 803             // Should handle this better
 804         } catch (IOException ioe) {
 805             // Should handle this better
 806         }
 807     }
 808 
 809 
 810     /**
 811      * Updates the Frame elements {@code HTML.Attribute.SRC attribute}
 812      * and fires a {@code ChangedUpdate} event.
 813      *
 814      * @param element a FRAME element whose SRC attribute will be updated
 815      * @param url     a string specifying the new value for the SRC attribute
 816      */
 817     private void updateFrame(Element element, String url) {
 818 
 819         try {
 820             writeLock();
 821             DefaultDocumentEvent changes = new DefaultDocumentEvent(element.getStartOffset(),
 822                                                                     1,
 823                                                                     DocumentEvent.EventType.CHANGE);
 824             AttributeSet sCopy = element.getAttributes().copyAttributes();
 825             MutableAttributeSet attr = (MutableAttributeSet) element.getAttributes();
 826             changes.addEdit(new AttributeUndoableEdit(element, sCopy, false));
 827             attr.removeAttribute(HTML.Attribute.SRC);
 828             attr.addAttribute(HTML.Attribute.SRC, url);
 829             changes.end();
 830             fireChangedUpdate(changes);
 831             fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
 832         } finally {


 840      * @return true if document will be viewed in a frame, otherwise false
 841      */
 842     boolean isFrameDocument() {
 843         return frameDocument;
 844     }
 845 
 846     /**
 847      * Sets a boolean state about whether the document will be
 848      * viewed in a frame.
 849      * @param frameDoc  true if the document will be viewed in a frame,
 850      *          otherwise false
 851      */
 852     void setFrameDocumentState(boolean frameDoc) {
 853         this.frameDocument = frameDoc;
 854     }
 855 
 856     /**
 857      * Adds the specified map, this will remove a Map that has been
 858      * previously registered with the same name.
 859      *
 860      * @param map  the {@code Map} to be registered
 861      */
 862     void addMap(Map map) {
 863         String     name = map.getName();
 864 
 865         if (name != null) {
 866             Object     maps = getProperty(MAP_PROPERTY);
 867 
 868             if (maps == null) {
 869                 maps = new Hashtable<>(11);
 870                 putProperty(MAP_PROPERTY, maps);
 871             }
 872             if (maps instanceof Hashtable) {
 873                 @SuppressWarnings("unchecked")
 874                 Hashtable<Object, Object> tmp = (Hashtable)maps;
 875                 tmp.put("#" + name, map);
 876             }
 877         }
 878     }
 879 
 880     /**
 881      * Removes a previously registered map.
 882      * @param map the {@code Map} to be removed
 883      */
 884     void removeMap(Map map) {
 885         String     name = map.getName();
 886 
 887         if (name != null) {
 888             Object     maps = getProperty(MAP_PROPERTY);
 889 
 890             if (maps instanceof Hashtable) {
 891                 ((Hashtable)maps).remove("#" + name);
 892             }
 893         }
 894     }
 895 
 896     /**
 897      * Returns the Map associated with the given name.
 898      * @param name the name of the desired {@code Map}
 899      * @return the {@code Map} or {@code null} if it can't
 900      *          be found, or if {@code name} is {@code null}
 901      */
 902     Map getMap(String name) {
 903         if (name != null) {
 904             Object     maps = getProperty(MAP_PROPERTY);
 905 
 906             if (maps != null && (maps instanceof Hashtable)) {
 907                 return (Map)((Hashtable)maps).get(name);
 908             }
 909         }
 910         return null;
 911     }
 912 
 913     /**
 914      * Returns an {@code Enumeration} of the possible Maps.
 915      * @return the enumerated list of maps, or {@code null}
 916      *          if the maps are not an instance of {@code Hashtable}
 917      */
 918     Enumeration<Object> getMaps() {
 919         Object     maps = getProperty(MAP_PROPERTY);
 920 
 921         if (maps instanceof Hashtable) {
 922             @SuppressWarnings("unchecked")
 923             Hashtable<Object, Object> tmp = (Hashtable) maps;
 924             return tmp.elements();
 925         }
 926         return null;
 927     }
 928 
 929     /**
 930      * Sets the content type language used for style sheets that do not
 931      * explicitly specify the type. The default is text/css.
 932      * @param contentType  the content type language for the style sheets
 933      */
 934     /* public */
 935     void setDefaultStyleSheetType(String contentType) {
 936         putProperty(StyleType, contentType);
 937     }
 938 
 939     /**
 940      * Returns the content type language used for style sheets. The default
 941      * is text/css.
 942      * @return the content type language used for the style sheets
 943      */
 944     /* public */
 945     String getDefaultStyleSheetType() {
 946         String retValue = (String)getProperty(StyleType);
 947         if (retValue == null) {
 948             return "text/css";
 949         }
 950         return retValue;
 951     }
 952 
 953     /**
 954      * Sets the parser that is used by the methods that insert html
 955      * into the existing document, such as {@code setInnerHTML},
 956      * and {@code setOuterHTML}.
 957      * <p>
 958      * {@code HTMLEditorKit.createDefaultDocument} will set the parser
 959      * for you. If you create an {@code HTMLDocument} by hand,
 960      * be sure and set the parser accordingly.
 961      * @param parser the parser to be used for text insertion
 962      *
 963      * @since 1.3
 964      */
 965     public void setParser(HTMLEditorKit.Parser parser) {
 966         this.parser = parser;
 967         putProperty("__PARSER__", null);
 968     }
 969 
 970     /**
 971      * Returns the parser that is used when inserting HTML into the existing
 972      * document.
 973      * @return the parser used for text insertion
 974      *
 975      * @since 1.3
 976      */
 977     public HTMLEditorKit.Parser getParser() {
 978         Object p = getProperty("__PARSER__");
 979 
 980         if (p instanceof HTMLEditorKit.Parser) {
 981             return (HTMLEditorKit.Parser)p;
 982         }
 983         return parser;
 984     }
 985 
 986     /**
 987      * Replaces the children of the given element with the contents
 988      * specified as an HTML string.
 989      *
 990      * <p>This will be seen as at least two events, n inserts followed by
 991      * a remove.</p>
 992      *
 993      * <p>Consider the following structure (the {@code elem}
 994      * parameter is <b>in bold</b>).</p>
 995      *
 996      * <pre>
 997      *     &lt;body&gt;
 998      *       |
 999      *     <b>&lt;div&gt;</b>
1000      *      /  \
1001      *    &lt;p&gt;   &lt;p&gt;
1002      * </pre>
1003      *
1004      * <p>Invoking {@code setInnerHTML(elem, "<ul><li>")}
1005      * results in the following structure (new elements are <font
1006      * style="color: red;">in red</font>).</p>
1007      *
1008      * <pre>
1009      *     &lt;body&gt;
1010      *       |
1011      *     <b>&lt;div&gt;</b>
1012      *         \
1013      *         <font style="color: red;">&lt;ul&gt;</font>
1014      *           \
1015      *           <font style="color: red;">&lt;li&gt;</font>
1016      * </pre>
1017      *
1018      * <p>Parameter {@code elem} must not be a leaf element,
1019      * otherwise an {@code IllegalArgumentException} is thrown.
1020      * If either {@code elem} or {@code htmlText} parameter
1021      * is {@code null}, no changes are made to the document.</p>
1022      *
1023      * <p>For this to work correctly, the document must have an
1024      * {@code HTMLEditorKit.Parser} set. This will be the case
1025      * if the document was created from an HTMLEditorKit via the
1026      * {@code createDefaultDocument} method.</p>
1027      *
1028      * @param elem the branch element whose children will be replaced
1029      * @param htmlText the string to be parsed and assigned to {@code elem}
1030      * @throws IllegalArgumentException if {@code elem} is a leaf
1031      * @throws IllegalStateException if an {@code HTMLEditorKit.Parser}
1032      *         has not been defined
1033      * @throws BadLocationException if replacement is impossible because of
1034      *         a structural issue
1035      * @throws IOException if an I/O exception occurs
1036      * @since 1.3
1037      */
1038     public void setInnerHTML(Element elem, String htmlText) throws
1039                              BadLocationException, IOException {
1040         verifyParser();
1041         if (elem != null && elem.isLeaf()) {
1042             throw new IllegalArgumentException
1043                 ("Can not set inner HTML of a leaf");
1044         }
1045         if (elem != null && htmlText != null) {
1046             int oldCount = elem.getElementCount();
1047             int insertPosition = elem.getStartOffset();
1048             insertHTML(elem, elem.getStartOffset(), htmlText, true);
1049             if (elem.getElementCount() > oldCount) {
1050                 // Elements were inserted, do the cleanup.
1051                 removeElements(elem, elem.getElementCount() - oldCount,
1052                                oldCount);
1053             }
1054         }
1055     }
1056 
1057     /**
1058      * Replaces the given element in the parent with the contents
1059      * specified as an HTML string.
1060      *
1061      * <p>This will be seen as at least two events, n inserts followed by
1062      * a remove.</p>
1063      *
1064      * <p>When replacing a leaf this will attempt to make sure there is
1065      * a newline present if one is needed. This may result in an additional
1066      * element being inserted. Consider, if you were to replace a character
1067      * element that contained a newline with &lt;img&gt; this would create
1068      * two elements, one for the image, and one for the newline.</p>
1069      *
1070      * <p>If you try to replace the element at length you will most
1071      * likely end up with two elements, eg
1072      * {@code setOuterHTML(getCharacterElement (getLength()), "blah")}
1073      * will result in two leaf elements at the end, one
1074      * representing 'blah', and the other representing the end
1075      * element.</p>
1076      *
1077      * <p>Consider the following structure (the {@code elem}
1078      * parameter is <b>in bold</b>).</p>
1079      *
1080      * <pre>
1081      *     &lt;body&gt;
1082      *       |
1083      *     <b>&lt;div&gt;</b>
1084      *      /  \
1085      *    &lt;p&gt;   &lt;p&gt;
1086      * </pre>
1087      *
1088      * <p>Invoking {@code setOuterHTML(elem, "<ul><li>")}
1089      * results in the following structure (new elements are <font
1090      * style="color: red;">in red</font>).</p>
1091      *
1092      * <pre>
1093      *    &lt;body&gt;
1094      *      |
1095      *     <font style="color: red;">&lt;ul&gt;</font>
1096      *       \
1097      *       <font style="color: red;">&lt;li&gt;</font>
1098      * </pre>
1099      *
1100      * <p>If either {@code elem} or {@code htmlText}
1101      * parameter is {@code null}, no changes are made to the
1102      * document.</p>
1103      *
1104      * <p>For this to work correctly, the document must have an
1105      * HTMLEditorKit.Parser set. This will be the case if the document
1106      * was created from an HTMLEditorKit via the
1107      * {@code createDefaultDocument} method.</p>
1108      *
1109      * @param elem the element to replace
1110      * @param htmlText the string to be parsed and inserted in place of {@code elem}
1111      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1112      *         been set
1113      * @throws BadLocationException if replacement is impossible because of
1114      *         a structural issue
1115      * @throws IOException if an I/O exception occurs
1116      * @since 1.3
1117      */
1118     public void setOuterHTML(Element elem, String htmlText) throws
1119                             BadLocationException, IOException {
1120         verifyParser();
1121         if (elem != null && elem.getParentElement() != null &&
1122             htmlText != null) {
1123             int start = elem.getStartOffset();
1124             int end = elem.getEndOffset();
1125             int startLength = getLength();
1126             // We don't want a newline if elem is a leaf, and doesn't contain
1127             // a newline.
1128             boolean wantsNewline = !elem.isLeaf();
1129             if (!wantsNewline && (end > startLength ||
1130                                  getText(end - 1, 1).charAt(0) == NEWLINE[0])){
1131                 wantsNewline = true;
1132             }
1133             Element parent = elem.getParentElement();
1134             int oldCount = parent.getElementCount();
1135             insertHTML(parent, start, htmlText, wantsNewline);
1136             // Remove old.
1137             int newLength = getLength();
1138             if (oldCount != parent.getElementCount()) {
1139                 int removeIndex = parent.getElementIndex(start + newLength -
1140                                                          startLength);
1141                 removeElements(parent, removeIndex, 1);
1142             }
1143         }
1144     }
1145 
1146     /**
1147      * Inserts the HTML specified as a string at the start
1148      * of the element.
1149      *
1150      * <p>Consider the following structure (the {@code elem}
1151      * parameter is <b>in bold</b>).</p>
1152      *
1153      * <pre>
1154      *     &lt;body&gt;
1155      *       |
1156      *     <b>&lt;div&gt;</b>
1157      *      /  \
1158      *    &lt;p&gt;   &lt;p&gt;
1159      * </pre>
1160      *
1161      * <p>Invoking
1162      * {@code insertAfterStart(elem, "<ul><li>")}
1163      * results in the following structure
1164      * (new elements are <font style="color: red;">in red</font>).</p>
1165      *
1166      * <pre>
1167      *        &lt;body&gt;
1168      *          |
1169      *        <b>&lt;div&gt;</b>
1170      *       /  |  \
1171      *    <font style="color: red;">&lt;ul&gt;</font> &lt;p&gt; &lt;p&gt;
1172      *     /
1173      *  <font style="color: red;">&lt;li&gt;</font>
1174      * </pre>
1175      *
1176      * <p>Unlike the {@code insertBeforeStart} method, new
1177      *  elements become <em>children</em> of the specified element,
1178      *  not siblings.</p>
1179      *
1180      * <p>Parameter {@code elem} must not be a leaf element,
1181      * otherwise an {@code IllegalArgumentException} is thrown.
1182      * If either {@code elem} or {@code htmlText} parameter
1183      * is {@code null}, no changes are made to the document.</p>
1184      *
1185      * <p>For this to work correctly, the document must have an
1186      * {@code HTMLEditorKit.Parser} set. This will be the case
1187      * if the document was created from an HTMLEditorKit via the
1188      * {@code createDefaultDocument} method.</p>
1189      *
1190      * @param elem the branch element to be the root for the new text
1191      * @param htmlText the string to be parsed and assigned to {@code elem}
1192      * @throws IllegalArgumentException if {@code elem} is a leaf
1193      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1194      *         been set on the document
1195      * @throws BadLocationException if insertion is impossible because of
1196      *         a structural issue
1197      * @throws IOException if an I/O exception occurs
1198      * @since 1.3
1199      */
1200     public void insertAfterStart(Element elem, String htmlText) throws
1201                                  BadLocationException, IOException {
1202         verifyParser();
1203 
1204         if (elem == null || htmlText == null) {
1205             return;
1206         }
1207 
1208         if (elem.isLeaf()) {
1209             throw new IllegalArgumentException
1210                 ("Can not insert HTML after start of a leaf");
1211         }
1212         insertHTML(elem, elem.getStartOffset(), htmlText, false);
1213     }
1214 
1215     /**
1216      * Inserts the HTML specified as a string at the end of
1217      * the element.
1218      *
1219      * <p> If {@code elem}'s children are leaves, and the
1220      * character at a {@code elem.getEndOffset() - 1} is a newline,
1221      * this will insert before the newline so that there isn't text after
1222      * the newline.</p>
1223      *
1224      * <p>Consider the following structure (the {@code elem}
1225      * parameter is <b>in bold</b>).</p>
1226      *
1227      * <pre>
1228      *     &lt;body&gt;
1229      *       |
1230      *     <b>&lt;div&gt;</b>
1231      *      /  \
1232      *    &lt;p&gt;   &lt;p&gt;
1233      * </pre>
1234      *
1235      * <p>Invoking {@code insertBeforeEnd(elem, "<ul><li>")}
1236      * results in the following structure (new elements are <font
1237      * style="color: red;">in red</font>).</p>
1238      *
1239      * <pre>
1240      *        &lt;body&gt;
1241      *          |
1242      *        <b>&lt;div&gt;</b>
1243      *       /  |  \
1244      *     &lt;p&gt; &lt;p&gt; <font style="color: red;">&lt;ul&gt;</font>
1245      *               \
1246      *               <font style="color: red;">&lt;li&gt;</font>
1247      * </pre>
1248      *
1249      * <p>Unlike the {@code insertAfterEnd} method, new elements
1250      * become <em>children</em> of the specified element, not
1251      * siblings.</p>
1252      *
1253      * <p>Parameter {@code elem} must not be a leaf element,
1254      * otherwise an {@code IllegalArgumentException} is thrown.
1255      * If either {@code elem} or {@code htmlText} parameter
1256      * is {@code null}, no changes are made to the document.</p>
1257      *
1258      * <p>For this to work correctly, the document must have an
1259      * {@code HTMLEditorKit.Parser} set. This will be the case
1260      * if the document was created from an HTMLEditorKit via the
1261      * {@code createDefaultDocument} method.</p>
1262      *
1263      * @param elem the element to be the root for the new text
1264      * @param htmlText the string to be parsed and assigned to {@code elem}
1265      * @throws IllegalArgumentException if {@code elem} is a leaf
1266      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1267      *         been set on the document
1268      * @throws BadLocationException if insertion is impossible because of
1269      *         a structural issue
1270      * @throws IOException if an I/O exception occurs
1271      * @since 1.3
1272      */
1273     public void insertBeforeEnd(Element elem, String htmlText) throws
1274                                 BadLocationException, IOException {
1275         verifyParser();
1276         if (elem != null && elem.isLeaf()) {
1277             throw new IllegalArgumentException
1278                 ("Can not set inner HTML before end of leaf");
1279         }
1280         if (elem != null) {
1281             int offset = elem.getEndOffset();
1282             if (elem.getElement(elem.getElementIndex(offset - 1)).isLeaf() &&
1283                 getText(offset - 1, 1).charAt(0) == NEWLINE[0]) {
1284                 offset--;
1285             }
1286             insertHTML(elem, offset, htmlText, false);
1287         }
1288     }
1289 
1290     /**
1291      * Inserts the HTML specified as a string before the start of
1292      * the given element.
1293      *
1294      * <p>Consider the following structure (the {@code elem}
1295      * parameter is <b>in bold</b>).</p>
1296      *
1297      * <pre>
1298      *     &lt;body&gt;
1299      *       |
1300      *     <b>&lt;div&gt;</b>
1301      *      /  \
1302      *    &lt;p&gt;   &lt;p&gt;
1303      * </pre>
1304      *
1305      * <p>Invoking
1306      * {@code insertBeforeStart(elem, "<ul><li>")}
1307      * results in the following structure
1308      * (new elements are <font style="color: red;">in red</font>).</p>
1309      *
1310      * <pre>
1311      *        &lt;body&gt;
1312      *         /  \
1313      *      <font style="color: red;">&lt;ul&gt;</font> <b>&lt;div&gt;</b>
1314      *       /    /  \
1315      *     <font style="color: red;">&lt;li&gt;</font> &lt;p&gt;  &lt;p&gt;
1316      * </pre>
1317      *
1318      * <p>Unlike the {@code insertAfterStart} method, new
1319      * elements become <em>siblings</em> of the specified element, not
1320      * children.</p>
1321      *
1322      * <p>If either {@code elem} or {@code htmlText}
1323      * parameter is {@code null}, no changes are made to the
1324      * document.</p>
1325      *
1326      * <p>For this to work correctly, the document must have an
1327      * {@code HTMLEditorKit.Parser} set. This will be the case
1328      * if the document was created from an HTMLEditorKit via the
1329      * {@code createDefaultDocument} method.</p>
1330      *
1331      * @param elem the element the content is inserted before
1332      * @param htmlText the string to be parsed and inserted before {@code elem}
1333      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1334      *         been set on the document
1335      * @throws BadLocationException if insertion is impossible because of
1336      *         a structural issue
1337      * @throws IOException if an I/O exception occurs
1338      * @since 1.3
1339      */
1340     public void insertBeforeStart(Element elem, String htmlText) throws
1341                                   BadLocationException, IOException {
1342         verifyParser();
1343         if (elem != null) {
1344             Element parent = elem.getParentElement();
1345 
1346             if (parent != null) {
1347                 insertHTML(parent, elem.getStartOffset(), htmlText, false);
1348             }
1349         }
1350     }
1351 
1352     /**
1353      * Inserts the HTML specified as a string after the end of the
1354      * given element.
1355      *
1356      * <p>Consider the following structure (the {@code elem}
1357      * parameter is <b>in bold</b>).</p>
1358      *
1359      * <pre>
1360      *     &lt;body&gt;
1361      *       |
1362      *     <b>&lt;div&gt;</b>
1363      *      /  \
1364      *    &lt;p&gt;   &lt;p&gt;
1365      * </pre>
1366      *
1367      * <p>Invoking {@code insertAfterEnd(elem, "<ul><li>")}
1368      * results in the following structure (new elements are <font
1369      * style="color: red;">in red</font>).</p>
1370      *
1371      * <pre>
1372      *        &lt;body&gt;
1373      *         /  \
1374      *      <b>&lt;div&gt;</b> <font style="color: red;">&lt;ul&gt;</font>
1375      *       / \    \
1376      *     &lt;p&gt; &lt;p&gt;  <font style="color: red;">&lt;li&gt;</font>
1377      * </pre>
1378      *
1379      * <p>Unlike the {@code insertBeforeEnd} method, new elements
1380      * become <em>siblings</em> of the specified element, not
1381      * children.</p>
1382      *
1383      * <p>If either {@code elem} or {@code htmlText}
1384      * parameter is {@code null}, no changes are made to the
1385      * document.</p>
1386      *
1387      * <p>For this to work correctly, the document must have an
1388      * {@code HTMLEditorKit.Parser} set. This will be the case
1389      * if the document was created from an HTMLEditorKit via the
1390      * {@code createDefaultDocument} method.</p>
1391      *
1392      * @param elem the element the content is inserted after
1393      * @param htmlText the string to be parsed and inserted after {@code elem}
1394      * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1395      *         been set on the document
1396      * @throws BadLocationException if insertion is impossible because of
1397      *         a structural issue
1398      * @throws IOException if an I/O exception occurs
1399      * @since 1.3
1400      */
1401     public void insertAfterEnd(Element elem, String htmlText) throws
1402                                BadLocationException, IOException {
1403         verifyParser();
1404         if (elem != null) {
1405             Element parent = elem.getParentElement();
1406 
1407             if (parent != null) {
1408                 // If we are going to insert the string into the body
1409                 // section, it is necessary to set the corrsponding flag.
1410                 if (HTML.Tag.BODY.name.equals(parent.getName())) {
1411                     insertInBody = true;
1412                 }
1413                 int offset = elem.getEndOffset();
1414                 if (offset > (getLength() + 1)) {
1415                     offset--;
1416                 }
1417                 else if (elem.isLeaf() && getText(offset - 1, 1).
1418                     charAt(0) == NEWLINE[0]) {
1419                     offset--;
1420                 }
1421                 insertHTML(parent, offset, htmlText, false);
1422                 // Cleanup the flag, if any.
1423                 if (insertInBody) {
1424                     insertInBody = false;
1425                 }
1426             }
1427         }
1428     }
1429 
1430     /**
1431      * Returns the element that has the given id {@code Attribute}.
1432      * If the element can't be found, {@code null} is returned.
1433      * Note that this method works on an {@code Attribute},
1434      * <i>not</i> a character tag.  In the following HTML snippet:
1435      * {@code <a id="HelloThere">} the attribute is
1436      * 'id' and the character tag is 'a'.
1437      * This is a convenience method for
1438      * {@code getElement(RootElement, HTML.Attribute.id, id)}.
1439      * This is not thread-safe.
1440      *
1441      * @param id  the string representing the desired {@code Attribute}
1442      * @return the element with the specified {@code Attribute}
1443      *          or {@code null} if it can't be found,
1444      *          or {@code null} if {@code id} is {@code null}
1445      * @see javax.swing.text.html.HTML.Attribute
1446      * @since 1.3
1447      */
1448     public Element getElement(String id) {
1449         if (id == null) {
1450             return null;
1451         }
1452         return getElement(getDefaultRootElement(), HTML.Attribute.ID, id,
1453                           true);
1454     }
1455 
1456     /**
1457      * Returns the child element of {@code e} that contains the
1458      * attribute, {@code attribute} with value {@code value}, or
1459      * {@code null} if one isn't found. This is not thread-safe.
1460      *
1461      * @param e the root element where the search begins
1462      * @param attribute the desired {@code Attribute}
1463      * @param value the values for the specified {@code Attribute}
1464      * @return the element with the specified {@code Attribute}
1465      *          and the specified {@code value}, or {@code null}
1466      *          if it can't be found
1467      * @see javax.swing.text.html.HTML.Attribute
1468      * @since 1.3
1469      */
1470     public Element getElement(Element e, Object attribute, Object value) {
1471         return getElement(e, attribute, value, true);
1472     }
1473 
1474     /**
1475      * Returns the child element of {@code e} that contains the
1476      * attribute, {@code attribute} with value {@code value}, or
1477      * {@code null} if one isn't found. This is not thread-safe.
1478      * <p>
1479      * If {@code searchLeafAttributes} is true, and {@code e} is
1480      * a leaf, any attributes that are instances of {@code HTML.Tag}
1481      * with a value that is an {@code AttributeSet} will also be checked.
1482      *
1483      * @param e the root element where the search begins
1484      * @param attribute the desired {@code Attribute}
1485      * @param value the values for the specified {@code Attribute}
1486      * @return the element with the specified {@code Attribute}
1487      *          and the specified {@code value}, or {@code null}
1488      *          if it can't be found
1489      * @see javax.swing.text.html.HTML.Attribute
1490      */
1491     private Element getElement(Element e, Object attribute, Object value,
1492                                boolean searchLeafAttributes) {
1493         AttributeSet attr = e.getAttributes();
1494 
1495         if (attr != null && attr.isDefined(attribute)) {
1496             if (value.equals(attr.getAttribute(attribute))) {
1497                 return e;
1498             }
1499         }
1500         if (!e.isLeaf()) {
1501             for (int counter = 0, maxCounter = e.getElementCount();
1502                  counter < maxCounter; counter++) {
1503                 Element retValue = getElement(e.getElement(counter), attribute,
1504                                               value, searchLeafAttributes);
1505 
1506                 if (retValue != null) {
1507                     return retValue;


1515             if (names != null) {
1516                 while (names.hasMoreElements()) {
1517                     Object name = names.nextElement();
1518                     if ((name instanceof HTML.Tag) &&
1519                         (attr.getAttribute(name) instanceof AttributeSet)) {
1520 
1521                         AttributeSet check = (AttributeSet)attr.
1522                                              getAttribute(name);
1523                         if (check.isDefined(attribute) &&
1524                             value.equals(check.getAttribute(attribute))) {
1525                             return e;
1526                         }
1527                     }
1528                 }
1529             }
1530         }
1531         return null;
1532     }
1533 
1534     /**
1535      * Verifies the document has an {@code HTMLEditorKit.Parser} set.
1536      * If {@code getParser} returns {@code null}, this will throw an
1537      * IllegalStateException.
1538      *
1539      * @throws IllegalStateException if the document does not have a Parser
1540      */
1541     private void verifyParser() {
1542         if (getParser() == null) {
1543             throw new IllegalStateException("No HTMLEditorKit.Parser");
1544         }
1545     }
1546 
1547     /**
1548      * Installs a default Parser if one has not been installed yet.
1549      */
1550     private void installParserIfNecessary() {
1551         if (getParser() == null) {
1552             setParser(new HTMLEditorKit().getParser());
1553         }
1554     }
1555 
1556     /**
1557      * Inserts a string of HTML into the document at the given position.
1558      * {@code parent} is used to identify the location to insert the
1559      * {@code html}. If {@code parent} is a leaf this can have
1560      * unexpected results.
1561      */
1562     private void insertHTML(Element parent, int offset, String html,
1563                             boolean wantsTrailingNewline)
1564                  throws BadLocationException, IOException {
1565         if (parent != null && html != null) {
1566             HTMLEditorKit.Parser parser = getParser();
1567             if (parser != null) {
1568                 int lastOffset = Math.max(0, offset - 1);
1569                 Element charElement = getCharacterElement(lastOffset);
1570                 Element commonParent = parent;
1571                 int pop = 0;
1572                 int push = 0;
1573 
1574                 if (parent.getStartOffset() > lastOffset) {
1575                     while (commonParent != null &&
1576                            commonParent.getStartOffset() > lastOffset) {
1577                         commonParent = commonParent.getParentElement();
1578                         push++;
1579                     }


1583                     }
1584                 }
1585                 while (charElement != null && charElement != commonParent) {
1586                     pop++;
1587                     charElement = charElement.getParentElement();
1588                 }
1589                 if (charElement != null) {
1590                     // Found it, do the insert.
1591                     HTMLReader reader = new HTMLReader(offset, pop - 1, push,
1592                                                        null, false, true,
1593                                                        wantsTrailingNewline);
1594 
1595                     parser.parse(new StringReader(html), reader, true);
1596                     reader.flush();
1597                 }
1598             }
1599         }
1600     }
1601 
1602     /**
1603      * Removes child Elements of the passed in Element {@code e}. This
1604      * will do the necessary cleanup to ensure the element representing the
1605      * end character is correctly created.
1606      * <p>This is not a general purpose method, it assumes that {@code e}
1607      * will still have at least one child after the remove, and it assumes
1608      * the character at {@code e.getStartOffset() - 1} is a newline and
1609      * is of length 1.
1610      */
1611     private void removeElements(Element e, int index, int count) throws BadLocationException {
1612         writeLock();
1613         try {
1614             int start = e.getElement(index).getStartOffset();
1615             int end = e.getElement(index + count - 1).getEndOffset();
1616             if (end > getLength()) {
1617                 removeElementsAtEnd(e, index, count, start, end);
1618             }
1619             else {
1620                 removeElements(e, index, count, start, end);
1621             }
1622         } finally {
1623             writeUnlock();
1624         }
1625     }
1626 
1627     /**
1628      * Called to remove child elements of {@code e} when one of the
1629      * elements to remove is representing the end character.
1630      * <p>Since the Content will not allow a removal to the end character
1631      * this will do a remove from {@code start - 1} to {@code end}.
1632      * The end Element(s) will be removed, and the element representing
1633      * {@code start - 1} to {@code start} will be recreated. This
1634      * Element has to be recreated as after the content removal its offsets
1635      * become {@code start - 1} to {@code start - 1}.
1636      */
1637     private void removeElementsAtEnd(Element e, int index, int count,
1638                          int start, int end) throws BadLocationException {
1639         // index must be > 0 otherwise no insert would have happened.
1640         boolean isLeaf = (e.getElement(index - 1).isLeaf());
1641         DefaultDocumentEvent dde = new DefaultDocumentEvent(
1642                        start - 1, end - start + 1, DocumentEvent.
1643                        EventType.REMOVE);
1644 
1645         if (isLeaf) {
1646             Element endE = getCharacterElement(getLength());
1647             // e.getElement(index - 1) should represent the newline.
1648             index--;
1649             if (endE.getParentElement() != e) {
1650                 // The hiearchies don't match, we'll have to manually
1651                 // recreate the leaf at e.getElement(index - 1)
1652                 replace(dde, e, index, ++count, start, end, true, true);
1653             }
1654             else {
1655                 // The hierarchies for the end Element and


1661         }
1662         else {
1663             // Not a leaf, descend until we find the leaf representing
1664             // start - 1 and remove it.
1665             Element newLineE = e.getElement(index - 1);
1666             while (!newLineE.isLeaf()) {
1667                 newLineE = newLineE.getElement(newLineE.getElementCount() - 1);
1668             }
1669             newLineE = newLineE.getParentElement();
1670             replace(dde, e, index, count, start, end, false, false);
1671             replace(dde, newLineE, newLineE.getElementCount() - 1, 1, start,
1672                     end, true, true);
1673         }
1674         postRemoveUpdate(dde);
1675         dde.end();
1676         fireRemoveUpdate(dde);
1677         fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
1678     }
1679 
1680     /**
1681      * This is used by {@code removeElementsAtEnd}, it removes
1682      * {@code count} elements starting at {@code start} from
1683      * {@code e}.  If {@code remove} is true text of length
1684      * {@code start - 1} to {@code end - 1} is removed.  If
1685      * {@code create} is true a new leaf is created of length 1.
1686      */
1687     private void replace(DefaultDocumentEvent dde, Element e, int index,
1688                          int count, int start, int end, boolean remove,
1689                          boolean create) throws BadLocationException {
1690         Element[] added;
1691         AttributeSet attrs = e.getElement(index).getAttributes();
1692         Element[] removed = new Element[count];
1693 
1694         for (int counter = 0; counter < count; counter++) {
1695             removed[counter] = e.getElement(counter + index);
1696         }
1697         if (remove) {
1698             UndoableEdit u = getContent().remove(start - 1, end - start);
1699             if (u != null) {
1700                 dde.addEdit(u);
1701             }
1702         }
1703         if (create) {
1704             added = new Element[1];
1705             added[0] = createLeafElement(e, attrs, start - 1, start);


1877         ((MutableAttributeSet)contentAttributeSet).
1878                         addAttribute(StyleConstants.NameAttribute,
1879                                      HTML.Tag.CONTENT);
1880         NEWLINE = new char[1];
1881         NEWLINE[0] = '\n';
1882     }
1883 
1884 
1885     /**
1886      * An iterator to iterate over a particular type of
1887      * tag.  The iterator is not thread safe.  If reliable
1888      * access to the document is not already ensured by
1889      * the context under which the iterator is being used,
1890      * its use should be performed under the protection of
1891      * Document.render.
1892      */
1893     public abstract static class Iterator {
1894 
1895         /**
1896          * Return the attributes for this tag.
1897          * @return the {@code AttributeSet} for this tag, or
1898          *      {@code null} if none can be found
1899          */
1900         public abstract AttributeSet getAttributes();
1901 
1902         /**
1903          * Returns the start of the range for which the current occurrence of
1904          * the tag is defined and has the same attributes.
1905          *
1906          * @return the start of the range, or -1 if it can't be found
1907          */
1908         public abstract int getStartOffset();
1909 
1910         /**
1911          * Returns the end of the range for which the current occurrence of
1912          * the tag is defined and has the same attributes.
1913          *
1914          * @return the end of the range
1915          */
1916         public abstract int getEndOffset();
1917 
1918         /**


1934          * Type of tag this iterator represents.
1935          * @return the tag
1936          */
1937         public abstract HTML.Tag getTag();
1938     }
1939 
1940     /**
1941      * An iterator to iterate over a particular type of tag.
1942      */
1943     static class LeafIterator extends Iterator {
1944 
1945         LeafIterator(HTML.Tag t, Document doc) {
1946             tag = t;
1947             pos = new ElementIterator(doc);
1948             endOffset = 0;
1949             next();
1950         }
1951 
1952         /**
1953          * Returns the attributes for this tag.
1954          * @return the {@code AttributeSet} for this tag,
1955          *              or {@code null} if none can be found
1956          */
1957         public AttributeSet getAttributes() {
1958             Element elem = pos.current();
1959             if (elem != null) {
1960                 AttributeSet a = (AttributeSet)
1961                     elem.getAttributes().getAttribute(tag);
1962                 if (a == null) {
1963                     a = elem.getAttributes();
1964                 }
1965                 return a;
1966             }
1967             return null;
1968         }
1969 
1970         /**
1971          * Returns the start of the range for which the current occurrence of
1972          * the tag is defined and has the same attributes.
1973          *
1974          * @return the start of the range, or -1 if it can't be found
1975          */


1998         public void next() {
1999             for (nextLeaf(pos); isValid(); nextLeaf(pos)) {
2000                 Element elem = pos.current();
2001                 if (elem.getStartOffset() >= endOffset) {
2002                     AttributeSet a = pos.current().getAttributes();
2003 
2004                     if (a.isDefined(tag) ||
2005                         a.getAttribute(StyleConstants.NameAttribute) == tag) {
2006 
2007                         // we found the next one
2008                         setEndOffset();
2009                         break;
2010                     }
2011                 }
2012             }
2013         }
2014 
2015         /**
2016          * Returns the type of tag this iterator represents.
2017          *
2018          * @return the {@code HTML.Tag} that this iterator represents.
2019          * @see javax.swing.text.html.HTML.Tag
2020          */
2021         public HTML.Tag getTag() {
2022             return tag;
2023         }
2024 
2025         /**
2026          * Returns true if the current position is not {@code null}.
2027          * @return true if current position is not {@code null},
2028          *              otherwise returns false
2029          */
2030         public boolean isValid() {
2031             return (pos.current() != null);
2032         }
2033 
2034         /**
2035          * Moves the given iterator to the next leaf element.
2036          * @param iter  the iterator to be scanned
2037          */
2038         void nextLeaf(ElementIterator iter) {
2039             for (iter.next(); iter.current() != null; iter.next()) {
2040                 Element e = iter.current();
2041                 if (e.isLeaf()) {
2042                     break;
2043                 }
2044             }
2045         }
2046 
2047         /**
2048          * Marches a cloned iterator forward to locate the end
2049          * of the run.  This sets the value of {@code endOffset}.
2050          */
2051         void setEndOffset() {
2052             AttributeSet a0 = getAttributes();
2053             endOffset = pos.current().getEndOffset();
2054             ElementIterator fwd = (ElementIterator) pos.clone();
2055             for (nextLeaf(fwd); fwd.current() != null; nextLeaf(fwd)) {
2056                 Element e = fwd.current();
2057                 AttributeSet a1 = (AttributeSet) e.getAttributes().getAttribute(tag);
2058                 if ((a1 == null) || (! a1.equals(a0))) {
2059                     break;
2060                 }
2061                 endOffset = e.getEndOffset();
2062             }
2063         }
2064 
2065         private int endOffset;
2066         private HTML.Tag tag;
2067         private ElementIterator pos;
2068 
2069     }
2070 
2071     /**
2072      * An HTML reader to load an HTML document with an HTML
2073      * element structure.  This is a set of callbacks from
2074      * the parser, implemented to create a set of elements
2075      * tagged with attributes.  The parse builds up tokens
2076      * (ElementSpec) that describe the element subtree desired,
2077      * and burst it into the document under the protection of
2078      * a write lock using the insert method on the document
2079      * outer class.
2080      * <p>
2081      * The reader can be configured by registering actions
2082      * (of type {@code HTMLDocument.HTMLReader.TagAction})
2083      * that describe how to handle the action.  The idea behind
2084      * the actions provided is that the most natural text editing
2085      * operations can be provided if the element structure boils
2086      * down to paragraphs with runs of some kind of style
2087      * in them.  Some things are more naturally specified
2088      * structurally, so arbitrary structure should be allowed
2089      * above the paragraphs, but will need to be edited with structural
2090      * actions.  The implication of this is that some of the
2091      * HTML elements specified in the stream being parsed will
2092      * be collapsed into attributes, and in some cases paragraphs
2093      * will be synthesized.  When HTML elements have been
2094      * converted to attributes, the attribute key will be of
2095      * type HTML.Tag, and the value will be of type AttributeSet
2096      * so that no information is lost.  This enables many of the
2097      * existing actions to work so that the user can type input,
2098      * hit the return key, backspace, delete, etc and have a
2099      * reasonable result.  Selections can be created, and attributes
2100      * applied or removed, etc.  With this in mind, the work done
2101      * by the reader can be categorized into the following kinds
2102      * of tasks:


2116      * <dt>Special
2117      * <dd>Produce an embedded graphical element.
2118      * <dt>Form
2119      * <dd>Produce an element that is like the embedded graphical
2120      * element, except that it also has a component model associated
2121      * with it.
2122      * <dt>Hidden
2123      * <dd>Create an element that is hidden from view when the
2124      * document is being viewed read-only, and visible when the
2125      * document is being edited.  This is useful to keep the
2126      * model from losing information, and used to store things
2127      * like comments and unrecognized tags.
2128      *
2129      * </dl>
2130      * <p>
2131      * Currently, &lt;APPLET&gt;, &lt;PARAM&gt;, &lt;MAP&gt;, &lt;AREA&gt;, &lt;LINK&gt;,
2132      * &lt;SCRIPT&gt; and &lt;STYLE&gt; are unsupported.
2133      *
2134      * <p>
2135      * The assignment of the actions described is shown in the
2136      * following table for the tags defined in {@code HTML.Tag}.
2137      * <table border=1 summary="HTML tags and assigned actions">
2138      * <tr><th>Tag</th><th>Action</th></tr>
2139      * <tr><td>{@code HTML.Tag.A}         <td>CharacterAction
2140      * <tr><td>{@code HTML.Tag.ADDRESS}   <td>CharacterAction
2141      * <tr><td>{@code HTML.Tag.APPLET}    <td>HiddenAction
2142      * <tr><td>{@code HTML.Tag.AREA}      <td>AreaAction
2143      * <tr><td>{@code HTML.Tag.B}         <td>CharacterAction
2144      * <tr><td>{@code HTML.Tag.BASE}      <td>BaseAction
2145      * <tr><td>{@code HTML.Tag.BASEFONT}  <td>CharacterAction
2146      * <tr><td>{@code HTML.Tag.BIG}       <td>CharacterAction
2147      * <tr><td>{@code HTML.Tag.BLOCKQUOTE}<td>BlockAction
2148      * <tr><td>{@code HTML.Tag.BODY}      <td>BlockAction
2149      * <tr><td>{@code HTML.Tag.BR}        <td>SpecialAction
2150      * <tr><td>{@code HTML.Tag.CAPTION}   <td>BlockAction
2151      * <tr><td>{@code HTML.Tag.CENTER}    <td>BlockAction
2152      * <tr><td>{@code HTML.Tag.CITE}      <td>CharacterAction
2153      * <tr><td>{@code HTML.Tag.CODE}      <td>CharacterAction
2154      * <tr><td>{@code HTML.Tag.DD}        <td>BlockAction
2155      * <tr><td>{@code HTML.Tag.DFN}       <td>CharacterAction
2156      * <tr><td>{@code HTML.Tag.DIR}       <td>BlockAction
2157      * <tr><td>{@code HTML.Tag.DIV}       <td>BlockAction
2158      * <tr><td>{@code HTML.Tag.DL}        <td>BlockAction
2159      * <tr><td>{@code HTML.Tag.DT}        <td>ParagraphAction
2160      * <tr><td>{@code HTML.Tag.EM}        <td>CharacterAction
2161      * <tr><td>{@code HTML.Tag.FONT}      <td>CharacterAction
2162      * <tr><td>{@code HTML.Tag.FORM}      <td>As of 1.4 a BlockAction
2163      * <tr><td>{@code HTML.Tag.FRAME}     <td>SpecialAction
2164      * <tr><td>{@code HTML.Tag.FRAMESET}  <td>BlockAction
2165      * <tr><td>{@code HTML.Tag.H1}        <td>ParagraphAction
2166      * <tr><td>{@code HTML.Tag.H2}        <td>ParagraphAction
2167      * <tr><td>{@code HTML.Tag.H3}        <td>ParagraphAction
2168      * <tr><td>{@code HTML.Tag.H4}        <td>ParagraphAction
2169      * <tr><td>{@code HTML.Tag.H5}        <td>ParagraphAction
2170      * <tr><td>{@code HTML.Tag.H6}        <td>ParagraphAction
2171      * <tr><td>{@code HTML.Tag.HEAD}      <td>HeadAction
2172      * <tr><td>{@code HTML.Tag.HR}        <td>SpecialAction
2173      * <tr><td>{@code HTML.Tag.HTML}      <td>BlockAction
2174      * <tr><td>{@code HTML.Tag.I}         <td>CharacterAction
2175      * <tr><td>{@code HTML.Tag.IMG}       <td>SpecialAction
2176      * <tr><td>{@code HTML.Tag.INPUT}     <td>FormAction
2177      * <tr><td>{@code HTML.Tag.ISINDEX}   <td>IsndexAction
2178      * <tr><td>{@code HTML.Tag.KBD}       <td>CharacterAction
2179      * <tr><td>{@code HTML.Tag.LI}        <td>BlockAction
2180      * <tr><td>{@code HTML.Tag.LINK}      <td>LinkAction
2181      * <tr><td>{@code HTML.Tag.MAP}       <td>MapAction
2182      * <tr><td>{@code HTML.Tag.MENU}      <td>BlockAction
2183      * <tr><td>{@code HTML.Tag.META}      <td>MetaAction
2184      * <tr><td>{@code HTML.Tag.NOFRAMES}  <td>BlockAction
2185      * <tr><td>{@code HTML.Tag.OBJECT}    <td>SpecialAction
2186      * <tr><td>{@code HTML.Tag.OL}        <td>BlockAction
2187      * <tr><td>{@code HTML.Tag.OPTION}    <td>FormAction
2188      * <tr><td>{@code HTML.Tag.P}         <td>ParagraphAction
2189      * <tr><td>{@code HTML.Tag.PARAM}     <td>HiddenAction
2190      * <tr><td>{@code HTML.Tag.PRE}       <td>PreAction
2191      * <tr><td>{@code HTML.Tag.SAMP}      <td>CharacterAction
2192      * <tr><td>{@code HTML.Tag.SCRIPT}    <td>HiddenAction
2193      * <tr><td>{@code HTML.Tag.SELECT}    <td>FormAction
2194      * <tr><td>{@code HTML.Tag.SMALL}     <td>CharacterAction
2195      * <tr><td>{@code HTML.Tag.STRIKE}    <td>CharacterAction
2196      * <tr><td>{@code HTML.Tag.S}         <td>CharacterAction
2197      * <tr><td>{@code HTML.Tag.STRONG}    <td>CharacterAction
2198      * <tr><td>{@code HTML.Tag.STYLE}     <td>StyleAction
2199      * <tr><td>{@code HTML.Tag.SUB}       <td>CharacterAction
2200      * <tr><td>{@code HTML.Tag.SUP}       <td>CharacterAction
2201      * <tr><td>{@code HTML.Tag.TABLE}     <td>BlockAction
2202      * <tr><td>{@code HTML.Tag.TD}        <td>BlockAction
2203      * <tr><td>{@code HTML.Tag.TEXTAREA}  <td>FormAction
2204      * <tr><td>{@code HTML.Tag.TH}        <td>BlockAction
2205      * <tr><td>{@code HTML.Tag.TITLE}     <td>TitleAction
2206      * <tr><td>{@code HTML.Tag.TR}        <td>BlockAction
2207      * <tr><td>{@code HTML.Tag.TT}        <td>CharacterAction
2208      * <tr><td>{@code HTML.Tag.U}         <td>CharacterAction
2209      * <tr><td>{@code HTML.Tag.UL}        <td>BlockAction
2210      * <tr><td>{@code HTML.Tag.VAR}       <td>CharacterAction
2211      * </table>
2212      * <p>
2213      * Once &lt;/html&gt; is encountered, the Actions are no longer notified.
2214      */
2215     public class HTMLReader extends HTMLEditorKit.ParserCallback {
2216 
2217         /**
2218          * Constructs an HTMLReader using default pop and push depth and no tag to insert.
2219          *
2220          * @param offset the starting offset
2221          */
2222         public HTMLReader(int offset) {
2223             this(offset, 0, 0, null);
2224         }
2225 
2226         /**
2227          * Constructs an HTMLReader.
2228          *
2229          * @param offset the starting offset
2230          * @param popDepth how many parents to ascend before insert new element
2231          * @param pushDepth how many parents to descend (relative to popDepth) before
2232          *                  inserting
2233          * @param insertTag a tag to insert (may be null)
2234          */
2235         public HTMLReader(int offset, int popDepth, int pushDepth,
2236                           HTML.Tag insertTag) {
2237             this(offset, popDepth, pushDepth, insertTag, true, false, true);
2238         }
2239 
2240         /**
2241          * Generates a RuntimeException (will eventually generate
2242          * a BadLocationException when API changes are alloced) if inserting
2243          * into non empty document, {@code insertTag} is
2244          * non-{@code null}, and {@code offset} is not in the body.
2245          */
2246         // PENDING(sky): Add throws BadLocationException and remove
2247         // RuntimeException
2248         HTMLReader(int offset, int popDepth, int pushDepth,
2249                    HTML.Tag insertTag, boolean insertInsertTag,
2250                    boolean insertAfterImplied, boolean wantsTrailingNewline) {
2251             emptyDocument = (getLength() == 0);
2252             isStyleCSS = "text/css".equals(getDefaultStyleSheetType());
2253             this.offset = offset;
2254             threshold = HTMLDocument.this.getTokenThreshold();
2255             tagMap = new Hashtable<HTML.Tag, TagAction>(57);
2256             TagAction na = new TagAction();
2257             TagAction ba = new BlockAction();
2258             TagAction pa = new ParagraphAction();
2259             TagAction ca = new CharacterAction();
2260             TagAction sa = new SpecialAction();
2261             TagAction fa = new FormAction();
2262             TagAction ha = new HiddenAction();
2263             TagAction conv = new ConvertAction();
2264 


2348             else {
2349                 foundInsertTag = true;
2350             }
2351             if (insertAfterImplied) {
2352                 this.popDepth = popDepth;
2353                 this.pushDepth = pushDepth;
2354                 this.insertAfterImplied = true;
2355                 foundInsertTag = false;
2356                 midInsert = false;
2357                 this.insertInsertTag = true;
2358                 this.wantsTrailingNewline = wantsTrailingNewline;
2359             }
2360             else {
2361                 midInsert = (!emptyDocument && insertTag == null);
2362                 if (midInsert) {
2363                     generateEndsSpecsForMidInsert();
2364                 }
2365             }
2366 
2367             /**
2368              * This block initializes the {@code inParagraph} flag.
2369              * It is left in {@code false} value automatically
2370              * if the target document is empty or future inserts
2371              * were positioned into the 'body' tag.
2372              */
2373             if (!emptyDocument && !midInsert) {
2374                 int targetOffset = Math.max(this.offset - 1, 0);
2375                 Element elem =
2376                         HTMLDocument.this.getCharacterElement(targetOffset);
2377                 /* Going up by the left document structure path */
2378                 for (int i = 0; i <= this.popDepth; i++) {
2379                     elem = elem.getParentElement();
2380                 }
2381                 /* Going down by the right document structure path */
2382                 for (int i = 0; i < this.pushDepth; i++) {
2383                     int index = elem.getElementIndex(this.offset);
2384                     elem = elem.getElement(index);
2385                 }
2386                 AttributeSet attrs = elem.getAttributes();
2387                 if (attrs != null) {
2388                     HTML.Tag tagToInsertInto =
2389                             (HTML.Tag) attrs.getAttribute(StyleConstants.NameAttribute);
2390                     if (tagToInsertInto != null) {
2391                         this.inParagraph = tagToInsertInto.isParagraph();
2392                     }
2393                 }
2394             }
2395         }
2396 
2397         /**
2398          * Generates an initial batch of end {@code ElementSpecs}
2399          * in parseBuffer to position future inserts into the body.
2400          */
2401         private void generateEndsSpecsForMidInsert() {
2402             int           count = heightToElementWithName(HTML.Tag.BODY,
2403                                                    Math.max(0, offset - 1));
2404             boolean       joinNext = false;
2405 
2406             if (count == -1 && offset > 0) {
2407                 count = heightToElementWithName(HTML.Tag.BODY, offset);
2408                 if (count != -1) {
2409                     // Previous isn't in body, but current is. Have to
2410                     // do some end specs, followed by join next.
2411                     count = depthTo(offset - 1) - 1;
2412                     joinNext = true;
2413                 }
2414             }
2415             if (count == -1) {
2416                 throw new RuntimeException("Must insert new content into body element-");
2417             }
2418             if (count != -1) {


2443             }
2444             // We should probably throw an exception if (count == -1)
2445             // Or look for the body and reset the offset.
2446         }
2447 
2448         /**
2449          * @return number of parents to reach the child at offset.
2450          */
2451         private int depthTo(int offset) {
2452             Element       e = getDefaultRootElement();
2453             int           count = 0;
2454 
2455             while (!e.isLeaf()) {
2456                 count++;
2457                 e = e.getElement(e.getElementIndex(offset));
2458             }
2459             return count;
2460         }
2461 
2462         /**
2463          * @return number of parents of the leaf at {@code offset}
2464          *         until a parent with name, {@code name} has been
2465          *         found. -1 indicates no matching parent with
2466          *         {@code name}.
2467          */
2468         private int heightToElementWithName(Object name, int offset) {
2469             Element       e = getCharacterElement(offset).getParentElement();
2470             int           count = 0;
2471 
2472             while (e != null && e.getAttributes().getAttribute
2473                    (StyleConstants.NameAttribute) != name) {
2474                 count++;
2475                 e = e.getParentElement();
2476             }
2477             return (e == null) ? -1 : count;
2478         }
2479 
2480         /**
2481          * This will make sure there aren't two BODYs (the second is
2482          * typically created when you do a remove all, and then an insert).
2483          */
2484         private void adjustEndElement() {
2485             int length = getLength();
2486             if (length == 0) {


2692                 if (inBlock == 0 && (foundInsertTag ||
2693                                      insertTag != HTML.Tag.COMMENT)) {
2694                     // Comment outside of body, will not be able to show it,
2695                     // but can add it as a property on the Document.
2696                     addExternalComment(new String(data));
2697                     return;
2698                 }
2699                 SimpleAttributeSet sas = new SimpleAttributeSet();
2700                 sas.addAttribute(HTML.Attribute.COMMENT, new String(data));
2701                 addSpecialElement(HTML.Tag.COMMENT, sas);
2702             }
2703 
2704             TagAction action = tagMap.get(HTML.Tag.COMMENT);
2705             if (action != null) {
2706                 action.start(HTML.Tag.COMMENT, new SimpleAttributeSet());
2707                 action.end(HTML.Tag.COMMENT);
2708             }
2709         }
2710 
2711         /**
2712          * Adds the comment {@code comment} to the set of comments
2713          * maintained outside of the scope of elements.
2714          */
2715         private void addExternalComment(String comment) {
2716             Object comments = getProperty(AdditionalComments);
2717             if (comments != null && !(comments instanceof Vector)) {
2718                 // No place to put comment.
2719                 return;
2720             }
2721             if (comments == null) {
2722                 comments = new Vector<>();
2723                 putProperty(AdditionalComments, comments);
2724             }
2725             @SuppressWarnings("unchecked")
2726             Vector<Object> v = (Vector<Object>)comments;
2727             v.addElement(comment);
2728         }
2729 
2730         /**
2731          * Callback from the parser.  Route to the appropriate
2732          * handler for the tag.


2766                 styleAttributes = getStyleSheet().getDeclaration(decl);
2767                 a.addAttributes(styleAttributes);
2768             }
2769             else {
2770                 styleAttributes = null;
2771             }
2772 
2773             TagAction action = tagMap.get(t);
2774             if (action != null) {
2775                 action.start(t, a);
2776                 action.end(t);
2777             }
2778             else if (getPreservesUnknownTags()) {
2779                 // unknown tag, only add if should preserve it.
2780                 addSpecialElement(t, a);
2781             }
2782         }
2783 
2784         /**
2785          * This is invoked after the stream has been parsed, but before
2786          * {@code flush}. {@code eol} will be one of \n, \r
2787          * or \r\n, which ever is encountered the most in parsing the
2788          * stream.
2789          *
2790          * @since 1.3
2791          */
2792         public void handleEndOfLineString(String eol) {
2793             if (emptyDocument && eol != null) {
2794                 putProperty(DefaultEditorKit.EndOfLineStringProperty,
2795                             eol);
2796             }
2797         }
2798 
2799         // ---- tag handling support ------------------------------
2800 
2801         /**
2802          * Registers a handler for the given tag.  By default
2803          * all of the well-known tags will have been registered.
2804          * This can be used to change the handling of a particular
2805          * tag or to add support for custom tags.
2806          *


3367                 }
3368             }
3369 
3370             void addParameter(AttributeSet a) {
3371                 String name = (String) a.getAttribute(HTML.Attribute.NAME);
3372                 String value = (String) a.getAttribute(HTML.Attribute.VALUE);
3373                 if ((name != null) && (value != null)) {
3374                     ElementSpec objSpec = parseBuffer.lastElement();
3375                     MutableAttributeSet objAttr = (MutableAttributeSet) objSpec.getAttributes();
3376                     objAttr.addAttribute(name, value);
3377                 }
3378             }
3379         }
3380 
3381         /**
3382          * Action to support forms by building all of the elements
3383          * used to represent form controls.  This will process
3384          * the &lt;INPUT&gt;, &lt;TEXTAREA&gt;, &lt;SELECT&gt;,
3385          * and &lt;OPTION&gt; tags.  The element created by
3386          * this action is expected to have the attribute
3387          * {@code StyleConstants.ModelAttribute} set to
3388          * the model that holds the state for the form control.
3389          * This enables multiple views, and allows document to
3390          * be iterated over picking up the data of the form.
3391          * The following are the model assignments for the
3392          * various type of form elements.
3393          * <table summary="model assignments for the various types of form elements">
3394          * <tr>
3395          *   <th>Element Type
3396          *   <th>Model Type
3397          * <tr>
3398          *   <td>input, type button
3399          *   <td>{@link DefaultButtonModel}
3400          * <tr>
3401          *   <td>input, type checkbox
3402          *   <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
3403          * <tr>
3404          *   <td>input, type image
3405          *   <td>{@link DefaultButtonModel}
3406          * <tr>
3407          *   <td>input, type password


3555                         if ( radioButtonGroupsMap == null ) { //fix for 4772743
3556                            radioButtonGroupsMap = new HashMap<String, ButtonGroup>();
3557                         }
3558                         ButtonGroup radioButtonGroup = radioButtonGroupsMap.get(name);
3559                         if (radioButtonGroup == null) {
3560                             radioButtonGroup = new ButtonGroup();
3561                             radioButtonGroupsMap.put(name,radioButtonGroup);
3562                         }
3563                         model.setGroup(radioButtonGroup);
3564                     }
3565                     boolean checked = (attr.getAttribute(HTML.Attribute.CHECKED) != null);
3566                     model.setSelected(checked);
3567                     attr.addAttribute(StyleConstants.ModelAttribute, model);
3568                 }
3569             }
3570 
3571             /**
3572              * If a &lt;SELECT&gt; tag is being processed, this
3573              * model will be a reference to the model being filled
3574              * with the &lt;OPTION&gt; elements (which produce
3575              * objects of type {@code Option}.
3576              */
3577             Object selectModel;
3578             int optionCount;
3579         }
3580 
3581 
3582         // --- utility methods used by the reader ------------------
3583 
3584         /**
3585          * Pushes the current character style on a stack in preparation
3586          * for forming a new nested character style.
3587          */
3588         protected void pushCharacterStyle() {
3589             charAttrStack.push(charAttr.copyAttributes());
3590         }
3591 
3592         /**
3593          * Pops a previously pushed character style off the stack
3594          * to return to a previous style.
3595          */


3814             int size = parseBuffer.size();
3815             if (endOfStream && (insertTag != null || insertAfterImplied) &&
3816                 size > 0) {
3817                 adjustEndSpecsForPartialInsert();
3818                 size = parseBuffer.size();
3819             }
3820             ElementSpec[] spec = new ElementSpec[size];
3821             parseBuffer.copyInto(spec);
3822 
3823             if (oldLength == 0 && (insertTag == null && !insertAfterImplied)) {
3824                 create(spec);
3825             } else {
3826                 insert(offset, spec);
3827             }
3828             parseBuffer.removeAllElements();
3829             offset += HTMLDocument.this.getLength() - oldLength;
3830             flushCount++;
3831         }
3832 
3833         /**
3834          * This will be invoked for the last flush, if {@code insertTag}
3835          * is non null.
3836          */
3837         private void adjustEndSpecsForPartialInsert() {
3838             int size = parseBuffer.size();
3839             if (insertTagDepthDelta < 0) {
3840                 // When inserting via an insertTag, the depths (of the tree
3841                 // being read in, and existing hierarchy) may not match up.
3842                 // This attemps to clean it up.
3843                 int removeCounter = insertTagDepthDelta;
3844                 while (removeCounter < 0 && size >= 0 &&
3845                         parseBuffer.elementAt(size - 1).
3846                        getType() == ElementSpec.EndTagType) {
3847                     parseBuffer.removeElementAt(--size);
3848                     removeCounter++;
3849                 }
3850             }
3851             if (flushCount == 0 && (!insertAfterImplied ||
3852                                     !wantsTrailingNewline)) {
3853                 // If this starts with content (or popDepth > 0 &&
3854                 // pushDepth > 0) and ends with EndTagTypes, make sure


3892                                    counter--) {
3893                     ElementSpec spec = parseBuffer.elementAt(counter);
3894                     if (spec.getType() == ElementSpec.ContentType) {
3895                         if (spec.getArray()[spec.getLength() - 1] != '\n') {
3896                             SimpleAttributeSet attrs =new SimpleAttributeSet();
3897 
3898                             attrs.addAttribute(StyleConstants.NameAttribute,
3899                                                HTML.Tag.CONTENT);
3900                             parseBuffer.insertElementAt(new ElementSpec(
3901                                     attrs,
3902                                     ElementSpec.ContentType, NEWLINE, 0, 1),
3903                                     counter + 1);
3904                         }
3905                         break;
3906                     }
3907                 }
3908             }
3909         }
3910 
3911         /**
3912          * Adds the CSS rules in {@code rules}.
3913          */
3914         void addCSSRules(String rules) {
3915             StyleSheet ss = getStyleSheet();
3916             ss.addRule(rules);
3917         }
3918 
3919         /**
3920          * Adds the CSS stylesheet at {@code href} to the known list
3921          * of stylesheets.
3922          */
3923         void linkCSSStyleSheet(String href) {
3924             URL url;
3925             try {
3926                 url = new URL(base, href);
3927             } catch (MalformedURLException mfe) {
3928                 try {
3929                     url = new URL(href);
3930                 } catch (MalformedURLException mfe2) {
3931                     url = null;
3932                 }
3933             }
3934             if (url != null) {
3935                 getStyleSheet().importStyleSheet(url);
3936             }
3937         }
3938 
3939         /**
3940          * Returns true if can insert starting at {@code t}. This
3941          * will return false if the insert tag is set, and hasn't been found
3942          * yet.
3943          */
3944         private boolean canInsertTag(HTML.Tag t, AttributeSet attr,
3945                                      boolean isBlockTag) {
3946             if (!foundInsertTag) {
3947                 boolean needPImplied = ((t == HTML.Tag.IMPLIED)
3948                                                           && (!inParagraph)
3949                                                           && (!inPre));
3950                 if (needPImplied && (nextTagAfterPImplied != null)) {
3951 
3952                     /*
3953                      * If insertTag == null then just proceed to
3954                      * foundInsertTag() call below and return true.
3955                      */
3956                     if (insertTag != null) {
3957                         boolean nextTagIsInsertTag =
3958                                 isInsertTag(nextTagAfterPImplied);
3959                         if ( (! nextTagIsInsertTag) || (! insertInsertTag) ) {
3960                             return false;


4058             insertTagDepthDelta = depthTo(Math.max(0, offset - 1)) -
4059                                   popDepth + pushDepth - inBlock;
4060             if (isBlockTag) {
4061                 // A start spec will be added (for this tag), so we account
4062                 // for it here.
4063                 insertTagDepthDelta++;
4064             }
4065             else {
4066                 // An implied paragraph close (end spec) is going to be added,
4067                 // so we account for it here.
4068                 insertTagDepthDelta--;
4069                 inParagraph = true;
4070                 lastWasNewline = false;
4071             }
4072         }
4073 
4074         /**
4075          * This is set to true when and end is invoked for {@literal <html>}.
4076          */
4077         private boolean receivedEndHTML;
4078         /** Number of times {@code flushBuffer} has been invoked. */
4079         private int flushCount;
4080         /** If true, behavior is similar to insertTag, but instead of
4081          * waiting for insertTag will wait for first Element without
4082          * an 'implied' attribute and begin inserting then. */
4083         private boolean insertAfterImplied;
4084         /** This is only used if insertAfterImplied is true. If false, only
4085          * inserting content, and there is a trailing newline it is removed. */
4086         private boolean wantsTrailingNewline;
4087         int threshold;
4088         int offset;
4089         boolean inParagraph = false;
4090         boolean impliedP = false;
4091         boolean inPre = false;
4092         boolean inTextArea = false;
4093         TextAreaDocument textAreaDocument = null;
4094         boolean inTitle = false;
4095         boolean lastWasNewline = true;
4096         boolean emptyAnchor;
4097         /** True if (!emptyDocument &amp;&amp; insertTag == null), this is used so
4098          * much it is cached. */


< prev index next >