0N/A/*
3261N/A * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/Apackage javax.swing.text.html;
0N/A
0N/Aimport java.awt.font.TextAttribute;
0N/Aimport java.util.*;
0N/Aimport java.net.URL;
0N/Aimport java.net.MalformedURLException;
0N/Aimport java.io.*;
0N/Aimport javax.swing.*;
0N/Aimport javax.swing.event.*;
0N/Aimport javax.swing.text.*;
0N/Aimport javax.swing.undo.*;
0N/Aimport sun.swing.SwingUtilities2;
2450N/Aimport static sun.swing.SwingUtilities2.IMPLIED_CR;
0N/A
0N/A/**
0N/A * A document that models HTML. The purpose of this model is to
0N/A * support both browsing and editing. As a result, the structure
0N/A * described by an HTML document is not exactly replicated by default.
0N/A * The element structure that is modeled by default, is built by the
0N/A * class <code>HTMLDocument.HTMLReader</code>, which implements the
0N/A * <code>HTMLEditorKit.ParserCallback</code> protocol that the parser
0N/A * expects. To change the structure one can subclass
0N/A * <code>HTMLReader</code>, and reimplement the method {@link
0N/A * #getReader(int)} to return the new reader implementation. The
0N/A * documentation for <code>HTMLReader</code> should be consulted for
0N/A * the details of the default structure created. The intent is that
0N/A * the document be non-lossy (although reproducing the HTML format may
0N/A * result in a different format).
0N/A *
0N/A * <p>The document models only HTML, and makes no attempt to store
0N/A * view attributes in it. The elements are identified by the
0N/A * <code>StyleContext.NameAttribute</code> attribute, which should
0N/A * always have a value of type <code>HTML.Tag</code> that identifies
0N/A * the kind of element. Some of the elements (such as comments) are
0N/A * synthesized. The <code>HTMLFactory</code> uses this attribute to
0N/A * determine what kind of view to build.</p>
0N/A *
0N/A * <p>This document supports incremental loading. The
0N/A * <code>TokenThreshold</code> property controls how much of the parse
0N/A * is buffered before trying to update the element structure of the
0N/A * document. This property is set by the <code>EditorKit</code> so
0N/A * that subclasses can disable it.</p>
0N/A *
0N/A * <p>The <code>Base</code> property determines the URL against which
0N/A * relative URLs are resolved. By default, this will be the
0N/A * <code>Document.StreamDescriptionProperty</code> if the value of the
0N/A * property is a URL. If a &lt;BASE&gt; tag is encountered, the base
0N/A * will become the URL specified by that tag. Because the base URL is
0N/A * a property, it can of course be set directly.</p>
0N/A *
0N/A * <p>The default content storage mechanism for this document is a gap
0N/A * buffer (<code>GapContent</code>). Alternatives can be supplied by
0N/A * using the constructor that takes a <code>Content</code>
0N/A * implementation.</p>
0N/A *
0N/A * <h2>Modifying HTMLDocument</h2>
0N/A *
0N/A * <p>In addition to the methods provided by Document and
0N/A * StyledDocument for mutating an HTMLDocument, HTMLDocument provides
0N/A * a number of convenience methods. The following methods can be used
0N/A * to insert HTML content into an existing document.</p>
0N/A *
0N/A * <ul>
0N/A * <li>{@link #setInnerHTML(Element, String)}</li>
0N/A * <li>{@link #setOuterHTML(Element, String)}</li>
0N/A * <li>{@link #insertBeforeStart(Element, String)}</li>
0N/A * <li>{@link #insertAfterStart(Element, String)}</li>
0N/A * <li>{@link #insertBeforeEnd(Element, String)}</li>
0N/A * <li>{@link #insertAfterEnd(Element, String)}</li>
0N/A * </ul>
0N/A *
0N/A * <p>The following examples illustrate using these methods. Each
0N/A * example assumes the HTML document is initialized in the following
0N/A * way:</p>
0N/A *
0N/A * <pre>
0N/A * JEditorPane p = new JEditorPane();
0N/A * p.setContentType("text/html");
0N/A * p.setText("..."); // Document text is provided below.
0N/A * HTMLDocument d = (HTMLDocument) p.getDocument();
0N/A * </pre>
0N/A *
0N/A * <p>With the following HTML content:</p>
0N/A *
0N/A * <pre>
0N/A * &lt;html>
0N/A * &lt;head>
0N/A * &lt;title>An example HTMLDocument&lt;/title>
0N/A * &lt;style type="text/css">
0N/A * div { background-color: silver; }
0N/A * ul { color: red; }
0N/A * &lt;/style>
0N/A * &lt;/head>
0N/A * &lt;body>
0N/A * &lt;div id="BOX">
0N/A * &lt;p>Paragraph 1&lt;/p>
0N/A * &lt;p>Paragraph 2&lt;/p>
0N/A * &lt;/div>
0N/A * &lt;/body>
0N/A * &lt;/html>
0N/A * </pre>
0N/A *
0N/A * <p>All the methods for modifying an HTML document require an {@link
0N/A * Element}. Elements can be obtained from an HTML document by using
0N/A * the method {@link #getElement(Element e, Object attribute, Object
0N/A * value)}. It returns the first descendant element that contains the
0N/A * specified attribute with the given value, in depth-first order.
0N/A * For example, <code>d.getElement(d.getDefaultRootElement(),
0N/A * StyleConstants.NameAttribute, HTML.Tag.P)</code> returns the first
0N/A * paragraph element.</p>
0N/A *
0N/A * <p>A convenient shortcut for locating elements is the method {@link
0N/A * #getElement(String)}; returns an element whose <code>ID</code>
0N/A * attribute matches the specified value. For example,
0N/A * <code>d.getElement("BOX")</code> returns the <code>DIV</code>
0N/A * element.</p>
0N/A *
0N/A * <p>The {@link #getIterator(HTML.Tag t)} method can also be used for
0N/A * finding all occurrences of the specified HTML tag in the
0N/A * document.</p>
0N/A *
0N/A * <h3>Inserting elements</h3>
0N/A *
0N/A * <p>Elements can be inserted before or after the existing children
0N/A * of any non-leaf element by using the methods
0N/A * <code>insertAfterStart</code> and <code>insertBeforeEnd</code>.
0N/A * For example, if <code>e</code> is the <code>DIV</code> element,
0N/A * <code>d.insertAfterStart(e, "&lt;ul>&lt;li>List
0N/A * Item&lt;/li>&lt;/ul>")</code> inserts the list before the first
0N/A * paragraph, and <code>d.insertBeforeEnd(e, "&lt;ul>&lt;li>List
0N/A * Item&lt;/li>&lt;/ul>")</code> inserts the list after the last
0N/A * paragraph. The <code>DIV</code> block becomes the parent of the
0N/A * newly inserted elements.</p>
0N/A *
0N/A * <p>Sibling elements can be inserted before or after any element by
0N/A * using the methods <code>insertBeforeStart</code> and
0N/A * <code>insertAfterEnd</code>. For example, if <code>e</code> is the
0N/A * <code>DIV</code> element, <code>d.insertBeforeStart(e,
0N/A * "&lt;ul>&lt;li>List Item&lt;/li>&lt;/ul>")</code> inserts the list
0N/A * before the <code>DIV</code> element, and <code>d.insertAfterEnd(e,
0N/A * "&lt;ul>&lt;li>List Item&lt;/li>&lt;/ul>")</code> inserts the list
0N/A * after the <code>DIV</code> element. The newly inserted elements
0N/A * become siblings of the <code>DIV</code> element.</p>
0N/A *
0N/A * <h3>Replacing elements</h3>
0N/A *
0N/A * <p>Elements and all their descendants can be replaced by using the
0N/A * methods <code>setInnerHTML</code> and <code>setOuterHTML</code>.
0N/A * For example, if <code>e</code> is the <code>DIV</code> element,
0N/A * <code>d.setInnerHTML(e, "&lt;ul>&lt;li>List
0N/A * Item&lt;/li>&lt;/ul>")</code> replaces all children paragraphs with
0N/A * the list, and <code>d.setOuterHTML(e, "&lt;ul>&lt;li>List
0N/A * Item&lt;/li>&lt;/ul>")</code> replaces the <code>DIV</code> element
0N/A * itself. In latter case the parent of the list is the
0N/A * <code>BODY</code> element.
0N/A *
0N/A * <h3>Summary</h3>
0N/A *
0N/A * <p>The following table shows the example document and the results
0N/A * of various methods described above.</p>
0N/A *
0N/A * <table border=1 cellspacing=0>
0N/A * <tr>
0N/A * <th>Example</th>
0N/A * <th><code>insertAfterStart</code></th>
0N/A * <th><code>insertBeforeEnd</code></th>
0N/A * <th><code>insertBeforeStart</code></th>
0N/A * <th><code>insertAfterEnd</code></th>
0N/A * <th><code>setInnerHTML</code></th>
0N/A * <th><code>setOuterHTML</code></th>
0N/A * </tr>
0N/A * <tr valign="top">
0N/A * <td nowrap="nowrap">
0N/A * <div style="background-color: silver;">
0N/A * <p>Paragraph 1</p>
0N/A * <p>Paragraph 2</p>
0N/A * </div>
0N/A * </td>
0N/A * <!--insertAfterStart-->
0N/A * <td nowrap="nowrap">
0N/A * <div style="background-color: silver;">
0N/A * <ul style="color: red;">
0N/A * <li>List Item</li>
0N/A * </ul>
0N/A * <p>Paragraph 1</p>
0N/A * <p>Paragraph 2</p>
0N/A * </div>
0N/A * </td>
0N/A * <!--insertBeforeEnd-->
0N/A * <td nowrap="nowrap">
0N/A * <div style="background-color: silver;">
0N/A * <p>Paragraph 1</p>
0N/A * <p>Paragraph 2</p>
0N/A * <ul style="color: red;">
0N/A * <li>List Item</li>
0N/A * </ul>
0N/A * </div>
0N/A * </td>
0N/A * <!--insertBeforeStart-->
0N/A * <td nowrap="nowrap">
0N/A * <ul style="color: red;">
0N/A * <li>List Item</li>
0N/A * </ul>
0N/A * <div style="background-color: silver;">
0N/A * <p>Paragraph 1</p>
0N/A * <p>Paragraph 2</p>
0N/A * </div>
0N/A * </td>
0N/A * <!--insertAfterEnd-->
0N/A * <td nowrap="nowrap">
0N/A * <div style="background-color: silver;">
0N/A * <p>Paragraph 1</p>
0N/A * <p>Paragraph 2</p>
0N/A * </div>
0N/A * <ul style="color: red;">
0N/A * <li>List Item</li>
0N/A * </ul>
0N/A * </td>
0N/A * <!--setInnerHTML-->
0N/A * <td nowrap="nowrap">
0N/A * <div style="background-color: silver;">
0N/A * <ul style="color: red;">
0N/A * <li>List Item</li>
0N/A * </ul>
0N/A * </div>
0N/A * </td>
0N/A * <!--setOuterHTML-->
0N/A * <td nowrap="nowrap">
0N/A * <ul style="color: red;">
0N/A * <li>List Item</li>
0N/A * </ul>
0N/A * </td>
0N/A * </tr>
0N/A * </table>
0N/A *
0N/A * <p><strong>Warning:</strong> Serialized objects of this class will
0N/A * not be compatible with future Swing releases. The current
0N/A * serialization support is appropriate for short term storage or RMI
0N/A * between applications running the same version of Swing. As of 1.4,
0N/A * support for long term storage of all JavaBeans<sup><font
0N/A * size="-2">TM</font></sup> has been added to the
0N/A * <code>java.beans</code> package. Please see {@link
0N/A * java.beans.XMLEncoder}.</p>
0N/A *
0N/A * @author Timothy Prinzing
0N/A * @author Scott Violet
0N/A * @author Sunita Mani
0N/A */
0N/Apublic class HTMLDocument extends DefaultStyledDocument {
0N/A /**
0N/A * Constructs an HTML document using the default buffer size
0N/A * and a default <code>StyleSheet</code>. This is a convenience
0N/A * method for the constructor
0N/A * <code>HTMLDocument(Content, StyleSheet)</code>.
0N/A */
0N/A public HTMLDocument() {
0N/A this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet());
0N/A }
0N/A
0N/A /**
0N/A * Constructs an HTML document with the default content
0N/A * storage implementation and the specified style/attribute
0N/A * storage mechanism. This is a convenience method for the
0N/A * constructor
0N/A * <code>HTMLDocument(Content, StyleSheet)</code>.
0N/A *
0N/A * @param styles the styles
0N/A */
0N/A public HTMLDocument(StyleSheet styles) {
0N/A this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
0N/A }
0N/A
0N/A /**
0N/A * Constructs an HTML document with the given content
0N/A * storage implementation and the given style/attribute
0N/A * storage mechanism.
0N/A *
0N/A * @param c the container for the content
0N/A * @param styles the styles
0N/A */
0N/A public HTMLDocument(Content c, StyleSheet styles) {
0N/A super(c, styles);
0N/A }
0N/A
0N/A /**
0N/A * Fetches the reader for the parser to use when loading the document
0N/A * with HTML. This is implemented to return an instance of
0N/A * <code>HTMLDocument.HTMLReader</code>.
0N/A * Subclasses can reimplement this
0N/A * method to change how the document gets structured if desired.
0N/A * (For example, to handle custom tags, or structurally represent character
0N/A * style elements.)
0N/A *
0N/A * @param pos the starting position
0N/A * @return the reader used by the parser to load the document
0N/A */
0N/A public HTMLEditorKit.ParserCallback getReader(int pos) {
0N/A Object desc = getProperty(Document.StreamDescriptionProperty);
0N/A if (desc instanceof URL) {
0N/A setBase((URL)desc);
0N/A }
0N/A HTMLReader reader = new HTMLReader(pos);
0N/A return reader;
0N/A }
0N/A
0N/A /**
0N/A * Returns the reader for the parser to use to load the document
0N/A * with HTML. This is implemented to return an instance of
0N/A * <code>HTMLDocument.HTMLReader</code>.
0N/A * Subclasses can reimplement this
0N/A * method to change how the document gets structured if desired.
0N/A * (For example, to handle custom tags, or structurally represent character
0N/A * style elements.)
0N/A * <p>This is a convenience method for
0N/A * <code>getReader(int, int, int, HTML.Tag, TRUE)</code>.
0N/A *
0N/A * @param popDepth the number of <code>ElementSpec.EndTagTypes</code>
0N/A * to generate before inserting
0N/A * @param pushDepth the number of <code>ElementSpec.StartTagTypes</code>
0N/A * with a direction of <code>ElementSpec.JoinNextDirection</code>
0N/A * that should be generated before inserting,
0N/A * but after the end tags have been generated
0N/A * @param insertTag the first tag to start inserting into document
0N/A * @return the reader used by the parser to load the document
0N/A */
0N/A public HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
0N/A int pushDepth,
0N/A HTML.Tag insertTag) {
0N/A return getReader(pos, popDepth, pushDepth, insertTag, true);
0N/A }
0N/A
0N/A /**
0N/A * Fetches the reader for the parser to use to load the document
0N/A * with HTML. This is implemented to return an instance of
0N/A * HTMLDocument.HTMLReader. Subclasses can reimplement this
0N/A * method to change how the document get structured if desired
0N/A * (e.g. to handle custom tags, structurally represent character
0N/A * style elements, etc.).
0N/A *
0N/A * @param popDepth the number of <code>ElementSpec.EndTagTypes</code>
0N/A * to generate before inserting
0N/A * @param pushDepth the number of <code>ElementSpec.StartTagTypes</code>
0N/A * with a direction of <code>ElementSpec.JoinNextDirection</code>
0N/A * that should be generated before inserting,
0N/A * but after the end tags have been generated
0N/A * @param insertTag the first tag to start inserting into document
0N/A * @param insertInsertTag false if all the Elements after insertTag should
0N/A * be inserted; otherwise insertTag will be inserted
0N/A * @return the reader used by the parser to load the document
0N/A */
0N/A HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
0N/A int pushDepth,
0N/A HTML.Tag insertTag,
0N/A boolean insertInsertTag) {
0N/A Object desc = getProperty(Document.StreamDescriptionProperty);
0N/A if (desc instanceof URL) {
0N/A setBase((URL)desc);
0N/A }
0N/A HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth,
0N/A insertTag, insertInsertTag, false,
0N/A true);
0N/A return reader;
0N/A }
0N/A
0N/A /**
0N/A * Returns the location to resolve relative URLs against. By
0N/A * default this will be the document's URL if the document
0N/A * was loaded from a URL. If a base tag is found and
0N/A * can be parsed, it will be used as the base location.
0N/A *
0N/A * @return the base location
0N/A */
0N/A public URL getBase() {
0N/A return base;
0N/A }
0N/A
0N/A /**
0N/A * Sets the location to resolve relative URLs against. By
0N/A * default this will be the document's URL if the document
0N/A * was loaded from a URL. If a base tag is found and
0N/A * can be parsed, it will be used as the base location.
0N/A * <p>This also sets the base of the <code>StyleSheet</code>
0N/A * to be <code>u</code> as well as the base of the document.
0N/A *
0N/A * @param u the desired base URL
0N/A */
0N/A public void setBase(URL u) {
0N/A base = u;
0N/A getStyleSheet().setBase(u);
0N/A }
0N/A
0N/A /**
0N/A * Inserts new elements in bulk. This is how elements get created
0N/A * in the document. The parsing determines what structure is needed
0N/A * and creates the specification as a set of tokens that describe the
0N/A * edit while leaving the document free of a write-lock. This method
0N/A * can then be called in bursts by the reader to acquire a write-lock
0N/A * for a shorter duration (i.e. while the document is actually being
0N/A * altered).
0N/A *
0N/A * @param offset the starting offset
0N/A * @param data the element data
0N/A * @exception BadLocationException if the given position does not
0N/A * represent a valid location in the associated document.
0N/A */
0N/A protected void insert(int offset, ElementSpec[] data) throws BadLocationException {
0N/A super.insert(offset, data);
0N/A }
0N/A
0N/A /**
0N/A * Updates document structure as a result of text insertion. This
0N/A * will happen within a write lock. This implementation simply
0N/A * parses the inserted content for line breaks and builds up a set
0N/A * of instructions for the element buffer.
0N/A *
0N/A * @param chng a description of the document change
0N/A * @param attr the attributes
0N/A */
0N/A protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
0N/A if(attr == null) {
0N/A attr = contentAttributeSet;
0N/A }
0N/A
0N/A // If this is the composed text element, merge the content attribute to it
0N/A else if (attr.isDefined(StyleConstants.ComposedTextAttribute)) {
0N/A ((MutableAttributeSet)attr).addAttributes(contentAttributeSet);
0N/A }
0N/A
0N/A if (attr.isDefined(IMPLIED_CR)) {
0N/A ((MutableAttributeSet)attr).removeAttribute(IMPLIED_CR);
0N/A }
0N/A
0N/A super.insertUpdate(chng, attr);
0N/A }
0N/A
0N/A /**
0N/A * Replaces the contents of the document with the given
0N/A * element specifications. This is called before insert if
0N/A * the loading is done in bursts. This is the only method called
0N/A * if loading the document entirely in one burst.
0N/A *
0N/A * @param data the new contents of the document
0N/A */
0N/A protected void create(ElementSpec[] data) {
0N/A super.create(data);
0N/A }
0N/A
0N/A /**
0N/A * Sets attributes for a paragraph.
0N/A * <p>
0N/A * This method is thread safe, although most Swing methods
0N/A * are not. Please see
0N/A * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0N/A * to Use Threads</A> for more information.
0N/A *
0N/A * @param offset the offset into the paragraph (must be at least 0)
0N/A * @param length the number of characters affected (must be at least 0)
0N/A * @param s the attributes
0N/A * @param replace whether to replace existing attributes, or merge them
0N/A */
0N/A public void setParagraphAttributes(int offset, int length, AttributeSet s,
0N/A boolean replace) {
0N/A try {
0N/A writeLock();
0N/A // Make sure we send out a change for the length of the paragraph.
0N/A int end = Math.min(offset + length, getLength());
0N/A Element e = getParagraphElement(offset);
0N/A offset = e.getStartOffset();
0N/A e = getParagraphElement(end);
0N/A length = Math.max(0, e.getEndOffset() - offset);
0N/A DefaultDocumentEvent changes =
0N/A new DefaultDocumentEvent(offset, length,
0N/A DocumentEvent.EventType.CHANGE);
0N/A AttributeSet sCopy = s.copyAttributes();
0N/A int lastEnd = Integer.MAX_VALUE;
0N/A for (int pos = offset; pos <= end; pos = lastEnd) {
0N/A Element paragraph = getParagraphElement(pos);
0N/A if (lastEnd == paragraph.getEndOffset()) {
0N/A lastEnd++;
0N/A }
0N/A else {
0N/A lastEnd = paragraph.getEndOffset();
0N/A }
0N/A MutableAttributeSet attr =
0N/A (MutableAttributeSet) paragraph.getAttributes();
0N/A changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
0N/A if (replace) {
0N/A attr.removeAttributes(attr);
0N/A }
0N/A attr.addAttributes(s);
0N/A }
0N/A changes.end();
0N/A fireChangedUpdate(changes);
0N/A fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
0N/A } finally {
0N/A writeUnlock();
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Fetches the <code>StyleSheet</code> with the document-specific display
0N/A * rules (CSS) that were specified in the HTML document itself.
0N/A *
0N/A * @return the <code>StyleSheet</code>
0N/A */
0N/A public StyleSheet getStyleSheet() {
0N/A return (StyleSheet) getAttributeContext();
0N/A }
0N/A
0N/A /**
0N/A * Fetches an iterator for the specified HTML tag.
0N/A * This can be used for things like iterating over the
0N/A * set of anchors contained, or iterating over the input
0N/A * elements.
0N/A *
0N/A * @param t the requested <code>HTML.Tag</code>
0N/A * @return the <code>Iterator</code> for the given HTML tag
0N/A * @see javax.swing.text.html.HTML.Tag
0N/A */
0N/A public Iterator getIterator(HTML.Tag t) {
0N/A if (t.isBlock()) {
0N/A // TBD
0N/A return null;
0N/A }
0N/A return new LeafIterator(t, this);
0N/A }
0N/A
0N/A /**
0N/A * Creates a document leaf element that directly represents
0N/A * text (doesn't have any children). This is implemented
0N/A * to return an element of type
0N/A * <code>HTMLDocument.RunElement</code>.
0N/A *
0N/A * @param parent the parent element
0N/A * @param a the attributes for the element
0N/A * @param p0 the beginning of the range (must be at least 0)
0N/A * @param p1 the end of the range (must be at least p0)
0N/A * @return the new element
0N/A */
0N/A protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
0N/A return new RunElement(parent, a, p0, p1);
0N/A }
0N/A
0N/A /**
0N/A * Creates a document branch element, that can contain other elements.
0N/A * This is implemented to return an element of type
0N/A * <code>HTMLDocument.BlockElement</code>.
0N/A *
0N/A * @param parent the parent element
0N/A * @param a the attributes
0N/A * @return the element
0N/A */
0N/A protected Element createBranchElement(Element parent, AttributeSet a) {
0N/A return new BlockElement(parent, a);
0N/A }
0N/A
0N/A /**
0N/A * Creates the root element to be used to represent the
0N/A * default document structure.
0N/A *
0N/A * @return the element base
0N/A */
0N/A protected AbstractElement createDefaultRoot() {
0N/A // grabs a write-lock for this initialization and
0N/A // abandon it during initialization so in normal
0N/A // operation we can detect an illegitimate attempt
0N/A // to mutate attributes.
0N/A writeLock();
0N/A MutableAttributeSet a = new SimpleAttributeSet();
0N/A a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.HTML);
0N/A BlockElement html = new BlockElement(null, a.copyAttributes());
0N/A a.removeAttributes(a);
0N/A a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.BODY);
0N/A BlockElement body = new BlockElement(html, a.copyAttributes());
0N/A a.removeAttributes(a);
0N/A a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.P);
0N/A getStyleSheet().addCSSAttributeFromHTML(a, CSS.Attribute.MARGIN_TOP, "0");
0N/A BlockElement paragraph = new BlockElement(body, a.copyAttributes());
0N/A a.removeAttributes(a);
0N/A a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
0N/A RunElement brk = new RunElement(paragraph, a, 0, 1);
0N/A Element[] buff = new Element[1];
0N/A buff[0] = brk;
0N/A paragraph.replace(0, 0, buff);
0N/A buff[0] = paragraph;
0N/A body.replace(0, 0, buff);
0N/A buff[0] = body;
0N/A html.replace(0, 0, buff);
0N/A writeUnlock();
0N/A return html;
0N/A }
0N/A
0N/A /**
0N/A * Sets the number of tokens to buffer before trying to update
0N/A * the documents element structure.
0N/A *
0N/A * @param n the number of tokens to buffer
0N/A */
0N/A public void setTokenThreshold(int n) {
0N/A putProperty(TokenThreshold, new Integer(n));
0N/A }
0N/A
0N/A /**
0N/A * Gets the number of tokens to buffer before trying to update
0N/A * the documents element structure. The default value is
0N/A * <code>Integer.MAX_VALUE</code>.
0N/A *
0N/A * @return the number of tokens to buffer
0N/A */
0N/A public int getTokenThreshold() {
0N/A Integer i = (Integer) getProperty(TokenThreshold);
0N/A if (i != null) {
0N/A return i.intValue();
0N/A }
0N/A return Integer.MAX_VALUE;
0N/A }
0N/A
0N/A /**
0N/A * Determines how unknown tags are handled by the parser.
0N/A * If set to true, unknown
0N/A * tags are put in the model, otherwise they are dropped.
0N/A *
0N/A * @param preservesTags true if unknown tags should be
0N/A * saved in the model, otherwise tags are dropped
0N/A * @see javax.swing.text.html.HTML.Tag
0N/A */
0N/A public void setPreservesUnknownTags(boolean preservesTags) {
0N/A preservesUnknownTags = preservesTags;
0N/A }
0N/A
0N/A /**
0N/A * Returns the behavior the parser observes when encountering
0N/A * unknown tags.
0N/A *
0N/A * @see javax.swing.text.html.HTML.Tag
0N/A * @return true if unknown tags are to be preserved when parsing
0N/A */
0N/A public boolean getPreservesUnknownTags() {
0N/A return preservesUnknownTags;
0N/A }
0N/A
0N/A /**
0N/A * Processes <code>HyperlinkEvents</code> that
0N/A * are generated by documents in an HTML frame.
0N/A * The <code>HyperlinkEvent</code> type, as the parameter suggests,
0N/A * is <code>HTMLFrameHyperlinkEvent</code>.
0N/A * In addition to the typical information contained in a
0N/A * <code>HyperlinkEvent</code>,
0N/A * this event contains the element that corresponds to the frame in
0N/A * which the click happened (the source element) and the
0N/A * target name. The target name has 4 possible values:
0N/A * <ul>
0N/A * <li> _self
0N/A * <li> _parent
0N/A * <li> _top
0N/A * <li> a named frame
0N/A * </ul>
0N/A *
0N/A * If target is _self, the action is to change the value of the
0N/A * <code>HTML.Attribute.SRC</code> attribute and fires a
0N/A * <code>ChangedUpdate</code> event.
0N/A *<p>
0N/A * If the target is _parent, then it deletes the parent element,
0N/A * which is a &lt;FRAMESET&gt; element, and inserts a new &lt;FRAME&gt;
0N/A * element, and sets its <code>HTML.Attribute.SRC</code> attribute
0N/A * to have a value equal to the destination URL and fire a
0N/A * <code>RemovedUpdate</code> and <code>InsertUpdate</code>.
0N/A *<p>
0N/A * If the target is _top, this method does nothing. In the implementation
0N/A * of the view for a frame, namely the <code>FrameView</code>,
0N/A * the processing of _top is handled. Given that _top implies
0N/A * replacing the entire document, it made sense to handle this outside
0N/A * of the document that it will replace.
0N/A *<p>
0N/A * If the target is a named frame, then the element hierarchy is searched
0N/A * for an element with a name equal to the target, its
0N/A * <code>HTML.Attribute.SRC</code> attribute is updated and a
0N/A * <code>ChangedUpdate</code> event is fired.
0N/A *
0N/A * @param e the event
0N/A */
0N/A public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent e) {
0N/A String frameName = e.getTarget();
0N/A Element element = e.getSourceElement();
0N/A String urlStr = e.getURL().toString();
0N/A
0N/A if (frameName.equals("_self")) {
0N/A /*
0N/A The source and destination elements
0N/A are the same.
0N/A */
0N/A updateFrame(element, urlStr);
0N/A } else if (frameName.equals("_parent")) {
0N/A /*
0N/A The destination is the parent of the frame.
0N/A */
0N/A updateFrameSet(element.getParentElement(), urlStr);
0N/A } else {
0N/A /*
0N/A locate a named frame
0N/A */
0N/A Element targetElement = findFrame(frameName);
0N/A if (targetElement != null) {
0N/A updateFrame(targetElement, urlStr);
0N/A }
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Searches the element hierarchy for an FRAME element
0N/A * that has its name attribute equal to the <code>frameName</code>.
0N/A *
0N/A * @param frameName
0N/A * @return the element whose NAME attribute has a value of
0N/A * <code>frameName</code>; returns <code>null</code>
0N/A * if not found
0N/A */
0N/A private Element findFrame(String frameName) {
0N/A ElementIterator it = new ElementIterator(this);
611N/A Element next;
0N/A
0N/A while ((next = it.next()) != null) {
0N/A AttributeSet attr = next.getAttributes();
0N/A if (matchNameAttribute(attr, HTML.Tag.FRAME)) {
0N/A String frameTarget = (String)attr.getAttribute(HTML.Attribute.NAME);
0N/A if (frameTarget != null && frameTarget.equals(frameName)) {
0N/A break;
0N/A }
0N/A }
0N/A }
0N/A return next;
0N/A }
0N/A
0N/A /**
0N/A * Returns true if <code>StyleConstants.NameAttribute</code> is
0N/A * equal to the tag that is passed in as a parameter.
0N/A *
0N/A * @param attr the attributes to be matched
0N/A * @param tag the value to be matched
0N/A * @return true if there is a match, false otherwise
0N/A * @see javax.swing.text.html.HTML.Attribute
0N/A */
0N/A static boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
0N/A Object o = attr.getAttribute(StyleConstants.NameAttribute);
0N/A if (o instanceof HTML.Tag) {
0N/A HTML.Tag name = (HTML.Tag) o;
0N/A if (name == tag) {
0N/A return true;
0N/A }
0N/A }
0N/A return false;
0N/A }
0N/A
0N/A /**
0N/A * Replaces a frameset branch Element with a frame leaf element.
0N/A *
0N/A * @param element the frameset element to remove
0N/A * @param url the value for the SRC attribute for the
0N/A * new frame that will replace the frameset
0N/A */
0N/A private void updateFrameSet(Element element, String url) {
0N/A try {
0N/A int startOffset = element.getStartOffset();
0N/A int endOffset = Math.min(getLength(), element.getEndOffset());
0N/A String html = "<frame";
0N/A if (url != null) {
0N/A html += " src=\"" + url + "\"";
0N/A }
0N/A html += ">";
0N/A installParserIfNecessary();
0N/A setOuterHTML(element, html);
0N/A } catch (BadLocationException e1) {
0N/A // Should handle this better
0N/A } catch (IOException ioe) {
0N/A // Should handle this better
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Updates the Frame elements <code>HTML.Attribute.SRC attribute</code>
0N/A * and fires a <code>ChangedUpdate</code> event.
0N/A *
0N/A * @param element a FRAME element whose SRC attribute will be updated
0N/A * @param url a string specifying the new value for the SRC attribute
0N/A */
0N/A private void updateFrame(Element element, String url) {
0N/A
0N/A try {
0N/A writeLock();
0N/A DefaultDocumentEvent changes = new DefaultDocumentEvent(element.getStartOffset(),
0N/A 1,
0N/A DocumentEvent.EventType.CHANGE);
0N/A AttributeSet sCopy = element.getAttributes().copyAttributes();
0N/A MutableAttributeSet attr = (MutableAttributeSet) element.getAttributes();
0N/A changes.addEdit(new AttributeUndoableEdit(element, sCopy, false));
0N/A attr.removeAttribute(HTML.Attribute.SRC);
0N/A attr.addAttribute(HTML.Attribute.SRC, url);
0N/A changes.end();
0N/A fireChangedUpdate(changes);
0N/A fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
0N/A } finally {
0N/A writeUnlock();
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Returns true if the document will be viewed in a frame.
0N/A * @return true if document will be viewed in a frame, otherwise false
0N/A */
0N/A boolean isFrameDocument() {
0N/A return frameDocument;
0N/A }
0N/A
0N/A /**
0N/A * Sets a boolean state about whether the document will be
0N/A * viewed in a frame.
0N/A * @param frameDoc true if the document will be viewed in a frame,
0N/A * otherwise false
0N/A */
0N/A void setFrameDocumentState(boolean frameDoc) {
0N/A this.frameDocument = frameDoc;
0N/A }
0N/A
0N/A /**
0N/A * Adds the specified map, this will remove a Map that has been
0N/A * previously registered with the same name.
0N/A *
0N/A * @param map the <code>Map</code> to be registered
0N/A */
0N/A void addMap(Map map) {
0N/A String name = map.getName();
0N/A
0N/A if (name != null) {
0N/A Object maps = getProperty(MAP_PROPERTY);
0N/A
0N/A if (maps == null) {
0N/A maps = new Hashtable(11);
0N/A putProperty(MAP_PROPERTY, maps);
0N/A }
0N/A if (maps instanceof Hashtable) {
0N/A ((Hashtable)maps).put("#" + name, map);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Removes a previously registered map.
0N/A * @param map the <code>Map</code> to be removed
0N/A */
0N/A void removeMap(Map map) {
0N/A String name = map.getName();
0N/A
0N/A if (name != null) {
0N/A Object maps = getProperty(MAP_PROPERTY);
0N/A
0N/A if (maps instanceof Hashtable) {
0N/A ((Hashtable)maps).remove("#" + name);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Returns the Map associated with the given name.
611N/A * @param name the name of the desired <code>Map</code>
0N/A * @return the <code>Map</code> or <code>null</code> if it can't
0N/A * be found, or if <code>name</code> is <code>null</code>
0N/A */
0N/A Map getMap(String name) {
0N/A if (name != null) {
0N/A Object maps = getProperty(MAP_PROPERTY);
0N/A
0N/A if (maps != null && (maps instanceof Hashtable)) {
0N/A return (Map)((Hashtable)maps).get(name);
0N/A }
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A /**
0N/A * Returns an <code>Enumeration</code> of the possible Maps.
0N/A * @return the enumerated list of maps, or <code>null</code>
0N/A * if the maps are not an instance of <code>Hashtable</code>
0N/A */
0N/A Enumeration getMaps() {
0N/A Object maps = getProperty(MAP_PROPERTY);
0N/A
0N/A if (maps instanceof Hashtable) {
0N/A return ((Hashtable)maps).elements();
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A /**
0N/A * Sets the content type language used for style sheets that do not
0N/A * explicitly specify the type. The default is text/css.
0N/A * @param contentType the content type language for the style sheets
0N/A */
0N/A /* public */
0N/A void setDefaultStyleSheetType(String contentType) {
0N/A putProperty(StyleType, contentType);
0N/A }
0N/A
0N/A /**
0N/A * Returns the content type language used for style sheets. The default
0N/A * is text/css.
0N/A * @return the content type language used for the style sheets
0N/A */
0N/A /* public */
0N/A String getDefaultStyleSheetType() {
0N/A String retValue = (String)getProperty(StyleType);
0N/A if (retValue == null) {
0N/A return "text/css";
0N/A }
0N/A return retValue;
0N/A }
0N/A
0N/A /**
0N/A * Sets the parser that is used by the methods that insert html
0N/A * into the existing document, such as <code>setInnerHTML</code>,
0N/A * and <code>setOuterHTML</code>.
0N/A * <p>
0N/A * <code>HTMLEditorKit.createDefaultDocument</code> will set the parser
0N/A * for you. If you create an <code>HTMLDocument</code> by hand,
0N/A * be sure and set the parser accordingly.
0N/A * @param parser the parser to be used for text insertion
0N/A *
0N/A * @since 1.3
0N/A */
0N/A public void setParser(HTMLEditorKit.Parser parser) {
0N/A this.parser = parser;
0N/A putProperty("__PARSER__", null);
0N/A }
0N/A
0N/A /**
0N/A * Returns the parser that is used when inserting HTML into the existing
0N/A * document.
0N/A * @return the parser used for text insertion
0N/A *
0N/A * @since 1.3
0N/A */
0N/A public HTMLEditorKit.Parser getParser() {
0N/A Object p = getProperty("__PARSER__");
0N/A
0N/A if (p instanceof HTMLEditorKit.Parser) {
0N/A return (HTMLEditorKit.Parser)p;
0N/A }
0N/A return parser;
0N/A }
0N/A
0N/A /**
0N/A * Replaces the children of the given element with the contents
0N/A * specified as an HTML string.
0N/A *
0N/A * <p>This will be seen as at least two events, n inserts followed by
0N/A * a remove.</p>
0N/A *
0N/A * <p>Consider the following structure (the <code>elem</code>
0N/A * parameter is <b>in bold</b>).</p>
0N/A *
0N/A * <pre>
0N/A * &lt;body>
0N/A * |
0N/A * <b>&lt;div></b>
0N/A * / \
0N/A * &lt;p> &lt;p>
0N/A * </pre>
0N/A *
0N/A * <p>Invoking <code>setInnerHTML(elem, "&lt;ul>&lt;li>")</code>
0N/A * results in the following structure (new elements are <font
0N/A * color="red">in red</font>).</p>
0N/A *
0N/A * <pre>
0N/A * &lt;body>
0N/A * |
0N/A * <b>&lt;div></b>
0N/A * \
0N/A * <font color="red">&lt;ul></font>
0N/A * \
0N/A * <font color="red">&lt;li></font>
0N/A * </pre>
0N/A *
0N/A * <p>Parameter <code>elem</code> must not be a leaf element,
0N/A * otherwise an <code>IllegalArgumentException</code> is thrown.
0N/A * If either <code>elem</code> or <code>htmlText</code> parameter
0N/A * is <code>null</code>, no changes are made to the document.</p>
0N/A *
0N/A * <p>For this to work correcty, the document must have an
0N/A * <code>HTMLEditorKit.Parser</code> set. This will be the case
0N/A * if the document was created from an HTMLEditorKit via the
0N/A * <code>createDefaultDocument</code> method.</p>
0N/A *
0N/A * @param elem the branch element whose children will be replaced
0N/A * @param htmlText the string to be parsed and assigned to <code>elem</code>
0N/A * @throws IllegalArgumentException if <code>elem</code> is a leaf
0N/A * @throws IllegalStateException if an <code>HTMLEditorKit.Parser</code>
0N/A * has not been defined
0N/A * @since 1.3
0N/A */
0N/A public void setInnerHTML(Element elem, String htmlText) throws
0N/A BadLocationException, IOException {
0N/A verifyParser();
0N/A if (elem != null && elem.isLeaf()) {
0N/A throw new IllegalArgumentException
0N/A ("Can not set inner HTML of a leaf");
0N/A }
0N/A if (elem != null && htmlText != null) {
0N/A int oldCount = elem.getElementCount();
0N/A int insertPosition = elem.getStartOffset();
0N/A insertHTML(elem, elem.getStartOffset(), htmlText, true);
0N/A if (elem.getElementCount() > oldCount) {
0N/A // Elements were inserted, do the cleanup.
0N/A removeElements(elem, elem.getElementCount() - oldCount,
0N/A oldCount);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Replaces the given element in the parent with the contents
0N/A * specified as an HTML string.
0N/A *
0N/A * <p>This will be seen as at least two events, n inserts followed by
0N/A * a remove.</p>
0N/A *
0N/A * <p>When replacing a leaf this will attempt to make sure there is
0N/A * a newline present if one is needed. This may result in an additional
0N/A * element being inserted. Consider, if you were to replace a character
0N/A * element that contained a newline with &lt;img&gt; this would create
0N/A * two elements, one for the image, ane one for the newline.</p>
0N/A *
0N/A * <p>If you try to replace the element at length you will most
0N/A * likely end up with two elements, eg
0N/A * <code>setOuterHTML(getCharacterElement (getLength()),
0N/A * "blah")</code> will result in two leaf elements at the end, one
0N/A * representing 'blah', and the other representing the end
0N/A * element.</p>
0N/A *
0N/A * <p>Consider the following structure (the <code>elem</code>
0N/A * parameter is <b>in bold</b>).</p>
0N/A *
0N/A * <pre>
0N/A * &lt;body>
0N/A * |
0N/A * <b>&lt;div></b>
0N/A * / \
0N/A * &lt;p> &lt;p>
0N/A * </pre>
0N/A *
0N/A * <p>Invoking <code>setOuterHTML(elem, "&lt;ul>&lt;li>")</code>
0N/A * results in the following structure (new elements are <font
0N/A * color="red">in red</font>).</p>
0N/A *
0N/A * <pre>
0N/A * &lt;body>
0N/A * |
0N/A * <font color="red">&lt;ul></font>
0N/A * \
0N/A * <font color="red">&lt;li></font>
0N/A * </pre>
0N/A *
0N/A * <p>If either <code>elem</code> or <code>htmlText</code>
0N/A * parameter is <code>null</code>, no changes are made to the
0N/A * document.</p>
0N/A *
0N/A * <p>For this to work correcty, the document must have an
0N/A * HTMLEditorKit.Parser set. This will be the case if the document
0N/A * was created from an HTMLEditorKit via the
0N/A * <code>createDefaultDocument</code> method.</p>
0N/A *
0N/A * @param elem the element to replace
0N/A * @param htmlText the string to be parsed and inserted in place of <code>elem</code>
0N/A * @throws IllegalStateException if an HTMLEditorKit.Parser has not
0N/A * been set
0N/A * @since 1.3
0N/A */
0N/A public void setOuterHTML(Element elem, String htmlText) throws
0N/A BadLocationException, IOException {
0N/A verifyParser();
0N/A if (elem != null && elem.getParentElement() != null &&
0N/A htmlText != null) {
0N/A int start = elem.getStartOffset();
0N/A int end = elem.getEndOffset();
0N/A int startLength = getLength();
0N/A // We don't want a newline if elem is a leaf, and doesn't contain
0N/A // a newline.
0N/A boolean wantsNewline = !elem.isLeaf();
0N/A if (!wantsNewline && (end > startLength ||
0N/A getText(end - 1, 1).charAt(0) == NEWLINE[0])){
0N/A wantsNewline = true;
0N/A }
0N/A Element parent = elem.getParentElement();
0N/A int oldCount = parent.getElementCount();
0N/A insertHTML(parent, start, htmlText, wantsNewline);
0N/A // Remove old.
0N/A int newLength = getLength();
0N/A if (oldCount != parent.getElementCount()) {
0N/A int removeIndex = parent.getElementIndex(start + newLength -
0N/A startLength);
0N/A removeElements(parent, removeIndex, 1);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Inserts the HTML specified as a string at the start
0N/A * of the element.
0N/A *
0N/A * <p>Consider the following structure (the <code>elem</code>
0N/A * parameter is <b>in bold</b>).</p>
0N/A *
0N/A * <pre>
0N/A * &lt;body>
0N/A * |
0N/A * <b>&lt;div></b>
0N/A * / \
0N/A * &lt;p> &lt;p>
0N/A * </pre>
0N/A *
0N/A * <p>Invoking <code>insertAfterStart(elem,
0N/A * "&lt;ul>&lt;li>")</code> results in the following structure
0N/A * (new elements are <font color="red">in red</font>).</p>
0N/A *
0N/A * <pre>
0N/A * &lt;body>
0N/A * |
0N/A * <b>&lt;div></b>
0N/A * / | \
0N/A * <font color="red">&lt;ul></font> &lt;p> &lt;p>
0N/A * /
0N/A * <font color="red">&lt;li></font>
0N/A * </pre>
0N/A *
0N/A * <p>Unlike the <code>insertBeforeStart</code> method, new
0N/A * elements become <em>children</em> of the specified element,
0N/A * not siblings.</p>
0N/A *
0N/A * <p>Parameter <code>elem</code> must not be a leaf element,
0N/A * otherwise an <code>IllegalArgumentException</code> is thrown.
0N/A * If either <code>elem</code> or <code>htmlText</code> parameter
0N/A * is <code>null</code>, no changes are made to the document.</p>
0N/A *
0N/A * <p>For this to work correcty, the document must have an
0N/A * <code>HTMLEditorKit.Parser</code> set. This will be the case
0N/A * if the document was created from an HTMLEditorKit via the
0N/A * <code>createDefaultDocument</code> method.</p>
0N/A *
0N/A * @param elem the branch element to be the root for the new text
0N/A * @param htmlText the string to be parsed and assigned to <code>elem</code>
0N/A * @throws IllegalArgumentException if <code>elem</code> is a leaf
0N/A * @throws IllegalStateException if an HTMLEditorKit.Parser has not
0N/A * been set on the document
0N/A * @since 1.3
0N/A */
0N/A public void insertAfterStart(Element elem, String htmlText) throws
0N/A BadLocationException, IOException {
0N/A verifyParser();
0N/A if (elem != null && elem.isLeaf()) {
0N/A throw new IllegalArgumentException
0N/A ("Can not insert HTML after start of a leaf");
0N/A }
0N/A insertHTML(elem, elem.getStartOffset(), htmlText, false);
0N/A }
0N/A
0N/A /**
0N/A * Inserts the HTML specified as a string at the end of
0N/A * the element.
0N/A *
0N/A * <p> If <code>elem</code>'s children are leaves, and the
0N/A * character at a <code>elem.getEndOffset() - 1</code> is a newline,
0N/A * this will insert before the newline so that there isn't text after
0N/A * the newline.</p>
0N/A *
0N/A * <p>Consider the following structure (the <code>elem</code>
0N/A * parameter is <b>in bold</b>).</p>
0N/A *
0N/A * <pre>
0N/A * &lt;body>
0N/A * |
0N/A * <b>&lt;div></b>
0N/A * / \
0N/A * &lt;p> &lt;p>
0N/A * </pre>
0N/A *
0N/A * <p>Invoking <code>insertBeforeEnd(elem, "&lt;ul>&lt;li>")</code>
0N/A * results in the following structure (new elements are <font
0N/A * color="red">in red</font>).</p>
0N/A *
0N/A * <pre>
0N/A * &lt;body>
0N/A * |
0N/A * <b>&lt;div></b>
0N/A * / | \
0N/A * &lt;p> &lt;p> <font color="red">&lt;ul></font>
0N/A * \
0N/A * <font color="red">&lt;li></font>
0N/A * </pre>
0N/A *
0N/A * <p>Unlike the <code>insertAfterEnd</code> method, new elements
0N/A * become <em>children</em> of the specified element, not
0N/A * siblings.</p>
0N/A *
0N/A * <p>Parameter <code>elem</code> must not be a leaf element,
0N/A * otherwise an <code>IllegalArgumentException</code> is thrown.
0N/A * If either <code>elem</code> or <code>htmlText</code> parameter
0N/A * is <code>null</code>, no changes are made to the document.</p>
0N/A *
0N/A * <p>For this to work correcty, the document must have an
0N/A * <code>HTMLEditorKit.Parser</code> set. This will be the case
0N/A * if the document was created from an HTMLEditorKit via the
0N/A * <code>createDefaultDocument</code> method.</p>
0N/A *
0N/A * @param elem the element to be the root for the new text
0N/A * @param htmlText the string to be parsed and assigned to <code>elem</code>
0N/A * @throws IllegalArgumentException if <code>elem</code> is a leaf
0N/A * @throws IllegalStateException if an HTMLEditorKit.Parser has not
0N/A * been set on the document
0N/A * @since 1.3
0N/A */
0N/A public void insertBeforeEnd(Element elem, String htmlText) throws
0N/A BadLocationException, IOException {
0N/A verifyParser();
0N/A if (elem != null && elem.isLeaf()) {
0N/A throw new IllegalArgumentException
0N/A ("Can not set inner HTML before end of leaf");
0N/A }
0N/A if (elem != null) {
0N/A int offset = elem.getEndOffset();
0N/A if (elem.getElement(elem.getElementIndex(offset - 1)).isLeaf() &&
0N/A getText(offset - 1, 1).charAt(0) == NEWLINE[0]) {
0N/A offset--;
0N/A }
0N/A insertHTML(elem, offset, htmlText, false);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Inserts the HTML specified as a string before the start of
0N/A * the given element.
0N/A *
0N/A * <p>Consider the following structure (the <code>elem</code>
0N/A * parameter is <b>in bold</b>).</p>
0N/A *
0N/A * <pre>
0N/A * &lt;body>
0N/A * |
0N/A * <b>&lt;div></b>
0N/A * / \
0N/A * &lt;p> &lt;p>
0N/A * </pre>
0N/A *
0N/A * <p>Invoking <code>insertBeforeStart(elem,
0N/A * "&lt;ul>&lt;li>")</code> results in the following structure
0N/A * (new elements are <font color="red">in red</font>).</p>
0N/A *
0N/A * <pre>
0N/A * &lt;body>
0N/A * / \
0N/A * <font color="red">&lt;ul></font> <b>&lt;div></b>
0N/A * / / \
0N/A * <font color="red">&lt;li></font> &lt;p> &lt;p>
0N/A * </pre>
0N/A *
0N/A * <p>Unlike the <code>insertAfterStart</code> method, new
0N/A * elements become <em>siblings</em> of the specified element, not
0N/A * children.</p>
0N/A *
0N/A * <p>If either <code>elem</code> or <code>htmlText</code>
0N/A * parameter is <code>null</code>, no changes are made to the
0N/A * document.</p>
0N/A *
0N/A * <p>For this to work correcty, the document must have an
0N/A * <code>HTMLEditorKit.Parser</code> set. This will be the case
0N/A * if the document was created from an HTMLEditorKit via the
0N/A * <code>createDefaultDocument</code> method.</p>
0N/A *
0N/A * @param elem the element the content is inserted before
0N/A * @param htmlText the string to be parsed and inserted before <code>elem</code>
0N/A * @throws IllegalStateException if an HTMLEditorKit.Parser has not
0N/A * been set on the document
0N/A * @since 1.3
0N/A */
0N/A public void insertBeforeStart(Element elem, String htmlText) throws
0N/A BadLocationException, IOException {
0N/A verifyParser();
0N/A if (elem != null) {
0N/A Element parent = elem.getParentElement();
0N/A
0N/A if (parent != null) {
0N/A insertHTML(parent, elem.getStartOffset(), htmlText, false);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Inserts the HTML specified as a string after the the end of the
0N/A * given element.
0N/A *
0N/A * <p>Consider the following structure (the <code>elem</code>
0N/A * parameter is <b>in bold</b>).</p>
0N/A *
0N/A * <pre>
0N/A * &lt;body>
0N/A * |
0N/A * <b>&lt;div></b>
0N/A * / \
0N/A * &lt;p> &lt;p>
0N/A * </pre>
0N/A *
0N/A * <p>Invoking <code>insertAfterEnd(elem, "&lt;ul>&lt;li>")</code>
0N/A * results in the following structure (new elements are <font
0N/A * color="red">in red</font>).</p>
0N/A *
0N/A * <pre>
0N/A * &lt;body>
0N/A * / \
0N/A * <b>&lt;div></b> <font color="red">&lt;ul></font>
0N/A * / \ \
0N/A * &lt;p> &lt;p> <font color="red">&lt;li></font>
0N/A * </pre>
0N/A *
0N/A * <p>Unlike the <code>insertBeforeEnd</code> method, new elements
0N/A * become <em>siblings</em> of the specified element, not
0N/A * children.</p>
0N/A *
0N/A * <p>If either <code>elem</code> or <code>htmlText</code>
0N/A * parameter is <code>null</code>, no changes are made to the
0N/A * document.</p>
0N/A *
0N/A * <p>For this to work correcty, the document must have an
0N/A * <code>HTMLEditorKit.Parser</code> set. This will be the case
0N/A * if the document was created from an HTMLEditorKit via the
0N/A * <code>createDefaultDocument</code> method.</p>
0N/A *
0N/A * @param elem the element the content is inserted after
0N/A * @param htmlText the string to be parsed and inserted after <code>elem</code>
0N/A * @throws IllegalStateException if an HTMLEditorKit.Parser has not
0N/A * been set on the document
0N/A * @since 1.3
0N/A */
0N/A public void insertAfterEnd(Element elem, String htmlText) throws
0N/A BadLocationException, IOException {
0N/A verifyParser();
0N/A if (elem != null) {
0N/A Element parent = elem.getParentElement();
0N/A
0N/A if (parent != null) {
0N/A int offset = elem.getEndOffset();
0N/A if (offset > getLength()) {
0N/A offset--;
0N/A }
0N/A else if (elem.isLeaf() && getText(offset - 1, 1).
0N/A charAt(0) == NEWLINE[0]) {
0N/A offset--;
0N/A }
0N/A insertHTML(parent, offset, htmlText, false);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Returns the element that has the given id <code>Attribute</code>.
0N/A * If the element can't be found, <code>null</code> is returned.
0N/A * Note that this method works on an <code>Attribute</code>,
0N/A * <i>not</i> a character tag. In the following HTML snippet:
0N/A * <code>&lt;a id="HelloThere"&gt;</code> the attribute is
0N/A * 'id' and the character tag is 'a'.
0N/A * This is a convenience method for
0N/A * <code>getElement(RootElement, HTML.Attribute.id, id)</code>.
0N/A * This is not thread-safe.
0N/A *
0N/A * @param id the string representing the desired <code>Attribute</code>
0N/A * @return the element with the specified <code>Attribute</code>
0N/A * or <code>null</code> if it can't be found,
0N/A * or <code>null</code> if <code>id</code> is <code>null</code>
0N/A * @see javax.swing.text.html.HTML.Attribute
0N/A * @since 1.3
0N/A */
0N/A public Element getElement(String id) {
0N/A if (id == null) {
0N/A return null;
0N/A }
0N/A return getElement(getDefaultRootElement(), HTML.Attribute.ID, id,
0N/A true);
0N/A }
0N/A
0N/A /**
0N/A * Returns the child element of <code>e</code> that contains the
0N/A * attribute, <code>attribute</code> with value <code>value</code>, or
0N/A * <code>null</code> if one isn't found. This is not thread-safe.
0N/A *
0N/A * @param e the root element where the search begins
0N/A * @param attribute the desired <code>Attribute</code>
0N/A * @param value the values for the specified <code>Attribute</code>
0N/A * @return the element with the specified <code>Attribute</code>
0N/A * and the specified <code>value</code>, or <code>null</code>
0N/A * if it can't be found
0N/A * @see javax.swing.text.html.HTML.Attribute
0N/A * @since 1.3
0N/A */
0N/A public Element getElement(Element e, Object attribute, Object value) {
0N/A return getElement(e, attribute, value, true);
0N/A }
0N/A
0N/A /**
0N/A * Returns the child element of <code>e</code> that contains the
0N/A * attribute, <code>attribute</code> with value <code>value</code>, or
0N/A * <code>null</code> if one isn't found. This is not thread-safe.
0N/A * <p>
0N/A * If <code>searchLeafAttributes</code> is true, and <code>e</code> is
0N/A * a leaf, any attributes that are instances of <code>HTML.Tag</code>
0N/A * with a value that is an <code>AttributeSet</code> will also be checked.
0N/A *
0N/A * @param e the root element where the search begins
0N/A * @param attribute the desired <code>Attribute</code>
0N/A * @param value the values for the specified <code>Attribute</code>
0N/A * @return the element with the specified <code>Attribute</code>
0N/A * and the specified <code>value</code>, or <code>null</code>
0N/A * if it can't be found
0N/A * @see javax.swing.text.html.HTML.Attribute
0N/A */
0N/A private Element getElement(Element e, Object attribute, Object value,
0N/A boolean searchLeafAttributes) {
0N/A AttributeSet attr = e.getAttributes();
0N/A
0N/A if (attr != null && attr.isDefined(attribute)) {
0N/A if (value.equals(attr.getAttribute(attribute))) {
0N/A return e;
0N/A }
0N/A }
0N/A if (!e.isLeaf()) {
0N/A for (int counter = 0, maxCounter = e.getElementCount();
0N/A counter < maxCounter; counter++) {
0N/A Element retValue = getElement(e.getElement(counter), attribute,
0N/A value, searchLeafAttributes);
0N/A
0N/A if (retValue != null) {
0N/A return retValue;
0N/A }
0N/A }
0N/A }
0N/A else if (searchLeafAttributes && attr != null) {
0N/A // For some leaf elements we store the actual attributes inside
0N/A // the AttributeSet of the Element (such as anchors).
0N/A Enumeration names = attr.getAttributeNames();
0N/A if (names != null) {
0N/A while (names.hasMoreElements()) {
0N/A Object name = names.nextElement();
0N/A if ((name instanceof HTML.Tag) &&
0N/A (attr.getAttribute(name) instanceof AttributeSet)) {
0N/A
0N/A AttributeSet check = (AttributeSet)attr.
0N/A getAttribute(name);
0N/A if (check.isDefined(attribute) &&
0N/A value.equals(check.getAttribute(attribute))) {
0N/A return e;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A /**
0N/A * Verifies the document has an <code>HTMLEditorKit.Parser</code> set.
0N/A * If <code>getParser</code> returns <code>null</code>, this will throw an
0N/A * IllegalStateException.
0N/A *
0N/A * @throws IllegalStateException if the document does not have a Parser
0N/A */
0N/A private void verifyParser() {
0N/A if (getParser() == null) {
0N/A throw new IllegalStateException("No HTMLEditorKit.Parser");
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Installs a default Parser if one has not been installed yet.
0N/A */
0N/A private void installParserIfNecessary() {
0N/A if (getParser() == null) {
0N/A setParser(new HTMLEditorKit().getParser());
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Inserts a string of HTML into the document at the given position.
0N/A * <code>parent</code> is used to identify the location to insert the
0N/A * <code>html</code>. If <code>parent</code> is a leaf this can have
0N/A * unexpected results.
0N/A */
0N/A private void insertHTML(Element parent, int offset, String html,
0N/A boolean wantsTrailingNewline)
0N/A throws BadLocationException, IOException {
0N/A if (parent != null && html != null) {
0N/A HTMLEditorKit.Parser parser = getParser();
0N/A if (parser != null) {
0N/A int lastOffset = Math.max(0, offset - 1);
0N/A Element charElement = getCharacterElement(lastOffset);
0N/A Element commonParent = parent;
0N/A int pop = 0;
0N/A int push = 0;
0N/A
0N/A if (parent.getStartOffset() > lastOffset) {
0N/A while (commonParent != null &&
0N/A commonParent.getStartOffset() > lastOffset) {
0N/A commonParent = commonParent.getParentElement();
0N/A push++;
0N/A }
0N/A if (commonParent == null) {
0N/A throw new BadLocationException("No common parent",
0N/A offset);
0N/A }
0N/A }
0N/A while (charElement != null && charElement != commonParent) {
0N/A pop++;
0N/A charElement = charElement.getParentElement();
0N/A }
0N/A if (charElement != null) {
0N/A // Found it, do the insert.
0N/A HTMLReader reader = new HTMLReader(offset, pop - 1, push,
0N/A null, false, true,
0N/A wantsTrailingNewline);
0N/A
0N/A parser.parse(new StringReader(html), reader, true);
0N/A reader.flush();
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Removes child Elements of the passed in Element <code>e</code>. This
0N/A * will do the necessary cleanup to ensure the element representing the
0N/A * end character is correctly created.
0N/A * <p>This is not a general purpose method, it assumes that <code>e</code>
0N/A * will still have at least one child after the remove, and it assumes
0N/A * the character at <code>e.getStartOffset() - 1</code> is a newline and
0N/A * is of length 1.
0N/A */
0N/A private void removeElements(Element e, int index, int count) throws BadLocationException {
0N/A writeLock();
0N/A try {
0N/A int start = e.getElement(index).getStartOffset();
0N/A int end = e.getElement(index + count - 1).getEndOffset();
0N/A if (end > getLength()) {
0N/A removeElementsAtEnd(e, index, count, start, end);
0N/A }
0N/A else {
0N/A removeElements(e, index, count, start, end);
0N/A }
0N/A } finally {
0N/A writeUnlock();
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Called to remove child elements of <code>e</code> when one of the
0N/A * elements to remove is representing the end character.
0N/A * <p>Since the Content will not allow a removal to the end character
0N/A * this will do a remove from <code>start - 1</code> to <code>end</code>.
0N/A * The end Element(s) will be removed, and the element representing
0N/A * <code>start - 1</code> to <code>start</code> will be recreated. This
0N/A * Element has to be recreated as after the content removal its offsets
0N/A * become <code>start - 1</code> to <code>start - 1</code>.
0N/A */
0N/A private void removeElementsAtEnd(Element e, int index, int count,
0N/A int start, int end) throws BadLocationException {
0N/A // index must be > 0 otherwise no insert would have happened.
0N/A boolean isLeaf = (e.getElement(index - 1).isLeaf());
0N/A DefaultDocumentEvent dde = new DefaultDocumentEvent(
0N/A start - 1, end - start + 1, DocumentEvent.
0N/A EventType.REMOVE);
0N/A
0N/A if (isLeaf) {
0N/A Element endE = getCharacterElement(getLength());
0N/A // e.getElement(index - 1) should represent the newline.
0N/A index--;
0N/A if (endE.getParentElement() != e) {
0N/A // The hiearchies don't match, we'll have to manually
0N/A // recreate the leaf at e.getElement(index - 1)
0N/A replace(dde, e, index, ++count, start, end, true, true);
0N/A }
0N/A else {
0N/A // The hierarchies for the end Element and
0N/A // e.getElement(index - 1), match, we can safely remove
0N/A // the Elements and the end content will be aligned
0N/A // appropriately.
0N/A replace(dde, e, index, count, start, end, true, false);
0N/A }
0N/A }
0N/A else {
0N/A // Not a leaf, descend until we find the leaf representing
0N/A // start - 1 and remove it.
0N/A Element newLineE = e.getElement(index - 1);
0N/A while (!newLineE.isLeaf()) {
0N/A newLineE = newLineE.getElement(newLineE.getElementCount() - 1);
0N/A }
0N/A newLineE = newLineE.getParentElement();
0N/A replace(dde, e, index, count, start, end, false, false);
0N/A replace(dde, newLineE, newLineE.getElementCount() - 1, 1, start,
0N/A end, true, true);
0N/A }
0N/A postRemoveUpdate(dde);
0N/A dde.end();
0N/A fireRemoveUpdate(dde);
0N/A fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
0N/A }
0N/A
0N/A /**
0N/A * This is used by <code>removeElementsAtEnd</code>, it removes
0N/A * <code>count</code> elements starting at <code>start</code> from
0N/A * <code>e</code>. If <code>remove</code> is true text of length
0N/A * <code>start - 1</code> to <code>end - 1</code> is removed. If
0N/A * <code>create</code> is true a new leaf is created of length 1.
0N/A */
0N/A private void replace(DefaultDocumentEvent dde, Element e, int index,
0N/A int count, int start, int end, boolean remove,
0N/A boolean create) throws BadLocationException {
0N/A Element[] added;
0N/A AttributeSet attrs = e.getElement(index).getAttributes();
0N/A Element[] removed = new Element[count];
0N/A
0N/A for (int counter = 0; counter < count; counter++) {
0N/A removed[counter] = e.getElement(counter + index);
0N/A }
0N/A if (remove) {
0N/A UndoableEdit u = getContent().remove(start - 1, end - start);
0N/A if (u != null) {
0N/A dde.addEdit(u);
0N/A }
0N/A }
0N/A if (create) {
0N/A added = new Element[1];
0N/A added[0] = createLeafElement(e, attrs, start - 1, start);
0N/A }
0N/A else {
0N/A added = new Element[0];
0N/A }
0N/A dde.addEdit(new ElementEdit(e, index, removed, added));
0N/A ((AbstractDocument.BranchElement)e).replace(
0N/A index, removed.length, added);
0N/A }
0N/A
0N/A /**
0N/A * Called to remove child Elements when the end is not touched.
0N/A */
0N/A private void removeElements(Element e, int index, int count,
0N/A int start, int end) throws BadLocationException {
0N/A Element[] removed = new Element[count];
0N/A Element[] added = new Element[0];
0N/A for (int counter = 0; counter < count; counter++) {
0N/A removed[counter] = e.getElement(counter + index);
0N/A }
0N/A DefaultDocumentEvent dde = new DefaultDocumentEvent
0N/A (start, end - start, DocumentEvent.EventType.REMOVE);
0N/A ((AbstractDocument.BranchElement)e).replace(index, removed.length,
0N/A added);
0N/A dde.addEdit(new ElementEdit(e, index, removed, added));
0N/A UndoableEdit u = getContent().remove(start, end - start);
0N/A if (u != null) {
0N/A dde.addEdit(u);
0N/A }
0N/A postRemoveUpdate(dde);
0N/A dde.end();
0N/A fireRemoveUpdate(dde);
0N/A if (u != null) {
0N/A fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
0N/A }
0N/A }
0N/A
0N/A
0N/A // These two are provided for inner class access. The are named different
0N/A // than the super class as the super class implementations are final.
0N/A void obtainLock() {
0N/A writeLock();
0N/A }
0N/A
0N/A void releaseLock() {
0N/A writeUnlock();
0N/A }
0N/A
0N/A //
0N/A // Provided for inner class access.
0N/A //
0N/A
0N/A /**
0N/A * Notifies all listeners that have registered interest for
0N/A * notification on this event type. The event instance
0N/A * is lazily created using the parameters passed into
0N/A * the fire method.
0N/A *
0N/A * @param e the event
0N/A * @see EventListenerList
0N/A */
0N/A protected void fireChangedUpdate(DocumentEvent e) {
0N/A super.fireChangedUpdate(e);
0N/A }
0N/A
0N/A /**
0N/A * Notifies all listeners that have registered interest for
0N/A * notification on this event type. The event instance
0N/A * is lazily created using the parameters passed into
0N/A * the fire method.
0N/A *
0N/A * @param e the event
0N/A * @see EventListenerList
0N/A */
0N/A protected void fireUndoableEditUpdate(UndoableEditEvent e) {
0N/A super.fireUndoableEditUpdate(e);
0N/A }
0N/A
0N/A boolean hasBaseTag() {
0N/A return hasBaseTag;
0N/A }
0N/A
0N/A String getBaseTarget() {
0N/A return baseTarget;
0N/A }
0N/A
0N/A /*
0N/A * state defines whether the document is a frame document
0N/A * or not.
0N/A */
0N/A private boolean frameDocument = false;
0N/A private boolean preservesUnknownTags = true;
0N/A
0N/A /*
0N/A * Used to store button groups for radio buttons in
0N/A * a form.
0N/A */
611N/A private HashMap<String, ButtonGroup> radioButtonGroupsMap;
0N/A
0N/A /**
0N/A * Document property for the number of tokens to buffer
0N/A * before building an element subtree to represent them.
0N/A */
0N/A static final String TokenThreshold = "token threshold";
0N/A
0N/A private static final int MaxThreshold = 10000;
0N/A
0N/A private static final int StepThreshold = 5;
0N/A
0N/A
0N/A /**
0N/A * Document property key value. The value for the key will be a Vector
0N/A * of Strings that are comments not found in the body.
0N/A */
0N/A public static final String AdditionalComments = "AdditionalComments";
0N/A
0N/A /**
0N/A * Document property key value. The value for the key will be a
0N/A * String indicating the default type of stylesheet links.
0N/A */
0N/A /* public */ static final String StyleType = "StyleType";
0N/A
0N/A /**
0N/A * The location to resolve relative URLs against. By
0N/A * default this will be the document's URL if the document
0N/A * was loaded from a URL. If a base tag is found and
0N/A * can be parsed, it will be used as the base location.
0N/A */
0N/A URL base;
0N/A
0N/A /**
0N/A * does the document have base tag
0N/A */
0N/A boolean hasBaseTag = false;
0N/A
0N/A /**
0N/A * BASE tag's TARGET attribute value
0N/A */
0N/A private String baseTarget = null;
0N/A
0N/A /**
0N/A * The parser that is used when inserting html into the existing
0N/A * document.
0N/A */
0N/A private HTMLEditorKit.Parser parser;
0N/A
0N/A /**
0N/A * Used for inserts when a null AttributeSet is supplied.
0N/A */
0N/A private static AttributeSet contentAttributeSet;
0N/A
0N/A /**
0N/A * Property Maps are registered under, will be a Hashtable.
0N/A */
0N/A static String MAP_PROPERTY = "__MAP__";
0N/A
0N/A private static char[] NEWLINE;
0N/A
0N/A /**
0N/A * I18N property key.
0N/A *
611N/A * @see AbstractDocument#I18NProperty
0N/A */
0N/A private static final String I18NProperty = "i18n";
0N/A
0N/A static {
0N/A contentAttributeSet = new SimpleAttributeSet();
0N/A ((MutableAttributeSet)contentAttributeSet).
0N/A addAttribute(StyleConstants.NameAttribute,
0N/A HTML.Tag.CONTENT);
0N/A NEWLINE = new char[1];
0N/A NEWLINE[0] = '\n';
0N/A }
0N/A
0N/A
0N/A /**
0N/A * An iterator to iterate over a particular type of
0N/A * tag. The iterator is not thread safe. If reliable
0N/A * access to the document is not already ensured by
0N/A * the context under which the iterator is being used,
0N/A * its use should be performed under the protection of
0N/A * Document.render.
0N/A */
0N/A public static abstract class Iterator {
0N/A
0N/A /**
0N/A * Return the attributes for this tag.
0N/A * @return the <code>AttributeSet</code> for this tag, or
0N/A * <code>null</code> if none can be found
0N/A */
0N/A public abstract AttributeSet getAttributes();
0N/A
0N/A /**
0N/A * Returns the start of the range for which the current occurrence of
0N/A * the tag is defined and has the same attributes.
0N/A *
0N/A * @return the start of the range, or -1 if it can't be found
0N/A */
0N/A public abstract int getStartOffset();
0N/A
0N/A /**
0N/A * Returns the end of the range for which the current occurrence of
0N/A * the tag is defined and has the same attributes.
0N/A *
0N/A * @return the end of the range
0N/A */
0N/A public abstract int getEndOffset();
0N/A
0N/A /**
0N/A * Move the iterator forward to the next occurrence
0N/A * of the tag it represents.
0N/A */
0N/A public abstract void next();
0N/A
0N/A /**
0N/A * Indicates if the iterator is currently
0N/A * representing an occurrence of a tag. If
0N/A * false there are no more tags for this iterator.
0N/A * @return true if the iterator is currently representing an
0N/A * occurrence of a tag, otherwise returns false
0N/A */
0N/A public abstract boolean isValid();
0N/A
0N/A /**
0N/A * Type of tag this iterator represents.
0N/A */
0N/A public abstract HTML.Tag getTag();
0N/A }
0N/A
0N/A /**
0N/A * An iterator to iterate over a particular type of tag.
0N/A */
0N/A static class LeafIterator extends Iterator {
0N/A
0N/A LeafIterator(HTML.Tag t, Document doc) {
0N/A tag = t;
0N/A pos = new ElementIterator(doc);
0N/A endOffset = 0;
0N/A next();
0N/A }
0N/A
0N/A /**
0N/A * Returns the attributes for this tag.
0N/A * @return the <code>AttributeSet</code> for this tag,
0N/A * or <code>null</code> if none can be found
0N/A */
0N/A public AttributeSet getAttributes() {
0N/A Element elem = pos.current();
0N/A if (elem != null) {
0N/A AttributeSet a = (AttributeSet)
0N/A elem.getAttributes().getAttribute(tag);
0N/A if (a == null) {
611N/A a = elem.getAttributes();
0N/A }
0N/A return a;
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A /**
0N/A * Returns the start of the range for which the current occurrence of
0N/A * the tag is defined and has the same attributes.
0N/A *
0N/A * @return the start of the range, or -1 if it can't be found
0N/A */
0N/A public int getStartOffset() {
0N/A Element elem = pos.current();
0N/A if (elem != null) {
0N/A return elem.getStartOffset();
0N/A }
0N/A return -1;
0N/A }
0N/A
0N/A /**
0N/A * Returns the end of the range for which the current occurrence of
0N/A * the tag is defined and has the same attributes.
0N/A *
0N/A * @return the end of the range
0N/A */
0N/A public int getEndOffset() {
0N/A return endOffset;
0N/A }
0N/A
0N/A /**
0N/A * Moves the iterator forward to the next occurrence
0N/A * of the tag it represents.
0N/A */
0N/A public void next() {
0N/A for (nextLeaf(pos); isValid(); nextLeaf(pos)) {
0N/A Element elem = pos.current();
0N/A if (elem.getStartOffset() >= endOffset) {
0N/A AttributeSet a = pos.current().getAttributes();
0N/A
0N/A if (a.isDefined(tag) ||
0N/A a.getAttribute(StyleConstants.NameAttribute) == tag) {
0N/A
0N/A // we found the next one
0N/A setEndOffset();
0N/A break;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Returns the type of tag this iterator represents.
0N/A *
0N/A * @return the <code>HTML.Tag</code> that this iterator represents.
0N/A * @see javax.swing.text.html.HTML.Tag
0N/A */
0N/A public HTML.Tag getTag() {
0N/A return tag;
0N/A }
0N/A
0N/A /**
0N/A * Returns true if the current position is not <code>null</code>.
0N/A * @return true if current position is not <code>null</code>,
0N/A * otherwise returns false
0N/A */
0N/A public boolean isValid() {
0N/A return (pos.current() != null);
0N/A }
0N/A
0N/A /**
0N/A * Moves the given iterator to the next leaf element.
0N/A * @param iter the iterator to be scanned
0N/A */
0N/A void nextLeaf(ElementIterator iter) {
0N/A for (iter.next(); iter.current() != null; iter.next()) {
0N/A Element e = iter.current();
0N/A if (e.isLeaf()) {
0N/A break;
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Marches a cloned iterator forward to locate the end
0N/A * of the run. This sets the value of <code>endOffset</code>.
0N/A */
0N/A void setEndOffset() {
0N/A AttributeSet a0 = getAttributes();
0N/A endOffset = pos.current().getEndOffset();
0N/A ElementIterator fwd = (ElementIterator) pos.clone();
0N/A for (nextLeaf(fwd); fwd.current() != null; nextLeaf(fwd)) {
0N/A Element e = fwd.current();
0N/A AttributeSet a1 = (AttributeSet) e.getAttributes().getAttribute(tag);
0N/A if ((a1 == null) || (! a1.equals(a0))) {
0N/A break;
0N/A }
0N/A endOffset = e.getEndOffset();
0N/A }
0N/A }
0N/A
0N/A private int endOffset;
0N/A private HTML.Tag tag;
0N/A private ElementIterator pos;
0N/A
0N/A }
0N/A
0N/A /**
0N/A * An HTML reader to load an HTML document with an HTML
0N/A * element structure. This is a set of callbacks from
0N/A * the parser, implemented to create a set of elements
0N/A * tagged with attributes. The parse builds up tokens
0N/A * (ElementSpec) that describe the element subtree desired,
0N/A * and burst it into the document under the protection of
0N/A * a write lock using the insert method on the document
0N/A * outer class.
0N/A * <p>
0N/A * The reader can be configured by registering actions
0N/A * (of type <code>HTMLDocument.HTMLReader.TagAction</code>)
0N/A * that describe how to handle the action. The idea behind
0N/A * the actions provided is that the most natural text editing
0N/A * operations can be provided if the element structure boils
0N/A * down to paragraphs with runs of some kind of style
0N/A * in them. Some things are more naturally specified
0N/A * structurally, so arbitrary structure should be allowed
0N/A * above the paragraphs, but will need to be edited with structural
0N/A * actions. The implication of this is that some of the
0N/A * HTML elements specified in the stream being parsed will
0N/A * be collapsed into attributes, and in some cases paragraphs
0N/A * will be synthesized. When HTML elements have been
0N/A * converted to attributes, the attribute key will be of
0N/A * type HTML.Tag, and the value will be of type AttributeSet
0N/A * so that no information is lost. This enables many of the
0N/A * existing actions to work so that the user can type input,
0N/A * hit the return key, backspace, delete, etc and have a
0N/A * reasonable result. Selections can be created, and attributes
0N/A * applied or removed, etc. With this in mind, the work done
0N/A * by the reader can be categorized into the following kinds
0N/A * of tasks:
0N/A * <dl>
0N/A * <dt>Block
0N/A * <dd>Build the structure like it's specified in the stream.
0N/A * This produces elements that contain other elements.
0N/A * <dt>Paragraph
0N/A * <dd>Like block except that it's expected that the element
0N/A * will be used with a paragraph view so a paragraph element
0N/A * won't need to be synthesized.
0N/A * <dt>Character
0N/A * <dd>Contribute the element as an attribute that will start
0N/A * and stop at arbitrary text locations. This will ultimately
0N/A * be mixed into a run of text, with all of the currently
0N/A * flattened HTML character elements.
0N/A * <dt>Special
0N/A * <dd>Produce an embedded graphical element.
0N/A * <dt>Form
0N/A * <dd>Produce an element that is like the embedded graphical
0N/A * element, except that it also has a component model associated
0N/A * with it.
0N/A * <dt>Hidden
0N/A * <dd>Create an element that is hidden from view when the
0N/A * document is being viewed read-only, and visible when the
0N/A * document is being edited. This is useful to keep the
0N/A * model from losing information, and used to store things
0N/A * like comments and unrecognized tags.
0N/A *
0N/A * </dl>
0N/A * <p>
0N/A * Currently, &lt;APPLET&gt;, &lt;PARAM&gt;, &lt;MAP&gt;, &lt;AREA&gt;, &lt;LINK&gt;,
0N/A * &lt;SCRIPT&gt; and &lt;STYLE&gt; are unsupported.
0N/A *
0N/A * <p>
0N/A * The assignment of the actions described is shown in the
0N/A * following table for the tags defined in <code>HTML.Tag</code>.<P>
0N/A * <table border=1 summary="HTML tags and assigned actions">
0N/A * <tr><th>Tag</th><th>Action</th></tr>
0N/A * <tr><td><code>HTML.Tag.A</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.ADDRESS</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.APPLET</code> <td>HiddenAction
0N/A * <tr><td><code>HTML.Tag.AREA</code> <td>AreaAction
0N/A * <tr><td><code>HTML.Tag.B</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.BASE</code> <td>BaseAction
0N/A * <tr><td><code>HTML.Tag.BASEFONT</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.BIG</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.BLOCKQUOTE</code><td>BlockAction
0N/A * <tr><td><code>HTML.Tag.BODY</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.BR</code> <td>SpecialAction
0N/A * <tr><td><code>HTML.Tag.CAPTION</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.CENTER</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.CITE</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.CODE</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.DD</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.DFN</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.DIR</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.DIV</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.DL</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.DT</code> <td>ParagraphAction
0N/A * <tr><td><code>HTML.Tag.EM</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.FONT</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.FORM</code> <td>As of 1.4 a BlockAction
0N/A * <tr><td><code>HTML.Tag.FRAME</code> <td>SpecialAction
0N/A * <tr><td><code>HTML.Tag.FRAMESET</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.H1</code> <td>ParagraphAction
0N/A * <tr><td><code>HTML.Tag.H2</code> <td>ParagraphAction
0N/A * <tr><td><code>HTML.Tag.H3</code> <td>ParagraphAction
0N/A * <tr><td><code>HTML.Tag.H4</code> <td>ParagraphAction
0N/A * <tr><td><code>HTML.Tag.H5</code> <td>ParagraphAction
0N/A * <tr><td><code>HTML.Tag.H6</code> <td>ParagraphAction
0N/A * <tr><td><code>HTML.Tag.HEAD</code> <td>HeadAction
0N/A * <tr><td><code>HTML.Tag.HR</code> <td>SpecialAction
0N/A * <tr><td><code>HTML.Tag.HTML</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.I</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.IMG</code> <td>SpecialAction
0N/A * <tr><td><code>HTML.Tag.INPUT</code> <td>FormAction
0N/A * <tr><td><code>HTML.Tag.ISINDEX</code> <td>IsndexAction
0N/A * <tr><td><code>HTML.Tag.KBD</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.LI</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.LINK</code> <td>LinkAction
0N/A * <tr><td><code>HTML.Tag.MAP</code> <td>MapAction
0N/A * <tr><td><code>HTML.Tag.MENU</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.META</code> <td>MetaAction
0N/A * <tr><td><code>HTML.Tag.NOFRAMES</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.OBJECT</code> <td>SpecialAction
0N/A * <tr><td><code>HTML.Tag.OL</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.OPTION</code> <td>FormAction
0N/A * <tr><td><code>HTML.Tag.P</code> <td>ParagraphAction
0N/A * <tr><td><code>HTML.Tag.PARAM</code> <td>HiddenAction
0N/A * <tr><td><code>HTML.Tag.PRE</code> <td>PreAction
0N/A * <tr><td><code>HTML.Tag.SAMP</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.SCRIPT</code> <td>HiddenAction
0N/A * <tr><td><code>HTML.Tag.SELECT</code> <td>FormAction
0N/A * <tr><td><code>HTML.Tag.SMALL</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.STRIKE</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.S</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.STRONG</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.STYLE</code> <td>StyleAction
0N/A * <tr><td><code>HTML.Tag.SUB</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.SUP</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.TABLE</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.TD</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.TEXTAREA</code> <td>FormAction
0N/A * <tr><td><code>HTML.Tag.TH</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.TITLE</code> <td>TitleAction
0N/A * <tr><td><code>HTML.Tag.TR</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.TT</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.U</code> <td>CharacterAction
0N/A * <tr><td><code>HTML.Tag.UL</code> <td>BlockAction
0N/A * <tr><td><code>HTML.Tag.VAR</code> <td>CharacterAction
0N/A * </table>
0N/A * <p>
0N/A * Once &lt;/html> is encountered, the Actions are no longer notified.
0N/A */
0N/A public class HTMLReader extends HTMLEditorKit.ParserCallback {
0N/A
0N/A public HTMLReader(int offset) {
0N/A this(offset, 0, 0, null);
0N/A }
0N/A
0N/A public HTMLReader(int offset, int popDepth, int pushDepth,
0N/A HTML.Tag insertTag) {
0N/A this(offset, popDepth, pushDepth, insertTag, true, false, true);
0N/A }
0N/A
0N/A /**
0N/A * Generates a RuntimeException (will eventually generate
0N/A * a BadLocationException when API changes are alloced) if inserting
0N/A * into non empty document, <code>insertTag</code> is
0N/A * non-<code>null</code>, and <code>offset</code> is not in the body.
0N/A */
0N/A // PENDING(sky): Add throws BadLocationException and remove
0N/A // RuntimeException
0N/A HTMLReader(int offset, int popDepth, int pushDepth,
0N/A HTML.Tag insertTag, boolean insertInsertTag,
0N/A boolean insertAfterImplied, boolean wantsTrailingNewline) {
0N/A emptyDocument = (getLength() == 0);
0N/A isStyleCSS = "text/css".equals(getDefaultStyleSheetType());
0N/A this.offset = offset;
0N/A threshold = HTMLDocument.this.getTokenThreshold();
611N/A tagMap = new Hashtable<HTML.Tag, TagAction>(57);
0N/A TagAction na = new TagAction();
0N/A TagAction ba = new BlockAction();
0N/A TagAction pa = new ParagraphAction();
0N/A TagAction ca = new CharacterAction();
0N/A TagAction sa = new SpecialAction();
0N/A TagAction fa = new FormAction();
0N/A TagAction ha = new HiddenAction();
0N/A TagAction conv = new ConvertAction();
0N/A
0N/A // register handlers for the well known tags
0N/A tagMap.put(HTML.Tag.A, new AnchorAction());
0N/A tagMap.put(HTML.Tag.ADDRESS, ca);
0N/A tagMap.put(HTML.Tag.APPLET, ha);
0N/A tagMap.put(HTML.Tag.AREA, new AreaAction());
0N/A tagMap.put(HTML.Tag.B, conv);
0N/A tagMap.put(HTML.Tag.BASE, new BaseAction());
0N/A tagMap.put(HTML.Tag.BASEFONT, ca);
0N/A tagMap.put(HTML.Tag.BIG, ca);
0N/A tagMap.put(HTML.Tag.BLOCKQUOTE, ba);
0N/A tagMap.put(HTML.Tag.BODY, ba);
0N/A tagMap.put(HTML.Tag.BR, sa);
0N/A tagMap.put(HTML.Tag.CAPTION, ba);
0N/A tagMap.put(HTML.Tag.CENTER, ba);
0N/A tagMap.put(HTML.Tag.CITE, ca);
0N/A tagMap.put(HTML.Tag.CODE, ca);
0N/A tagMap.put(HTML.Tag.DD, ba);
0N/A tagMap.put(HTML.Tag.DFN, ca);
0N/A tagMap.put(HTML.Tag.DIR, ba);
0N/A tagMap.put(HTML.Tag.DIV, ba);
0N/A tagMap.put(HTML.Tag.DL, ba);
0N/A tagMap.put(HTML.Tag.DT, pa);
0N/A tagMap.put(HTML.Tag.EM, ca);
0N/A tagMap.put(HTML.Tag.FONT, conv);
0N/A tagMap.put(HTML.Tag.FORM, new FormTagAction());
0N/A tagMap.put(HTML.Tag.FRAME, sa);
0N/A tagMap.put(HTML.Tag.FRAMESET, ba);
0N/A tagMap.put(HTML.Tag.H1, pa);
0N/A tagMap.put(HTML.Tag.H2, pa);
0N/A tagMap.put(HTML.Tag.H3, pa);
0N/A tagMap.put(HTML.Tag.H4, pa);
0N/A tagMap.put(HTML.Tag.H5, pa);
0N/A tagMap.put(HTML.Tag.H6, pa);
0N/A tagMap.put(HTML.Tag.HEAD, new HeadAction());
0N/A tagMap.put(HTML.Tag.HR, sa);
0N/A tagMap.put(HTML.Tag.HTML, ba);
0N/A tagMap.put(HTML.Tag.I, conv);
0N/A tagMap.put(HTML.Tag.IMG, sa);
0N/A tagMap.put(HTML.Tag.INPUT, fa);
0N/A tagMap.put(HTML.Tag.ISINDEX, new IsindexAction());
0N/A tagMap.put(HTML.Tag.KBD, ca);
0N/A tagMap.put(HTML.Tag.LI, ba);
0N/A tagMap.put(HTML.Tag.LINK, new LinkAction());
0N/A tagMap.put(HTML.Tag.MAP, new MapAction());
0N/A tagMap.put(HTML.Tag.MENU, ba);
0N/A tagMap.put(HTML.Tag.META, new MetaAction());
0N/A tagMap.put(HTML.Tag.NOBR, ca);
0N/A tagMap.put(HTML.Tag.NOFRAMES, ba);
0N/A tagMap.put(HTML.Tag.OBJECT, sa);
0N/A tagMap.put(HTML.Tag.OL, ba);
0N/A tagMap.put(HTML.Tag.OPTION, fa);
0N/A tagMap.put(HTML.Tag.P, pa);
0N/A tagMap.put(HTML.Tag.PARAM, new ObjectAction());
0N/A tagMap.put(HTML.Tag.PRE, new PreAction());
0N/A tagMap.put(HTML.Tag.SAMP, ca);
0N/A tagMap.put(HTML.Tag.SCRIPT, ha);
0N/A tagMap.put(HTML.Tag.SELECT, fa);
0N/A tagMap.put(HTML.Tag.SMALL, ca);
0N/A tagMap.put(HTML.Tag.SPAN, ca);
0N/A tagMap.put(HTML.Tag.STRIKE, conv);
0N/A tagMap.put(HTML.Tag.S, ca);
0N/A tagMap.put(HTML.Tag.STRONG, ca);
0N/A tagMap.put(HTML.Tag.STYLE, new StyleAction());
0N/A tagMap.put(HTML.Tag.SUB, conv);
0N/A tagMap.put(HTML.Tag.SUP, conv);
0N/A tagMap.put(HTML.Tag.TABLE, ba);
0N/A tagMap.put(HTML.Tag.TD, ba);
0N/A tagMap.put(HTML.Tag.TEXTAREA, fa);
0N/A tagMap.put(HTML.Tag.TH, ba);
0N/A tagMap.put(HTML.Tag.TITLE, new TitleAction());
0N/A tagMap.put(HTML.Tag.TR, ba);
0N/A tagMap.put(HTML.Tag.TT, ca);
0N/A tagMap.put(HTML.Tag.U, conv);
0N/A tagMap.put(HTML.Tag.UL, ba);
0N/A tagMap.put(HTML.Tag.VAR, ca);
0N/A
0N/A if (insertTag != null) {
0N/A this.insertTag = insertTag;
0N/A this.popDepth = popDepth;
0N/A this.pushDepth = pushDepth;
0N/A this.insertInsertTag = insertInsertTag;
0N/A foundInsertTag = false;
0N/A }
0N/A else {
0N/A foundInsertTag = true;
0N/A }
0N/A if (insertAfterImplied) {
0N/A this.popDepth = popDepth;
0N/A this.pushDepth = pushDepth;
0N/A this.insertAfterImplied = true;
0N/A foundInsertTag = false;
0N/A midInsert = false;
0N/A this.insertInsertTag = true;
0N/A this.wantsTrailingNewline = wantsTrailingNewline;
0N/A }
0N/A else {
0N/A midInsert = (!emptyDocument && insertTag == null);
0N/A if (midInsert) {
0N/A generateEndsSpecsForMidInsert();
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * This block initializes the <code>inParagraph</code> flag.
0N/A * It is left in <code>false</code> value automatically
0N/A * if the target document is empty or future inserts
0N/A * were positioned into the 'body' tag.
0N/A */
0N/A if (!emptyDocument && !midInsert) {
0N/A int targetOffset = Math.max(this.offset - 1, 0);
0N/A Element elem =
0N/A HTMLDocument.this.getCharacterElement(targetOffset);
0N/A /* Going up by the left document structure path */
0N/A for (int i = 0; i <= this.popDepth; i++) {
0N/A elem = elem.getParentElement();
0N/A }
0N/A /* Going down by the right document structure path */
0N/A for (int i = 0; i < this.pushDepth; i++) {
0N/A int index = elem.getElementIndex(this.offset);
0N/A elem = elem.getElement(index);
0N/A }
0N/A AttributeSet attrs = elem.getAttributes();
0N/A if (attrs != null) {
0N/A HTML.Tag tagToInsertInto =
0N/A (HTML.Tag) attrs.getAttribute(StyleConstants.NameAttribute);
0N/A if (tagToInsertInto != null) {
0N/A this.inParagraph = tagToInsertInto.isParagraph();
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Generates an initial batch of end <code>ElementSpecs</code>
0N/A * in parseBuffer to position future inserts into the body.
0N/A */
0N/A private void generateEndsSpecsForMidInsert() {
0N/A int count = heightToElementWithName(HTML.Tag.BODY,
0N/A Math.max(0, offset - 1));
0N/A boolean joinNext = false;
0N/A
0N/A if (count == -1 && offset > 0) {
0N/A count = heightToElementWithName(HTML.Tag.BODY, offset);
0N/A if (count != -1) {
0N/A // Previous isn't in body, but current is. Have to
0N/A // do some end specs, followed by join next.
0N/A count = depthTo(offset - 1) - 1;
0N/A joinNext = true;
0N/A }
0N/A }
0N/A if (count == -1) {
0N/A throw new RuntimeException("Must insert new content into body element-");
0N/A }
0N/A if (count != -1) {
0N/A // Insert a newline, if necessary.
0N/A try {
0N/A if (!joinNext && offset > 0 &&
0N/A !getText(offset - 1, 1).equals("\n")) {
0N/A SimpleAttributeSet newAttrs = new SimpleAttributeSet();
0N/A newAttrs.addAttribute(StyleConstants.NameAttribute,
0N/A HTML.Tag.CONTENT);
0N/A ElementSpec spec = new ElementSpec(newAttrs,
0N/A ElementSpec.ContentType, NEWLINE, 0, 1);
0N/A parseBuffer.addElement(spec);
0N/A }
0N/A // Should never throw, but will catch anyway.
0N/A } catch (BadLocationException ble) {}
0N/A while (count-- > 0) {
0N/A parseBuffer.addElement(new ElementSpec
0N/A (null, ElementSpec.EndTagType));
0N/A }
0N/A if (joinNext) {
0N/A ElementSpec spec = new ElementSpec(null, ElementSpec.
0N/A StartTagType);
0N/A
0N/A spec.setDirection(ElementSpec.JoinNextDirection);
0N/A parseBuffer.addElement(spec);
0N/A }
0N/A }
0N/A // We should probably throw an exception if (count == -1)
0N/A // Or look for the body and reset the offset.
0N/A }
0N/A
0N/A /**
0N/A * @return number of parents to reach the child at offset.
0N/A */
0N/A private int depthTo(int offset) {
0N/A Element e = getDefaultRootElement();
0N/A int count = 0;
0N/A
0N/A while (!e.isLeaf()) {
0N/A count++;
0N/A e = e.getElement(e.getElementIndex(offset));
0N/A }
0N/A return count;
0N/A }
0N/A
0N/A /**
0N/A * @return number of parents of the leaf at <code>offset</code>
0N/A * until a parent with name, <code>name</code> has been
0N/A * found. -1 indicates no matching parent with
0N/A * <code>name</code>.
0N/A */
0N/A private int heightToElementWithName(Object name, int offset) {
0N/A Element e = getCharacterElement(offset).getParentElement();
0N/A int count = 0;
0N/A
0N/A while (e != null && e.getAttributes().getAttribute
0N/A (StyleConstants.NameAttribute) != name) {
0N/A count++;
0N/A e = e.getParentElement();
0N/A }
0N/A return (e == null) ? -1 : count;
0N/A }
0N/A
0N/A /**
0N/A * This will make sure there aren't two BODYs (the second is
0N/A * typically created when you do a remove all, and then an insert).
0N/A */
0N/A private void adjustEndElement() {
0N/A int length = getLength();
0N/A if (length == 0) {
0N/A return;
0N/A }
0N/A obtainLock();
0N/A try {
0N/A Element[] pPath = getPathTo(length - 1);
0N/A int pLength = pPath.length;
0N/A if (pLength > 1 && pPath[1].getAttributes().getAttribute
0N/A (StyleConstants.NameAttribute) == HTML.Tag.BODY &&
0N/A pPath[1].getEndOffset() == length) {
0N/A String lastText = getText(length - 1, 1);
611N/A DefaultDocumentEvent event;
0N/A Element[] added;
0N/A Element[] removed;
0N/A int index;
0N/A // Remove the fake second body.
0N/A added = new Element[0];
0N/A removed = new Element[1];
0N/A index = pPath[0].getElementIndex(length);
0N/A removed[0] = pPath[0].getElement(index);
0N/A ((BranchElement)pPath[0]).replace(index, 1, added);
0N/A ElementEdit firstEdit = new ElementEdit(pPath[0], index,
0N/A removed, added);
0N/A
0N/A // Insert a new element to represent the end that the
0N/A // second body was representing.
0N/A SimpleAttributeSet sas = new SimpleAttributeSet();
0N/A sas.addAttribute(StyleConstants.NameAttribute,
0N/A HTML.Tag.CONTENT);
0N/A sas.addAttribute(IMPLIED_CR, Boolean.TRUE);
0N/A added = new Element[1];
0N/A added[0] = createLeafElement(pPath[pLength - 1],
0N/A sas, length, length + 1);
0N/A index = pPath[pLength - 1].getElementCount();
0N/A ((BranchElement)pPath[pLength - 1]).replace(index, 0,
0N/A added);
0N/A event = new DefaultDocumentEvent(length, 1,
0N/A DocumentEvent.EventType.CHANGE);
0N/A event.addEdit(new ElementEdit(pPath[pLength - 1],
0N/A index, new Element[0], added));
0N/A event.addEdit(firstEdit);
0N/A event.end();
0N/A fireChangedUpdate(event);
0N/A fireUndoableEditUpdate(new UndoableEditEvent(this, event));
0N/A
0N/A if (lastText.equals("\n")) {
0N/A // We now have two \n's, one part of the Document.
0N/A // We need to remove one
0N/A event = new DefaultDocumentEvent(length - 1, 1,
0N/A DocumentEvent.EventType.REMOVE);
0N/A removeUpdate(event);
0N/A UndoableEdit u = getContent().remove(length - 1, 1);
0N/A if (u != null) {
0N/A event.addEdit(u);
0N/A }
0N/A postRemoveUpdate(event);
0N/A // Mark the edit as done.
0N/A event.end();
0N/A fireRemoveUpdate(event);
0N/A fireUndoableEditUpdate(new UndoableEditEvent(
0N/A this, event));
0N/A }
0N/A }
0N/A }
0N/A catch (BadLocationException ble) {
0N/A }
0N/A finally {
0N/A releaseLock();
0N/A }
0N/A }
0N/A
0N/A private Element[] getPathTo(int offset) {
611N/A Stack<Element> elements = new Stack<Element>();
0N/A Element e = getDefaultRootElement();
0N/A int index;
0N/A while (!e.isLeaf()) {
0N/A elements.push(e);
0N/A e = e.getElement(e.getElementIndex(offset));
0N/A }
0N/A Element[] retValue = new Element[elements.size()];
0N/A elements.copyInto(retValue);
0N/A return retValue;
0N/A }
0N/A
0N/A // -- HTMLEditorKit.ParserCallback methods --------------------
0N/A
0N/A /**
0N/A * The last method called on the reader. It allows
0N/A * any pending changes to be flushed into the document.
0N/A * Since this is currently loading synchronously, the entire
0N/A * set of changes are pushed in at this point.
0N/A */
0N/A public void flush() throws BadLocationException {
0N/A if (emptyDocument && !insertAfterImplied) {
0N/A if (HTMLDocument.this.getLength() > 0 ||
0N/A parseBuffer.size() > 0) {
0N/A flushBuffer(true);
0N/A adjustEndElement();
0N/A }
0N/A // We won't insert when
0N/A }
0N/A else {
0N/A flushBuffer(true);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Called by the parser to indicate a block of text was
0N/A * encountered.
0N/A */
0N/A public void handleText(char[] data, int pos) {
0N/A if (receivedEndHTML || (midInsert && !inBody)) {
0N/A return;
0N/A }
0N/A
0N/A // see if complex glyph layout support is needed
0N/A if(HTMLDocument.this.getProperty(I18NProperty).equals( Boolean.FALSE ) ) {
0N/A // if a default direction of right-to-left has been specified,
0N/A // we want complex layout even if the text is all left to right.
0N/A Object d = getProperty(TextAttribute.RUN_DIRECTION);
0N/A if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
0N/A HTMLDocument.this.putProperty( I18NProperty, Boolean.TRUE);
0N/A } else {
0N/A if (SwingUtilities2.isComplexLayout(data, 0, data.length)) {
0N/A HTMLDocument.this.putProperty( I18NProperty, Boolean.TRUE);
0N/A }
0N/A }
0N/A }
0N/A
0N/A if (inTextArea) {
0N/A textAreaContent(data);
0N/A } else if (inPre) {
0N/A preContent(data);
0N/A } else if (inTitle) {
0N/A putProperty(Document.TitleProperty, new String(data));
0N/A } else if (option != null) {
0N/A option.setLabel(new String(data));
0N/A } else if (inStyle) {
0N/A if (styles != null) {
0N/A styles.addElement(new String(data));
0N/A }
0N/A } else if (inBlock > 0) {
0N/A if (!foundInsertTag && insertAfterImplied) {
0N/A // Assume content should be added.
0N/A foundInsertTag(false);
0N/A foundInsertTag = true;
0N/A inParagraph = impliedP = true;
0N/A }
0N/A if (data.length >= 1) {
0N/A addContent(data, 0, data.length);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Callback from the parser. Route to the appropriate
0N/A * handler for the tag.
0N/A */
0N/A public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
0N/A if (receivedEndHTML) {
0N/A return;
0N/A }
0N/A if (midInsert && !inBody) {
0N/A if (t == HTML.Tag.BODY) {
0N/A inBody = true;
0N/A // Increment inBlock since we know we are in the body,
0N/A // this is needed incase an implied-p is needed. If
0N/A // inBlock isn't incremented, and an implied-p is
0N/A // encountered, addContent won't be called!
0N/A inBlock++;
0N/A }
0N/A return;
0N/A }
0N/A if (!inBody && t == HTML.Tag.BODY) {
0N/A inBody = true;
0N/A }
0N/A if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
0N/A // Map the style attributes.
0N/A String decl = (String)a.getAttribute(HTML.Attribute.STYLE);
0N/A a.removeAttribute(HTML.Attribute.STYLE);
0N/A styleAttributes = getStyleSheet().getDeclaration(decl);
0N/A a.addAttributes(styleAttributes);
0N/A }
0N/A else {
0N/A styleAttributes = null;
0N/A }
611N/A TagAction action = tagMap.get(t);
0N/A
0N/A if (action != null) {
0N/A action.start(t, a);
0N/A }
0N/A }
0N/A
0N/A public void handleComment(char[] data, int pos) {
0N/A if (receivedEndHTML) {
0N/A addExternalComment(new String(data));
0N/A return;
0N/A }
0N/A if (inStyle) {
0N/A if (styles != null) {
0N/A styles.addElement(new String(data));
0N/A }
0N/A }
0N/A else if (getPreservesUnknownTags()) {
0N/A if (inBlock == 0 && (foundInsertTag ||
0N/A insertTag != HTML.Tag.COMMENT)) {
0N/A // Comment outside of body, will not be able to show it,
0N/A // but can add it as a property on the Document.
0N/A addExternalComment(new String(data));
0N/A return;
0N/A }
0N/A SimpleAttributeSet sas = new SimpleAttributeSet();
0N/A sas.addAttribute(HTML.Attribute.COMMENT, new String(data));
0N/A addSpecialElement(HTML.Tag.COMMENT, sas);
0N/A }
0N/A
611N/A TagAction action = tagMap.get(HTML.Tag.COMMENT);
0N/A if (action != null) {
0N/A action.start(HTML.Tag.COMMENT, new SimpleAttributeSet());
0N/A action.end(HTML.Tag.COMMENT);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Adds the comment <code>comment</code> to the set of comments
0N/A * maintained outside of the scope of elements.
0N/A */
0N/A private void addExternalComment(String comment) {
0N/A Object comments = getProperty(AdditionalComments);
0N/A if (comments != null && !(comments instanceof Vector)) {
0N/A // No place to put comment.
0N/A return;
0N/A }
0N/A if (comments == null) {
0N/A comments = new Vector();
0N/A putProperty(AdditionalComments, comments);
0N/A }
0N/A ((Vector)comments).addElement(comment);
0N/A }
0N/A
0N/A /**
0N/A * Callback from the parser. Route to the appropriate
0N/A * handler for the tag.
0N/A */
0N/A public void handleEndTag(HTML.Tag t, int pos) {
0N/A if (receivedEndHTML || (midInsert && !inBody)) {
0N/A return;
0N/A }
0N/A if (t == HTML.Tag.HTML) {
0N/A receivedEndHTML = true;
0N/A }
0N/A if (t == HTML.Tag.BODY) {
0N/A inBody = false;
0N/A if (midInsert) {
0N/A inBlock--;
0N/A }
0N/A }
611N/A TagAction action = tagMap.get(t);
0N/A if (action != null) {
0N/A action.end(t);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Callback from the parser. Route to the appropriate
0N/A * handler for the tag.
0N/A */
0N/A public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
0N/A if (receivedEndHTML || (midInsert && !inBody)) {
0N/A return;
0N/A }
0N/A
0N/A if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
0N/A // Map the style attributes.
0N/A String decl = (String)a.getAttribute(HTML.Attribute.STYLE);
0N/A a.removeAttribute(HTML.Attribute.STYLE);
0N/A styleAttributes = getStyleSheet().getDeclaration(decl);
0N/A a.addAttributes(styleAttributes);
0N/A }
0N/A else {
0N/A styleAttributes = null;
0N/A }
0N/A
611N/A TagAction action = tagMap.get(t);
0N/A if (action != null) {
0N/A action.start(t, a);
0N/A action.end(t);
0N/A }
0N/A else if (getPreservesUnknownTags()) {
0N/A // unknown tag, only add if should preserve it.
0N/A addSpecialElement(t, a);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * This is invoked after the stream has been parsed, but before
0N/A * <code>flush</code>. <code>eol</code> will be one of \n, \r
0N/A * or \r\n, which ever is encountered the most in parsing the
0N/A * stream.
0N/A *
0N/A * @since 1.3
0N/A */
0N/A public void handleEndOfLineString(String eol) {
0N/A if (emptyDocument && eol != null) {
0N/A putProperty(DefaultEditorKit.EndOfLineStringProperty,
0N/A eol);
0N/A }
0N/A }
0N/A
0N/A // ---- tag handling support ------------------------------
0N/A
0N/A /**
0N/A * Registers a handler for the given tag. By default
0N/A * all of the well-known tags will have been registered.
0N/A * This can be used to change the handling of a particular
0N/A * tag or to add support for custom tags.
0N/A */
0N/A protected void registerTag(HTML.Tag t, TagAction a) {
0N/A tagMap.put(t, a);
0N/A }
0N/A
0N/A /**
0N/A * An action to be performed in response
0N/A * to parsing a tag. This allows customization
0N/A * of how each tag is handled and avoids a large
0N/A * switch statement.
0N/A */
0N/A public class TagAction {
0N/A
0N/A /**
0N/A * Called when a start tag is seen for the
0N/A * type of tag this action was registered
0N/A * to. The tag argument indicates the actual
0N/A * tag for those actions that are shared across
0N/A * many tags. By default this does nothing and
0N/A * completely ignores the tag.
0N/A */
0N/A public void start(HTML.Tag t, MutableAttributeSet a) {
0N/A }
0N/A
0N/A /**
0N/A * Called when an end tag is seen for the
0N/A * type of tag this action was registered
0N/A * to. The tag argument indicates the actual
0N/A * tag for those actions that are shared across
0N/A * many tags. By default this does nothing and
0N/A * completely ignores the tag.
0N/A */
0N/A public void end(HTML.Tag t) {
0N/A }
0N/A
0N/A }
0N/A
0N/A public class BlockAction extends TagAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet attr) {
0N/A blockOpen(t, attr);
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A blockClose(t);
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Action used for the actual element form tag. This is named such
0N/A * as there was already a public class named FormAction.
0N/A */
0N/A private class FormTagAction extends BlockAction {
0N/A public void start(HTML.Tag t, MutableAttributeSet attr) {
0N/A super.start(t, attr);
0N/A // initialize a ButtonGroupsMap when
0N/A // FORM tag is encountered. This will
0N/A // be used for any radio buttons that
0N/A // might be defined in the FORM.
0N/A // for new group new ButtonGroup will be created (fix for 4529702)
0N/A // group name is a key in radioButtonGroupsMap
611N/A radioButtonGroupsMap = new HashMap<String, ButtonGroup>();
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A super.end(t);
0N/A // reset the button group to null since
0N/A // the form has ended.
0N/A radioButtonGroupsMap = null;
0N/A }
0N/A }
0N/A
0N/A
0N/A public class ParagraphAction extends BlockAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet a) {
0N/A super.start(t, a);
0N/A inParagraph = true;
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A super.end(t);
0N/A inParagraph = false;
0N/A }
0N/A }
0N/A
0N/A public class SpecialAction extends TagAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet a) {
0N/A addSpecialElement(t, a);
0N/A }
0N/A
0N/A }
0N/A
0N/A public class IsindexAction extends TagAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet a) {
0N/A blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
0N/A addSpecialElement(t, a);
0N/A blockClose(HTML.Tag.IMPLIED);
0N/A }
0N/A
0N/A }
0N/A
0N/A
0N/A public class HiddenAction extends TagAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet a) {
0N/A addSpecialElement(t, a);
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A if (!isEmpty(t)) {
0N/A MutableAttributeSet a = new SimpleAttributeSet();
0N/A a.addAttribute(HTML.Attribute.ENDTAG, "true");
0N/A addSpecialElement(t, a);
0N/A }
0N/A }
0N/A
0N/A boolean isEmpty(HTML.Tag t) {
0N/A if (t == HTML.Tag.APPLET ||
0N/A t == HTML.Tag.SCRIPT) {
0N/A return false;
0N/A }
0N/A return true;
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Subclass of HiddenAction to set the content type for style sheets,
0N/A * and to set the name of the default style sheet.
0N/A */
0N/A class MetaAction extends HiddenAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet a) {
0N/A Object equiv = a.getAttribute(HTML.Attribute.HTTPEQUIV);
0N/A if (equiv != null) {
0N/A equiv = ((String)equiv).toLowerCase();
0N/A if (equiv.equals("content-style-type")) {
0N/A String value = (String)a.getAttribute
0N/A (HTML.Attribute.CONTENT);
0N/A setDefaultStyleSheetType(value);
0N/A isStyleCSS = "text/css".equals
0N/A (getDefaultStyleSheetType());
0N/A }
0N/A else if (equiv.equals("default-style")) {
0N/A defaultStyle = (String)a.getAttribute
0N/A (HTML.Attribute.CONTENT);
0N/A }
0N/A }
0N/A super.start(t, a);
0N/A }
0N/A
0N/A boolean isEmpty(HTML.Tag t) {
0N/A return true;
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * End if overridden to create the necessary stylesheets that
0N/A * are referenced via the link tag. It is done in this manner
0N/A * as the meta tag can be used to specify an alternate style sheet,
0N/A * and is not guaranteed to come before the link tags.
0N/A */
0N/A class HeadAction extends BlockAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet a) {
0N/A inHead = true;
0N/A // This check of the insertTag is put in to avoid considering
0N/A // the implied-p that is generated for the head. This allows
0N/A // inserts for HR to work correctly.
0N/A if ((insertTag == null && !insertAfterImplied) ||
0N/A (insertTag == HTML.Tag.HEAD) ||
0N/A (insertAfterImplied &&
0N/A (foundInsertTag || !a.isDefined(IMPLIED)))) {
0N/A super.start(t, a);
0N/A }
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A inHead = inStyle = false;
0N/A // See if there is a StyleSheet to link to.
0N/A if (styles != null) {
0N/A boolean isDefaultCSS = isStyleCSS;
0N/A for (int counter = 0, maxCounter = styles.size();
0N/A counter < maxCounter;) {
0N/A Object value = styles.elementAt(counter);
0N/A if (value == HTML.Tag.LINK) {
0N/A handleLink((AttributeSet)styles.
0N/A elementAt(++counter));
0N/A counter++;
0N/A }
0N/A else {
0N/A // Rule.
0N/A // First element gives type.
0N/A String type = (String)styles.elementAt(++counter);
0N/A boolean isCSS = (type == null) ? isDefaultCSS :
0N/A type.equals("text/css");
0N/A while (++counter < maxCounter &&
0N/A (styles.elementAt(counter)
0N/A instanceof String)) {
0N/A if (isCSS) {
0N/A addCSSRules((String)styles.elementAt
0N/A (counter));
0N/A }
0N/A }
0N/A }
0N/A }
0N/A }
0N/A if ((insertTag == null && !insertAfterImplied) ||
0N/A insertTag == HTML.Tag.HEAD ||
0N/A (insertAfterImplied && foundInsertTag)) {
0N/A super.end(t);
0N/A }
0N/A }
0N/A
0N/A boolean isEmpty(HTML.Tag t) {
0N/A return false;
0N/A }
0N/A
0N/A private void handleLink(AttributeSet attr) {
0N/A // Link.
0N/A String type = (String)attr.getAttribute(HTML.Attribute.TYPE);
0N/A if (type == null) {
0N/A type = getDefaultStyleSheetType();
0N/A }
0N/A // Only choose if type==text/css
0N/A // Select link if rel==stylesheet.
0N/A // Otherwise if rel==alternate stylesheet and
0N/A // title matches default style.
0N/A if (type.equals("text/css")) {
0N/A String rel = (String)attr.getAttribute(HTML.Attribute.REL);
0N/A String title = (String)attr.getAttribute
0N/A (HTML.Attribute.TITLE);
0N/A String media = (String)attr.getAttribute
0N/A (HTML.Attribute.MEDIA);
0N/A if (media == null) {
0N/A media = "all";
0N/A }
0N/A else {
0N/A media = media.toLowerCase();
0N/A }
0N/A if (rel != null) {
0N/A rel = rel.toLowerCase();
0N/A if ((media.indexOf("all") != -1 ||
0N/A media.indexOf("screen") != -1) &&
0N/A (rel.equals("stylesheet") ||
0N/A (rel.equals("alternate stylesheet") &&
0N/A title.equals(defaultStyle)))) {
0N/A linkCSSStyleSheet((String)attr.getAttribute
0N/A (HTML.Attribute.HREF));
0N/A }
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * A subclass to add the AttributeSet to styles if the
0N/A * attributes contains an attribute for 'rel' with value
0N/A * 'stylesheet' or 'alternate stylesheet'.
0N/A */
0N/A class LinkAction extends HiddenAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet a) {
0N/A String rel = (String)a.getAttribute(HTML.Attribute.REL);
0N/A if (rel != null) {
0N/A rel = rel.toLowerCase();
0N/A if (rel.equals("stylesheet") ||
0N/A rel.equals("alternate stylesheet")) {
0N/A if (styles == null) {
611N/A styles = new Vector<Object>(3);
0N/A }
0N/A styles.addElement(t);
0N/A styles.addElement(a.copyAttributes());
0N/A }
0N/A }
0N/A super.start(t, a);
0N/A }
0N/A }
0N/A
0N/A class MapAction extends TagAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet a) {
0N/A lastMap = new Map((String)a.getAttribute(HTML.Attribute.NAME));
0N/A addMap(lastMap);
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A }
0N/A }
0N/A
0N/A
0N/A class AreaAction extends TagAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet a) {
0N/A if (lastMap != null) {
0N/A lastMap.addArea(a.copyAttributes());
0N/A }
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A }
0N/A }
0N/A
0N/A
0N/A class StyleAction extends TagAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet a) {
0N/A if (inHead) {
0N/A if (styles == null) {
611N/A styles = new Vector<Object>(3);
0N/A }
0N/A styles.addElement(t);
0N/A styles.addElement(a.getAttribute(HTML.Attribute.TYPE));
0N/A inStyle = true;
0N/A }
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A inStyle = false;
0N/A }
0N/A
0N/A boolean isEmpty(HTML.Tag t) {
0N/A return false;
0N/A }
0N/A }
0N/A
0N/A
0N/A public class PreAction extends BlockAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet attr) {
0N/A inPre = true;
0N/A blockOpen(t, attr);
0N/A attr.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
0N/A blockOpen(HTML.Tag.IMPLIED, attr);
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A blockClose(HTML.Tag.IMPLIED);
0N/A // set inPre to false after closing, so that if a newline
0N/A // is added it won't generate a blockOpen.
0N/A inPre = false;
0N/A blockClose(t);
0N/A }
0N/A }
0N/A
0N/A public class CharacterAction extends TagAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet attr) {
0N/A pushCharacterStyle();
0N/A if (!foundInsertTag) {
0N/A // Note that the third argument should really be based off
0N/A // inParagraph and impliedP. If we're wrong (that is
0N/A // insertTagDepthDelta shouldn't be changed), we'll end up
0N/A // removing an extra EndSpec, which won't matter anyway.
0N/A boolean insert = canInsertTag(t, attr, false);
0N/A if (foundInsertTag) {
0N/A if (!inParagraph) {
0N/A inParagraph = impliedP = true;
0N/A }
0N/A }
0N/A if (!insert) {
0N/A return;
0N/A }
0N/A }
0N/A if (attr.isDefined(IMPLIED)) {
0N/A attr.removeAttribute(IMPLIED);
0N/A }
0N/A charAttr.addAttribute(t, attr.copyAttributes());
0N/A if (styleAttributes != null) {
0N/A charAttr.addAttributes(styleAttributes);
0N/A }
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A popCharacterStyle();
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Provides conversion of HTML tag/attribute
0N/A * mappings that have a corresponding StyleConstants
0N/A * and CSS mapping. The conversion is to CSS attributes.
0N/A */
0N/A class ConvertAction extends TagAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet attr) {
0N/A pushCharacterStyle();
0N/A if (!foundInsertTag) {
0N/A // Note that the third argument should really be based off
0N/A // inParagraph and impliedP. If we're wrong (that is
0N/A // insertTagDepthDelta shouldn't be changed), we'll end up
0N/A // removing an extra EndSpec, which won't matter anyway.
0N/A boolean insert = canInsertTag(t, attr, false);
0N/A if (foundInsertTag) {
0N/A if (!inParagraph) {
0N/A inParagraph = impliedP = true;
0N/A }
0N/A }
0N/A if (!insert) {
0N/A return;
0N/A }
0N/A }
0N/A if (attr.isDefined(IMPLIED)) {
0N/A attr.removeAttribute(IMPLIED);
0N/A }
0N/A if (styleAttributes != null) {
0N/A charAttr.addAttributes(styleAttributes);
0N/A }
0N/A // We also need to add attr, otherwise we lose custom
0N/A // attributes, including class/id for style lookups, and
0N/A // further confuse style lookup (doesn't have tag).
0N/A charAttr.addAttribute(t, attr.copyAttributes());
0N/A StyleSheet sheet = getStyleSheet();
0N/A if (t == HTML.Tag.B) {
0N/A sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_WEIGHT, "bold");
0N/A } else if (t == HTML.Tag.I) {
0N/A sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_STYLE, "italic");
0N/A } else if (t == HTML.Tag.U) {
0N/A Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
0N/A String value = "underline";
0N/A value = (v != null) ? value + "," + v.toString() : value;
0N/A sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
0N/A } else if (t == HTML.Tag.STRIKE) {
0N/A Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
0N/A String value = "line-through";
0N/A value = (v != null) ? value + "," + v.toString() : value;
0N/A sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
0N/A } else if (t == HTML.Tag.SUP) {
0N/A Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
0N/A String value = "sup";
0N/A value = (v != null) ? value + "," + v.toString() : value;
0N/A sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
0N/A } else if (t == HTML.Tag.SUB) {
0N/A Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
0N/A String value = "sub";
0N/A value = (v != null) ? value + "," + v.toString() : value;
0N/A sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
0N/A } else if (t == HTML.Tag.FONT) {
0N/A String color = (String) attr.getAttribute(HTML.Attribute.COLOR);
0N/A if (color != null) {
0N/A sheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color);
0N/A }
0N/A String face = (String) attr.getAttribute(HTML.Attribute.FACE);
0N/A if (face != null) {
0N/A sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY, face);
0N/A }
0N/A String size = (String) attr.getAttribute(HTML.Attribute.SIZE);
0N/A if (size != null) {
0N/A sheet.addCSSAttributeFromHTML(charAttr, CSS.Attribute.FONT_SIZE, size);
0N/A }
0N/A }
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A popCharacterStyle();
0N/A }
0N/A
0N/A }
0N/A
0N/A class AnchorAction extends CharacterAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet attr) {
0N/A // set flag to catch empty anchors
0N/A emptyAnchor = true;
0N/A super.start(t, attr);
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A if (emptyAnchor) {
0N/A // if the anchor was empty it was probably a
0N/A // named anchor point and we don't want to throw
0N/A // it away.
0N/A char[] one = new char[1];
0N/A one[0] = '\n';
0N/A addContent(one, 0, 1);
0N/A }
0N/A super.end(t);
0N/A }
0N/A }
0N/A
0N/A class TitleAction extends HiddenAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet attr) {
0N/A inTitle = true;
0N/A super.start(t, attr);
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A inTitle = false;
0N/A super.end(t);
0N/A }
0N/A
0N/A boolean isEmpty(HTML.Tag t) {
0N/A return false;
0N/A }
0N/A }
0N/A
0N/A
0N/A class BaseAction extends TagAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet attr) {
0N/A String href = (String) attr.getAttribute(HTML.Attribute.HREF);
0N/A if (href != null) {
0N/A try {
0N/A URL newBase = new URL(base, href);
0N/A setBase(newBase);
0N/A hasBaseTag = true;
0N/A } catch (MalformedURLException ex) {
0N/A }
0N/A }
0N/A baseTarget = (String) attr.getAttribute(HTML.Attribute.TARGET);
0N/A }
0N/A }
0N/A
0N/A class ObjectAction extends SpecialAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet a) {
0N/A if (t == HTML.Tag.PARAM) {
0N/A addParameter(a);
0N/A } else {
0N/A super.start(t, a);
0N/A }
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A if (t != HTML.Tag.PARAM) {
0N/A super.end(t);
0N/A }
0N/A }
0N/A
0N/A void addParameter(AttributeSet a) {
0N/A String name = (String) a.getAttribute(HTML.Attribute.NAME);
0N/A String value = (String) a.getAttribute(HTML.Attribute.VALUE);
0N/A if ((name != null) && (value != null)) {
611N/A ElementSpec objSpec = parseBuffer.lastElement();
0N/A MutableAttributeSet objAttr = (MutableAttributeSet) objSpec.getAttributes();
0N/A objAttr.addAttribute(name, value);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Action to support forms by building all of the elements
0N/A * used to represent form controls. This will process
0N/A * the &lt;INPUT&gt;, &lt;TEXTAREA&gt;, &lt;SELECT&gt;,
0N/A * and &lt;OPTION&gt; tags. The element created by
0N/A * this action is expected to have the attribute
0N/A * <code>StyleConstants.ModelAttribute</code> set to
0N/A * the model that holds the state for the form control.
0N/A * This enables multiple views, and allows document to
0N/A * be iterated over picking up the data of the form.
0N/A * The following are the model assignments for the
0N/A * various type of form elements.
0N/A * <table summary="model assignments for the various types of form elements">
0N/A * <tr>
0N/A * <th>Element Type
0N/A * <th>Model Type
0N/A * <tr>
0N/A * <td>input, type button
0N/A * <td>{@link DefaultButtonModel}
0N/A * <tr>
0N/A * <td>input, type checkbox
0N/A * <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
0N/A * <tr>
0N/A * <td>input, type image
0N/A * <td>{@link DefaultButtonModel}
0N/A * <tr>
0N/A * <td>input, type password
0N/A * <td>{@link PlainDocument}
0N/A * <tr>
0N/A * <td>input, type radio
0N/A * <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
0N/A * <tr>
0N/A * <td>input, type reset
0N/A * <td>{@link DefaultButtonModel}
0N/A * <tr>
0N/A * <td>input, type submit
0N/A * <td>{@link DefaultButtonModel}
0N/A * <tr>
0N/A * <td>input, type text or type is null.
0N/A * <td>{@link PlainDocument}
0N/A * <tr>
0N/A * <td>select
0N/A * <td>{@link DefaultComboBoxModel} or an {@link DefaultListModel}, with an item type of Option
0N/A * <tr>
0N/A * <td>textarea
0N/A * <td>{@link PlainDocument}
0N/A * </table>
0N/A *
0N/A */
0N/A public class FormAction extends SpecialAction {
0N/A
0N/A public void start(HTML.Tag t, MutableAttributeSet attr) {
0N/A if (t == HTML.Tag.INPUT) {
0N/A String type = (String)
0N/A attr.getAttribute(HTML.Attribute.TYPE);
0N/A /*
0N/A * if type is not defined teh default is
0N/A * assumed to be text.
0N/A */
0N/A if (type == null) {
0N/A type = "text";
0N/A attr.addAttribute(HTML.Attribute.TYPE, "text");
0N/A }
0N/A setModel(type, attr);
0N/A } else if (t == HTML.Tag.TEXTAREA) {
0N/A inTextArea = true;
0N/A textAreaDocument = new TextAreaDocument();
0N/A attr.addAttribute(StyleConstants.ModelAttribute,
0N/A textAreaDocument);
0N/A } else if (t == HTML.Tag.SELECT) {
0N/A int size = HTML.getIntegerAttributeValue(attr,
0N/A HTML.Attribute.SIZE,
0N/A 1);
611N/A boolean multiple = attr.getAttribute(HTML.Attribute.MULTIPLE) != null;
0N/A if ((size > 1) || multiple) {
0N/A OptionListModel m = new OptionListModel();
0N/A if (multiple) {
0N/A m.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
0N/A }
0N/A selectModel = m;
0N/A } else {
0N/A selectModel = new OptionComboBoxModel();
0N/A }
0N/A attr.addAttribute(StyleConstants.ModelAttribute,
0N/A selectModel);
0N/A
0N/A }
0N/A
0N/A // build the element, unless this is an option.
0N/A if (t == HTML.Tag.OPTION) {
0N/A option = new Option(attr);
0N/A
0N/A if (selectModel instanceof OptionListModel) {
0N/A OptionListModel m = (OptionListModel)selectModel;
0N/A m.addElement(option);
0N/A if (option.isSelected()) {
0N/A m.addSelectionInterval(optionCount, optionCount);
0N/A m.setInitialSelection(optionCount);
0N/A }
0N/A } else if (selectModel instanceof OptionComboBoxModel) {
0N/A OptionComboBoxModel m = (OptionComboBoxModel)selectModel;
0N/A m.addElement(option);
0N/A if (option.isSelected()) {
0N/A m.setSelectedItem(option);
0N/A m.setInitialSelection(option);
0N/A }
0N/A }
0N/A optionCount++;
0N/A } else {
0N/A super.start(t, attr);
0N/A }
0N/A }
0N/A
0N/A public void end(HTML.Tag t) {
0N/A if (t == HTML.Tag.OPTION) {
0N/A option = null;
0N/A } else {
0N/A if (t == HTML.Tag.SELECT) {
0N/A selectModel = null;
0N/A optionCount = 0;
0N/A } else if (t == HTML.Tag.TEXTAREA) {
0N/A inTextArea = false;
0N/A
0N/A /* Now that the textarea has ended,
0N/A * store the entire initial text
0N/A * of the text area. This will
0N/A * enable us to restore the initial
0N/A * state if a reset is requested.
0N/A */
0N/A textAreaDocument.storeInitialText();
0N/A }
0N/A super.end(t);
0N/A }
0N/A }
0N/A
0N/A void setModel(String type, MutableAttributeSet attr) {
0N/A if (type.equals("submit") ||
0N/A type.equals("reset") ||
0N/A type.equals("image")) {
0N/A
0N/A // button model
0N/A attr.addAttribute(StyleConstants.ModelAttribute,
0N/A new DefaultButtonModel());
0N/A } else if (type.equals("text") ||
0N/A type.equals("password")) {
0N/A // plain text model
0N/A int maxLength = HTML.getIntegerAttributeValue(
0N/A attr, HTML.Attribute.MAXLENGTH, -1);
0N/A Document doc;
0N/A
0N/A if (maxLength > 0) {
0N/A doc = new FixedLengthDocument(maxLength);
0N/A }
0N/A else {
0N/A doc = new PlainDocument();
0N/A }
0N/A String value = (String)
0N/A attr.getAttribute(HTML.Attribute.VALUE);
0N/A try {
0N/A doc.insertString(0, value, null);
0N/A } catch (BadLocationException e) {
0N/A }
0N/A attr.addAttribute(StyleConstants.ModelAttribute, doc);
0N/A } else if (type.equals("file")) {
0N/A // plain text model
0N/A attr.addAttribute(StyleConstants.ModelAttribute,
0N/A new PlainDocument());
0N/A } else if (type.equals("checkbox") ||
0N/A type.equals("radio")) {
0N/A JToggleButton.ToggleButtonModel model = new JToggleButton.ToggleButtonModel();
0N/A if (type.equals("radio")) {
0N/A String name = (String) attr.getAttribute(HTML.Attribute.NAME);
0N/A if ( radioButtonGroupsMap == null ) { //fix for 4772743
611N/A radioButtonGroupsMap = new HashMap<String, ButtonGroup>();
0N/A }
611N/A ButtonGroup radioButtonGroup = radioButtonGroupsMap.get(name);
0N/A if (radioButtonGroup == null) {
0N/A radioButtonGroup = new ButtonGroup();
0N/A radioButtonGroupsMap.put(name,radioButtonGroup);
0N/A }
0N/A model.setGroup(radioButtonGroup);
0N/A }
0N/A boolean checked = (attr.getAttribute(HTML.Attribute.CHECKED) != null);
0N/A model.setSelected(checked);
0N/A attr.addAttribute(StyleConstants.ModelAttribute, model);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * If a &lt;SELECT&gt; tag is being processed, this
0N/A * model will be a reference to the model being filled
0N/A * with the &lt;OPTION&gt; elements (which produce
0N/A * objects of type <code>Option</code>.
0N/A */
0N/A Object selectModel;
0N/A int optionCount;
0N/A }
0N/A
0N/A
0N/A // --- utility methods used by the reader ------------------
0N/A
0N/A /**
0N/A * Pushes the current character style on a stack in preparation
0N/A * for forming a new nested character style.
0N/A */
0N/A protected void pushCharacterStyle() {
0N/A charAttrStack.push(charAttr.copyAttributes());
0N/A }
0N/A
0N/A /**
0N/A * Pops a previously pushed character style off the stack
0N/A * to return to a previous style.
0N/A */
0N/A protected void popCharacterStyle() {
0N/A if (!charAttrStack.empty()) {
0N/A charAttr = (MutableAttributeSet) charAttrStack.peek();
0N/A charAttrStack.pop();
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Adds the given content to the textarea document.
0N/A * This method gets called when we are in a textarea
0N/A * context. Therefore all text that is seen belongs
0N/A * to the text area and is hence added to the
0N/A * TextAreaDocument associated with the text area.
0N/A */
0N/A protected void textAreaContent(char[] data) {
0N/A try {
0N/A textAreaDocument.insertString(textAreaDocument.getLength(), new String(data), null);
0N/A } catch (BadLocationException e) {
0N/A // Should do something reasonable
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Adds the given content that was encountered in a
0N/A * PRE element. This synthesizes lines to hold the
0N/A * runs of text, and makes calls to addContent to
0N/A * actually add the text.
0N/A */
0N/A protected void preContent(char[] data) {
0N/A int last = 0;
0N/A for (int i = 0; i < data.length; i++) {
0N/A if (data[i] == '\n') {
0N/A addContent(data, last, i - last + 1);
0N/A blockClose(HTML.Tag.IMPLIED);
0N/A MutableAttributeSet a = new SimpleAttributeSet();
0N/A a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
0N/A blockOpen(HTML.Tag.IMPLIED, a);
0N/A last = i + 1;
0N/A }
0N/A }
0N/A if (last < data.length) {
0N/A addContent(data, last, data.length - last);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Adds an instruction to the parse buffer to create a
0N/A * block element with the given attributes.
0N/A */
0N/A protected void blockOpen(HTML.Tag t, MutableAttributeSet attr) {
0N/A if (impliedP) {
0N/A blockClose(HTML.Tag.IMPLIED);
0N/A }
0N/A
0N/A inBlock++;
0N/A
0N/A if (!canInsertTag(t, attr, true)) {
0N/A return;
0N/A }
0N/A if (attr.isDefined(IMPLIED)) {
0N/A attr.removeAttribute(IMPLIED);
0N/A }
0N/A lastWasNewline = false;
0N/A attr.addAttribute(StyleConstants.NameAttribute, t);
0N/A ElementSpec es = new ElementSpec(
0N/A attr.copyAttributes(), ElementSpec.StartTagType);
0N/A parseBuffer.addElement(es);
0N/A }
0N/A
0N/A /**
0N/A * Adds an instruction to the parse buffer to close out
0N/A * a block element of the given type.
0N/A */
0N/A protected void blockClose(HTML.Tag t) {
0N/A inBlock--;
0N/A
0N/A if (!foundInsertTag) {
0N/A return;
0N/A }
0N/A
0N/A // Add a new line, if the last character wasn't one. This is
0N/A // needed for proper positioning of the cursor. addContent
0N/A // with true will force an implied paragraph to be generated if
0N/A // there isn't one. This may result in a rather bogus structure
0N/A // (perhaps a table with a child pargraph), but the paragraph
0N/A // is needed for proper positioning and display.
0N/A if(!lastWasNewline) {
0N/A pushCharacterStyle();
0N/A charAttr.addAttribute(IMPLIED_CR, Boolean.TRUE);
0N/A addContent(NEWLINE, 0, 1, true);
0N/A popCharacterStyle();
0N/A lastWasNewline = true;
0N/A }
0N/A
0N/A if (impliedP) {
0N/A impliedP = false;
0N/A inParagraph = false;
0N/A if (t != HTML.Tag.IMPLIED) {
0N/A blockClose(HTML.Tag.IMPLIED);
0N/A }
0N/A }
0N/A // an open/close with no content will be removed, so we
0N/A // add a space of content to keep the element being formed.
0N/A ElementSpec prev = (parseBuffer.size() > 0) ?
611N/A parseBuffer.lastElement() : null;
0N/A if (prev != null && prev.getType() == ElementSpec.StartTagType) {
0N/A char[] one = new char[1];
0N/A one[0] = ' ';
0N/A addContent(one, 0, 1);
0N/A }
0N/A ElementSpec es = new ElementSpec(
0N/A null, ElementSpec.EndTagType);
0N/A parseBuffer.addElement(es);
0N/A }
0N/A
0N/A /**
0N/A * Adds some text with the current character attributes.
0N/A *
0N/A * @param data the content to add
0N/A * @param offs the initial offset
0N/A * @param length the length
0N/A */
0N/A protected void addContent(char[] data, int offs, int length) {
0N/A addContent(data, offs, length, true);
0N/A }
0N/A
0N/A /**
0N/A * Adds some text with the current character attributes.
0N/A *
0N/A * @param data the content to add
0N/A * @param offs the initial offset
0N/A * @param length the length
0N/A * @param generateImpliedPIfNecessary whether to generate implied
0N/A * paragraphs
0N/A */
0N/A protected void addContent(char[] data, int offs, int length,
0N/A boolean generateImpliedPIfNecessary) {
0N/A if (!foundInsertTag) {
0N/A return;
0N/A }
0N/A
0N/A if (generateImpliedPIfNecessary && (! inParagraph) && (! inPre)) {
0N/A blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
0N/A inParagraph = true;
0N/A impliedP = true;
0N/A }
0N/A emptyAnchor = false;
0N/A charAttr.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
0N/A AttributeSet a = charAttr.copyAttributes();
0N/A ElementSpec es = new ElementSpec(
0N/A a, ElementSpec.ContentType, data, offs, length);
0N/A parseBuffer.addElement(es);
0N/A
0N/A if (parseBuffer.size() > threshold) {
0N/A if ( threshold <= MaxThreshold ) {
0N/A threshold *= StepThreshold;
0N/A }
0N/A try {
0N/A flushBuffer(false);
0N/A } catch (BadLocationException ble) {
0N/A }
0N/A }
0N/A if(length > 0) {
0N/A lastWasNewline = (data[offs + length - 1] == '\n');
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Adds content that is basically specified entirely
0N/A * in the attribute set.
0N/A */
0N/A protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a) {
0N/A if ((t != HTML.Tag.FRAME) && (! inParagraph) && (! inPre)) {
0N/A nextTagAfterPImplied = t;
0N/A blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
0N/A nextTagAfterPImplied = null;
0N/A inParagraph = true;
0N/A impliedP = true;
0N/A }
0N/A if (!canInsertTag(t, a, t.isBlock())) {
0N/A return;
0N/A }
0N/A if (a.isDefined(IMPLIED)) {
0N/A a.removeAttribute(IMPLIED);
0N/A }
0N/A emptyAnchor = false;
0N/A a.addAttributes(charAttr);
0N/A a.addAttribute(StyleConstants.NameAttribute, t);
0N/A char[] one = new char[1];
0N/A one[0] = ' ';
0N/A ElementSpec es = new ElementSpec(
0N/A a.copyAttributes(), ElementSpec.ContentType, one, 0, 1);
0N/A parseBuffer.addElement(es);
0N/A // Set this to avoid generating a newline for frames, frames
0N/A // shouldn't have any content, and shouldn't need a newline.
0N/A if (t == HTML.Tag.FRAME) {
0N/A lastWasNewline = true;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Flushes the current parse buffer into the document.
0N/A * @param endOfStream true if there is no more content to parser
0N/A */
0N/A void flushBuffer(boolean endOfStream) throws BadLocationException {
0N/A int oldLength = HTMLDocument.this.getLength();
0N/A int size = parseBuffer.size();
0N/A if (endOfStream && (insertTag != null || insertAfterImplied) &&
0N/A size > 0) {
0N/A adjustEndSpecsForPartialInsert();
0N/A size = parseBuffer.size();
0N/A }
0N/A ElementSpec[] spec = new ElementSpec[size];
0N/A parseBuffer.copyInto(spec);
0N/A
0N/A if (oldLength == 0 && (insertTag == null && !insertAfterImplied)) {
0N/A create(spec);
0N/A } else {
0N/A insert(offset, spec);
0N/A }
0N/A parseBuffer.removeAllElements();
0N/A offset += HTMLDocument.this.getLength() - oldLength;
0N/A flushCount++;
0N/A }
0N/A
0N/A /**
0N/A * This will be invoked for the last flush, if <code>insertTag</code>
0N/A * is non null.
0N/A */
0N/A private void adjustEndSpecsForPartialInsert() {
0N/A int size = parseBuffer.size();
0N/A if (insertTagDepthDelta < 0) {
0N/A // When inserting via an insertTag, the depths (of the tree
0N/A // being read in, and existing hiearchy) may not match up.
0N/A // This attemps to clean it up.
0N/A int removeCounter = insertTagDepthDelta;
0N/A while (removeCounter < 0 && size >= 0 &&
611N/A parseBuffer.elementAt(size - 1).
0N/A getType() == ElementSpec.EndTagType) {
0N/A parseBuffer.removeElementAt(--size);
0N/A removeCounter++;
0N/A }
0N/A }
0N/A if (flushCount == 0 && (!insertAfterImplied ||
0N/A !wantsTrailingNewline)) {
0N/A // If this starts with content (or popDepth > 0 &&
0N/A // pushDepth > 0) and ends with EndTagTypes, make sure
0N/A // the last content isn't a \n, otherwise will end up with
0N/A // an extra \n in the middle of content.
0N/A int index = 0;
0N/A if (pushDepth > 0) {
611N/A if (parseBuffer.elementAt(0).getType() ==
0N/A ElementSpec.ContentType) {
0N/A index++;
0N/A }
0N/A }
0N/A index += (popDepth + pushDepth);
0N/A int cCount = 0;
0N/A int cStart = index;
611N/A while (index < size && parseBuffer.elementAt
611N/A (index).getType() == ElementSpec.ContentType) {
0N/A index++;
0N/A cCount++;
0N/A }
0N/A if (cCount > 1) {
611N/A while (index < size && parseBuffer.elementAt
611N/A (index).getType() == ElementSpec.EndTagType) {
0N/A index++;
0N/A }
0N/A if (index == size) {
611N/A char[] lastText = parseBuffer.elementAt
611N/A (cStart + cCount - 1).getArray();
0N/A if (lastText.length == 1 && lastText[0] == NEWLINE[0]){
0N/A index = cStart + cCount - 1;
0N/A while (size > index) {
0N/A parseBuffer.removeElementAt(--size);
0N/A }
0N/A }
0N/A }
0N/A }
0N/A }
0N/A if (wantsTrailingNewline) {
0N/A // Make sure there is in fact a newline
0N/A for (int counter = parseBuffer.size() - 1; counter >= 0;
0N/A counter--) {
611N/A ElementSpec spec = parseBuffer.elementAt(counter);
0N/A if (spec.getType() == ElementSpec.ContentType) {
0N/A if (spec.getArray()[spec.getLength() - 1] != '\n') {
0N/A SimpleAttributeSet attrs =new SimpleAttributeSet();
0N/A
0N/A attrs.addAttribute(StyleConstants.NameAttribute,
0N/A HTML.Tag.CONTENT);
0N/A parseBuffer.insertElementAt(new ElementSpec(
0N/A attrs,
0N/A ElementSpec.ContentType, NEWLINE, 0, 1),
0N/A counter + 1);
0N/A }
0N/A break;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Adds the CSS rules in <code>rules</code>.
0N/A */
0N/A void addCSSRules(String rules) {
0N/A StyleSheet ss = getStyleSheet();
0N/A ss.addRule(rules);
0N/A }
0N/A
0N/A /**
0N/A * Adds the CSS stylesheet at <code>href</code> to the known list
0N/A * of stylesheets.
0N/A */
0N/A void linkCSSStyleSheet(String href) {
611N/A URL url;
0N/A try {
0N/A url = new URL(base, href);
0N/A } catch (MalformedURLException mfe) {
0N/A try {
0N/A url = new URL(href);
0N/A } catch (MalformedURLException mfe2) {
0N/A url = null;
0N/A }
0N/A }
0N/A if (url != null) {
0N/A getStyleSheet().importStyleSheet(url);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Returns true if can insert starting at <code>t</code>. This
0N/A * will return false if the insert tag is set, and hasn't been found
0N/A * yet.
0N/A */
0N/A private boolean canInsertTag(HTML.Tag t, AttributeSet attr,
0N/A boolean isBlockTag) {
0N/A if (!foundInsertTag) {
0N/A boolean needPImplied = ((t == HTML.Tag.IMPLIED)
0N/A && (!inParagraph)
0N/A && (!inPre));
0N/A if (needPImplied && (nextTagAfterPImplied != null)) {
0N/A
0N/A /*
0N/A * If insertTag == null then just proceed to
0N/A * foundInsertTag() call below and return true.
0N/A */
0N/A if (insertTag != null) {
0N/A boolean nextTagIsInsertTag =
0N/A isInsertTag(nextTagAfterPImplied);
0N/A if ( (! nextTagIsInsertTag) || (! insertInsertTag) ) {
0N/A return false;
0N/A }
0N/A }
0N/A /*
0N/A * Proceed to foundInsertTag() call...
0N/A */
0N/A } else if ((insertTag != null && !isInsertTag(t))
0N/A || (insertAfterImplied
0N/A && (attr == null
0N/A || attr.isDefined(IMPLIED)
0N/A || t == HTML.Tag.IMPLIED
0N/A )
0N/A )
0N/A ) {
0N/A return false;
0N/A }
0N/A
0N/A // Allow the insert if t matches the insert tag, or
0N/A // insertAfterImplied is true and the element is implied.
0N/A foundInsertTag(isBlockTag);
0N/A if (!insertInsertTag) {
0N/A return false;
0N/A }
0N/A }
0N/A return true;
0N/A }
0N/A
0N/A private boolean isInsertTag(HTML.Tag tag) {
0N/A return (insertTag == tag);
0N/A }
0N/A
0N/A private void foundInsertTag(boolean isBlockTag) {
0N/A foundInsertTag = true;
0N/A if (!insertAfterImplied && (popDepth > 0 || pushDepth > 0)) {
0N/A try {
0N/A if (offset == 0 || !getText(offset - 1, 1).equals("\n")) {
0N/A // Need to insert a newline.
0N/A AttributeSet newAttrs = null;
0N/A boolean joinP = true;
0N/A
0N/A if (offset != 0) {
0N/A // Determine if we can use JoinPrevious, we can't
0N/A // if the Element has some attributes that are
0N/A // not meant to be duplicated.
0N/A Element charElement = getCharacterElement
0N/A (offset - 1);
0N/A AttributeSet attrs = charElement.getAttributes();
0N/A
0N/A if (attrs.isDefined(StyleConstants.
0N/A ComposedTextAttribute)) {
0N/A joinP = false;
0N/A }
0N/A else {
0N/A Object name = attrs.getAttribute
0N/A (StyleConstants.NameAttribute);
0N/A if (name instanceof HTML.Tag) {
0N/A HTML.Tag tag = (HTML.Tag)name;
0N/A if (tag == HTML.Tag.IMG ||
0N/A tag == HTML.Tag.HR ||
0N/A tag == HTML.Tag.COMMENT ||
0N/A (tag instanceof HTML.UnknownTag)) {
0N/A joinP = false;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A if (!joinP) {
0N/A // If not joining with the previous element, be
0N/A // sure and set the name (otherwise it will be
0N/A // inherited).
0N/A newAttrs = new SimpleAttributeSet();
0N/A ((SimpleAttributeSet)newAttrs).addAttribute
0N/A (StyleConstants.NameAttribute,
0N/A HTML.Tag.CONTENT);
0N/A }
0N/A ElementSpec es = new ElementSpec(newAttrs,
0N/A ElementSpec.ContentType, NEWLINE, 0,
0N/A NEWLINE.length);
0N/A if (joinP) {
0N/A es.setDirection(ElementSpec.
0N/A JoinPreviousDirection);
0N/A }
0N/A parseBuffer.addElement(es);
0N/A }
0N/A } catch (BadLocationException ble) {}
0N/A }
0N/A // pops
0N/A for (int counter = 0; counter < popDepth; counter++) {
0N/A parseBuffer.addElement(new ElementSpec(null, ElementSpec.
0N/A EndTagType));
0N/A }
0N/A // pushes
0N/A for (int counter = 0; counter < pushDepth; counter++) {
0N/A ElementSpec es = new ElementSpec(null, ElementSpec.
0N/A StartTagType);
0N/A es.setDirection(ElementSpec.JoinNextDirection);
0N/A parseBuffer.addElement(es);
0N/A }
0N/A insertTagDepthDelta = depthTo(Math.max(0, offset - 1)) -
0N/A popDepth + pushDepth - inBlock;
0N/A if (isBlockTag) {
0N/A // A start spec will be added (for this tag), so we account
0N/A // for it here.
0N/A insertTagDepthDelta++;
0N/A }
0N/A else {
0N/A // An implied paragraph close (end spec) is going to be added,
0N/A // so we account for it here.
0N/A insertTagDepthDelta--;
0N/A inParagraph = true;
0N/A lastWasNewline = false;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * This is set to true when and end is invoked for <html>.
0N/A */
0N/A private boolean receivedEndHTML;
0N/A /** Number of times <code>flushBuffer</code> has been invoked. */
0N/A private int flushCount;
0N/A /** If true, behavior is similiar to insertTag, but instead of
0N/A * waiting for insertTag will wait for first Element without
0N/A * an 'implied' attribute and begin inserting then. */
0N/A private boolean insertAfterImplied;
0N/A /** This is only used if insertAfterImplied is true. If false, only
0N/A * inserting content, and there is a trailing newline it is removed. */
0N/A private boolean wantsTrailingNewline;
0N/A int threshold;
0N/A int offset;
0N/A boolean inParagraph = false;
0N/A boolean impliedP = false;
0N/A boolean inPre = false;
0N/A boolean inTextArea = false;
0N/A TextAreaDocument textAreaDocument = null;
0N/A boolean inTitle = false;
0N/A boolean lastWasNewline = true;
0N/A boolean emptyAnchor;
0N/A /** True if (!emptyDocument && insertTag == null), this is used so
0N/A * much it is cached. */
0N/A boolean midInsert;
0N/A /** True when the body has been encountered. */
0N/A boolean inBody;
0N/A /** If non null, gives parent Tag that insert is to happen at. */
0N/A HTML.Tag insertTag;
0N/A /** If true, the insertTag is inserted, otherwise elements after
0N/A * the insertTag is found are inserted. */
0N/A boolean insertInsertTag;
0N/A /** Set to true when insertTag has been found. */
0N/A boolean foundInsertTag;
0N/A /** When foundInsertTag is set to true, this will be updated to
0N/A * reflect the delta between the two structures. That is, it
0N/A * will be the depth the inserts are happening at minus the
0N/A * depth of the tags being passed in. A value of 0 (the common
0N/A * case) indicates the structures match, a value greater than 0 indicates
0N/A * the insert is happening at a deeper depth than the stream is
0N/A * parsing, and a value less than 0 indicates the insert is happening earlier
0N/A * in the tree that the parser thinks and that we will need to remove
0N/A * EndTagType specs in the flushBuffer method.
0N/A */
0N/A int insertTagDepthDelta;
0N/A /** How many parents to ascend before insert new elements. */
0N/A int popDepth;
0N/A /** How many parents to descend (relative to popDepth) before
0N/A * inserting. */
0N/A int pushDepth;
0N/A /** Last Map that was encountered. */
0N/A Map lastMap;
0N/A /** Set to true when a style element is encountered. */
0N/A boolean inStyle = false;
0N/A /** Name of style to use. Obtained from Meta tag. */
0N/A String defaultStyle;
0N/A /** Vector describing styles that should be include. Will consist
0N/A * of a bunch of HTML.Tags, which will either be:
0N/A * <p>LINK: in which case it is followed by an AttributeSet
0N/A * <p>STYLE: in which case the following element is a String
0N/A * indicating the type (may be null), and the elements following
0N/A * it until the next HTML.Tag are the rules as Strings.
0N/A */
611N/A Vector<Object> styles;
0N/A /** True if inside the head tag. */
0N/A boolean inHead = false;
0N/A /** Set to true if the style language is text/css. Since this is
0N/A * used alot, it is cached. */
0N/A boolean isStyleCSS;
0N/A /** True if inserting into an empty document. */
0N/A boolean emptyDocument;
0N/A /** Attributes from a style Attribute. */
0N/A AttributeSet styleAttributes;
0N/A
0N/A /**
0N/A * Current option, if in an option element (needed to
0N/A * load the label.
0N/A */
0N/A Option option;
0N/A
611N/A protected Vector<ElementSpec> parseBuffer = new Vector<ElementSpec>();
0N/A protected MutableAttributeSet charAttr = new TaggedAttributeSet();
611N/A Stack<AttributeSet> charAttrStack = new Stack<AttributeSet>();
611N/A Hashtable<HTML.Tag, TagAction> tagMap;
0N/A int inBlock = 0;
0N/A
0N/A /**
0N/A * This attribute is sometimes used to refer to next tag
0N/A * to be handled after p-implied when the latter is
0N/A * the current tag which is being handled.
0N/A */
0N/A private HTML.Tag nextTagAfterPImplied = null;
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Used by StyleSheet to determine when to avoid removing HTML.Tags
0N/A * matching StyleConstants.
0N/A */
0N/A static class TaggedAttributeSet extends SimpleAttributeSet {
0N/A TaggedAttributeSet() {
0N/A super();
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * An element that represents a chunk of text that has
0N/A * a set of HTML character level attributes assigned to
0N/A * it.
0N/A */
0N/A public class RunElement extends LeafElement {
0N/A
0N/A /**
0N/A * Constructs an element that represents content within the
0N/A * document (has no children).
0N/A *
0N/A * @param parent the parent element
0N/A * @param a the element attributes
0N/A * @param offs0 the start offset (must be at least 0)
0N/A * @param offs1 the end offset (must be at least offs0)
0N/A * @since 1.4
0N/A */
0N/A public RunElement(Element parent, AttributeSet a, int offs0, int offs1) {
0N/A super(parent, a, offs0, offs1);
0N/A }
0N/A
0N/A /**
0N/A * Gets the name of the element.
0N/A *
0N/A * @return the name, null if none
0N/A */
0N/A public String getName() {
0N/A Object o = getAttribute(StyleConstants.NameAttribute);
0N/A if (o != null) {
0N/A return o.toString();
0N/A }
0N/A return super.getName();
0N/A }
0N/A
0N/A /**
0N/A * Gets the resolving parent. HTML attributes are not inherited
0N/A * at the model level so we override this to return null.
0N/A *
0N/A * @return null, there are none
0N/A * @see AttributeSet#getResolveParent
0N/A */
0N/A public AttributeSet getResolveParent() {
0N/A return null;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * An element that represents a structural <em>block</em> of
0N/A * HTML.
0N/A */
0N/A public class BlockElement extends BranchElement {
0N/A
0N/A /**
0N/A * Constructs a composite element that initially contains
0N/A * no children.
0N/A *
0N/A * @param parent the parent element
0N/A * @param a the attributes for the element
0N/A * @since 1.4
0N/A */
0N/A public BlockElement(Element parent, AttributeSet a) {
0N/A super(parent, a);
0N/A }
0N/A
0N/A /**
0N/A * Gets the name of the element.
0N/A *
0N/A * @return the name, null if none
0N/A */
0N/A public String getName() {
0N/A Object o = getAttribute(StyleConstants.NameAttribute);
0N/A if (o != null) {
0N/A return o.toString();
0N/A }
0N/A return super.getName();
0N/A }
0N/A
0N/A /**
0N/A * Gets the resolving parent. HTML attributes are not inherited
0N/A * at the model level so we override this to return null.
0N/A *
0N/A * @return null, there are none
0N/A * @see AttributeSet#getResolveParent
0N/A */
0N/A public AttributeSet getResolveParent() {
0N/A return null;
0N/A }
0N/A
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Document that allows you to set the maximum length of the text.
0N/A */
0N/A private static class FixedLengthDocument extends PlainDocument {
0N/A private int maxLength;
0N/A
0N/A public FixedLengthDocument(int maxLength) {
0N/A this.maxLength = maxLength;
0N/A }
0N/A
0N/A public void insertString(int offset, String str, AttributeSet a)
0N/A throws BadLocationException {
0N/A if (str != null && str.length() + getLength() <= maxLength) {
0N/A super.insertString(offset, str, a);
0N/A }
0N/A }
0N/A }
0N/A}