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, ca);
 2229               tagMap.put(HTML.Tag.FONT, conv);
 2230               tagMap.put(HTML.Tag.FORM, new FormTagAction());
 2231               tagMap.put(HTML.Tag.FRAME, sa);
 2232               tagMap.put(HTML.Tag.FRAMESET, ba);
 2233               tagMap.put(HTML.Tag.H1, pa);
 2234               tagMap.put(HTML.Tag.H2, pa);
 2235               tagMap.put(HTML.Tag.H3, pa);
 2236               tagMap.put(HTML.Tag.H4, pa);
 2237               tagMap.put(HTML.Tag.H5, pa);
 2238               tagMap.put(HTML.Tag.H6, pa);
 2239               tagMap.put(HTML.Tag.HEAD, new HeadAction());
 2240               tagMap.put(HTML.Tag.HR, sa);
 2241               tagMap.put(HTML.Tag.HTML, ba);
 2242               tagMap.put(HTML.Tag.I, conv);
 2243               tagMap.put(HTML.Tag.IMG, sa);
 2244               tagMap.put(HTML.Tag.INPUT, fa);
 2245               tagMap.put(HTML.Tag.ISINDEX, new IsindexAction());
 2246               tagMap.put(HTML.Tag.KBD, ca);
 2247               tagMap.put(HTML.Tag.LI, ba);
 2248               tagMap.put(HTML.Tag.LINK, new LinkAction());
 2249               tagMap.put(HTML.Tag.MAP, new MapAction());
 2250               tagMap.put(HTML.Tag.MENU, ba);
 2251               tagMap.put(HTML.Tag.META, new MetaAction());
 2252               tagMap.put(HTML.Tag.NOBR, ca);
 2253               tagMap.put(HTML.Tag.NOFRAMES, ba);
 2254               tagMap.put(HTML.Tag.OBJECT, sa);
 2255               tagMap.put(HTML.Tag.OL, ba);
 2256               tagMap.put(HTML.Tag.OPTION, fa);
 2257               tagMap.put(HTML.Tag.P, pa);
 2258               tagMap.put(HTML.Tag.PARAM, new ObjectAction());
 2259               tagMap.put(HTML.Tag.PRE, new PreAction());
 2260               tagMap.put(HTML.Tag.SAMP, ca);
 2261               tagMap.put(HTML.Tag.SCRIPT, ha);
 2262               tagMap.put(HTML.Tag.SELECT, fa);
 2263               tagMap.put(HTML.Tag.SMALL, ca);
 2264               tagMap.put(HTML.Tag.SPAN, ca);
 2265               tagMap.put(HTML.Tag.STRIKE, conv);
 2266               tagMap.put(HTML.Tag.S, ca);
 2267               tagMap.put(HTML.Tag.STRONG, ca);
 2268               tagMap.put(HTML.Tag.STYLE, new StyleAction());
 2269               tagMap.put(HTML.Tag.SUB, conv);
 2270               tagMap.put(HTML.Tag.SUP, conv);
 2271               tagMap.put(HTML.Tag.TABLE, ba);
 2272               tagMap.put(HTML.Tag.TD, ba);
 2273               tagMap.put(HTML.Tag.TEXTAREA, fa);
 2274               tagMap.put(HTML.Tag.TH, ba);
 2275               tagMap.put(HTML.Tag.TITLE, new TitleAction());
 2276               tagMap.put(HTML.Tag.TR, ba);
 2277               tagMap.put(HTML.Tag.TT, ca);
 2278               tagMap.put(HTML.Tag.U, conv);
 2279               tagMap.put(HTML.Tag.UL, ba);
 2280               tagMap.put(HTML.Tag.VAR, ca);
 2281   
 2282               if (insertTag != null) {
 2283                   this.insertTag = insertTag;
 2284                   this.popDepth = popDepth;
 2285                   this.pushDepth = pushDepth;
 2286                   this.insertInsertTag = insertInsertTag;
 2287                   foundInsertTag = false;
 2288               }
 2289               else {
 2290                   foundInsertTag = true;
 2291               }
 2292               if (insertAfterImplied) {
 2293                   this.popDepth = popDepth;
 2294                   this.pushDepth = pushDepth;
 2295                   this.insertAfterImplied = true;
 2296                   foundInsertTag = false;
 2297                   midInsert = false;
 2298                   this.insertInsertTag = true;
 2299                   this.wantsTrailingNewline = wantsTrailingNewline;
 2300               }
 2301               else {
 2302                   midInsert = (!emptyDocument && insertTag == null);
 2303                   if (midInsert) {
 2304                       generateEndsSpecsForMidInsert();
 2305                   }
 2306               }
 2307   
 2308               /**
 2309                * This block initializes the <code>inParagraph</code> flag.
 2310                * It is left in <code>false</code> value automatically
 2311                * if the target document is empty or future inserts
 2312                * were positioned into the 'body' tag.
 2313                */
 2314               if (!emptyDocument && !midInsert) {
 2315                   int targetOffset = Math.max(this.offset - 1, 0);
 2316                   Element elem =
 2317                           HTMLDocument.this.getCharacterElement(targetOffset);
 2318                   /* Going up by the left document structure path */
 2319                   for (int i = 0; i <= this.popDepth; i++) {
 2320                       elem = elem.getParentElement();
 2321                   }
 2322                   /* Going down by the right document structure path */
 2323                   for (int i = 0; i < this.pushDepth; i++) {
 2324                       int index = elem.getElementIndex(this.offset);
 2325                       elem = elem.getElement(index);
 2326                   }
 2327                   AttributeSet attrs = elem.getAttributes();
 2328                   if (attrs != null) {
 2329                       HTML.Tag tagToInsertInto =
 2330                               (HTML.Tag) attrs.getAttribute(StyleConstants.NameAttribute);
 2331                       if (tagToInsertInto != null) {
 2332                           this.inParagraph = tagToInsertInto.isParagraph();
 2333                       }
 2334                   }
 2335               }
 2336           }
 2337   
 2338           /**
 2339            * Generates an initial batch of end <code>ElementSpecs</code>
 2340            * in parseBuffer to position future inserts into the body.
 2341            */
 2342           private void generateEndsSpecsForMidInsert() {
 2343               int           count = heightToElementWithName(HTML.Tag.BODY,
 2344                                                      Math.max(0, offset - 1));
 2345               boolean       joinNext = false;
 2346   
 2347               if (count == -1 && offset > 0) {
 2348                   count = heightToElementWithName(HTML.Tag.BODY, offset);
 2349                   if (count != -1) {
 2350                       // Previous isn't in body, but current is. Have to
 2351                       // do some end specs, followed by join next.
 2352                       count = depthTo(offset - 1) - 1;
 2353                       joinNext = true;
 2354                   }
 2355               }
 2356               if (count == -1) {
 2357                   throw new RuntimeException("Must insert new content into body element-");
 2358               }
 2359               if (count != -1) {
 2360                   // Insert a newline, if necessary.
 2361                   try {
 2362                       if (!joinNext && offset > 0 &&
 2363                           !getText(offset - 1, 1).equals("\n")) {
 2364                           SimpleAttributeSet newAttrs = new SimpleAttributeSet();
 2365                           newAttrs.addAttribute(StyleConstants.NameAttribute,
 2366                                                 HTML.Tag.CONTENT);
 2367                           ElementSpec spec = new ElementSpec(newAttrs,
 2368                                       ElementSpec.ContentType, NEWLINE, 0, 1);
 2369                           parseBuffer.addElement(spec);
 2370                       }
 2371                       // Should never throw, but will catch anyway.
 2372                   } catch (BadLocationException ble) {}
 2373                   while (count-- > 0) {
 2374                       parseBuffer.addElement(new ElementSpec
 2375                                              (null, ElementSpec.EndTagType));
 2376                   }
 2377                   if (joinNext) {
 2378                       ElementSpec spec = new ElementSpec(null, ElementSpec.
 2379                                                          StartTagType);
 2380   
 2381                       spec.setDirection(ElementSpec.JoinNextDirection);
 2382                       parseBuffer.addElement(spec);
 2383                   }
 2384               }
 2385               // We should probably throw an exception if (count == -1)
 2386               // Or look for the body and reset the offset.
 2387           }
 2388   
 2389           /**
 2390            * @return number of parents to reach the child at offset.
 2391            */
 2392           private int depthTo(int offset) {
 2393               Element       e = getDefaultRootElement();
 2394               int           count = 0;
 2395   
 2396               while (!e.isLeaf()) {
 2397                   count++;
 2398                   e = e.getElement(e.getElementIndex(offset));
 2399               }
 2400               return count;
 2401           }
 2402   
 2403           /**
 2404            * @return number of parents of the leaf at <code>offset</code>
 2405            *         until a parent with name, <code>name</code> has been
 2406            *         found. -1 indicates no matching parent with
 2407            *         <code>name</code>.
 2408            */
 2409           private int heightToElementWithName(Object name, int offset) {
 2410               Element       e = getCharacterElement(offset).getParentElement();
 2411               int           count = 0;
 2412   
 2413               while (e != null && e.getAttributes().getAttribute
 2414                      (StyleConstants.NameAttribute) != name) {
 2415                   count++;
 2416                   e = e.getParentElement();
 2417               }
 2418               return (e == null) ? -1 : count;
 2419           }
 2420   
 2421           /**
 2422            * This will make sure there aren't two BODYs (the second is
 2423            * typically created when you do a remove all, and then an insert).
 2424            */
 2425           private void adjustEndElement() {
 2426               int length = getLength();
 2427               if (length == 0) {
 2428                   return;
 2429               }
 2430               obtainLock();
 2431               try {
 2432                   Element[] pPath = getPathTo(length - 1);
 2433                   int pLength = pPath.length;
 2434                   if (pLength > 1 && pPath[1].getAttributes().getAttribute
 2435                            (StyleConstants.NameAttribute) == HTML.Tag.BODY &&
 2436                            pPath[1].getEndOffset() == length) {
 2437                       String lastText = getText(length - 1, 1);
 2438                       DefaultDocumentEvent event = null;
 2439                       Element[] added;
 2440                       Element[] removed;
 2441                       int index;
 2442                       // Remove the fake second body.
 2443                       added = new Element[0];
 2444                       removed = new Element[1];
 2445                       index = pPath[0].getElementIndex(length);
 2446                       removed[0] = pPath[0].getElement(index);
 2447                       ((BranchElement)pPath[0]).replace(index, 1, added);
 2448                       ElementEdit firstEdit = new ElementEdit(pPath[0], index,
 2449                                                               removed, added);
 2450   
 2451                       // Insert a new element to represent the end that the
 2452                       // second body was representing.
 2453                       SimpleAttributeSet sas = new SimpleAttributeSet();
 2454                       sas.addAttribute(StyleConstants.NameAttribute,
 2455                                            HTML.Tag.CONTENT);
 2456                       sas.addAttribute(IMPLIED_CR, Boolean.TRUE);
 2457                       added = new Element[1];
 2458                       added[0] = createLeafElement(pPath[pLength - 1],
 2459                                                        sas, length, length + 1);
 2460                       index = pPath[pLength - 1].getElementCount();
 2461                       ((BranchElement)pPath[pLength - 1]).replace(index, 0,
 2462                                                                   added);
 2463                       event = new DefaultDocumentEvent(length, 1,
 2464                                               DocumentEvent.EventType.CHANGE);
 2465                       event.addEdit(new ElementEdit(pPath[pLength - 1],
 2466                                            index, new Element[0], added));
 2467                       event.addEdit(firstEdit);
 2468                       event.end();
 2469                       fireChangedUpdate(event);
 2470                       fireUndoableEditUpdate(new UndoableEditEvent(this, event));
 2471   
 2472                       if (lastText.equals("\n")) {
 2473                           // We now have two \n's, one part of the Document.
 2474                           // We need to remove one
 2475                           event = new DefaultDocumentEvent(length - 1, 1,
 2476                                              DocumentEvent.EventType.REMOVE);
 2477                           removeUpdate(event);
 2478                           UndoableEdit u = getContent().remove(length - 1, 1);
 2479                           if (u != null) {
 2480                               event.addEdit(u);
 2481                           }
 2482                           postRemoveUpdate(event);
 2483                           // Mark the edit as done.
 2484                           event.end();
 2485                           fireRemoveUpdate(event);
 2486                           fireUndoableEditUpdate(new UndoableEditEvent(
 2487                                                  this, event));
 2488                       }
 2489                   }
 2490               }
 2491               catch (BadLocationException ble) {
 2492               }
 2493               finally {
 2494                   releaseLock();
 2495               }
 2496           }
 2497   
 2498           private Element[] getPathTo(int offset) {
 2499               Stack elements = new Stack();
 2500               Element e = getDefaultRootElement();
 2501               int index;
 2502               while (!e.isLeaf()) {
 2503                   elements.push(e);
 2504                   e = e.getElement(e.getElementIndex(offset));
 2505               }
 2506               Element[] retValue = new Element[elements.size()];
 2507               elements.copyInto(retValue);
 2508               return retValue;
 2509           }
 2510   
 2511           // -- HTMLEditorKit.ParserCallback methods --------------------
 2512   
 2513           /**
 2514            * The last method called on the reader.  It allows
 2515            * any pending changes to be flushed into the document.
 2516            * Since this is currently loading synchronously, the entire
 2517            * set of changes are pushed in at this point.
 2518            */
 2519           public void flush() throws BadLocationException {
 2520               if (emptyDocument && !insertAfterImplied) {
 2521                   if (HTMLDocument.this.getLength() > 0 ||
 2522                                         parseBuffer.size() > 0) {
 2523                       flushBuffer(true);
 2524                       adjustEndElement();
 2525                   }
 2526                   // We won't insert when
 2527               }
 2528               else {
 2529                   flushBuffer(true);
 2530               }
 2531           }
 2532   
 2533           /**
 2534            * Called by the parser to indicate a block of text was
 2535            * encountered.
 2536            */
 2537           public void handleText(char[] data, int pos) {
 2538               if (receivedEndHTML || (midInsert && !inBody)) {
 2539                   return;
 2540               }
 2541   
 2542               // see if complex glyph layout support is needed
 2543               if(HTMLDocument.this.getProperty(I18NProperty).equals( Boolean.FALSE ) ) {
 2544                   // if a default direction of right-to-left has been specified,
 2545                   // we want complex layout even if the text is all left to right.
 2546                   Object d = getProperty(TextAttribute.RUN_DIRECTION);
 2547                   if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
 2548                       HTMLDocument.this.putProperty( I18NProperty, Boolean.TRUE);
 2549                   } else {
 2550                       if (SwingUtilities2.isComplexLayout(data, 0, data.length)) {
 2551                           HTMLDocument.this.putProperty( I18NProperty, Boolean.TRUE);
 2552                       }
 2553                   }
 2554               }
 2555   
 2556               if (inTextArea) {
 2557                   textAreaContent(data);
 2558               } else if (inPre) {
 2559                   preContent(data);
 2560               } else if (inTitle) {
 2561                   putProperty(Document.TitleProperty, new String(data));
 2562               } else if (option != null) {
 2563                   option.setLabel(new String(data));
 2564               } else if (inStyle) {
 2565                   if (styles != null) {
 2566                       styles.addElement(new String(data));
 2567                   }
 2568               } else if (inBlock > 0) {
 2569                   if (!foundInsertTag && insertAfterImplied) {
 2570                       // Assume content should be added.
 2571                       foundInsertTag(false);
 2572                       foundInsertTag = true;
 2573                       inParagraph = impliedP = true;
 2574                   }
 2575                   if (data.length >= 1) {
 2576                       addContent(data, 0, data.length);
 2577                   }
 2578               }
 2579           }
 2580   
 2581           /**
 2582            * Callback from the parser.  Route to the appropriate
 2583            * handler for the tag.
 2584            */
 2585           public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
 2586               if (receivedEndHTML) {
 2587                   return;
 2588               }
 2589               if (midInsert && !inBody) {
 2590                   if (t == HTML.Tag.BODY) {
 2591                       inBody = true;
 2592                       // Increment inBlock since we know we are in the body,
 2593                       // this is needed incase an implied-p is needed. If
 2594                       // inBlock isn't incremented, and an implied-p is
 2595                       // encountered, addContent won't be called!
 2596                       inBlock++;
 2597                   }
 2598                   return;
 2599               }
 2600               if (!inBody && t == HTML.Tag.BODY) {
 2601                   inBody = true;
 2602               }
 2603               if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
 2604                   // Map the style attributes.
 2605                   String decl = (String)a.getAttribute(HTML.Attribute.STYLE);
 2606                   a.removeAttribute(HTML.Attribute.STYLE);
 2607                   styleAttributes = getStyleSheet().getDeclaration(decl);
 2608                   a.addAttributes(styleAttributes);
 2609               }
 2610               else {
 2611                   styleAttributes = null;
 2612               }
 2613               TagAction action = (TagAction) tagMap.get(t);
 2614   
 2615               if (action != null) {
 2616                   action.start(t, a);
 2617               }
 2618           }
 2619   
 2620           public void handleComment(char[] data, int pos) {
 2621               if (receivedEndHTML) {
 2622                   addExternalComment(new String(data));
 2623                   return;
 2624               }
 2625               if (inStyle) {
 2626                   if (styles != null) {
 2627                       styles.addElement(new String(data));
 2628                   }
 2629               }
 2630               else if (getPreservesUnknownTags()) {
 2631                   if (inBlock == 0 && (foundInsertTag ||
 2632                                        insertTag != HTML.Tag.COMMENT)) {
 2633                       // Comment outside of body, will not be able to show it,
 2634                       // but can add it as a property on the Document.
 2635                       addExternalComment(new String(data));
 2636                       return;
 2637                   }
 2638                   SimpleAttributeSet sas = new SimpleAttributeSet();
 2639                   sas.addAttribute(HTML.Attribute.COMMENT, new String(data));
 2640                   addSpecialElement(HTML.Tag.COMMENT, sas);
 2641               }
 2642   
 2643               TagAction action = (TagAction)tagMap.get(HTML.Tag.COMMENT);
 2644               if (action != null) {
 2645                   action.start(HTML.Tag.COMMENT, new SimpleAttributeSet());
 2646                   action.end(HTML.Tag.COMMENT);
 2647               }
 2648           }
 2649   
 2650           /**
 2651            * Adds the comment <code>comment</code> to the set of comments
 2652            * maintained outside of the scope of elements.
 2653            */
 2654           private void addExternalComment(String comment) {
 2655               Object comments = getProperty(AdditionalComments);
 2656               if (comments != null && !(comments instanceof Vector)) {
 2657                   // No place to put comment.
 2658                   return;
 2659               }
 2660               if (comments == null) {
 2661                   comments = new Vector();
 2662                   putProperty(AdditionalComments, comments);
 2663               }
 2664               ((Vector)comments).addElement(comment);
 2665           }
 2666   
 2667           /**
 2668            * Callback from the parser.  Route to the appropriate
 2669            * handler for the tag.
 2670            */
 2671           public void handleEndTag(HTML.Tag t, int pos) {
 2672               if (receivedEndHTML || (midInsert && !inBody)) {
 2673                   return;
 2674               }
 2675               if (t == HTML.Tag.HTML) {
 2676                   receivedEndHTML = true;
 2677               }
 2678               if (t == HTML.Tag.BODY) {
 2679                   inBody = false;
 2680                   if (midInsert) {
 2681                       inBlock--;
 2682                   }
 2683               }
 2684               TagAction action = (TagAction) tagMap.get(t);
 2685               if (action != null) {
 2686                   action.end(t);
 2687               }
 2688           }
 2689   
 2690           /**
 2691            * Callback from the parser.  Route to the appropriate
 2692            * handler for the tag.
 2693            */
 2694           public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
 2695               if (receivedEndHTML || (midInsert && !inBody)) {
 2696                   return;
 2697               }
 2698   
 2699               if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
 2700                   // Map the style attributes.
 2701                   String decl = (String)a.getAttribute(HTML.Attribute.STYLE);
 2702                   a.removeAttribute(HTML.Attribute.STYLE);
 2703                   styleAttributes = getStyleSheet().getDeclaration(decl);
 2704                   a.addAttributes(styleAttributes);
 2705               }
 2706               else {
 2707                   styleAttributes = null;
 2708               }
 2709   
 2710               TagAction action = (TagAction) tagMap.get(t);
 2711               if (action != null) {
 2712                   action.start(t, a);
 2713                   action.end(t);
 2714               }
 2715               else if (getPreservesUnknownTags()) {
 2716                   // unknown tag, only add if should preserve it.
 2717                   addSpecialElement(t, a);
 2718               }
 2719           }
 2720   
 2721           /**
 2722            * This is invoked after the stream has been parsed, but before
 2723            * <code>flush</code>. <code>eol</code> will be one of \n, \r
 2724            * or \r\n, which ever is encountered the most in parsing the
 2725            * stream.
 2726            *
 2727            * @since 1.3
 2728            */
 2729           public void handleEndOfLineString(String eol) {
 2730               if (emptyDocument && eol != null) {
 2731                   putProperty(DefaultEditorKit.EndOfLineStringProperty,
 2732                               eol);
 2733               }
 2734           }
 2735   
 2736           // ---- tag handling support ------------------------------
 2737   
 2738           /**
 2739            * Registers a handler for the given tag.  By default
 2740            * all of the well-known tags will have been registered.
 2741            * This can be used to change the handling of a particular
 2742            * tag or to add support for custom tags.
 2743            */
 2744           protected void registerTag(HTML.Tag t, TagAction a) {
 2745               tagMap.put(t, a);
 2746           }
 2747   
 2748           /**
 2749            * An action to be performed in response
 2750            * to parsing a tag.  This allows customization
 2751            * of how each tag is handled and avoids a large
 2752            * switch statement.
 2753            */
 2754           public class TagAction {
 2755   
 2756               /**
 2757                * Called when a start tag is seen for the
 2758                * type of tag this action was registered
 2759                * to.  The tag argument indicates the actual
 2760                * tag for those actions that are shared across
 2761                * many tags.  By default this does nothing and
 2762                * completely ignores the tag.
 2763                */
 2764               public void start(HTML.Tag t, MutableAttributeSet a) {
 2765               }
 2766   
 2767               /**
 2768                * Called when an end tag is seen for the
 2769                * type of tag this action was registered
 2770                * to.  The tag argument indicates the actual
 2771                * tag for those actions that are shared across
 2772                * many tags.  By default this does nothing and
 2773                * completely ignores the tag.
 2774                */
 2775               public void end(HTML.Tag t) {
 2776               }
 2777   
 2778           }
 2779   
 2780           public class BlockAction extends TagAction {
 2781   
 2782               public void start(HTML.Tag t, MutableAttributeSet attr) {
 2783                   blockOpen(t, attr);
 2784               }
 2785   
 2786               public void end(HTML.Tag t) {
 2787                   blockClose(t);
 2788               }
 2789           }
 2790   
 2791   
 2792           /**
 2793            * Action used for the actual element form tag. This is named such
 2794            * as there was already a public class named FormAction.
 2795            */
 2796           private class FormTagAction extends BlockAction {
 2797               public void start(HTML.Tag t, MutableAttributeSet attr) {
 2798                   super.start(t, attr);
 2799                   // initialize a ButtonGroupsMap when
 2800                   // FORM tag is encountered.  This will
 2801                   // be used for any radio buttons that
 2802                   // might be defined in the FORM.
 2803                   // for new group new ButtonGroup will be created (fix for 4529702)
 2804                   // group name is a key in radioButtonGroupsMap
 2805                   radioButtonGroupsMap = new HashMap();
 2806               }
 2807   
 2808               public void end(HTML.Tag t) {
 2809                   super.end(t);
 2810                   // reset the button group to null since
 2811                   // the form has ended.
 2812                   radioButtonGroupsMap = null;
 2813               }
 2814           }
 2815   
 2816   
 2817           public class ParagraphAction extends BlockAction {
 2818   
 2819               public void start(HTML.Tag t, MutableAttributeSet a) {
 2820                   super.start(t, a);
 2821                   inParagraph = true;
 2822               }
 2823   
 2824               public void end(HTML.Tag t) {
 2825                   super.end(t);
 2826                   inParagraph = false;
 2827               }
 2828           }
 2829   
 2830           public class SpecialAction extends TagAction {
 2831   
 2832               public void start(HTML.Tag t, MutableAttributeSet a) {
 2833                   addSpecialElement(t, a);
 2834               }
 2835   
 2836           }
 2837   
 2838           public class IsindexAction extends TagAction {
 2839   
 2840               public void start(HTML.Tag t, MutableAttributeSet a) {
 2841                   blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
 2842                   addSpecialElement(t, a);
 2843                   blockClose(HTML.Tag.IMPLIED);
 2844               }
 2845   
 2846           }
 2847   
 2848   
 2849           public class HiddenAction extends TagAction {
 2850   
 2851               public void start(HTML.Tag t, MutableAttributeSet a) {
 2852                   addSpecialElement(t, a);
 2853               }
 2854   
 2855               public void end(HTML.Tag t) {
 2856                   if (!isEmpty(t)) {
 2857                       MutableAttributeSet a = new SimpleAttributeSet();
 2858                       a.addAttribute(HTML.Attribute.ENDTAG, "true");
 2859                       addSpecialElement(t, a);
 2860                   }
 2861               }
 2862   
 2863               boolean isEmpty(HTML.Tag t) {
 2864                   if (t == HTML.Tag.APPLET ||
 2865                       t == HTML.Tag.SCRIPT) {
 2866                       return false;
 2867                   }
 2868                   return true;
 2869               }
 2870           }
 2871   
 2872   
 2873           /**
 2874            * Subclass of HiddenAction to set the content type for style sheets,
 2875            * and to set the name of the default style sheet.
 2876            */
 2877           class MetaAction extends HiddenAction {
 2878   
 2879               public void start(HTML.Tag t, MutableAttributeSet a) {
 2880                   Object equiv = a.getAttribute(HTML.Attribute.HTTPEQUIV);
 2881                   if (equiv != null) {
 2882                       equiv = ((String)equiv).toLowerCase();
 2883                       if (equiv.equals("content-style-type")) {
 2884                           String value = (String)a.getAttribute
 2885                                          (HTML.Attribute.CONTENT);
 2886                           setDefaultStyleSheetType(value);
 2887                           isStyleCSS = "text/css".equals
 2888                                         (getDefaultStyleSheetType());
 2889                       }
 2890                       else if (equiv.equals("default-style")) {
 2891                           defaultStyle = (String)a.getAttribute
 2892                                          (HTML.Attribute.CONTENT);
 2893                       }
 2894                   }
 2895                   super.start(t, a);
 2896               }
 2897   
 2898               boolean isEmpty(HTML.Tag t) {
 2899                   return true;
 2900               }
 2901           }
 2902   
 2903   
 2904           /**
 2905            * End if overridden to create the necessary stylesheets that
 2906            * are referenced via the link tag. It is done in this manner
 2907            * as the meta tag can be used to specify an alternate style sheet,
 2908            * and is not guaranteed to come before the link tags.
 2909            */
 2910           class HeadAction extends BlockAction {
 2911   
 2912               public void start(HTML.Tag t, MutableAttributeSet a) {
 2913                   inHead = true;
 2914                   // This check of the insertTag is put in to avoid considering
 2915                   // the implied-p that is generated for the head. This allows
 2916                   // inserts for HR to work correctly.
 2917                   if ((insertTag == null && !insertAfterImplied) ||
 2918                       (insertTag == HTML.Tag.HEAD) ||
 2919                       (insertAfterImplied &&
 2920                        (foundInsertTag || !a.isDefined(IMPLIED)))) {
 2921                       super.start(t, a);
 2922                   }
 2923               }
 2924   
 2925               public void end(HTML.Tag t) {
 2926                   inHead = inStyle = false;
 2927                   // See if there is a StyleSheet to link to.
 2928                   if (styles != null) {
 2929                       boolean isDefaultCSS = isStyleCSS;
 2930                       for (int counter = 0, maxCounter = styles.size();
 2931                            counter < maxCounter;) {
 2932                           Object value = styles.elementAt(counter);
 2933                           if (value == HTML.Tag.LINK) {
 2934                               handleLink((AttributeSet)styles.
 2935                                          elementAt(++counter));
 2936                               counter++;
 2937                           }
 2938                           else {
 2939                               // Rule.
 2940                               // First element gives type.
 2941                               String type = (String)styles.elementAt(++counter);
 2942                               boolean isCSS = (type == null) ? isDefaultCSS :
 2943                                               type.equals("text/css");
 2944                               while (++counter < maxCounter &&
 2945                                      (styles.elementAt(counter)
 2946                                       instanceof String)) {
 2947                                   if (isCSS) {
 2948                                       addCSSRules((String)styles.elementAt
 2949                                                   (counter));
 2950                                   }
 2951                               }
 2952                           }
 2953                       }
 2954                   }
 2955                   if ((insertTag == null && !insertAfterImplied) ||
 2956                       insertTag == HTML.Tag.HEAD ||
 2957                       (insertAfterImplied && foundInsertTag)) {
 2958                       super.end(t);
 2959                   }
 2960               }
 2961   
 2962               boolean isEmpty(HTML.Tag t) {
 2963                   return false;
 2964               }
 2965   
 2966               private void handleLink(AttributeSet attr) {
 2967                   // Link.
 2968                   String type = (String)attr.getAttribute(HTML.Attribute.TYPE);
 2969                   if (type == null) {
 2970                       type = getDefaultStyleSheetType();
 2971                   }
 2972                   // Only choose if type==text/css
 2973                   // Select link if rel==stylesheet.
 2974                   // Otherwise if rel==alternate stylesheet and
 2975                   //   title matches default style.
 2976                   if (type.equals("text/css")) {
 2977                       String rel = (String)attr.getAttribute(HTML.Attribute.REL);
 2978                       String title = (String)attr.getAttribute
 2979                                                  (HTML.Attribute.TITLE);
 2980                       String media = (String)attr.getAttribute
 2981                                                      (HTML.Attribute.MEDIA);
 2982                       if (media == null) {
 2983                           media = "all";
 2984                       }
 2985                       else {
 2986                           media = media.toLowerCase();
 2987                       }
 2988                       if (rel != null) {
 2989                           rel = rel.toLowerCase();
 2990                           if ((media.indexOf("all") != -1 ||
 2991                                media.indexOf("screen") != -1) &&
 2992                               (rel.equals("stylesheet") ||
 2993                                (rel.equals("alternate stylesheet") &&
 2994                                 title.equals(defaultStyle)))) {
 2995                               linkCSSStyleSheet((String)attr.getAttribute
 2996                                                 (HTML.Attribute.HREF));
 2997                           }
 2998                       }
 2999                   }
 3000               }
 3001           }
 3002   
 3003   
 3004           /**
 3005            * A subclass to add the AttributeSet to styles if the
 3006            * attributes contains an attribute for 'rel' with value
 3007            * 'stylesheet' or 'alternate stylesheet'.
 3008            */
 3009           class LinkAction extends HiddenAction {
 3010   
 3011               public void start(HTML.Tag t, MutableAttributeSet a) {
 3012                   String rel = (String)a.getAttribute(HTML.Attribute.REL);
 3013                   if (rel != null) {
 3014                       rel = rel.toLowerCase();
 3015                       if (rel.equals("stylesheet") ||
 3016                           rel.equals("alternate stylesheet")) {
 3017                           if (styles == null) {
 3018                               styles = new Vector(3);
 3019                           }
 3020                           styles.addElement(t);
 3021                           styles.addElement(a.copyAttributes());
 3022                       }
 3023                   }
 3024                   super.start(t, a);
 3025               }
 3026           }
 3027   
 3028           class MapAction extends TagAction {
 3029   
 3030               public void start(HTML.Tag t, MutableAttributeSet a) {
 3031                   lastMap = new Map((String)a.getAttribute(HTML.Attribute.NAME));
 3032                   addMap(lastMap);
 3033               }
 3034   
 3035               public void end(HTML.Tag t) {
 3036               }
 3037           }
 3038   
 3039   
 3040           class AreaAction extends TagAction {
 3041   
 3042               public void start(HTML.Tag t, MutableAttributeSet a) {
 3043                   if (lastMap != null) {
 3044                       lastMap.addArea(a.copyAttributes());
 3045                   }
 3046               }
 3047   
 3048               public void end(HTML.Tag t) {
 3049               }
 3050           }
 3051   
 3052   
 3053           class StyleAction extends TagAction {
 3054   
 3055               public void start(HTML.Tag t, MutableAttributeSet a) {
 3056                   if (inHead) {
 3057                       if (styles == null) {
 3058                           styles = new Vector(3);
 3059                       }
 3060                       styles.addElement(t);
 3061                       styles.addElement(a.getAttribute(HTML.Attribute.TYPE));
 3062                       inStyle = true;
 3063                   }
 3064               }
 3065   
 3066               public void end(HTML.Tag t) {
 3067                   inStyle = false;
 3068               }
 3069   
 3070               boolean isEmpty(HTML.Tag t) {
 3071                   return false;
 3072               }
 3073           }
 3074   
 3075   
 3076           public class PreAction extends BlockAction {
 3077   
 3078               public void start(HTML.Tag t, MutableAttributeSet attr) {
 3079                   inPre = true;
 3080                   blockOpen(t, attr);
 3081                   attr.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
 3082                   blockOpen(HTML.Tag.IMPLIED, attr);
 3083               }
 3084   
 3085               public void end(HTML.Tag t) {
 3086                   blockClose(HTML.Tag.IMPLIED);
 3087                   // set inPre to false after closing, so that if a newline
 3088                   // is added it won't generate a blockOpen.
 3089                   inPre = false;
 3090                   blockClose(t);
 3091               }
 3092           }
 3093   
 3094           public class CharacterAction extends TagAction {
 3095   
 3096               public void start(HTML.Tag t, MutableAttributeSet attr) {
 3097                   pushCharacterStyle();
 3098                   if (!foundInsertTag) {
 3099                       // Note that the third argument should really be based off
 3100                       // inParagraph and impliedP. If we're wrong (that is
 3101                       // insertTagDepthDelta shouldn't be changed), we'll end up
 3102                       // removing an extra EndSpec, which won't matter anyway.
 3103                       boolean insert = canInsertTag(t, attr, false);
 3104                       if (foundInsertTag) {
 3105                           if (!inParagraph) {
 3106                               inParagraph = impliedP = true;
 3107                           }
 3108                       }
 3109                       if (!insert) {
 3110                           return;
 3111                       }
 3112                   }
 3113                   if (attr.isDefined(IMPLIED)) {
 3114                       attr.removeAttribute(IMPLIED);
 3115                   }
 3116                   charAttr.addAttribute(t, attr.copyAttributes());
 3117                   if (styleAttributes != null) {
 3118                       charAttr.addAttributes(styleAttributes);
 3119                   }
 3120               }
 3121   
 3122               public void end(HTML.Tag t) {
 3123                   popCharacterStyle();
 3124               }
 3125           }
 3126   
 3127           /**
 3128            * Provides conversion of HTML tag/attribute
 3129            * mappings that have a corresponding StyleConstants
 3130            * and CSS mapping.  The conversion is to CSS attributes.
 3131            */
 3132           class ConvertAction extends TagAction {
 3133   
 3134               public void start(HTML.Tag t, MutableAttributeSet attr) {
 3135                   pushCharacterStyle();
 3136                   if (!foundInsertTag) {
 3137                       // Note that the third argument should really be based off
 3138                       // inParagraph and impliedP. If we're wrong (that is
 3139                       // insertTagDepthDelta shouldn't be changed), we'll end up
 3140                       // removing an extra EndSpec, which won't matter anyway.
 3141                       boolean insert = canInsertTag(t, attr, false);
 3142                       if (foundInsertTag) {
 3143                           if (!inParagraph) {
 3144                               inParagraph = impliedP = true;
 3145                           }
 3146                       }
 3147                       if (!insert) {
 3148                           return;
 3149                       }
 3150                   }
 3151                   if (attr.isDefined(IMPLIED)) {
 3152                       attr.removeAttribute(IMPLIED);
 3153                   }
 3154                   if (styleAttributes != null) {
 3155                       charAttr.addAttributes(styleAttributes);
 3156                   }
 3157                   // We also need to add attr, otherwise we lose custom
 3158                   // attributes, including class/id for style lookups, and
 3159                   // further confuse style lookup (doesn't have tag).
 3160                   charAttr.addAttribute(t, attr.copyAttributes());
 3161                   StyleSheet sheet = getStyleSheet();
 3162                   if (t == HTML.Tag.B) {
 3163                       sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_WEIGHT, "bold");
 3164                   } else if (t == HTML.Tag.I) {
 3165                       sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_STYLE, "italic");
 3166                   } else if (t == HTML.Tag.U) {
 3167                       Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
 3168                       String value = "underline";
 3169                       value = (v != null) ? value + "," + v.toString() : value;
 3170                       sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
 3171                   } else if (t == HTML.Tag.STRIKE) {
 3172                       Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
 3173                       String value = "line-through";
 3174                       value = (v != null) ? value + "," + v.toString() : value;
 3175                       sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
 3176                   } else if (t == HTML.Tag.SUP) {
 3177                       Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
 3178                       String value = "sup";
 3179                       value = (v != null) ? value + "," + v.toString() : value;
 3180                       sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
 3181                   } else if (t == HTML.Tag.SUB) {
 3182                       Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
 3183                       String value = "sub";
 3184                       value = (v != null) ? value + "," + v.toString() : value;
 3185                       sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
 3186                   } else if (t == HTML.Tag.FONT) {
 3187                       String color = (String) attr.getAttribute(HTML.Attribute.COLOR);
 3188                       if (color != null) {
 3189                           sheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color);
 3190                       }
 3191                       String face = (String) attr.getAttribute(HTML.Attribute.FACE);
 3192                       if (face != null) {
 3193                           sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY, face);
 3194                       }
 3195                       String size = (String) attr.getAttribute(HTML.Attribute.SIZE);
 3196                       if (size != null) {
 3197                           sheet.addCSSAttributeFromHTML(charAttr, CSS.Attribute.FONT_SIZE, size);
 3198                       }
 3199                   }
 3200               }
 3201   
 3202               public void end(HTML.Tag t) {
 3203                   popCharacterStyle();
 3204               }
 3205   
 3206           }
 3207   
 3208           class AnchorAction extends CharacterAction {
 3209   
 3210               public void start(HTML.Tag t, MutableAttributeSet attr) {
 3211                   // set flag to catch empty anchors
 3212                   emptyAnchor = true;
 3213                   super.start(t, attr);
 3214               }
 3215   
 3216               public void end(HTML.Tag t) {
 3217                   if (emptyAnchor) {
 3218                       // if the anchor was empty it was probably a
 3219                       // named anchor point and we don't want to throw
 3220                       // it away.
 3221                       char[] one = new char[1];
 3222                       one[0] = '\n';
 3223                       addContent(one, 0, 1);
 3224                   }
 3225                   super.end(t);
 3226               }
 3227           }
 3228   
 3229           class TitleAction extends HiddenAction {
 3230   
 3231               public void start(HTML.Tag t, MutableAttributeSet attr) {
 3232                   inTitle = true;
 3233                   super.start(t, attr);
 3234               }
 3235   
 3236               public void end(HTML.Tag t) {
 3237                   inTitle = false;
 3238                   super.end(t);
 3239               }
 3240   
 3241               boolean isEmpty(HTML.Tag t) {
 3242                   return false;
 3243               }
 3244           }
 3245   
 3246   
 3247           class BaseAction extends TagAction {
 3248   
 3249               public void start(HTML.Tag t, MutableAttributeSet attr) {
 3250                   String href = (String) attr.getAttribute(HTML.Attribute.HREF);
 3251                   if (href != null) {
 3252                       try {
 3253                           URL newBase = new URL(base, href);
 3254                           setBase(newBase);
 3255                           hasBaseTag = true;
 3256                       } catch (MalformedURLException ex) {
 3257                       }
 3258                   }
 3259                   baseTarget = (String) attr.getAttribute(HTML.Attribute.TARGET);
 3260               }
 3261           }
 3262   
 3263           class ObjectAction extends SpecialAction {
 3264   
 3265               public void start(HTML.Tag t, MutableAttributeSet a) {
 3266                   if (t == HTML.Tag.PARAM) {
 3267                       addParameter(a);
 3268                   } else {
 3269                       super.start(t, a);
 3270                   }
 3271               }
 3272   
 3273               public void end(HTML.Tag t) {
 3274                   if (t != HTML.Tag.PARAM) {
 3275                       super.end(t);
 3276                   }
 3277               }
 3278   
 3279               void addParameter(AttributeSet a) {
 3280                   String name = (String) a.getAttribute(HTML.Attribute.NAME);
 3281                   String value = (String) a.getAttribute(HTML.Attribute.VALUE);
 3282                   if ((name != null) && (value != null)) {
 3283                       ElementSpec objSpec = (ElementSpec) parseBuffer.lastElement();
 3284                       MutableAttributeSet objAttr = (MutableAttributeSet) objSpec.getAttributes();
 3285                       objAttr.addAttribute(name, value);
 3286                   }
 3287               }
 3288           }
 3289   
 3290           /**
 3291            * Action to support forms by building all of the elements
 3292            * used to represent form controls.  This will process
 3293            * the &lt;INPUT&gt;, &lt;TEXTAREA&gt;, &lt;SELECT&gt;,
 3294            * and &lt;OPTION&gt; tags.  The element created by
 3295            * this action is expected to have the attribute
 3296            * <code>StyleConstants.ModelAttribute</code> set to
 3297            * the model that holds the state for the form control.
 3298            * This enables multiple views, and allows document to
 3299            * be iterated over picking up the data of the form.
 3300            * The following are the model assignments for the
 3301            * various type of form elements.
 3302            * <table summary="model assignments for the various types of form elements">
 3303            * <tr>
 3304            *   <th>Element Type
 3305            *   <th>Model Type
 3306            * <tr>
 3307            *   <td>input, type button
 3308            *   <td>{@link DefaultButtonModel}
 3309            * <tr>
 3310            *   <td>input, type checkbox
 3311            *   <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
 3312            * <tr>
 3313            *   <td>input, type image
 3314            *   <td>{@link DefaultButtonModel}
 3315            * <tr>
 3316            *   <td>input, type password
 3317            *   <td>{@link PlainDocument}
 3318            * <tr>
 3319            *   <td>input, type radio
 3320            *   <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
 3321            * <tr>
 3322            *   <td>input, type reset
 3323            *   <td>{@link DefaultButtonModel}
 3324            * <tr>
 3325            *   <td>input, type submit
 3326            *   <td>{@link DefaultButtonModel}
 3327            * <tr>
 3328            *   <td>input, type text or type is null.
 3329            *   <td>{@link PlainDocument}
 3330            * <tr>
 3331            *   <td>select
 3332            *   <td>{@link DefaultComboBoxModel} or an {@link DefaultListModel}, with an item type of Option
 3333            * <tr>
 3334            *   <td>textarea
 3335            *   <td>{@link PlainDocument}
 3336            * </table>
 3337            *
 3338            */
 3339           public class FormAction extends SpecialAction {
 3340   
 3341               public void start(HTML.Tag t, MutableAttributeSet attr) {
 3342                   if (t == HTML.Tag.INPUT) {
 3343                       String type = (String)
 3344                           attr.getAttribute(HTML.Attribute.TYPE);
 3345                       /*
 3346                        * if type is not defined teh default is
 3347                        * assumed to be text.
 3348                        */
 3349                       if (type == null) {
 3350                           type = "text";
 3351                           attr.addAttribute(HTML.Attribute.TYPE, "text");
 3352                       }
 3353                       setModel(type, attr);
 3354                   } else if (t == HTML.Tag.TEXTAREA) {
 3355                       inTextArea = true;
 3356                       textAreaDocument = new TextAreaDocument();
 3357                       attr.addAttribute(StyleConstants.ModelAttribute,
 3358                                         textAreaDocument);
 3359                   } else if (t == HTML.Tag.SELECT) {
 3360                       int size = HTML.getIntegerAttributeValue(attr,
 3361                                                                HTML.Attribute.SIZE,
 3362                                                                1);
 3363                       boolean multiple = ((String)attr.getAttribute(HTML.Attribute.MULTIPLE) != null);
 3364                       if ((size > 1) || multiple) {
 3365                           OptionListModel m = new OptionListModel();
 3366                           if (multiple) {
 3367                               m.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
 3368                           }
 3369                           selectModel = m;
 3370                       } else {
 3371                           selectModel = new OptionComboBoxModel();
 3372                       }
 3373                       attr.addAttribute(StyleConstants.ModelAttribute,
 3374                                         selectModel);
 3375   
 3376                   }
 3377   
 3378                   // build the element, unless this is an option.
 3379                   if (t == HTML.Tag.OPTION) {
 3380                       option = new Option(attr);
 3381   
 3382                       if (selectModel instanceof OptionListModel) {
 3383                           OptionListModel m = (OptionListModel)selectModel;
 3384                           m.addElement(option);
 3385                           if (option.isSelected()) {
 3386                               m.addSelectionInterval(optionCount, optionCount);
 3387                               m.setInitialSelection(optionCount);
 3388                           }
 3389                       } else if (selectModel instanceof OptionComboBoxModel) {
 3390                           OptionComboBoxModel m = (OptionComboBoxModel)selectModel;
 3391                           m.addElement(option);
 3392                           if (option.isSelected()) {
 3393                               m.setSelectedItem(option);
 3394                               m.setInitialSelection(option);
 3395                           }
 3396                       }
 3397                       optionCount++;
 3398                   } else {
 3399                       super.start(t, attr);
 3400                   }
 3401               }
 3402   
 3403               public void end(HTML.Tag t) {
 3404                   if (t == HTML.Tag.OPTION) {
 3405                       option = null;
 3406                   } else {
 3407                       if (t == HTML.Tag.SELECT) {
 3408                           selectModel = null;
 3409                           optionCount = 0;
 3410                       } else if (t == HTML.Tag.TEXTAREA) {
 3411                           inTextArea = false;
 3412   
 3413                           /* Now that the textarea has ended,
 3414                            * store the entire initial text
 3415                            * of the text area.  This will
 3416                            * enable us to restore the initial
 3417                            * state if a reset is requested.
 3418                            */
 3419                           textAreaDocument.storeInitialText();
 3420                       }
 3421                       super.end(t);
 3422                   }
 3423               }
 3424   
 3425               void setModel(String type, MutableAttributeSet attr) {
 3426                   if (type.equals("submit") ||
 3427                       type.equals("reset") ||
 3428                       type.equals("image")) {
 3429   
 3430                       // button model
 3431                       attr.addAttribute(StyleConstants.ModelAttribute,
 3432                                         new DefaultButtonModel());
 3433                   } else if (type.equals("text") ||
 3434                              type.equals("password")) {
 3435                       // plain text model
 3436                       int maxLength = HTML.getIntegerAttributeValue(
 3437                                          attr, HTML.Attribute.MAXLENGTH, -1);
 3438                       Document doc;
 3439   
 3440                       if (maxLength > 0) {
 3441                           doc = new FixedLengthDocument(maxLength);
 3442                       }
 3443                       else {
 3444                           doc = new PlainDocument();
 3445                       }
 3446                       String value = (String)
 3447                           attr.getAttribute(HTML.Attribute.VALUE);
 3448                       try {
 3449                           doc.insertString(0, value, null);
 3450                       } catch (BadLocationException e) {
 3451                       }
 3452                       attr.addAttribute(StyleConstants.ModelAttribute, doc);
 3453                   } else if (type.equals("file")) {
 3454                       // plain text model
 3455                       attr.addAttribute(StyleConstants.ModelAttribute,
 3456                                         new PlainDocument());
 3457                   } else if (type.equals("checkbox") ||
 3458                              type.equals("radio")) {
 3459                       JToggleButton.ToggleButtonModel model = new JToggleButton.ToggleButtonModel();
 3460                       if (type.equals("radio")) {
 3461                           String name = (String) attr.getAttribute(HTML.Attribute.NAME);
 3462                           if ( radioButtonGroupsMap == null ) { //fix for 4772743
 3463                              radioButtonGroupsMap = new HashMap();
 3464                           }
 3465                           ButtonGroup radioButtonGroup = (ButtonGroup)radioButtonGroupsMap.get(name);
 3466                           if (radioButtonGroup == null) {
 3467                               radioButtonGroup = new ButtonGroup();
 3468                               radioButtonGroupsMap.put(name,radioButtonGroup);
 3469                           }
 3470                           model.setGroup(radioButtonGroup);
 3471                       }
 3472                       boolean checked = (attr.getAttribute(HTML.Attribute.CHECKED) != null);
 3473                       model.setSelected(checked);
 3474                       attr.addAttribute(StyleConstants.ModelAttribute, model);
 3475                   }
 3476               }
 3477   
 3478               /**
 3479                * If a &lt;SELECT&gt; tag is being processed, this
 3480                * model will be a reference to the model being filled
 3481                * with the &lt;OPTION&gt; elements (which produce
 3482                * objects of type <code>Option</code>.
 3483                */
 3484               Object selectModel;
 3485               int optionCount;
 3486           }
 3487   
 3488   
 3489           // --- utility methods used by the reader ------------------
 3490   
 3491           /**
 3492            * Pushes the current character style on a stack in preparation
 3493            * for forming a new nested character style.
 3494            */
 3495           protected void pushCharacterStyle() {
 3496               charAttrStack.push(charAttr.copyAttributes());
 3497           }
 3498   
 3499           /**
 3500            * Pops a previously pushed character style off the stack
 3501            * to return to a previous style.
 3502            */
 3503           protected void popCharacterStyle() {
 3504               if (!charAttrStack.empty()) {
 3505                   charAttr = (MutableAttributeSet) charAttrStack.peek();
 3506                   charAttrStack.pop();
 3507               }
 3508           }
 3509   
 3510           /**
 3511            * Adds the given content to the textarea document.
 3512            * This method gets called when we are in a textarea
 3513            * context.  Therefore all text that is seen belongs
 3514            * to the text area and is hence added to the
 3515            * TextAreaDocument associated with the text area.
 3516            */
 3517           protected void textAreaContent(char[] data) {
 3518               try {
 3519                   textAreaDocument.insertString(textAreaDocument.getLength(), new String(data), null);
 3520               } catch (BadLocationException e) {
 3521                   // Should do something reasonable
 3522               }
 3523           }
 3524   
 3525           /**
 3526            * Adds the given content that was encountered in a
 3527            * PRE element.  This synthesizes lines to hold the
 3528            * runs of text, and makes calls to addContent to
 3529            * actually add the text.
 3530            */
 3531           protected void preContent(char[] data) {
 3532               int last = 0;
 3533               for (int i = 0; i < data.length; i++) {
 3534                   if (data[i] == '\n') {
 3535                       addContent(data, last, i - last + 1);
 3536                       blockClose(HTML.Tag.IMPLIED);
 3537                       MutableAttributeSet a = new SimpleAttributeSet();
 3538                       a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
 3539                       blockOpen(HTML.Tag.IMPLIED, a);
 3540                       last = i + 1;
 3541                   }
 3542               }
 3543               if (last < data.length) {
 3544                   addContent(data, last, data.length - last);
 3545               }
 3546           }
 3547   
 3548           /**
 3549            * Adds an instruction to the parse buffer to create a
 3550            * block element with the given attributes.
 3551            */
 3552           protected void blockOpen(HTML.Tag t, MutableAttributeSet attr) {
 3553               if (impliedP) {
 3554                   blockClose(HTML.Tag.IMPLIED);
 3555               }
 3556   
 3557               inBlock++;
 3558   
 3559               if (!canInsertTag(t, attr, true)) {
 3560                   return;
 3561               }
 3562               if (attr.isDefined(IMPLIED)) {
 3563                   attr.removeAttribute(IMPLIED);
 3564               }
 3565               lastWasNewline = false;
 3566               attr.addAttribute(StyleConstants.NameAttribute, t);
 3567               ElementSpec es = new ElementSpec(
 3568                   attr.copyAttributes(), ElementSpec.StartTagType);
 3569               parseBuffer.addElement(es);
 3570           }
 3571   
 3572           /**
 3573            * Adds an instruction to the parse buffer to close out
 3574            * a block element of the given type.
 3575            */
 3576           protected void blockClose(HTML.Tag t) {
 3577               inBlock--;
 3578   
 3579               if (!foundInsertTag) {
 3580                   return;
 3581               }
 3582   
 3583               // Add a new line, if the last character wasn't one. This is
 3584               // needed for proper positioning of the cursor. addContent
 3585               // with true will force an implied paragraph to be generated if
 3586               // there isn't one. This may result in a rather bogus structure
 3587               // (perhaps a table with a child pargraph), but the paragraph
 3588               // is needed for proper positioning and display.
 3589               if(!lastWasNewline) {
 3590                   pushCharacterStyle();
 3591                   charAttr.addAttribute(IMPLIED_CR, Boolean.TRUE);
 3592                   addContent(NEWLINE, 0, 1, true);
 3593                   popCharacterStyle();
 3594                   lastWasNewline = true;
 3595               }
 3596   
 3597               if (impliedP) {
 3598                   impliedP = false;
 3599                   inParagraph = false;
 3600                   if (t != HTML.Tag.IMPLIED) {
 3601                       blockClose(HTML.Tag.IMPLIED);
 3602                   }
 3603               }
 3604               // an open/close with no content will be removed, so we
 3605               // add a space of content to keep the element being formed.
 3606               ElementSpec prev = (parseBuffer.size() > 0) ?
 3607                   (ElementSpec) parseBuffer.lastElement() : null;
 3608               if (prev != null && prev.getType() == ElementSpec.StartTagType) {
 3609                   char[] one = new char[1];
 3610                   one[0] = ' ';
 3611                   addContent(one, 0, 1);
 3612               }
 3613               ElementSpec es = new ElementSpec(
 3614                   null, ElementSpec.EndTagType);
 3615               parseBuffer.addElement(es);
 3616           }
 3617   
 3618           /**
 3619            * Adds some text with the current character attributes.
 3620            *
 3621            * @param data the content to add
 3622            * @param offs the initial offset
 3623            * @param length the length
 3624            */
 3625           protected void addContent(char[] data, int offs, int length) {
 3626               addContent(data, offs, length, true);
 3627           }
 3628   
 3629           /**
 3630            * Adds some text with the current character attributes.
 3631            *
 3632            * @param data the content to add
 3633            * @param offs the initial offset
 3634            * @param length the length
 3635            * @param generateImpliedPIfNecessary whether to generate implied
 3636            * paragraphs
 3637            */
 3638           protected void addContent(char[] data, int offs, int length,
 3639                                     boolean generateImpliedPIfNecessary) {
 3640               if (!foundInsertTag) {
 3641                   return;
 3642               }
 3643   
 3644               if (generateImpliedPIfNecessary && (! inParagraph) && (! inPre)) {
 3645                   blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
 3646                   inParagraph = true;
 3647                   impliedP = true;
 3648               }
 3649               emptyAnchor = false;
 3650               charAttr.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
 3651               AttributeSet a = charAttr.copyAttributes();
 3652               ElementSpec es = new ElementSpec(
 3653                   a, ElementSpec.ContentType, data, offs, length);
 3654               parseBuffer.addElement(es);
 3655   
 3656               if (parseBuffer.size() > threshold) {
 3657                   if ( threshold <= MaxThreshold ) {
 3658                       threshold *= StepThreshold;
 3659                   }
 3660                   try {
 3661                       flushBuffer(false);
 3662                   } catch (BadLocationException ble) {
 3663                   }
 3664               }
 3665               if(length > 0) {
 3666                   lastWasNewline = (data[offs + length - 1] == '\n');
 3667               }
 3668           }
 3669   
 3670           /**
 3671            * Adds content that is basically specified entirely
 3672            * in the attribute set.
 3673            */
 3674           protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a) {
 3675               if ((t != HTML.Tag.FRAME) && (! inParagraph) && (! inPre)) {
 3676                   nextTagAfterPImplied = t;
 3677                   blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
 3678                   nextTagAfterPImplied = null;
 3679                   inParagraph = true;
 3680                   impliedP = true;
 3681               }
 3682               if (!canInsertTag(t, a, t.isBlock())) {
 3683                   return;
 3684               }
 3685               if (a.isDefined(IMPLIED)) {
 3686                   a.removeAttribute(IMPLIED);
 3687               }
 3688               emptyAnchor = false;
 3689               a.addAttributes(charAttr);
 3690               a.addAttribute(StyleConstants.NameAttribute, t);
 3691               char[] one = new char[1];
 3692               one[0] = ' ';
 3693               ElementSpec es = new ElementSpec(
 3694                   a.copyAttributes(), ElementSpec.ContentType, one, 0, 1);
 3695               parseBuffer.addElement(es);
 3696               // Set this to avoid generating a newline for frames, frames
 3697               // shouldn't have any content, and shouldn't need a newline.
 3698               if (t == HTML.Tag.FRAME) {
 3699                   lastWasNewline = true;
 3700               }
 3701           }
 3702   
 3703           /**
 3704            * Flushes the current parse buffer into the document.
 3705            * @param endOfStream true if there is no more content to parser
 3706            */
 3707           void flushBuffer(boolean endOfStream) throws BadLocationException {
 3708               int oldLength = HTMLDocument.this.getLength();
 3709               int size = parseBuffer.size();
 3710               if (endOfStream && (insertTag != null || insertAfterImplied) &&
 3711                   size > 0) {
 3712                   adjustEndSpecsForPartialInsert();
 3713                   size = parseBuffer.size();
 3714               }
 3715               ElementSpec[] spec = new ElementSpec[size];
 3716               parseBuffer.copyInto(spec);
 3717   
 3718               if (oldLength == 0 && (insertTag == null && !insertAfterImplied)) {
 3719                   create(spec);
 3720               } else {
 3721                   insert(offset, spec);
 3722               }
 3723               parseBuffer.removeAllElements();
 3724               offset += HTMLDocument.this.getLength() - oldLength;
 3725               flushCount++;
 3726           }
 3727   
 3728           /**
 3729            * This will be invoked for the last flush, if <code>insertTag</code>
 3730            * is non null.
 3731            */
 3732           private void adjustEndSpecsForPartialInsert() {
 3733               int size = parseBuffer.size();
 3734               if (insertTagDepthDelta < 0) {
 3735                   // When inserting via an insertTag, the depths (of the tree
 3736                   // being read in, and existing hiearchy) may not match up.
 3737                   // This attemps to clean it up.
 3738                   int removeCounter = insertTagDepthDelta;
 3739                   while (removeCounter < 0 && size >= 0 &&
 3740                          ((ElementSpec)parseBuffer.elementAt(size - 1)).
 3741                          getType() == ElementSpec.EndTagType) {
 3742                       parseBuffer.removeElementAt(--size);
 3743                       removeCounter++;
 3744                   }
 3745               }
 3746               if (flushCount == 0 && (!insertAfterImplied ||
 3747                                       !wantsTrailingNewline)) {
 3748                   // If this starts with content (or popDepth > 0 &&
 3749                   // pushDepth > 0) and ends with EndTagTypes, make sure
 3750                   // the last content isn't a \n, otherwise will end up with
 3751                   // an extra \n in the middle of content.
 3752                   int index = 0;
 3753                   if (pushDepth > 0) {
 3754                       if (((ElementSpec)parseBuffer.elementAt(0)).getType() ==
 3755                           ElementSpec.ContentType) {
 3756                           index++;
 3757                       }
 3758                   }
 3759                   index += (popDepth + pushDepth);
 3760                   int cCount = 0;
 3761                   int cStart = index;
 3762                   while (index < size && ((ElementSpec)parseBuffer.elementAt
 3763                               (index)).getType() == ElementSpec.ContentType) {
 3764                       index++;
 3765                       cCount++;
 3766                   }
 3767                   if (cCount > 1) {
 3768                       while (index < size && ((ElementSpec)parseBuffer.elementAt
 3769                                  (index)).getType() == ElementSpec.EndTagType) {
 3770                           index++;
 3771                       }
 3772                       if (index == size) {
 3773                           char[] lastText = ((ElementSpec)parseBuffer.elementAt
 3774                                              (cStart + cCount - 1)).getArray();
 3775                           if (lastText.length == 1 && lastText[0] == NEWLINE[0]){
 3776                               index = cStart + cCount - 1;
 3777                               while (size > index) {
 3778                                   parseBuffer.removeElementAt(--size);
 3779                               }
 3780                           }
 3781                       }
 3782                   }
 3783               }
 3784               if (wantsTrailingNewline) {
 3785                   // Make sure there is in fact a newline
 3786                   for (int counter = parseBuffer.size() - 1; counter >= 0;
 3787                                      counter--) {
 3788                       ElementSpec spec = (ElementSpec)parseBuffer.
 3789                                                       elementAt(counter);
 3790                       if (spec.getType() == ElementSpec.ContentType) {
 3791                           if (spec.getArray()[spec.getLength() - 1] != '\n') {
 3792                               SimpleAttributeSet attrs =new SimpleAttributeSet();
 3793   
 3794                               attrs.addAttribute(StyleConstants.NameAttribute,
 3795                                                  HTML.Tag.CONTENT);
 3796                               parseBuffer.insertElementAt(new ElementSpec(
 3797                                       attrs,
 3798                                       ElementSpec.ContentType, NEWLINE, 0, 1),
 3799                                       counter + 1);
 3800                           }
 3801                           break;
 3802                       }
 3803                   }
 3804               }
 3805           }
 3806   
 3807           /**
 3808            * Adds the CSS rules in <code>rules</code>.
 3809            */
 3810           void addCSSRules(String rules) {
 3811               StyleSheet ss = getStyleSheet();
 3812               ss.addRule(rules);
 3813           }
 3814   
 3815           /**
 3816            * Adds the CSS stylesheet at <code>href</code> to the known list
 3817            * of stylesheets.
 3818            */
 3819           void linkCSSStyleSheet(String href) {
 3820               URL url = null;
 3821               try {
 3822                   url = new URL(base, href);
 3823               } catch (MalformedURLException mfe) {
 3824                   try {
 3825                       url = new URL(href);
 3826                   } catch (MalformedURLException mfe2) {
 3827                       url = null;
 3828                   }
 3829               }
 3830               if (url != null) {
 3831                   getStyleSheet().importStyleSheet(url);
 3832               }
 3833           }
 3834   
 3835           /**
 3836            * Returns true if can insert starting at <code>t</code>. This
 3837            * will return false if the insert tag is set, and hasn't been found
 3838            * yet.
 3839            */
 3840           private boolean canInsertTag(HTML.Tag t, AttributeSet attr,
 3841                                        boolean isBlockTag) {
 3842               if (!foundInsertTag) {
 3843                   boolean needPImplied = ((t == HTML.Tag.IMPLIED)
 3844                                                             && (!inParagraph)
 3845                                                             && (!inPre));
 3846                   if (needPImplied && (nextTagAfterPImplied != null)) {
 3847   
 3848                       /*
 3849                        * If insertTag == null then just proceed to
 3850                        * foundInsertTag() call below and return true.
 3851                        */
 3852                       if (insertTag != null) {
 3853                           boolean nextTagIsInsertTag =
 3854                                   isInsertTag(nextTagAfterPImplied);
 3855                           if ( (! nextTagIsInsertTag) || (! insertInsertTag) ) {
 3856                               return false;
 3857                           }
 3858                       }
 3859                       /*
 3860                        *  Proceed to foundInsertTag() call...
 3861                        */
 3862                    } else if ((insertTag != null && !isInsertTag(t))
 3863                                  || (insertAfterImplied
 3864                                       && (attr == null
 3865                                           || attr.isDefined(IMPLIED)
 3866                                           || t == HTML.Tag.IMPLIED
 3867                                          )
 3868                                      )
 3869                              ) {
 3870                       return false;
 3871                   }
 3872   
 3873                   // Allow the insert if t matches the insert tag, or
 3874                   // insertAfterImplied is true and the element is implied.
 3875                   foundInsertTag(isBlockTag);
 3876                   if (!insertInsertTag) {
 3877                       return false;
 3878                   }
 3879               }
 3880               return true;
 3881           }
 3882   
 3883           private boolean isInsertTag(HTML.Tag tag) {
 3884               return (insertTag == tag);
 3885           }
 3886   
 3887           private void foundInsertTag(boolean isBlockTag) {
 3888               foundInsertTag = true;
 3889               if (!insertAfterImplied && (popDepth > 0 || pushDepth > 0)) {
 3890                   try {
 3891                       if (offset == 0 || !getText(offset - 1, 1).equals("\n")) {
 3892                           // Need to insert a newline.
 3893                           AttributeSet newAttrs = null;
 3894                           boolean joinP = true;
 3895   
 3896                           if (offset != 0) {
 3897                               // Determine if we can use JoinPrevious, we can't
 3898                               // if the Element has some attributes that are
 3899                               // not meant to be duplicated.
 3900                               Element charElement = getCharacterElement
 3901                                                       (offset - 1);
 3902                               AttributeSet attrs = charElement.getAttributes();
 3903   
 3904                               if (attrs.isDefined(StyleConstants.
 3905                                                   ComposedTextAttribute)) {
 3906                                   joinP = false;
 3907                               }
 3908                               else {
 3909                                   Object name = attrs.getAttribute
 3910                                                 (StyleConstants.NameAttribute);
 3911                                   if (name instanceof HTML.Tag) {
 3912                                       HTML.Tag tag = (HTML.Tag)name;
 3913                                       if (tag == HTML.Tag.IMG ||
 3914                                           tag == HTML.Tag.HR ||
 3915                                           tag == HTML.Tag.COMMENT ||
 3916                                           (tag instanceof HTML.UnknownTag)) {
 3917                                           joinP = false;
 3918                                       }
 3919                                   }
 3920                               }
 3921                           }
 3922                           if (!joinP) {
 3923                               // If not joining with the previous element, be
 3924                               // sure and set the name (otherwise it will be
 3925                               // inherited).
 3926                               newAttrs = new SimpleAttributeSet();
 3927                               ((SimpleAttributeSet)newAttrs).addAttribute
 3928                                                 (StyleConstants.NameAttribute,
 3929                                                  HTML.Tag.CONTENT);
 3930                           }
 3931                           ElementSpec es = new ElementSpec(newAttrs,
 3932                                        ElementSpec.ContentType, NEWLINE, 0,
 3933                                        NEWLINE.length);
 3934                           if (joinP) {
 3935                               es.setDirection(ElementSpec.
 3936                                               JoinPreviousDirection);
 3937                           }
 3938                           parseBuffer.addElement(es);
 3939                       }
 3940                   } catch (BadLocationException ble) {}
 3941               }
 3942               // pops
 3943               for (int counter = 0; counter < popDepth; counter++) {
 3944                   parseBuffer.addElement(new ElementSpec(null, ElementSpec.
 3945                                                          EndTagType));
 3946               }
 3947               // pushes
 3948               for (int counter = 0; counter < pushDepth; counter++) {
 3949                   ElementSpec es = new ElementSpec(null, ElementSpec.
 3950                                                    StartTagType);
 3951                   es.setDirection(ElementSpec.JoinNextDirection);
 3952                   parseBuffer.addElement(es);
 3953               }
 3954               insertTagDepthDelta = depthTo(Math.max(0, offset - 1)) -
 3955                                     popDepth + pushDepth - inBlock;
 3956               if (isBlockTag) {
 3957                   // A start spec will be added (for this tag), so we account
 3958                   // for it here.
 3959                   insertTagDepthDelta++;
 3960               }
 3961               else {
 3962                   // An implied paragraph close (end spec) is going to be added,
 3963                   // so we account for it here.
 3964                   insertTagDepthDelta--;
 3965                   inParagraph = true;
 3966                   lastWasNewline = false;
 3967               }
 3968           }
 3969   
 3970           /**
 3971            * This is set to true when and end is invoked for <html>.
 3972            */
 3973           private boolean receivedEndHTML;
 3974           /** Number of times <code>flushBuffer</code> has been invoked. */
 3975           private int flushCount;
 3976           /** If true, behavior is similiar to insertTag, but instead of
 3977            * waiting for insertTag will wait for first Element without
 3978            * an 'implied' attribute and begin inserting then. */
 3979           private boolean insertAfterImplied;
 3980           /** This is only used if insertAfterImplied is true. If false, only
 3981            * inserting content, and there is a trailing newline it is removed. */
 3982           private boolean wantsTrailingNewline;
 3983           int threshold;
 3984           int offset;
 3985           boolean inParagraph = false;
 3986           boolean impliedP = false;
 3987           boolean inPre = false;
 3988           boolean inTextArea = false;
 3989           TextAreaDocument textAreaDocument = null;
 3990           boolean inTitle = false;
 3991           boolean lastWasNewline = true;
 3992           boolean emptyAnchor;
 3993           /** True if (!emptyDocument && insertTag == null), this is used so
 3994            * much it is cached. */
 3995           boolean midInsert;
 3996           /** True when the body has been encountered. */
 3997           boolean inBody;
 3998           /** If non null, gives parent Tag that insert is to happen at. */
 3999           HTML.Tag insertTag;
 4000           /** If true, the insertTag is inserted, otherwise elements after
 4001            * the insertTag is found are inserted. */
 4002           boolean insertInsertTag;
 4003           /** Set to true when insertTag has been found. */
 4004           boolean foundInsertTag;
 4005           /** When foundInsertTag is set to true, this will be updated to
 4006            * reflect the delta between the two structures. That is, it
 4007            * will be the depth the inserts are happening at minus the
 4008            * depth of the tags being passed in. A value of 0 (the common
 4009            * case) indicates the structures match, a value greater than 0 indicates
 4010            * the insert is happening at a deeper depth than the stream is
 4011            * parsing, and a value less than 0 indicates the insert is happening earlier
 4012            * in the tree that the parser thinks and that we will need to remove
 4013            * EndTagType specs in the flushBuffer method.
 4014            */
 4015           int insertTagDepthDelta;
 4016           /** How many parents to ascend before insert new elements. */
 4017           int popDepth;
 4018           /** How many parents to descend (relative to popDepth) before
 4019            * inserting. */
 4020           int pushDepth;
 4021           /** Last Map that was encountered. */
 4022           Map lastMap;
 4023           /** Set to true when a style element is encountered. */
 4024           boolean inStyle = false;
 4025           /** Name of style to use. Obtained from Meta tag. */
 4026           String defaultStyle;
 4027           /** Vector describing styles that should be include. Will consist
 4028            * of a bunch of HTML.Tags, which will either be:
 4029            * <p>LINK: in which case it is followed by an AttributeSet
 4030            * <p>STYLE: in which case the following element is a String
 4031            * indicating the type (may be null), and the elements following
 4032            * it until the next HTML.Tag are the rules as Strings.
 4033            */
 4034           Vector styles;
 4035           /** True if inside the head tag. */
 4036           boolean inHead = false;
 4037           /** Set to true if the style language is text/css. Since this is
 4038            * used alot, it is cached. */
 4039           boolean isStyleCSS;
 4040           /** True if inserting into an empty document. */
 4041           boolean emptyDocument;
 4042           /** Attributes from a style Attribute. */
 4043           AttributeSet styleAttributes;
 4044   
 4045           /**
 4046            * Current option, if in an option element (needed to
 4047            * load the label.
 4048            */
 4049           Option option;
 4050   
 4051           protected Vector<ElementSpec> parseBuffer = new Vector();    // Vector<ElementSpec>
 4052           protected MutableAttributeSet charAttr = new TaggedAttributeSet();
 4053           Stack charAttrStack = new Stack();
 4054           Hashtable tagMap;
 4055           int inBlock = 0;
 4056   
 4057           /**
 4058            * This attribute is sometimes used to refer to next tag
 4059            * to be handled after p-implied when the latter is
 4060            * the current tag which is being handled.
 4061            */
 4062           private HTML.Tag nextTagAfterPImplied = null;
 4063       }
 4064   
 4065   
 4066       /**
 4067        * Used by StyleSheet to determine when to avoid removing HTML.Tags
 4068        * matching StyleConstants.
 4069        */
 4070       static class TaggedAttributeSet extends SimpleAttributeSet {
 4071           TaggedAttributeSet() {
 4072               super();
 4073           }
 4074       }
 4075   
 4076   
 4077       /**
 4078        * An element that represents a chunk of text that has
 4079        * a set of HTML character level attributes assigned to
 4080        * it.
 4081        */
 4082       public class RunElement extends LeafElement {
 4083   
 4084           /**
 4085            * Constructs an element that represents content within the
 4086            * document (has no children).
 4087            *
 4088            * @param parent  the parent element
 4089            * @param a       the element attributes
 4090            * @param offs0   the start offset (must be at least 0)
 4091            * @param offs1   the end offset (must be at least offs0)
 4092            * @since 1.4
 4093            */
 4094           public RunElement(Element parent, AttributeSet a, int offs0, int offs1) {
 4095               super(parent, a, offs0, offs1);
 4096           }
 4097   
 4098           /**
 4099            * Gets the name of the element.
 4100            *
 4101            * @return the name, null if none
 4102            */
 4103           public String getName() {
 4104               Object o = getAttribute(StyleConstants.NameAttribute);
 4105               if (o != null) {
 4106                   return o.toString();
 4107               }
 4108               return super.getName();
 4109           }
 4110   
 4111           /**
 4112            * Gets the resolving parent.  HTML attributes are not inherited
 4113            * at the model level so we override this to return null.
 4114            *
 4115            * @return null, there are none
 4116            * @see AttributeSet#getResolveParent
 4117            */
 4118           public AttributeSet getResolveParent() {
 4119               return null;
 4120           }
 4121       }
 4122   
 4123       /**
 4124        * An element that represents a structural <em>block</em> of
 4125        * HTML.
 4126        */
 4127       public class BlockElement extends BranchElement {
 4128   
 4129           /**
 4130            * Constructs a composite element that initially contains
 4131            * no children.
 4132            *
 4133            * @param parent  the parent element
 4134            * @param a       the attributes for the element
 4135            * @since 1.4
 4136            */
 4137           public BlockElement(Element parent, AttributeSet a) {
 4138               super(parent, a);
 4139           }
 4140   
 4141           /**
 4142            * Gets the name of the element.
 4143            *
 4144            * @return the name, null if none
 4145            */
 4146           public String getName() {
 4147               Object o = getAttribute(StyleConstants.NameAttribute);
 4148               if (o != null) {
 4149                   return o.toString();
 4150               }
 4151               return super.getName();
 4152           }
 4153   
 4154           /**
 4155            * Gets the resolving parent.  HTML attributes are not inherited
 4156            * at the model level so we override this to return null.
 4157            *
 4158            * @return null, there are none
 4159            * @see AttributeSet#getResolveParent
 4160            */
 4161           public AttributeSet getResolveParent() {
 4162               return null;
 4163           }
 4164   
 4165       }
 4166   
 4167   
 4168       /**
 4169        * Document that allows you to set the maximum length of the text.
 4170        */
 4171       private static class FixedLengthDocument extends PlainDocument {
 4172           private int maxLength;
 4173   
 4174           public FixedLengthDocument(int maxLength) {
 4175               this.maxLength = maxLength;
 4176           }
 4177   
 4178           public void insertString(int offset, String str, AttributeSet a)
 4179               throws BadLocationException {
 4180               if (str != null && str.length() + getLength() <= maxLength) {
 4181                   super.insertString(offset, str, a);
 4182               }
 4183           }
 4184       }
 4185   }

Save This Page
Home » openjdk-7 » javax » swing » text » html » [javadoc | source]