Save This Page
Home » openjdk-7 » javax » swing » text » html » [javadoc | source]
    1   /*
    2    * Copyright 1997-2006 Sun Microsystems, Inc.  All Rights Reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any questions.
   24    */
   25   package javax.swing.text.html;
   26   
   27   import java.awt.Color;
   28   import java.awt.Component;
   29   import java.awt.font.TextAttribute;
   30   import java.util;
   31   import java.net.URL;
   32   import java.net.URLEncoder;
   33   import java.net.MalformedURLException;
   34   import java.io;
   35   import javax.swing;
   36   import javax.swing.event;
   37   import javax.swing.text;
   38   import javax.swing.undo;
   39   import java.text.Bidi;
   40   import sun.swing.SwingUtilities2;
   41   
   42   /**
   43    * A document that models HTML.  The purpose of this model is to
   44    * support both browsing and editing.  As a result, the structure
   45    * described by an HTML document is not exactly replicated by default.
   46    * The element structure that is modeled by default, is built by the
   47    * class <code>HTMLDocument.HTMLReader</code>, which implements the
   48    * <code>HTMLEditorKit.ParserCallback</code> protocol that the parser
   49    * expects.  To change the structure one can subclass
   50    * <code>HTMLReader</code>, and reimplement the method {@link
   51    * #getReader(int)} to return the new reader implementation.  The
   52    * documentation for <code>HTMLReader</code> should be consulted for
   53    * the details of the default structure created.  The intent is that
   54    * the document be non-lossy (although reproducing the HTML format may
   55    * result in a different format).
   56    *
   57    * <p>The document models only HTML, and makes no attempt to store
   58    * view attributes in it.  The elements are identified by the
   59    * <code>StyleContext.NameAttribute</code> attribute, which should
   60    * always have a value of type <code>HTML.Tag</code> that identifies
   61    * the kind of element.  Some of the elements (such as comments) are
   62    * synthesized.  The <code>HTMLFactory</code> uses this attribute to
   63    * determine what kind of view to build.</p>
   64    *
   65    * <p>This document supports incremental loading.  The
   66    * <code>TokenThreshold</code> property controls how much of the parse
   67    * is buffered before trying to update the element structure of the
   68    * document.  This property is set by the <code>EditorKit</code> so
   69    * that subclasses can disable it.</p>
   70    *
   71    * <p>The <code>Base</code> property determines the URL against which
   72    * relative URLs are resolved.  By default, this will be the
   73    * <code>Document.StreamDescriptionProperty</code> if the value of the
   74    * property is a URL.  If a &lt;BASE&gt; tag is encountered, the base
   75    * will become the URL specified by that tag.  Because the base URL is
   76    * a property, it can of course be set directly.</p>
   77    *
   78    * <p>The default content storage mechanism for this document is a gap
   79    * buffer (<code>GapContent</code>).  Alternatives can be supplied by
   80    * using the constructor that takes a <code>Content</code>
   81    * implementation.</p>
   82    *
   83    * <h2>Modifying HTMLDocument</h2>
   84    *
   85    * <p>In addition to the methods provided by Document and
   86    * StyledDocument for mutating an HTMLDocument, HTMLDocument provides
   87    * a number of convenience methods.  The following methods can be used
   88    * to insert HTML content into an existing document.</p>
   89    *
   90    * <ul>
   91    *   <li>{@link #setInnerHTML(Element, String)}</li>
   92    *   <li>{@link #setOuterHTML(Element, String)}</li>
   93    *   <li>{@link #insertBeforeStart(Element, String)}</li>
   94    *   <li>{@link #insertAfterStart(Element, String)}</li>
   95    *   <li>{@link #insertBeforeEnd(Element, String)}</li>
   96    *   <li>{@link #insertAfterEnd(Element, String)}</li>
   97    * </ul>
   98    *
   99    * <p>The following examples illustrate using these methods.  Each
  100    * example assumes the HTML document is initialized in the following
  101    * way:</p>
  102    *
  103    * <pre>
  104    * JEditorPane p = new JEditorPane();
  105    * p.setContentType("text/html");
  106    * p.setText("..."); // Document text is provided below.
  107    * HTMLDocument d = (HTMLDocument) p.getDocument();
  108    * </pre>
  109    *
  110    * <p>With the following HTML content:</p>
  111    *
  112    * <pre>
  113    * &lt;html>
  114    *   &lt;head>
  115    *     &lt;title>An example HTMLDocument&lt;/title>
  116    *     &lt;style type="text/css">
  117    *       div { background-color: silver; }
  118    *       ul { color: red; }
  119    *     &lt;/style>
  120    *   &lt;/head>
  121    *   &lt;body>
  122    *     &lt;div id="BOX">
  123    *       &lt;p>Paragraph 1&lt;/p>
  124    *       &lt;p>Paragraph 2&lt;/p>
  125    *     &lt;/div>
  126    *   &lt;/body>
  127    * &lt;/html>
  128    * </pre>
  129    *
  130    * <p>All the methods for modifying an HTML document require an {@link
  131    * Element}.  Elements can be obtained from an HTML document by using
  132    * the method {@link #getElement(Element e, Object attribute, Object
  133    * value)}.  It returns the first descendant element that contains the
  134    * specified attribute with the given value, in depth-first order.
  135    * For example, <code>d.getElement(d.getDefaultRootElement(),
  136    * StyleConstants.NameAttribute, HTML.Tag.P)</code> returns the first
  137    * paragraph element.</p>
  138    *
  139    * <p>A convenient shortcut for locating elements is the method {@link
  140    * #getElement(String)}; returns an element whose <code>ID</code>
  141    * attribute matches the specified value.  For example,
  142    * <code>d.getElement("BOX")</code> returns the <code>DIV</code>
  143    * element.</p>
  144    *
  145    * <p>The {@link #getIterator(HTML.Tag t)} method can also be used for
  146    * finding all occurrences of the specified HTML tag in the
  147    * document.</p>
  148    *
  149    * <h3>Inserting elements</h3>
  150    *
  151    * <p>Elements can be inserted before or after the existing children
  152    * of any non-leaf element by using the methods
  153    * <code>insertAfterStart</code> and <code>insertBeforeEnd</code>.
  154    * For example, if <code>e</code> is the <code>DIV</code> element,
  155    * <code>d.insertAfterStart(e, "&lt;ul>&lt;li>List
  156    * Item&lt;/li>&lt;/ul>")</code> inserts the list before the first
  157    * paragraph, and <code>d.insertBeforeEnd(e, "&lt;ul>&lt;li>List
  158    * Item&lt;/li>&lt;/ul>")</code> inserts the list after the last
  159    * paragraph.  The <code>DIV</code> block becomes the parent of the
  160    * newly inserted elements.</p>
  161    *
  162    * <p>Sibling elements can be inserted before or after any element by
  163    * using the methods <code>insertBeforeStart</code> and
  164    * <code>insertAfterEnd</code>.  For example, if <code>e</code> is the
  165    * <code>DIV</code> element, <code>d.insertBeforeStart(e,
  166    * "&lt;ul>&lt;li>List Item&lt;/li>&lt;/ul>")</code> inserts the list
  167    * before the <code>DIV</code> element, and <code>d.insertAfterEnd(e,
  168    * "&lt;ul>&lt;li>List Item&lt;/li>&lt;/ul>")</code> inserts the list
  169    * after the <code>DIV</code> element.  The newly inserted elements
  170    * become siblings of the <code>DIV</code> element.</p>
  171    *
  172    * <h3>Replacing elements</h3>
  173    *
  174    * <p>Elements and all their descendants can be replaced by using the
  175    * methods <code>setInnerHTML</code> and <code>setOuterHTML</code>.
  176    * For example, if <code>e</code> is the <code>DIV</code> element,
  177    * <code>d.setInnerHTML(e, "&lt;ul>&lt;li>List
  178    * Item&lt;/li>&lt;/ul>")</code> replaces all children paragraphs with
  179    * the list, and <code>d.setOuterHTML(e, "&lt;ul>&lt;li>List
  180    * Item&lt;/li>&lt;/ul>")</code> replaces the <code>DIV</code> element
  181    * itself.  In latter case the parent of the list is the
  182    * <code>BODY</code> 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>
  190    *   <tr>
  191    *     <th>Example</th>
  192    *     <th><code>insertAfterStart</code></th>
  193    *     <th><code>insertBeforeEnd</code></th>
  194    *     <th><code>insertBeforeStart</code></th>
  195    *     <th><code>insertAfterEnd</code></th>
  196    *     <th><code>setInnerHTML</code></th>
  197    *     <th><code>setOuterHTML</code></th>
  198    *   </tr>
  199    *   <tr valign="top">
  200    *     <td nowrap="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 nowrap="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 nowrap="nowrap">
  218    *       <div style="background-color: silver;">
  219    *         <p>Paragraph 1</p>
  220    *         <p>Paragraph 2</p>
  221    *         <ul style="color: red;">
  222    *           <li>List Item</li>
  223    *         </ul>
  224    *       </div>
  225    *     </td>
  226    * <!--insertBeforeStart-->
  227    *     <td nowrap="nowrap">
  228    *       <ul style="color: red;">
  229    *         <li>List Item</li>
  230    *       </ul>
  231    *       <div style="background-color: silver;">
  232    *         <p>Paragraph 1</p>
  233    *         <p>Paragraph 2</p>
  234    *       </div>
  235    *     </td>
  236    * <!--insertAfterEnd-->
  237    *     <td nowrap="nowrap">
  238    *       <div style="background-color: silver;">
  239    *         <p>Paragraph 1</p>
  240    *         <p>Paragraph 2</p>
  241    *       </div>
  242    *       <ul style="color: red;">
  243    *         <li>List Item</li>
  244    *       </ul>
  245    *     </td>
  246    * <!--setInnerHTML-->
  247    *     <td nowrap="nowrap">
  248    *       <div style="background-color: silver;">
  249    *         <ul style="color: red;">
  250    *           <li>List Item</li>
  251    *         </ul>
  252    *       </div>
  253    *     </td>
  254    * <!--setOuterHTML-->
  255    *     <td nowrap="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<sup><font
  268    * size="-2">TM</font></sup> has been added to the
  269    * <code>java.beans</code> package.  Please see {@link
  270    * java.beans.XMLEncoder}.</p>
  271    *
  272    * @author  Timothy Prinzing
  273    * @author  Scott Violet
  274    * @author  Sunita Mani
  275    */
  276   public class HTMLDocument extends DefaultStyledDocument {
  277       /**
  278        * Constructs an HTML document using the default buffer size
  279        * and a default <code>StyleSheet</code>.  This is a convenience
  280        * method for the constructor
  281        * <code>HTMLDocument(Content, StyleSheet)</code>.
  282        */
  283       public HTMLDocument() {
  284           this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet());
  285       }
  286   
  287       /**
  288        * Constructs an HTML document with the default content
  289        * storage implementation and the specified style/attribute
  290        * storage mechanism.  This is a convenience method for the
  291        * constructor
  292        * <code>HTMLDocument(Content, StyleSheet)</code>.
  293        *
  294        * @param styles  the styles
  295        */
  296       public HTMLDocument(StyleSheet styles) {
  297           this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
  298       }
  299   
  300       /**
  301        * Constructs an HTML document with the given content
  302        * storage implementation and the given style/attribute
  303        * storage mechanism.
  304        *
  305        * @param c  the container for the content
  306        * @param styles the styles
  307        */
  308       public HTMLDocument(Content c, StyleSheet styles) {
  309           super(c, styles);
  310       }
  311   
  312       /**
  313        * Fetches the reader for the parser to use when loading the document
  314        * with HTML.  This is implemented to return an instance of
  315        * <code>HTMLDocument.HTMLReader</code>.
  316        * Subclasses can reimplement this
  317        * method to change how the document gets structured if desired.
  318        * (For example, to handle custom tags, or structurally represent character
  319        * style elements.)
  320        *
  321        * @param pos the starting position
  322        * @return the reader used by the parser to load the document
  323        */
  324       public HTMLEditorKit.ParserCallback getReader(int pos) {
  325           Object desc = getProperty(Document.StreamDescriptionProperty);
  326           if (desc instanceof URL) {
  327               setBase((URL)desc);
  328           }
  329           HTMLReader reader = new HTMLReader(pos);
  330           return reader;
  331       }
  332   
  333       /**
  334        * Returns the reader for the parser to use to load the document
  335        * with HTML.  This is implemented to return an instance of
  336        * <code>HTMLDocument.HTMLReader</code>.
  337        * Subclasses can reimplement this
  338        * method to change how the document gets structured if desired.
  339        * (For example, to handle custom tags, or structurally represent character
  340        * style elements.)
  341        * <p>This is a convenience method for
  342        * <code>getReader(int, int, int, HTML.Tag, TRUE)</code>.
  343        *
  344        * @param popDepth   the number of <code>ElementSpec.EndTagTypes</code>
  345        *          to generate before inserting
  346        * @param pushDepth  the number of <code>ElementSpec.StartTagTypes</code>
  347        *          with a direction of <code>ElementSpec.JoinNextDirection</code>
  348        *          that should be generated before inserting,
  349        *          but after the end tags have been generated
  350        * @param insertTag  the first tag to start inserting into document
  351        * @return the reader used by the parser to load the document
  352        */
  353       public HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
  354                                                     int pushDepth,
  355                                                     HTML.Tag insertTag) {
  356           return getReader(pos, popDepth, pushDepth, insertTag, true);
  357       }
  358   
  359       /**
  360        * Fetches the reader for the parser to use to load the document
  361        * with HTML.  This is implemented to return an instance of
  362        * HTMLDocument.HTMLReader.  Subclasses can reimplement this
  363        * method to change how the document get structured if desired
  364        * (e.g. to handle custom tags, structurally represent character
  365        * style elements, etc.).
  366        *
  367        * @param popDepth   the number of <code>ElementSpec.EndTagTypes</code>
  368        *          to generate before inserting
  369        * @param pushDepth  the number of <code>ElementSpec.StartTagTypes</code>
  370        *          with a direction of <code>ElementSpec.JoinNextDirection</code>
  371        *          that should be generated before inserting,
  372        *          but after the end tags have been generated
  373        * @param insertTag  the first tag to start inserting into document
  374        * @param insertInsertTag  false if all the Elements after insertTag should
  375        *        be inserted; otherwise insertTag will be inserted
  376        * @return the reader used by the parser to load the document
  377        */
  378       HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
  379                                              int pushDepth,
  380                                              HTML.Tag insertTag,
  381                                              boolean insertInsertTag) {
  382           Object desc = getProperty(Document.StreamDescriptionProperty);
  383           if (desc instanceof URL) {
  384               setBase((URL)desc);
  385           }
  386           HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth,
  387                                              insertTag, insertInsertTag, false,
  388                                              true);
  389           return reader;
  390       }
  391   
  392       /**
  393        * Returns the location to resolve relative URLs against.  By
  394        * default this will be the document's URL if the document
  395        * was loaded from a URL.  If a base tag is found and
  396        * can be parsed, it will be used as the base location.
  397        *
  398        * @return the base location
  399        */
  400       public URL getBase() {
  401           return base;
  402       }
  403   
  404       /**
  405        * Sets the location to resolve relative URLs against.  By
  406        * default this will be the document's URL if the document
  407        * was loaded from a URL.  If a base tag is found and
  408        * can be parsed, it will be used as the base location.
  409        * <p>This also sets the base of the <code>StyleSheet</code>
  410        * to be <code>u</code> as well as the base of the document.
  411        *
  412        * @param u  the desired base URL
  413        */
  414       public void setBase(URL u) {
  415           base = u;
  416           getStyleSheet().setBase(u);
  417       }
  418   
  419       /**
  420        * Inserts new elements in bulk.  This is how elements get created
  421        * in the document.  The parsing determines what structure is needed
  422        * and creates the specification as a set of tokens that describe the
  423        * edit while leaving the document free of a write-lock.  This method
  424        * can then be called in bursts by the reader to acquire a write-lock
  425        * for a shorter duration (i.e. while the document is actually being
  426        * altered).
  427        *
  428        * @param offset the starting offset
  429        * @param data the element data
  430        * @exception BadLocationException  if the given position does not
  431        *   represent a valid location in the associated document.
  432        */
  433       protected void insert(int offset, ElementSpec[] data) throws BadLocationException {
  434           super.insert(offset, data);
  435       }
  436   
  437       /**
  438        * Updates document structure as a result of text insertion.  This
  439        * will happen within a write lock.  This implementation simply
  440        * parses the inserted content for line breaks and builds up a set
  441        * of instructions for the element buffer.
  442        *
  443        * @param chng a description of the document change
  444        * @param attr the attributes
  445        */
  446       protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
  447           if(attr == null) {
  448               attr = contentAttributeSet;
  449           }
  450   
  451           // If this is the composed text element, merge the content attribute to it
  452           else if (attr.isDefined(StyleConstants.ComposedTextAttribute)) {
  453               ((MutableAttributeSet)attr).addAttributes(contentAttributeSet);
  454           }
  455   
  456           if (attr.isDefined(IMPLIED_CR)) {
  457               ((MutableAttributeSet)attr).removeAttribute(IMPLIED_CR);
  458           }
  459   
  460           super.insertUpdate(chng, attr);
  461       }
  462   
  463       /**
  464        * Replaces the contents of the document with the given
  465        * element specifications.  This is called before insert if
  466        * the loading is done in bursts.  This is the only method called
  467        * if loading the document entirely in one burst.
  468        *
  469        * @param data  the new contents of the document
  470        */
  471       protected void create(ElementSpec[] data) {
  472           super.create(data);
  473       }
  474   
  475       /**
  476        * Sets attributes for a paragraph.
  477        * <p>
  478        * This method is thread safe, although most Swing methods
  479        * are not. Please see
  480        * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
  481        * to Use Threads</A> for more information.
  482        *
  483        * @param offset the offset into the paragraph (must be at least 0)
  484        * @param length the number of characters affected (must be at least 0)
  485        * @param s the attributes
  486        * @param replace whether to replace existing attributes, or merge them
  487        */
  488       public void setParagraphAttributes(int offset, int length, AttributeSet s,
  489                                          boolean replace) {
  490           try {
  491               writeLock();
  492               // Make sure we send out a change for the length of the paragraph.
  493               int end = Math.min(offset + length, getLength());
  494               Element e = getParagraphElement(offset);
  495               offset = e.getStartOffset();
  496               e = getParagraphElement(end);
  497               length = Math.max(0, e.getEndOffset() - offset);
  498               DefaultDocumentEvent changes =
  499                   new DefaultDocumentEvent(offset, length,
  500                                            DocumentEvent.EventType.CHANGE);
  501               AttributeSet sCopy = s.copyAttributes();
  502               int lastEnd = Integer.MAX_VALUE;
  503               for (int pos = offset; pos <= end; pos = lastEnd) {
  504                   Element paragraph = getParagraphElement(pos);
  505                   if (lastEnd == paragraph.getEndOffset()) {
  506                       lastEnd++;
  507                   }
  508                   else {
  509                       lastEnd = paragraph.getEndOffset();
  510                   }
  511                   MutableAttributeSet attr =
  512                       (MutableAttributeSet) paragraph.getAttributes();
  513                   changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
  514                   if (replace) {
  515                       attr.removeAttributes(attr);
  516                   }
  517                   attr.addAttributes(s);
  518               }
  519               changes.end();
  520               fireChangedUpdate(changes);
  521               fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
  522           } finally {
  523               writeUnlock();
  524           }
  525       }
  526   
  527       /**
  528        * Fetches the <code>StyleSheet</code> with the document-specific display
  529        * rules (CSS) that were specified in the HTML document itself.
  530        *
  531        * @return the <code>StyleSheet</code>
  532        */
  533       public StyleSheet getStyleSheet() {
  534           return (StyleSheet) getAttributeContext();
  535       }
  536   
  537       /**
  538        * Fetches an iterator for the specified HTML tag.
  539        * This can be used for things like iterating over the
  540        * set of anchors contained, or iterating over the input
  541        * elements.
  542        *
  543        * @param t the requested <code>HTML.Tag</code>
  544        * @return the <code>Iterator</code> for the given HTML tag
  545        * @see javax.swing.text.html.HTML.Tag
  546        */
  547       public Iterator getIterator(HTML.Tag t) {
  548           if (t.isBlock()) {
  549               // TBD
  550               return null;
  551           }
  552           return new LeafIterator(t, this);
  553       }
  554   
  555       /**
  556        * Creates a document leaf element that directly represents
  557        * text (doesn't have any children).  This is implemented
  558        * to return an element of type
  559        * <code>HTMLDocument.RunElement</code>.
  560        *
  561        * @param parent the parent element
  562        * @param a the attributes for the element
  563        * @param p0 the beginning of the range (must be at least 0)
  564        * @param p1 the end of the range (must be at least p0)
  565        * @return the new element
  566        */
  567       protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
  568           return new RunElement(parent, a, p0, p1);
  569       }
  570   
  571       /**
  572        * Creates a document branch element, that can contain other elements.
  573        * This is implemented to return an element of type
  574        * <code>HTMLDocument.BlockElement</code>.
  575        *
  576        * @param parent the parent element
  577        * @param a the attributes
  578        * @return the element
  579        */
  580       protected Element createBranchElement(Element parent, AttributeSet a) {
  581           return new BlockElement(parent, a);
  582       }
  583   
  584       /**
  585        * Creates the root element to be used to represent the
  586        * default document structure.
  587        *
  588        * @return the element base
  589        */
  590       protected AbstractElement createDefaultRoot() {
  591           // grabs a write-lock for this initialization and
  592           // abandon it during initialization so in normal
  593           // operation we can detect an illegitimate attempt
  594           // to mutate attributes.
  595           writeLock();
  596           MutableAttributeSet a = new SimpleAttributeSet();
  597           a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.HTML);
  598           BlockElement html = new BlockElement(null, a.copyAttributes());
  599           a.removeAttributes(a);
  600           a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.BODY);
  601           BlockElement body = new BlockElement(html, a.copyAttributes());
  602           a.removeAttributes(a);
  603           a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.P);
  604           getStyleSheet().addCSSAttributeFromHTML(a, CSS.Attribute.MARGIN_TOP, "0");
  605           BlockElement paragraph = new BlockElement(body, a.copyAttributes());
  606           a.removeAttributes(a);
  607           a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
  608           RunElement brk = new RunElement(paragraph, a, 0, 1);
  609           Element[] buff = new Element[1];
  610           buff[0] = brk;
  611           paragraph.replace(0, 0, buff);
  612           buff[0] = paragraph;
  613           body.replace(0, 0, buff);
  614           buff[0] = body;
  615           html.replace(0, 0, buff);
  616           writeUnlock();
  617           return html;
  618       }
  619   
  620       /**
  621        * Sets the number of tokens to buffer before trying to update
  622        * the documents element structure.
  623        *
  624        * @param n  the number of tokens to buffer
  625        */
  626       public void setTokenThreshold(int n) {
  627           putProperty(TokenThreshold, new Integer(n));
  628       }
  629   
  630       /**
  631        * Gets the number of tokens to buffer before trying to update
  632        * the documents element structure.  The default value is
  633        * <code>Integer.MAX_VALUE</code>.
  634        *
  635        * @return the number of tokens to buffer
  636        */
  637       public int getTokenThreshold() {
  638           Integer i = (Integer) getProperty(TokenThreshold);
  639           if (i != null) {
  640               return i.intValue();
  641           }
  642           return Integer.MAX_VALUE;
  643       }
  644   
  645       /**
  646        * Determines how unknown tags are handled by the parser.
  647        * If set to true, unknown
  648        * tags are put in the model, otherwise they are dropped.
  649        *
  650        * @param preservesTags  true if unknown tags should be
  651        *          saved in the model, otherwise tags are dropped
  652        * @see javax.swing.text.html.HTML.Tag
  653        */
  654       public void setPreservesUnknownTags(boolean preservesTags) {
  655           preservesUnknownTags = preservesTags;
  656       }
  657   
  658       /**
  659        * Returns the behavior the parser observes when encountering
  660        * unknown tags.
  661        *
  662        * @see javax.swing.text.html.HTML.Tag
  663        * @return true if unknown tags are to be preserved when parsing
  664        */
  665       public boolean getPreservesUnknownTags() {
  666           return preservesUnknownTags;
  667       }
  668   
  669       /**
  670        * Processes <code>HyperlinkEvents</code> that
  671        * are generated by documents in an HTML frame.
  672        * The <code>HyperlinkEvent</code> type, as the parameter suggests,
  673        * is <code>HTMLFrameHyperlinkEvent</code>.
  674        * In addition to the typical information contained in a
  675        * <code>HyperlinkEvent</code>,
  676        * this event contains the element that corresponds to the frame in
  677        * which the click happened (the source element) and the
  678        * target name.  The target name has 4 possible values:
  679        * <ul>
  680        * <li>  _self
  681        * <li>  _parent
  682        * <li>  _top
  683        * <li>  a named frame
  684        * </ul>
  685        *
  686        * If target is _self, the action is to change the value of the
  687        * <code>HTML.Attribute.SRC</code> attribute and fires a
  688        * <code>ChangedUpdate</code> event.
  689        *<p>
  690        * If the target is _parent, then it deletes the parent element,
  691        * which is a &lt;FRAMESET&gt; element, and inserts a new &lt;FRAME&gt;
  692        * element, and sets its <code>HTML.Attribute.SRC</code> attribute
  693        * to have a value equal to the destination URL and fire a
  694        * <code>RemovedUpdate</code> and <code>InsertUpdate</code>.
  695        *<p>
  696        * If the target is _top, this method does nothing. In the implementation
  697        * of the view for a frame, namely the <code>FrameView</code>,
  698        * the processing of _top is handled.  Given that _top implies
  699        * replacing the entire document, it made sense to handle this outside
  700        * of the document that it will replace.
  701        *<p>
  702        * If the target is a named frame, then the element hierarchy is searched
  703        * for an element with a name equal to the target, its
  704        * <code>HTML.Attribute.SRC</code> attribute is updated and a
  705        * <code>ChangedUpdate</code> event is fired.
  706        *
  707        * @param e the event
  708        */
  709       public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent e) {
  710           String frameName = e.getTarget();
  711           Element element = e.getSourceElement();
  712           String urlStr = e.getURL().toString();
  713   
  714           if (frameName.equals("_self")) {
  715               /*
  716                 The source and destination elements
  717                 are the same.
  718               */
  719               updateFrame(element, urlStr);
  720           } else if (frameName.equals("_parent")) {
  721               /*
  722                 The destination is the parent of the frame.
  723               */
  724               updateFrameSet(element.getParentElement(), urlStr);
  725           } else {
  726               /*
  727                 locate a named frame
  728               */
  729               Element targetElement = findFrame(frameName);
  730               if (targetElement != null) {
  731                   updateFrame(targetElement, urlStr);
  732               }
  733           }
  734       }
  735   
  736   
  737       /**
  738        * Searches the element hierarchy for an FRAME element
  739        * that has its name attribute equal to the <code>frameName</code>.
  740        *
  741        * @param frameName
  742        * @return the element whose NAME attribute has a value of
  743        *          <code>frameName</code>; returns <code>null</code>
  744        *          if not found
  745        */
  746       private Element findFrame(String frameName) {
  747           ElementIterator it = new ElementIterator(this);
  748           Element next = null;
  749   
  750           while ((next = it.next()) != null) {
  751               AttributeSet attr = next.getAttributes();
  752               if (matchNameAttribute(attr, HTML.Tag.FRAME)) {
  753                   String frameTarget = (String)attr.getAttribute(HTML.Attribute.NAME);
  754                   if (frameTarget != null && frameTarget.equals(frameName)) {
  755                       break;
  756                   }
  757               }
  758           }
  759           return next;
  760       }
  761   
  762       /**
  763        * Returns true if <code>StyleConstants.NameAttribute</code> is
  764        * equal to the tag that is passed in as a parameter.
  765        *
  766        * @param attr the attributes to be matched
  767        * @param tag the value to be matched
  768        * @return true if there is a match, false otherwise
  769        * @see javax.swing.text.html.HTML.Attribute
  770        */
  771       static boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
  772           Object o = attr.getAttribute(StyleConstants.NameAttribute);
  773           if (o instanceof HTML.Tag) {
  774               HTML.Tag name = (HTML.Tag) o;
  775               if (name == tag) {
  776                   return true;
  777               }
  778           }
  779           return false;
  780       }
  781   
  782       /**
  783        * Replaces a frameset branch Element with a frame leaf element.
  784        *
  785        * @param element the frameset element to remove
  786        * @param url     the value for the SRC attribute for the
  787        *                new frame that will replace the frameset
  788        */
  789       private void updateFrameSet(Element element, String url) {
  790           try {
  791               int startOffset = element.getStartOffset();
  792               int endOffset = Math.min(getLength(), element.getEndOffset());
  793               String html = "<frame";
  794               if (url != null) {
  795                   html += " src=\"" + url + "\"";
  796               }
  797               html += ">";
  798               installParserIfNecessary();
  799               setOuterHTML(element, html);
  800           } catch (BadLocationException e1) {
  801               // Should handle this better
  802           } catch (IOException ioe) {
  803               // Should handle this better
  804           }
  805       }
  806   
  807   
  808       /**
  809        * Updates the Frame elements <code>HTML.Attribute.SRC attribute</code>
  810        * and fires a <code>ChangedUpdate</code> event.
  811        *
  812        * @param element a FRAME element whose SRC attribute will be updated
  813        * @param url     a string specifying the new value for the SRC attribute
  814        */
  815       private void updateFrame(Element element, String url) {
  816   
  817           try {
  818               writeLock();
  819               DefaultDocumentEvent changes = new DefaultDocumentEvent(element.getStartOffset(),
  820                                                                       1,
  821                                                                       DocumentEvent.EventType.CHANGE);
  822               AttributeSet sCopy = element.getAttributes().copyAttributes();
  823               MutableAttributeSet attr = (MutableAttributeSet) element.getAttributes();
  824               changes.addEdit(new AttributeUndoableEdit(element, sCopy, false));
  825               attr.removeAttribute(HTML.Attribute.SRC);
  826               attr.addAttribute(HTML.Attribute.SRC, url);
  827               changes.end();
  828               fireChangedUpdate(changes);
  829               fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
  830           } finally {
  831               writeUnlock();
  832           }
  833       }
  834   
  835   
  836       /**
  837        * Returns true if the document will be viewed in a frame.
  838        * @return true if document will be viewed in a frame, otherwise false
  839        */
  840       boolean isFrameDocument() {
  841           return frameDocument;
  842       }
  843   
  844       /**
  845        * Sets a boolean state about whether the document will be
  846        * viewed in a frame.
  847        * @param frameDoc  true if the document will be viewed in a frame,
  848        *          otherwise false
  849        */
  850       void setFrameDocumentState(boolean frameDoc) {
  851           this.frameDocument = frameDoc;
  852       }
  853   
  854       /**
  855        * Adds the specified map, this will remove a Map that has been
  856        * previously registered with the same name.
  857        *
  858        * @param map  the <code>Map</code> to be registered
  859        */
  860       void addMap(Map map) {
  861           String     name = map.getName();
  862   
  863           if (name != null) {
  864               Object     maps = getProperty(MAP_PROPERTY);
  865   
  866               if (maps == null) {
  867                   maps = new Hashtable(11);
  868                   putProperty(MAP_PROPERTY, maps);
  869               }
  870               if (maps instanceof Hashtable) {
  871                   ((Hashtable)maps).put("#" + name, map);
  872               }
  873           }
  874       }
  875   
  876       /**
  877        * Removes a previously registered map.
  878        * @param map the <code>Map</code> to be removed
  879        */
  880       void removeMap(Map map) {
  881           String     name = map.getName();
  882   
  883           if (name != null) {
  884               Object     maps = getProperty(MAP_PROPERTY);
  885   
  886               if (maps instanceof Hashtable) {
  887                   ((Hashtable)maps).remove("#" + name);
  888               }
  889           }
  890       }
  891   
  892       /**
  893        * Returns the Map associated with the given name.
  894        * @param the name of the desired <code>Map</code>
  895        * @return the <code>Map</code> or <code>null</code> if it can't
  896        *          be found, or if <code>name</code> is <code>null</code>
  897        */
  898       Map getMap(String name) {
  899           if (name != null) {
  900               Object     maps = getProperty(MAP_PROPERTY);
  901   
  902               if (maps != null && (maps instanceof Hashtable)) {
  903                   return (Map)((Hashtable)maps).get(name);
  904               }
  905           }
  906           return null;
  907       }
  908   
  909       /**
  910        * Returns an <code>Enumeration</code> of the possible Maps.
  911        * @return the enumerated list of maps, or <code>null</code>
  912        *          if the maps are not an instance of <code>Hashtable</code>
  913        */
  914       Enumeration getMaps() {
  915           Object     maps = getProperty(MAP_PROPERTY);
  916   
  917           if (maps instanceof Hashtable) {
  918               return ((Hashtable)maps).elements();
  919           }
  920           return null;
  921       }
  922   
  923       /**
  924        * Sets the content type language used for style sheets that do not
  925        * explicitly specify the type. The default is text/css.
  926        * @param contentType  the content type language for the style sheets
  927        */
  928       /* public */
  929       void setDefaultStyleSheetType(String contentType) {
  930           putProperty(StyleType, contentType);
  931       }
  932   
  933       /**
  934        * Returns the content type language used for style sheets. The default
  935        * is text/css.
  936        * @return the content type language used for the style sheets
  937        */
  938       /* public */
  939       String getDefaultStyleSheetType() {
  940           String retValue = (String)getProperty(StyleType);
  941           if (retValue == null) {
  942               return "text/css";
  943           }
  944           return retValue;
  945       }
  946   
  947       /**
  948        * Sets the parser that is used by the methods that insert html
  949        * into the existing document, such as <code>setInnerHTML</code>,
  950        * and <code>setOuterHTML</code>.
  951        * <p>
  952        * <code>HTMLEditorKit.createDefaultDocument</code> will set the parser
  953        * for you. If you create an <code>HTMLDocument</code> by hand,
  954        * be sure and set the parser accordingly.
  955        * @param parser the parser to be used for text insertion
  956        *
  957        * @since 1.3
  958        */
  959       public void setParser(HTMLEditorKit.Parser parser) {
  960           this.parser = parser;
  961           putProperty("__PARSER__", null);
  962       }
  963   
  964       /**
  965        * Returns the parser that is used when inserting HTML into the existing
  966        * document.
  967        * @return the parser used for text insertion
  968        *
  969        * @since 1.3
  970        */
  971       public HTMLEditorKit.Parser getParser() {
  972           Object p = getProperty("__PARSER__");
  973   
  974           if (p instanceof HTMLEditorKit.Parser) {
  975               return (HTMLEditorKit.Parser)p;
  976           }
  977           return parser;
  978       }
  979   
  980       /**
  981        * Replaces the children of the given element with the contents
  982        * specified as an HTML string.
  983        *
  984        * <p>This will be seen as at least two events, n inserts followed by
  985        * a remove.</p>
  986        *
  987        * <p>Consider the following structure (the <code>elem</code>
  988        * parameter is <b>in bold</b>).</p>
  989        *
  990        * <pre>
  991        *     &lt;body>
  992        *       |
  993        *     <b>&lt;div></b>
  994        *      /  \
  995        *    &lt;p>   &lt;p>
  996        * </pre>
  997        *
  998        * <p>Invoking <code>setInnerHTML(elem, "&lt;ul>&lt;li>")</code>
  999        * results in the following structure (new elements are <font
 1000        * color="red">in red</font>).</p>
 1001        *
 1002        * <pre>
 1003        *     &lt;body>
 1004        *       |
 1005        *     <b>&lt;div></b>
 1006        *         \
 1007        *         <font color="red">&lt;ul></font>
 1008        *           \
 1009        *           <font color="red">&lt;li></font>
 1010        * </pre>
 1011        *
 1012        * <p>Parameter <code>elem</code> must not be a leaf element,
 1013        * otherwise an <code>IllegalArgumentException</code> is thrown.
 1014        * If either <code>elem</code> or <code>htmlText</code> parameter
 1015        * is <code>null</code>, no changes are made to the document.</p>
 1016        *
 1017        * <p>For this to work correcty, the document must have an
 1018        * <code>HTMLEditorKit.Parser</code> set. This will be the case
 1019        * if the document was created from an HTMLEditorKit via the
 1020        * <code>createDefaultDocument</code> method.</p>
 1021        *
 1022        * @param elem the branch element whose children will be replaced
 1023        * @param htmlText the string to be parsed and assigned to <code>elem</code>
 1024        * @throws IllegalArgumentException if <code>elem</code> is a leaf
 1025        * @throws IllegalStateException if an <code>HTMLEditorKit.Parser</code>
 1026        *         has not been defined
 1027        * @since 1.3
 1028        */
 1029       public void setInnerHTML(Element elem, String htmlText) throws
 1030                                BadLocationException, IOException {
 1031           verifyParser();
 1032           if (elem != null && elem.isLeaf()) {
 1033               throw new IllegalArgumentException
 1034                   ("Can not set inner HTML of a leaf");
 1035           }
 1036           if (elem != null && htmlText != null) {
 1037               int oldCount = elem.getElementCount();
 1038               int insertPosition = elem.getStartOffset();
 1039               insertHTML(elem, elem.getStartOffset(), htmlText, true);
 1040               if (elem.getElementCount() > oldCount) {
 1041                   // Elements were inserted, do the cleanup.
 1042                   removeElements(elem, elem.getElementCount() - oldCount,
 1043                                  oldCount);
 1044               }
 1045           }
 1046       }
 1047   
 1048       /**
 1049        * Replaces the given element in the parent with the contents
 1050        * specified as an HTML string.
 1051        *
 1052        * <p>This will be seen as at least two events, n inserts followed by
 1053        * a remove.</p>
 1054        *
 1055        * <p>When replacing a leaf this will attempt to make sure there is
 1056        * a newline present if one is needed. This may result in an additional
 1057        * element being inserted. Consider, if you were to replace a character
 1058        * element that contained a newline with &lt;img&gt; this would create
 1059        * two elements, one for the image, ane one for the newline.</p>
 1060        *
 1061        * <p>If you try to replace the element at length you will most
 1062        * likely end up with two elements, eg
 1063        * <code>setOuterHTML(getCharacterElement (getLength()),
 1064        * "blah")</code> will result in two leaf elements at the end, one
 1065        * representing 'blah', and the other representing the end
 1066        * element.</p>
 1067        *
 1068        * <p>Consider the following structure (the <code>elem</code>
 1069        * parameter is <b>in bold</b>).</p>
 1070        *
 1071        * <pre>
 1072        *     &lt;body>
 1073        *       |
 1074        *     <b>&lt;div></b>
 1075        *      /  \
 1076        *    &lt;p>   &lt;p>
 1077        * </pre>
 1078        *
 1079        * <p>Invoking <code>setOuterHTML(elem, "&lt;ul>&lt;li>")</code>
 1080        * results in the following structure (new elements are <font
 1081        * color="red">in red</font>).</p>
 1082        *
 1083        * <pre>
 1084        *    &lt;body>
 1085        *      |
 1086        *     <font color="red">&lt;ul></font>
 1087        *       \
 1088        *       <font color="red">&lt;li></font>
 1089        * </pre>
 1090        *
 1091        * <p>If either <code>elem</code> or <code>htmlText</code>
 1092        * parameter is <code>null</code>, no changes are made to the
 1093        * document.</p>
 1094        *
 1095        * <p>For this to work correcty, the document must have an
 1096        * HTMLEditorKit.Parser set. This will be the case if the document
 1097        * was created from an HTMLEditorKit via the
 1098        * <code>createDefaultDocument</code> method.</p>
 1099        *
 1100        * @param elem the element to replace
 1101        * @param htmlText the string to be parsed and inserted in place of <code>elem</code>
 1102        * @throws IllegalStateException if an HTMLEditorKit.Parser has not
 1103        *         been set
 1104        * @since 1.3
 1105        */
 1106       public void setOuterHTML(Element elem, String htmlText) throws
 1107                               BadLocationException, IOException {
 1108           verifyParser();
 1109           if (elem != null && elem.getParentElement() != null &&
 1110               htmlText != null) {
 1111               int start = elem.getStartOffset();
 1112               int end = elem.getEndOffset();
 1113               int startLength = getLength();
 1114               // We don't want a newline if elem is a leaf, and doesn't contain
 1115               // a newline.
 1116               boolean wantsNewline = !elem.isLeaf();
 1117               if (!wantsNewline && (end > startLength ||
 1118                                    getText(end - 1, 1).charAt(0) == NEWLINE[0])){
 1119                   wantsNewline = true;
 1120               }
 1121               Element parent = elem.getParentElement();
 1122               int oldCount = parent.getElementCount();
 1123               insertHTML(parent, start, htmlText, wantsNewline);
 1124               // Remove old.
 1125               int newLength = getLength();
 1126               if (oldCount != parent.getElementCount()) {
 1127                   int removeIndex = parent.getElementIndex(start + newLength -
 1128                                                            startLength);
 1129                   removeElements(parent, removeIndex, 1);
 1130               }
 1131           }
 1132       }
 1133   
 1134       /**
 1135        * Inserts the HTML specified as a string at the start
 1136        * of the element.
 1137        *
 1138        * <p>Consider the following structure (the <code>elem</code>
 1139        * parameter is <b>in bold</b>).</p>
 1140        *
 1141        * <pre>
 1142        *     &lt;body>
 1143        *       |
 1144        *     <b>&lt;div></b>
 1145        *      /  \
 1146        *    &lt;p>   &lt;p>
 1147        * </pre>
 1148        *
 1149        * <p>Invoking <code>insertAfterStart(elem,
 1150        * "&lt;ul>&lt;li>")</code> results in the following structure
 1151        * (new elements are <font color="red">in red</font>).</p>
 1152        *
 1153        * <pre>
 1154        *        &lt;body>
 1155        *          |
 1156        *        <b>&lt;div></b>
 1157        *       /  |  \
 1158        *    <font color="red">&lt;ul></font> &lt;p> &lt;p>
 1159        *     /
 1160        *  <font color="red">&lt;li></font>
 1161        * </pre>
 1162        *
 1163        * <p>Unlike the <code>insertBeforeStart</code> method, new
 1164        *  elements become <em>children</em> of the specified element,
 1165        *  not siblings.</p>
 1166        *
 1167        * <p>Parameter <code>elem</code> must not be a leaf element,
 1168        * otherwise an <code>IllegalArgumentException</code> is thrown.
 1169        * If either <code>elem</code> or <code>htmlText</code> parameter
 1170        * is <code>null</code>, no changes are made to the document.</p>
 1171        *
 1172        * <p>For this to work correcty, the document must have an
 1173        * <code>HTMLEditorKit.Parser</code> set. This will be the case
 1174        * if the document was created from an HTMLEditorKit via the
 1175        * <code>createDefaultDocument</code> method.</p>
 1176        *
 1177        * @param elem the branch element to be the root for the new text
 1178        * @param htmlText the string to be parsed and assigned to <code>elem</code>
 1179        * @throws IllegalArgumentException if <code>elem</code> is a leaf
 1180        * @throws IllegalStateException if an HTMLEditorKit.Parser has not
 1181        *         been set on the document
 1182        * @since 1.3
 1183        */
 1184       public void insertAfterStart(Element elem, String htmlText) throws
 1185                                    BadLocationException, IOException {
 1186           verifyParser();
 1187           if (elem != null && elem.isLeaf()) {
 1188               throw new IllegalArgumentException
 1189                   ("Can not insert HTML after start of a leaf");
 1190           }
 1191           insertHTML(elem, elem.getStartOffset(), htmlText, false);
 1192       }
 1193   
 1194       /**
 1195        * Inserts the HTML specified as a string at the end of
 1196        * the element.
 1197        *
 1198        * <p> If <code>elem</code>'s children are leaves, and the
 1199        * character at a <code>elem.getEndOffset() - 1</code> is a newline,
 1200        * this will insert before the newline so that there isn't text after
 1201        * the newline.</p>
 1202        *
 1203        * <p>Consider the following structure (the <code>elem</code>
 1204        * parameter is <b>in bold</b>).</p>
 1205        *
 1206        * <pre>
 1207        *     &lt;body>
 1208        *       |
 1209        *     <b>&lt;div></b>
 1210        *      /  \
 1211        *    &lt;p>   &lt;p>
 1212        * </pre>
 1213        *
 1214        * <p>Invoking <code>insertBeforeEnd(elem, "&lt;ul>&lt;li>")</code>
 1215        * results in the following structure (new elements are <font
 1216        * color="red">in red</font>).</p>
 1217        *
 1218        * <pre>
 1219        *        &lt;body>
 1220        *          |
 1221        *        <b>&lt;div></b>
 1222        *       /  |  \
 1223        *     &lt;p> &lt;p> <font color="red">&lt;ul></font>
 1224        *               \
 1225        *               <font color="red">&lt;li></font>
 1226        * </pre>
 1227        *
 1228        * <p>Unlike the <code>insertAfterEnd</code> method, new elements
 1229        * become <em>children</em> of the specified element, not
 1230        * siblings.</p>
 1231        *
 1232        * <p>Parameter <code>elem</code> must not be a leaf element,
 1233        * otherwise an <code>IllegalArgumentException</code> is thrown.
 1234        * If either <code>elem</code> or <code>htmlText</code> parameter
 1235        * is <code>null</code>, no changes are made to the document.</p>
 1236        *
 1237        * <p>For this to work correcty, the document must have an
 1238        * <code>HTMLEditorKit.Parser</code> set. This will be the case
 1239        * if the document was created from an HTMLEditorKit via the
 1240        * <code>createDefaultDocument</code> method.</p>
 1241        *
 1242        * @param elem the element to be the root for the new text
 1243        * @param htmlText the string to be parsed and assigned to <code>elem</code>
 1244        * @throws IllegalArgumentException if <code>elem</code> is a leaf
 1245        * @throws IllegalStateException if an HTMLEditorKit.Parser has not
 1246        *         been set on the document
 1247        * @since 1.3
 1248        */
 1249       public void insertBeforeEnd(Element elem, String htmlText) throws
 1250                                   BadLocationException, IOException {
 1251           verifyParser();
 1252           if (elem != null && elem.isLeaf()) {
 1253               throw new IllegalArgumentException
 1254                   ("Can not set inner HTML before end of leaf");
 1255           }
 1256           if (elem != null) {
 1257               int offset = elem.getEndOffset();
 1258               if (elem.getElement(elem.getElementIndex(offset - 1)).isLeaf() &&
 1259                   getText(offset - 1, 1).charAt(0) == NEWLINE[0]) {
 1260                   offset--;
 1261               }
 1262               insertHTML(elem, offset, htmlText, false);
 1263           }
 1264       }
 1265   
 1266       /**
 1267        * Inserts the HTML specified as a string before the start of
 1268        * the given element.
 1269        *
 1270        * <p>Consider the following structure (the <code>elem</code>
 1271        * parameter is <b>in bold</b>).</p>
 1272        *
 1273        * <pre>
 1274        *     &lt;body>
 1275        *       |
 1276        *     <b>&lt;div></b>
 1277        *      /  \
 1278        *    &lt;p>   &lt;p>
 1279        * </pre>
 1280        *
 1281        * <p>Invoking <code>insertBeforeStart(elem,
 1282        * "&lt;ul>&lt;li>")</code> results in the following structure
 1283        * (new elements are <font color="red">in red</font>).</p>
 1284        *
 1285        * <pre>
 1286        *        &lt;body>
 1287        *         /  \
 1288        *      <font color="red">&lt;ul></font> <b>&lt;div></b>
 1289        *       /    /  \
 1290        *     <font color="red">&lt;li></font> &lt;p>  &lt;p>
 1291        * </pre>
 1292        *
 1293        * <p>Unlike the <code>insertAfterStart</code> method, new
 1294        * elements become <em>siblings</em> of the specified element, not
 1295        * children.</p>
 1296        *
 1297        * <p>If either <code>elem</code> or <code>htmlText</code>
 1298        * parameter is <code>null</code>, no changes are made to the
 1299        * document.</p>
 1300        *
 1301        * <p>For this to work correcty, the document must have an
 1302        * <code>HTMLEditorKit.Parser</code> set. This will be the case
 1303        * if the document was created from an HTMLEditorKit via the
 1304        * <code>createDefaultDocument</code> method.</p>
 1305        *
 1306        * @param elem the element the content is inserted before
 1307        * @param htmlText the string to be parsed and inserted before <code>elem</code>
 1308        * @throws IllegalStateException if an HTMLEditorKit.Parser has not
 1309        *         been set on the document
 1310        * @since 1.3
 1311        */
 1312       public void insertBeforeStart(Element elem, String htmlText) throws
 1313                                     BadLocationException, IOException {
 1314           verifyParser();
 1315           if (elem != null) {
 1316               Element parent = elem.getParentElement();
 1317   
 1318               if (parent != null) {
 1319                   insertHTML(parent, elem.getStartOffset(), htmlText, false);
 1320               }
 1321           }
 1322       }
 1323   
 1324       /**
 1325        * Inserts the HTML specified as a string after the the end of the
 1326        * given element.
 1327        *
 1328        * <p>Consider the following structure (the <code>elem</code>
 1329        * parameter is <b>in bold</b>).</p>
 1330        *
 1331        * <pre>
 1332        *     &lt;body>
 1333        *       |
 1334        *     <b>&lt;div></b>
 1335        *      /  \
 1336        *    &lt;p>   &lt;p>
 1337        * </pre>
 1338        *
 1339        * <p>Invoking <code>insertAfterEnd(elem, "&lt;ul>&lt;li>")</code>
 1340        * results in the following structure (new elements are <font
 1341        * color="red">in red</font>).</p>
 1342        *
 1343        * <pre>
 1344        *        &lt;body>
 1345        *         /  \
 1346        *      <b>&lt;div></b> <font color="red">&lt;ul></font>
 1347        *       / \    \
 1348        *     &lt;p> &lt;p>  <font color="red">&lt;li></font>
 1349        * </pre>
 1350        *
 1351        * <p>Unlike the <code>insertBeforeEnd</code> method, new elements
 1352        * become <em>siblings</em> of the specified element, not
 1353        * children.</p>
 1354        *
 1355        * <p>If either <code>elem</code> or <code>htmlText</code>
 1356        * parameter is <code>null</code>, no changes are made to the
 1357        * document.</p>
 1358        *
 1359        * <p>For this to work correcty, the document must have an
 1360        * <code>HTMLEditorKit.Parser</code> set. This will be the case
 1361        * if the document was created from an HTMLEditorKit via the
 1362        * <code>createDefaultDocument</code> method.</p>
 1363        *
 1364        * @param elem the element the content is inserted after
 1365        * @param htmlText the string to be parsed and inserted after <code>elem</code>
 1366        * @throws IllegalStateException if an HTMLEditorKit.Parser has not
 1367        *         been set on the document
 1368        * @since 1.3
 1369        */
 1370       public void insertAfterEnd(Element elem, String htmlText) throws
 1371                                  BadLocationException, IOException {
 1372           verifyParser();
 1373           if (elem != null) {
 1374               Element parent = elem.getParentElement();
 1375   
 1376               if (parent != null) {
 1377                   int offset = elem.getEndOffset();
 1378                   if (offset > getLength()) {
 1379                       offset--;
 1380                   }
 1381                   else if (elem.isLeaf() && getText(offset - 1, 1).
 1382                       charAt(0) == NEWLINE[0]) {
 1383                       offset--;
 1384                   }
 1385                   insertHTML(parent, offset, htmlText, false);
 1386               }
 1387           }
 1388       }
 1389   
 1390       /**
 1391        * Returns the element that has the given id <code>Attribute</code>.
 1392        * If the element can't be found, <code>null</code> is returned.
 1393        * Note that this method works on an <code>Attribute</code>,
 1394        * <i>not</i> a character tag.  In the following HTML snippet:
 1395        * <code>&lt;a id="HelloThere"&gt;</code> the attribute is
 1396        * 'id' and the character tag is 'a'.
 1397        * This is a convenience method for
 1398        * <code>getElement(RootElement, HTML.Attribute.id, id)</code>.
 1399        * This is not thread-safe.
 1400        *
 1401        * @param id  the string representing the desired <code>Attribute</code>
 1402        * @return the element with the specified <code>Attribute</code>
 1403        *          or <code>null</code> if it can't be found,
 1404        *          or <code>null</code> if <code>id</code> is <code>null</code>
 1405        * @see javax.swing.text.html.HTML.Attribute
 1406        * @since 1.3
 1407        */
 1408       public Element getElement(String id) {
 1409           if (id == null) {
 1410               return null;
 1411           }
 1412           return getElement(getDefaultRootElement(), HTML.Attribute.ID, id,
 1413                             true);
 1414       }
 1415   
 1416       /**
 1417        * Returns the child element of <code>e</code> that contains the
 1418        * attribute, <code>attribute</code> with value <code>value</code>, or
 1419        * <code>null</code> if one isn't found. This is not thread-safe.
 1420        *
 1421        * @param e the root element where the search begins
 1422        * @param attribute the desired <code>Attribute</code>
 1423        * @param value the values for the specified <code>Attribute</code>
 1424        * @return the element with the specified <code>Attribute</code>
 1425        *          and the specified <code>value</code>, or <code>null</code>
 1426        *          if it can't be found
 1427        * @see javax.swing.text.html.HTML.Attribute
 1428        * @since 1.3
 1429        */
 1430       public Element getElement(Element e, Object attribute, Object value) {
 1431           return getElement(e, attribute, value, true);
 1432       }
 1433   
 1434       /**
 1435        * Returns the child element of <code>e</code> that contains the
 1436        * attribute, <code>attribute</code> with value <code>value</code>, or
 1437        * <code>null</code> if one isn't found. This is not thread-safe.
 1438        * <p>
 1439        * If <code>searchLeafAttributes</code> is true, and <code>e</code> is
 1440        * a leaf, any attributes that are instances of <code>HTML.Tag</code>
 1441        * with a value that is an <code>AttributeSet</code> will also be checked.
 1442        *
 1443        * @param e the root element where the search begins
 1444        * @param attribute the desired <code>Attribute</code>
 1445        * @param value the values for the specified <code>Attribute</code>
 1446        * @return the element with the specified <code>Attribute</code>
 1447        *          and the specified <code>value</code>, or <code>null</code>
 1448        *          if it can't be found
 1449        * @see javax.swing.text.html.HTML.Attribute
 1450        */
 1451       private Element getElement(Element e, Object attribute, Object value,
 1452                                  boolean searchLeafAttributes) {
 1453           AttributeSet attr = e.getAttributes();
 1454   
 1455           if (attr != null && attr.isDefined(attribute)) {
 1456               if (value.equals(attr.getAttribute(attribute))) {
 1457                   return e;
 1458               }
 1459           }
 1460           if (!e.isLeaf()) {
 1461               for (int counter = 0, maxCounter = e.getElementCount();
 1462                    counter < maxCounter; counter++) {
 1463                   Element retValue = getElement(e.getElement(counter), attribute,
 1464                                                 value, searchLeafAttributes);
 1465   
 1466                   if (retValue != null) {
 1467                       return retValue;
 1468                   }
 1469               }
 1470           }
 1471           else if (searchLeafAttributes && attr != null) {
 1472               // For some leaf elements we store the actual attributes inside
 1473               // the AttributeSet of the Element (such as anchors).
 1474               Enumeration names = attr.getAttributeNames();
 1475               if (names != null) {
 1476                   while (names.hasMoreElements()) {
 1477                       Object name = names.nextElement();
 1478                       if ((name instanceof HTML.Tag) &&
 1479                           (attr.getAttribute(name) instanceof AttributeSet)) {
 1480   
 1481                           AttributeSet check = (AttributeSet)attr.
 1482                                                getAttribute(name);
 1483                           if (check.isDefined(attribute) &&
 1484                               value.equals(check.getAttribute(attribute))) {
 1485                               return e;
 1486                           }
 1487                       }
 1488                   }
 1489               }
 1490           }
 1491           return null;
 1492       }
 1493   
 1494       /**
 1495        * Verifies the document has an <code>HTMLEditorKit.Parser</code> set.
 1496        * If <code>getParser</code> returns <code>null</code>, this will throw an
 1497        * IllegalStateException.
 1498        *
 1499        * @throws IllegalStateException if the document does not have a Parser
 1500        */
 1501       private void verifyParser() {
 1502           if (getParser() == null) {
 1503               throw new IllegalStateException("No HTMLEditorKit.Parser");
 1504           }
 1505       }
 1506   
 1507       /**
 1508        * Installs a default Parser if one has not been installed yet.
 1509        */
 1510       private void installParserIfNecessary() {
 1511           if (getParser() == null) {
 1512               setParser(new HTMLEditorKit().getParser());
 1513           }
 1514       }
 1515   
 1516       /**
 1517        * Inserts a string of HTML into the document at the given position.
 1518        * <code>parent</code> is used to identify the location to insert the
 1519        * <code>html</code>. If <code>parent</code> is a leaf this can have
 1520        * unexpected results.
 1521        */
 1522       private void insertHTML(Element parent, int offset, String html,
 1523                               boolean wantsTrailingNewline)
 1524                    throws BadLocationException, IOException {
 1525           if (parent != null && html != null) {
 1526               HTMLEditorKit.Parser parser = getParser();
 1527               if (parser != null) {
 1528                   int lastOffset = Math.max(0, offset - 1);
 1529                   Element charElement = getCharacterElement(lastOffset);
 1530                   Element commonParent = parent;
 1531                   int pop = 0;
 1532                   int push = 0;
 1533   
 1534                   if (parent.getStartOffset() > lastOffset) {
 1535                       while (commonParent != null &&
 1536                              commonParent.getStartOffset() > lastOffset) {
 1537                           commonParent = commonParent.getParentElement();
 1538                           push++;
 1539                       }
 1540                       if (commonParent == null) {
 1541                           throw new BadLocationException("No common parent",
 1542                                                          offset);
 1543                       }
 1544                   }
 1545                   while (charElement != null && charElement != commonParent) {
 1546                       pop++;
 1547                       charElement = charElement.getParentElement();
 1548                   }
 1549                   if (charElement != null) {
 1550                       // Found it, do the insert.
 1551                       HTMLReader reader = new HTMLReader(offset, pop - 1, push,
 1552                                                          null, false, true,
 1553                                                          wantsTrailingNewline);
 1554   
 1555                       parser.parse(new StringReader(html), reader, true);
 1556                       reader.flush();
 1557                   }
 1558               }
 1559           }
 1560       }
 1561   
 1562       /**
 1563        * Removes child Elements of the passed in Element <code>e</code>. This
 1564        * will do the necessary cleanup to ensure the element representing the
 1565        * end character is correctly created.
 1566        * <p>This is not a general purpose method, it assumes that <code>e</code>
 1567        * will still have at least one child after the remove, and it assumes
 1568        * the character at <code>e.getStartOffset() - 1</code> is a newline and
 1569        * is of length 1.
 1570        */
 1571       private void removeElements(Element e, int index, int count) throws BadLocationException {
 1572           writeLock();
 1573           try {
 1574               int start = e.getElement(index).getStartOffset();
 1575               int end = e.getElement(index + count - 1).getEndOffset();
 1576               if (end > getLength()) {
 1577                   removeElementsAtEnd(e, index, count, start, end);
 1578               }
 1579               else {
 1580                   removeElements(e, index, count, start, end);
 1581               }
 1582           } finally {
 1583               writeUnlock();
 1584           }
 1585       }
 1586   
 1587       /**
 1588        * Called to remove child elements of <code>e</code> when one of the
 1589        * elements to remove is representing the end character.
 1590        * <p>Since the Content will not allow a removal to the end character
 1591        * this will do a remove from <code>start - 1</code> to <code>end</code>.
 1592        * The end Element(s) will be removed, and the element representing
 1593        * <code>start - 1</code> to <code>start</code> will be recreated. This
 1594        * Element has to be recreated as after the content removal its offsets
 1595        * become <code>start - 1</code> to <code>start - 1</code>.
 1596        */
 1597       private void removeElementsAtEnd(Element e, int index, int count,
 1598                            int start, int end) throws BadLocationException {
 1599           // index must be > 0 otherwise no insert would have happened.
 1600           boolean isLeaf = (e.getElement(index - 1).isLeaf());
 1601           DefaultDocumentEvent dde = new DefaultDocumentEvent(
 1602                          start - 1, end - start + 1, DocumentEvent.
 1603                          EventType.REMOVE);
 1604   
 1605           if (isLeaf) {
 1606               Element endE = getCharacterElement(getLength());
 1607               // e.getElement(index - 1) should represent the newline.
 1608               index--;
 1609               if (endE.getParentElement() != e) {
 1610                   // The hiearchies don't match, we'll have to manually
 1611                   // recreate the leaf at e.getElement(index - 1)
 1612                   replace(dde, e, index, ++count, start, end, true, true);
 1613               }
 1614               else {
 1615                   // The hierarchies for the end Element and
 1616                   // e.getElement(index - 1), match, we can safely remove
 1617                   // the Elements and the end content will be aligned
 1618                   // appropriately.
 1619                   replace(dde, e, index, count, start, end, true, false);
 1620               }
 1621           }
 1622           else {
 1623               // Not a leaf, descend until we find the leaf representing
 1624               // start - 1 and remove it.
 1625               Element newLineE = e.getElement(index - 1);
 1626               while (!newLineE.isLeaf()) {
 1627                   newLineE = newLineE.getElement(newLineE.getElementCount() - 1);
 1628               }
 1629               newLineE = newLineE.getParentElement();
 1630               replace(dde, e, index, count, start, end, false, false);
 1631               replace(dde, newLineE, newLineE.getElementCount() - 1, 1, start,
 1632                       end, true, true);
 1633           }
 1634           postRemoveUpdate(dde);
 1635           dde.end();
 1636           fireRemoveUpdate(dde);
 1637           fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
 1638       }
 1639   
 1640       /**
 1641        * This is used by <code>removeElementsAtEnd</code>, it removes
 1642        * <code>count</code> elements starting at <code>start</code> from
 1643        * <code>e</code>.  If <code>remove</code> is true text of length
 1644        * <code>start - 1</code> to <code>end - 1</code> is removed.  If
 1645        * <code>create</code> is true a new leaf is created of length 1.
 1646        */
 1647       private void replace(DefaultDocumentEvent dde, Element e, int index,
 1648                            int count, int start, int end, boolean remove,
 1649                            boolean create) throws BadLocationException {
 1650           Element[] added;
 1651           AttributeSet attrs = e.getElement(index).getAttributes();
 1652           Element[] removed = new Element[count];
 1653   
 1654           for (int counter = 0; counter < count; counter++) {
 1655               removed[counter] = e.getElement(counter + index);
 1656           }
 1657           if (remove) {
 1658               UndoableEdit u = getContent().remove(start - 1, end - start);
 1659               if (u != null) {
 1660                   dde.addEdit(u);
 1661               }
 1662           }
 1663           if (create) {
 1664               added = new Element[1];
 1665               added[0] = createLeafElement(e, attrs, start - 1, start);
 1666           }
 1667           else {
 1668               added = new Element[0];
 1669           }
 1670           dde.addEdit(new ElementEdit(e, index, removed, added));
 1671           ((AbstractDocument.BranchElement)e).replace(
 1672                                                index, removed.length, added);
 1673       }
 1674   
 1675       /**
 1676        * Called to remove child Elements when the end is not touched.
 1677        */
 1678       private void removeElements(Element e, int index, int count,
 1679                                int start, int end) throws BadLocationException {
 1680           Element[] removed = new Element[count];
 1681           Element[] added = new Element[0];
 1682           for (int counter = 0; counter < count; counter++) {
 1683               removed[counter] = e.getElement(counter + index);
 1684           }
 1685           DefaultDocumentEvent dde = new DefaultDocumentEvent
 1686                   (start, end - start, DocumentEvent.EventType.REMOVE);
 1687           ((AbstractDocument.BranchElement)e).replace(index, removed.length,
 1688                                                       added);
 1689           dde.addEdit(new ElementEdit(e, index, removed, added));
 1690           UndoableEdit u = getContent().remove(start, end - start);
 1691           if (u != null) {
 1692               dde.addEdit(u);
 1693           }
 1694           postRemoveUpdate(dde);
 1695           dde.end();
 1696           fireRemoveUpdate(dde);
 1697           if (u != null) {
 1698               fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
 1699           }
 1700       }
 1701   
 1702   
 1703       // These two are provided for inner class access. The are named different
 1704       // than the super class as the super class implementations are final.
 1705       void obtainLock() {
 1706           writeLock();
 1707       }
 1708   
 1709       void releaseLock() {
 1710           writeUnlock();
 1711       }
 1712   
 1713       //
 1714       // Provided for inner class access.
 1715       //
 1716   
 1717       /**
 1718        * Notifies all listeners that have registered interest for
 1719        * notification on this event type.  The event instance
 1720        * is lazily created using the parameters passed into
 1721        * the fire method.
 1722        *
 1723        * @param e the event
 1724        * @see EventListenerList
 1725        */
 1726       protected void fireChangedUpdate(DocumentEvent e) {
 1727           super.fireChangedUpdate(e);
 1728       }
 1729   
 1730       /**
 1731        * Notifies all listeners that have registered interest for
 1732        * notification on this event type.  The event instance
 1733        * is lazily created using the parameters passed into
 1734        * the fire method.
 1735        *
 1736        * @param e the event
 1737        * @see EventListenerList
 1738        */
 1739       protected void fireUndoableEditUpdate(UndoableEditEvent e) {
 1740           super.fireUndoableEditUpdate(e);
 1741       }
 1742   
 1743       boolean hasBaseTag() {
 1744           return hasBaseTag;
 1745       }
 1746   
 1747       String getBaseTarget() {
 1748           return baseTarget;
 1749       }
 1750   
 1751       /*
 1752        * state defines whether the document is a frame document
 1753        * or not.
 1754        */
 1755       private boolean frameDocument = false;
 1756       private boolean preservesUnknownTags = true;
 1757   
 1758       /*
 1759        * Used to store button groups for radio buttons in
 1760        * a form.
 1761        */
 1762       private HashMap radioButtonGroupsMap;
 1763   
 1764       /**
 1765        * Document property for the number of tokens to buffer
 1766        * before building an element subtree to represent them.
 1767        */
 1768       static final String TokenThreshold = "token threshold";
 1769   
 1770       private static final int MaxThreshold = 10000;
 1771   
 1772       private static final int StepThreshold = 5;
 1773   
 1774   
 1775       /**
 1776        * Document property key value. The value for the key will be a Vector
 1777        * of Strings that are comments not found in the body.
 1778        */
 1779       public static final String AdditionalComments = "AdditionalComments";
 1780   
 1781       /**
 1782        * Document property key value. The value for the key will be a
 1783        * String indicating the default type of stylesheet links.
 1784        */
 1785       /* public */ static final String StyleType = "StyleType";
 1786   
 1787       /**
 1788        * The location to resolve relative URLs against.  By
 1789        * default this will be the document's URL if the document
 1790        * was loaded from a URL.  If a base tag is found and
 1791        * can be parsed, it will be used as the base location.
 1792        */
 1793       URL base;
 1794   
 1795       /**
 1796        * does the document have base tag
 1797        */
 1798       boolean hasBaseTag = false;
 1799   
 1800       /**
 1801        * BASE tag's TARGET attribute value
 1802        */
 1803       private String baseTarget = null;
 1804   
 1805       /**
 1806        * The parser that is used when inserting html into the existing
 1807        * document.
 1808        */
 1809       private HTMLEditorKit.Parser parser;
 1810   
 1811       /**
 1812        * Used for inserts when a null AttributeSet is supplied.
 1813        */
 1814       private static AttributeSet contentAttributeSet;
 1815   
 1816       /**
 1817        * Property Maps are registered under, will be a Hashtable.
 1818        */
 1819       static String MAP_PROPERTY = "__MAP__";
 1820   
 1821       private static char[] NEWLINE;
 1822       private static final String IMPLIED_CR = "CR";
 1823   
 1824       /**
 1825        * I18N property key.
 1826        *
 1827        * @see AbstractDocument.I18NProperty
 1828        */
 1829       private static final String I18NProperty = "i18n";
 1830   
 1831       static {
 1832           contentAttributeSet = new SimpleAttributeSet();
 1833           ((MutableAttributeSet)contentAttributeSet).
 1834                           addAttribute(StyleConstants.NameAttribute,
 1835                                        HTML.Tag.CONTENT);
 1836           NEWLINE = new char[1];
 1837           NEWLINE[0] = '\n';
 1838       }
 1839   
 1840   
 1841       /**
 1842        * An iterator to iterate over a particular type of
 1843        * tag.  The iterator is not thread safe.  If reliable
 1844        * access to the document is not already ensured by
 1845        * the context under which the iterator is being used,
 1846        * its use should be performed under the protection of
 1847        * Document.render.
 1848        */
 1849       public static abstract class Iterator {
 1850   
 1851           /**
 1852            * Return the attributes for this tag.
 1853            * @return the <code>AttributeSet</code> for this tag, or
 1854            *      <code>null</code> if none can be found
 1855            */
 1856           public abstract AttributeSet getAttributes();
 1857   
 1858           /**
 1859            * Returns the start of the range for which the current occurrence of
 1860            * the tag is defined and has the same attributes.
 1861            *
 1862            * @return the start of the range, or -1 if it can't be found
 1863            */
 1864           public abstract int getStartOffset();
 1865   
 1866           /**
 1867            * Returns the end of the range for which the current occurrence of
 1868            * the tag is defined and has the same attributes.
 1869            *
 1870            * @return the end of the range
 1871            */
 1872           public abstract int getEndOffset();
 1873   
 1874           /**
 1875            * Move the iterator forward to the next occurrence
 1876            * of the tag it represents.
 1877            */
 1878           public abstract void next();
 1879   
 1880           /**
 1881            * Indicates if the iterator is currently
 1882            * representing an occurrence of a tag.  If
 1883            * false there are no more tags for this iterator.
 1884            * @return true if the iterator is currently representing an
 1885            *              occurrence of a tag, otherwise returns false
 1886            */
 1887           public abstract boolean isValid();
 1888   
 1889           /**
 1890            * Type of tag this iterator represents.
 1891            */
 1892           public abstract HTML.Tag getTag();
 1893       }
 1894   
 1895       /**
 1896        * An iterator to iterate over a particular type of tag.
 1897        */
 1898       static class LeafIterator extends Iterator {
 1899   
 1900           LeafIterator(HTML.Tag t, Document doc) {
 1901               tag = t;
 1902               pos = new ElementIterator(doc);
 1903               endOffset = 0;
 1904               next();
 1905           }
 1906   
 1907           /**
 1908            * Returns the attributes for this tag.
 1909            * @return the <code>AttributeSet</code> for this tag,
 1910            *              or <code>null</code> if none can be found
 1911            */
 1912           public AttributeSet getAttributes() {
 1913               Element elem = pos.current();
 1914               if (elem != null) {
 1915                   AttributeSet a = (AttributeSet)
 1916                       elem.getAttributes().getAttribute(tag);
 1917                   if (a == null) {
 1918                       a = (AttributeSet)elem.getAttributes();
 1919                   }
 1920                   return a;
 1921               }
 1922               return null;
 1923           }
 1924   
 1925           /**
 1926            * Returns the start of the range for which the current occurrence of
 1927            * the tag is defined and has the same attributes.
 1928            *
 1929            * @return the start of the range, or -1 if it can't be found
 1930            */
 1931           public int getStartOffset() {
 1932               Element elem = pos.current();
 1933               if (elem != null) {
 1934                   return elem.getStartOffset();
 1935               }
 1936               return -1;
 1937           }
 1938   
 1939           /**
 1940            * Returns the end of the range for which the current occurrence of
 1941            * the tag is defined and has the same attributes.
 1942            *
 1943            * @return the end of the range
 1944            */
 1945           public int getEndOffset() {
 1946               return endOffset;
 1947           }
 1948   
 1949           /**
 1950            * Moves the iterator forward to the next occurrence
 1951            * of the tag it represents.
 1952            */
 1953           public void next() {
 1954               for (nextLeaf(pos); isValid(); nextLeaf(pos)) {
 1955                   Element elem = pos.current();
 1956                   if (elem.getStartOffset() >= endOffset) {
 1957                       AttributeSet a = pos.current().getAttributes();
 1958   
 1959                       if (a.isDefined(tag) ||
 1960                           a.getAttribute(StyleConstants.NameAttribute) == tag) {
 1961   
 1962                           // we found the next one
 1963                           setEndOffset();
 1964                           break;
 1965                       }
 1966                   }
 1967               }
 1968           }
 1969   
 1970           /**
 1971            * Returns the type of tag this iterator represents.
 1972            *
 1973            * @return the <code>HTML.Tag</code> that this iterator represents.
 1974            * @see javax.swing.text.html.HTML.Tag
 1975            */
 1976           public HTML.Tag getTag() {
 1977               return tag;
 1978           }
 1979   
 1980           /**
 1981            * Returns true if the current position is not <code>null</code>.
 1982            * @return true if current position is not <code>null</code>,
 1983            *              otherwise returns false
 1984            */
 1985           public boolean isValid() {
 1986               return (pos.current() != null);
 1987           }
 1988   
 1989           /**
 1990            * Moves the given iterator to the next leaf element.
 1991            * @param iter  the iterator to be scanned
 1992            */
 1993           void nextLeaf(ElementIterator iter) {
 1994               for (iter.next(); iter.current() != null; iter.next()) {
 1995                   Element e = iter.current();
 1996                   if (e.isLeaf()) {
 1997                       break;
 1998                   }
 1999               }
 2000           }
 2001   
 2002           /**
 2003            * Marches a cloned iterator forward to locate the end
 2004            * of the run.  This sets the value of <code>endOffset</code>.
 2005            */
 2006           void setEndOffset() {
 2007               AttributeSet a0 = getAttributes();
 2008               endOffset = pos.current().getEndOffset();
 2009               ElementIterator fwd = (ElementIterator) pos.clone();
 2010               for (nextLeaf(fwd); fwd.current() != null; nextLeaf(fwd)) {
 2011                   Element e = fwd.current();
 2012                   AttributeSet a1 = (AttributeSet) e.getAttributes().getAttribute(tag);
 2013                   if ((a1 == null) || (! a1.equals(a0))) {
 2014                       break;
 2015                   }
 2016                   endOffset = e.getEndOffset();
 2017               }
 2018           }
 2019   
 2020           private int endOffset;
 2021           private HTML.Tag tag;
 2022           private ElementIterator pos;
 2023   
 2024       }
 2025   
 2026       /**
 2027        * An HTML reader to load an HTML document with an HTML
 2028        * element structure.  This is a set of callbacks from
 2029        * the parser, implemented to create a set of elements
 2030        * tagged with attributes.  The parse builds up tokens
 2031        * (ElementSpec) that describe the element subtree desired,
 2032        * and burst it into the document under the protection of
 2033        * a write lock using the insert method on the document
 2034        * outer class.
 2035        * <p>
 2036        * The reader can be configured by registering actions
 2037        * (of type <code>HTMLDocument.HTMLReader.TagAction</code>)
 2038        * that describe how to handle the action.  The idea behind
 2039        * the actions provided is that the most natural text editing
 2040        * operations can be provided if the element structure boils
 2041        * down to paragraphs with runs of some kind of style
 2042        * in them.  Some things are more naturally specified
 2043        * structurally, so arbitrary structure should be allowed
 2044        * above the paragraphs, but will need to be edited with structural
 2045        * actions.  The implication of this is that some of the
 2046        * HTML elements specified in the stream being parsed will
 2047        * be collapsed into attributes, and in some cases paragraphs
 2048        * will be synthesized.  When HTML elements have been
 2049        * converted to attributes, the attribute key will be of
 2050        * type HTML.Tag, and the value will be of type AttributeSet
 2051        * so that no information is lost.  This enables many of the
 2052        * existing actions to work so that the user can type input,
 2053        * hit the return key, backspace, delete, etc and have a
 2054        * reasonable result.  Selections can be created, and attributes
 2055        * applied or removed, etc.  With this in mind, the work done
 2056        * by the reader can be categorized into the following kinds
 2057        * of tasks:
 2058        * <dl>
 2059        * <dt>Block
 2060        * <dd>Build the structure like it's specified in the stream.
 2061        * This produces elements that contain other elements.
 2062        * <dt>Paragraph
 2063        * <dd>Like block except that it's expected that the element
 2064        * will be used with a paragraph view so a paragraph element
 2065        * won't need to be synthesized.
 2066        * <dt>Character
 2067        * <dd>Contribute the element as an attribute that will start
 2068        * and stop at arbitrary text locations.  This will ultimately
 2069        * be mixed into a run of text, with all of the currently
 2070        * flattened HTML character elements.
 2071        * <dt>Special
 2072        * <dd>Produce an embedded graphical element.
 2073        * <dt>Form
 2074        * <dd>Produce an element that is like the embedded graphical
 2075        * element, except that it also has a component model associated
 2076        * with it.
 2077        * <dt>Hidden
 2078        * <dd>Create an element that is hidden from view when the
 2079        * document is being viewed read-only, and visible when the
 2080        * document is being edited.  This is useful to keep the
 2081        * model from losing information, and used to store things
 2082        * like comments and unrecognized tags.
 2083        *
 2084        * </dl>
 2085        * <p>
 2086        * Currently, &lt;APPLET&gt;, &lt;PARAM&gt;, &lt;MAP&gt;, &lt;AREA&gt;, &lt;LINK&gt;,
 2087        * &lt;SCRIPT&gt; and &lt;STYLE&gt; are unsupported.
 2088        *
 2089        * <p>
 2090        * The assignment of the actions described is shown in the
 2091        * following table for the tags defined in <code>HTML.Tag</code>.<P>
 2092        * <table border=1 summary="HTML tags and assigned actions">
 2093        * <tr><th>Tag</th><th>Action</th></tr>
 2094        * <tr><td><code>HTML.Tag.A</code>         <td>CharacterAction
 2095        * <tr><td><code>HTML.Tag.ADDRESS</code>   <td>CharacterAction
 2096        * <tr><td><code>HTML.Tag.APPLET</code>    <td>HiddenAction
 2097        * <tr><td><code>HTML.Tag.AREA</code>      <td>AreaAction
 2098        * <tr><td><code>HTML.Tag.B</code>         <td>CharacterAction
 2099        * <tr><td><code>HTML.Tag.BASE</code>      <td>BaseAction
 2100        * <tr><td><code>HTML.Tag.BASEFONT</code>  <td>CharacterAction
 2101        * <tr><td><code>HTML.Tag.BIG</code>       <td>CharacterAction
 2102        * <tr><td><code>HTML.Tag.BLOCKQUOTE</code><td>BlockAction
 2103        * <tr><td><code>HTML.Tag.BODY</code>      <td>BlockAction
 2104        * <tr><td><code>HTML.Tag.BR</code>        <td>SpecialAction
 2105        * <tr><td><code>HTML.Tag.CAPTION</code>   <td>BlockAction
 2106        * <tr><td><code>HTML.Tag.CENTER</code>    <td>BlockAction
 2107        * <tr><td><code>HTML.Tag.CITE</code>      <td>CharacterAction
 2108        * <tr><td><code>HTML.Tag.CODE</code>      <td>CharacterAction
 2109        * <tr><td><code>HTML.Tag.DD</code>        <td>BlockAction
 2110        * <tr><td><code>HTML.Tag.DFN</code>       <td>CharacterAction
 2111        * <tr><td><code>HTML.Tag.DIR</code>       <td>BlockAction
 2112        * <tr><td><code>HTML.Tag.DIV</code>       <td>BlockAction
 2113        * <tr><td><code>HTML.Tag.DL</code>        <td>BlockAction
 2114        * <tr><td><code>HTML.Tag.DT</code>        <td>ParagraphAction
 2115        * <tr><td><code>HTML.Tag.EM</code>        <td>CharacterAction
 2116        * <tr><td><code>HTML.Tag.FONT</code>      <td>CharacterAction
 2117        * <tr><td><code>HTML.Tag.FORM</code>      <td>As of 1.4 a BlockAction
 2118        * <tr><td><code>HTML.Tag.FRAME</code>     <td>SpecialAction
 2119        * <tr><td><code>HTML.Tag.FRAMESET</code>  <td>BlockAction
 2120        * <tr><td><code>HTML.Tag.H1</code>        <td>ParagraphAction
 2121        * <tr><td><code>HTML.Tag.H2</code>        <td>ParagraphAction
 2122        * <tr><td><code>HTML.Tag.H3</code>        <td>ParagraphAction
 2123        * <tr><td><code>HTML.Tag.H4</code>        <td>ParagraphAction
 2124        * <tr><td><code>HTML.Tag.H5</code>        <td>ParagraphAction
 2125        * <tr><td><code>HTML.Tag.H6</code>        <td>ParagraphAction
 2126        * <tr><td><code>HTML.Tag.HEAD</code>      <td>HeadAction
 2127        * <tr><td><code>HTML.Tag.HR</code>        <td>SpecialAction
 2128        * <tr><td><code>HTML.Tag.HTML</code>      <td>BlockAction
 2129        * <tr><td><code>HTML.Tag.I</code>         <td>CharacterAction
 2130        * <tr><td><code>HTML.Tag.IMG</code>       <td>SpecialAction
 2131        * <tr><td><code>HTML.Tag.INPUT</code>     <td>FormAction
 2132        * <tr><td><code>HTML.Tag.ISINDEX</code>   <td>IsndexAction
 2133        * <tr><td><code>HTML.Tag.KBD</code>       <td>CharacterAction
 2134        * <tr><td><code>HTML.Tag.LI</code>        <td>BlockAction
 2135        * <tr><td><code>HTML.Tag.LINK</code>      <td>LinkAction
 2136        * <tr><td><code>HTML.Tag.MAP</code>       <td>MapAction
 2137        * <tr><td><code>HTML.Tag.MENU</code>      <td>BlockAction
 2138        * <tr><td><code>HTML.Tag.META</code>      <td>MetaAction
 2139        * <tr><td><code>HTML.Tag.NOFRAMES</code>  <td>BlockAction
 2140        * <tr><td><code>HTML.Tag.OBJECT</code>    <td>SpecialAction
 2141        * <tr><td><code>HTML.Tag.OL</code>        <td>BlockAction
 2142        * <tr><td><code>HTML.Tag.OPTION</code>    <td>FormAction
 2143        * <tr><td><code>HTML.Tag.P</code>         <td>ParagraphAction
 2144        * <tr><td><code>HTML.Tag.PARAM</code>     <td>HiddenAction
 2145        * <tr><td><code>HTML.Tag.PRE</code>       <td>PreAction
 2146        * <tr><td><code>HTML.Tag.SAMP</code>      <td>CharacterAction
 2147        * <tr><td><code>HTML.Tag.SCRIPT</code>    <td>HiddenAction
 2148        * <tr><td><code>HTML.Tag.SELECT</code>    <td>FormAction
 2149        * <tr><td><code>HTML.Tag.SMALL</code>     <td>CharacterAction
 2150        * <tr><td><code>HTML.Tag.STRIKE</code>    <td>CharacterAction
 2151        * <tr><td><code>HTML.Tag.S</code>         <td>CharacterAction
 2152        * <tr><td><code>HTML.Tag.STRONG</code>    <td>CharacterAction
 2153        * <tr><td><code>HTML.Tag.STYLE</code>     <td>StyleAction
 2154        * <tr><td><code>HTML.Tag.SUB</code>       <td>CharacterAction
 2155        * <tr><td><code>HTML.Tag.SUP</code>       <td>CharacterAction
 2156        * <tr><td><code>HTML.Tag.TABLE</code>     <td>BlockAction
 2157        * <tr><td><code>HTML.Tag.TD</code>        <td>BlockAction
 2158        * <tr><td><code>HTML.Tag.TEXTAREA</code>  <td>FormAction
 2159        * <tr><td><code>HTML.Tag.TH</code>        <td>BlockAction
 2160        * <tr><td><code>HTML.Tag.TITLE</code>     <td>TitleAction
 2161        * <tr><td><code>HTML.Tag.TR</code>        <td>BlockAction
 2162        * <tr><td><code>HTML.Tag.TT</code>        <td>CharacterAction
 2163        * <tr><td><code>HTML.Tag.U</code>         <td>CharacterAction
 2164        * <tr><td><code>HTML.Tag.UL</code>        <td>BlockAction
 2165        * <tr><td><code>HTML.Tag.VAR</code>       <td>CharacterAction
 2166        * </table>
 2167        * <p>
 2168        * Once &lt;/html> is encountered, the Actions are no longer notified.
 2169        */
 2170       public class HTMLReader extends HTMLEditorKit.ParserCallback {
 2171   
 2172           public HTMLReader(int offset) {
 2173               this(offset, 0, 0, null);
 2174           }
 2175   
 2176           public HTMLReader(int offset, int popDepth, int pushDepth,
 2177                             HTML.Tag insertTag) {
 2178               this(offset, popDepth, pushDepth, insertTag, true, false, true);
 2179           }
 2180   
 2181           /**
 2182            * Generates a RuntimeException (will eventually generate
 2183            * a BadLocationException when API changes are alloced) if inserting
 2184            * into non empty document, <code>insertTag</code> is
 2185            * non-<code>null</code>, and <code>offset</code> is not in the body.
 2186            */
 2187           // PENDING(sky): Add throws BadLocationException and remove
 2188           // RuntimeException
 2189           HTMLReader(int offset, int popDepth, int pushDepth,
 2190                      HTML.Tag insertTag, boolean insertInsertTag,
 2191                      boolean insertAfterImplied, boolean wantsTrailingNewline) {
 2192               emptyDocument = (getLength() == 0);
 2193               isStyleCSS = "text/css".equals(getDefaultStyleSheetType());
 2194               this.offset = offset;
 2195               threshold = HTMLDocument.this.getTokenThreshold();
 2196               tagMap = new Hashtable(57);
 2197               TagAction na = new TagAction();
 2198               TagAction ba = new BlockAction();
 2199               TagAction pa = new ParagraphAction();
 2200               TagAction ca = new CharacterAction();
 2201               TagAction sa = new SpecialAction();
 2202               TagAction fa = new FormAction();
 2203               TagAction ha = new HiddenAction();
 2204               TagAction conv = new ConvertAction();
 2205   
 2206               // register handlers for the well known tags
 2207               tagMap.put(HTML.Tag.A, new AnchorAction());
 2208               tagMap.put(HTML.Tag.ADDRESS, ca);
 2209               tagMap.put(HTML.Tag.APPLET, ha);
 2210               tagMap.put(HTML.Tag.AREA, new AreaAction());
 2211               tagMap.put(HTML.Tag.B, conv);
 2212               tagMap.put(HTML.Tag.BASE, new BaseAction());
 2213               tagMap.put(HTML.Tag.BASEFONT, ca);
 2214               tagMap.put(HTML.Tag.BIG, ca);
 2215               tagMap.put(HTML.Tag.BLOCKQUOTE, ba);
 2216               tagMap.put(HTML.Tag.BODY, ba);
 2217               tagMap.put(HTML.Tag.BR, sa);
 2218               tagMap.put(HTML.Tag.CAPTION, ba);
 2219               tagMap.put(HTML.Tag.CENTER, ba);
 2220               tagMap.put(HTML.Tag.CITE, ca);
 2221               tagMap.put(HTML.Tag.CODE, ca);
 2222               tagMap.put(HTML.Tag.DD, ba);
 2223               tagMap.put(HTML.Tag.DFN, ca);
 2224               tagMap.put(HTML.Tag.DIR, ba);
 2225               tagMap.put(HTML.Tag.DIV, ba);
 2226               tagMap.put(HTML.Tag.DL, ba);
 2227               tagMap.put(HTML.Tag.DT, pa);
 2228               tagMap.put(HTML.Tag.EM