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

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

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