0001 /*
0002 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing.text.html;
0026
0027 import java.awt.Color;
0028 import java.awt.Component;
0029 import java.awt.font.TextAttribute;
0030 import java.util.*;
0031 import java.net.URL;
0032 import java.net.URLEncoder;
0033 import java.net.MalformedURLException;
0034 import java.io.*;
0035 import javax.swing.*;
0036 import javax.swing.event.*;
0037 import javax.swing.text.*;
0038 import javax.swing.undo.*;
0039 import java.text.Bidi;
0040 import sun.swing.SwingUtilities2;
0041
0042 /**
0043 * A document that models HTML. The purpose of this model is to
0044 * support both browsing and editing. As a result, the structure
0045 * described by an HTML document is not exactly replicated by default.
0046 * The element structure that is modeled by default, is built by the
0047 * class <code>HTMLDocument.HTMLReader</code>, which implements the
0048 * <code>HTMLEditorKit.ParserCallback</code> protocol that the parser
0049 * expects. To change the structure one can subclass
0050 * <code>HTMLReader</code>, and reimplement the method {@link
0051 * #getReader(int)} to return the new reader implementation. The
0052 * documentation for <code>HTMLReader</code> should be consulted for
0053 * the details of the default structure created. The intent is that
0054 * the document be non-lossy (although reproducing the HTML format may
0055 * result in a different format).
0056 *
0057 * <p>The document models only HTML, and makes no attempt to store
0058 * view attributes in it. The elements are identified by the
0059 * <code>StyleContext.NameAttribute</code> attribute, which should
0060 * always have a value of type <code>HTML.Tag</code> that identifies
0061 * the kind of element. Some of the elements (such as comments) are
0062 * synthesized. The <code>HTMLFactory</code> uses this attribute to
0063 * determine what kind of view to build.</p>
0064 *
0065 * <p>This document supports incremental loading. The
0066 * <code>TokenThreshold</code> property controls how much of the parse
0067 * is buffered before trying to update the element structure of the
0068 * document. This property is set by the <code>EditorKit</code> so
0069 * that subclasses can disable it.</p>
0070 *
0071 * <p>The <code>Base</code> property determines the URL against which
0072 * relative URLs are resolved. By default, this will be the
0073 * <code>Document.StreamDescriptionProperty</code> if the value of the
0074 * property is a URL. If a <BASE> tag is encountered, the base
0075 * will become the URL specified by that tag. Because the base URL is
0076 * a property, it can of course be set directly.</p>
0077 *
0078 * <p>The default content storage mechanism for this document is a gap
0079 * buffer (<code>GapContent</code>). Alternatives can be supplied by
0080 * using the constructor that takes a <code>Content</code>
0081 * implementation.</p>
0082 *
0083 * <h2>Modifying HTMLDocument</h2>
0084 *
0085 * <p>In addition to the methods provided by Document and
0086 * StyledDocument for mutating an HTMLDocument, HTMLDocument provides
0087 * a number of convenience methods. The following methods can be used
0088 * to insert HTML content into an existing document.</p>
0089 *
0090 * <ul>
0091 * <li>{@link #setInnerHTML(Element, String)}</li>
0092 * <li>{@link #setOuterHTML(Element, String)}</li>
0093 * <li>{@link #insertBeforeStart(Element, String)}</li>
0094 * <li>{@link #insertAfterStart(Element, String)}</li>
0095 * <li>{@link #insertBeforeEnd(Element, String)}</li>
0096 * <li>{@link #insertAfterEnd(Element, String)}</li>
0097 * </ul>
0098 *
0099 * <p>The following examples illustrate using these methods. Each
0100 * example assumes the HTML document is initialized in the following
0101 * way:</p>
0102 *
0103 * <pre>
0104 * JEditorPane p = new JEditorPane();
0105 * p.setContentType("text/html");
0106 * p.setText("..."); // Document text is provided below.
0107 * HTMLDocument d = (HTMLDocument) p.getDocument();
0108 * </pre>
0109 *
0110 * <p>With the following HTML content:</p>
0111 *
0112 * <pre>
0113 * <html>
0114 * <head>
0115 * <title>An example HTMLDocument</title>
0116 * <style type="text/css">
0117 * div { background-color: silver; }
0118 * ul { color: red; }
0119 * </style>
0120 * </head>
0121 * <body>
0122 * <div id="BOX">
0123 * <p>Paragraph 1</p>
0124 * <p>Paragraph 2</p>
0125 * </div>
0126 * </body>
0127 * </html>
0128 * </pre>
0129 *
0130 * <p>All the methods for modifying an HTML document require an {@link
0131 * Element}. Elements can be obtained from an HTML document by using
0132 * the method {@link #getElement(Element e, Object attribute, Object
0133 * value)}. It returns the first descendant element that contains the
0134 * specified attribute with the given value, in depth-first order.
0135 * For example, <code>d.getElement(d.getDefaultRootElement(),
0136 * StyleConstants.NameAttribute, HTML.Tag.P)</code> returns the first
0137 * paragraph element.</p>
0138 *
0139 * <p>A convenient shortcut for locating elements is the method {@link
0140 * #getElement(String)}; returns an element whose <code>ID</code>
0141 * attribute matches the specified value. For example,
0142 * <code>d.getElement("BOX")</code> returns the <code>DIV</code>
0143 * element.</p>
0144 *
0145 * <p>The {@link #getIterator(HTML.Tag t)} method can also be used for
0146 * finding all occurrences of the specified HTML tag in the
0147 * document.</p>
0148 *
0149 * <h3>Inserting elements</h3>
0150 *
0151 * <p>Elements can be inserted before or after the existing children
0152 * of any non-leaf element by using the methods
0153 * <code>insertAfterStart</code> and <code>insertBeforeEnd</code>.
0154 * For example, if <code>e</code> is the <code>DIV</code> element,
0155 * <code>d.insertAfterStart(e, "<ul><li>List
0156 * Item</li></ul>")</code> inserts the list before the first
0157 * paragraph, and <code>d.insertBeforeEnd(e, "<ul><li>List
0158 * Item</li></ul>")</code> inserts the list after the last
0159 * paragraph. The <code>DIV</code> block becomes the parent of the
0160 * newly inserted elements.</p>
0161 *
0162 * <p>Sibling elements can be inserted before or after any element by
0163 * using the methods <code>insertBeforeStart</code> and
0164 * <code>insertAfterEnd</code>. For example, if <code>e</code> is the
0165 * <code>DIV</code> element, <code>d.insertBeforeStart(e,
0166 * "<ul><li>List Item</li></ul>")</code> inserts the list
0167 * before the <code>DIV</code> element, and <code>d.insertAfterEnd(e,
0168 * "<ul><li>List Item</li></ul>")</code> inserts the list
0169 * after the <code>DIV</code> element. The newly inserted elements
0170 * become siblings of the <code>DIV</code> element.</p>
0171 *
0172 * <h3>Replacing elements</h3>
0173 *
0174 * <p>Elements and all their descendants can be replaced by using the
0175 * methods <code>setInnerHTML</code> and <code>setOuterHTML</code>.
0176 * For example, if <code>e</code> is the <code>DIV</code> element,
0177 * <code>d.setInnerHTML(e, "<ul><li>List
0178 * Item</li></ul>")</code> replaces all children paragraphs with
0179 * the list, and <code>d.setOuterHTML(e, "<ul><li>List
0180 * Item</li></ul>")</code> replaces the <code>DIV</code> element
0181 * itself. In latter case the parent of the list is the
0182 * <code>BODY</code> element.
0183 *
0184 * <h3>Summary</h3>
0185 *
0186 * <p>The following table shows the example document and the results
0187 * of various methods described above.</p>
0188 *
0189 * <table border=1 cellspacing=0>
0190 * <tr>
0191 * <th>Example</th>
0192 * <th><code>insertAfterStart</code></th>
0193 * <th><code>insertBeforeEnd</code></th>
0194 * <th><code>insertBeforeStart</code></th>
0195 * <th><code>insertAfterEnd</code></th>
0196 * <th><code>setInnerHTML</code></th>
0197 * <th><code>setOuterHTML</code></th>
0198 * </tr>
0199 * <tr valign="top">
0200 * <td nowrap="nowrap">
0201 * <div style="background-color: silver;">
0202 * <p>Paragraph 1</p>
0203 * <p>Paragraph 2</p>
0204 * </div>
0205 * </td>
0206 * <!--insertAfterStart-->
0207 * <td nowrap="nowrap">
0208 * <div style="background-color: silver;">
0209 * <ul style="color: red;">
0210 * <li>List Item</li>
0211 * </ul>
0212 * <p>Paragraph 1</p>
0213 * <p>Paragraph 2</p>
0214 * </div>
0215 * </td>
0216 * <!--insertBeforeEnd-->
0217 * <td nowrap="nowrap">
0218 * <div style="background-color: silver;">
0219 * <p>Paragraph 1</p>
0220 * <p>Paragraph 2</p>
0221 * <ul style="color: red;">
0222 * <li>List Item</li>
0223 * </ul>
0224 * </div>
0225 * </td>
0226 * <!--insertBeforeStart-->
0227 * <td nowrap="nowrap">
0228 * <ul style="color: red;">
0229 * <li>List Item</li>
0230 * </ul>
0231 * <div style="background-color: silver;">
0232 * <p>Paragraph 1</p>
0233 * <p>Paragraph 2</p>
0234 * </div>
0235 * </td>
0236 * <!--insertAfterEnd-->
0237 * <td nowrap="nowrap">
0238 * <div style="background-color: silver;">
0239 * <p>Paragraph 1</p>
0240 * <p>Paragraph 2</p>
0241 * </div>
0242 * <ul style="color: red;">
0243 * <li>List Item</li>
0244 * </ul>
0245 * </td>
0246 * <!--setInnerHTML-->
0247 * <td nowrap="nowrap">
0248 * <div style="background-color: silver;">
0249 * <ul style="color: red;">
0250 * <li>List Item</li>
0251 * </ul>
0252 * </div>
0253 * </td>
0254 * <!--setOuterHTML-->
0255 * <td nowrap="nowrap">
0256 * <ul style="color: red;">
0257 * <li>List Item</li>
0258 * </ul>
0259 * </td>
0260 * </tr>
0261 * </table>
0262 *
0263 * <p><strong>Warning:</strong> Serialized objects of this class will
0264 * not be compatible with future Swing releases. The current
0265 * serialization support is appropriate for short term storage or RMI
0266 * between applications running the same version of Swing. As of 1.4,
0267 * support for long term storage of all JavaBeans<sup><font
0268 * size="-2">TM</font></sup> has been added to the
0269 * <code>java.beans</code> package. Please see {@link
0270 * java.beans.XMLEncoder}.</p>
0271 *
0272 * @author Timothy Prinzing
0273 * @author Scott Violet
0274 * @author Sunita Mani
0275 * @version 1.187 05/05/07
0276 */
0277 public class HTMLDocument extends DefaultStyledDocument {
0278 /**
0279 * Constructs an HTML document using the default buffer size
0280 * and a default <code>StyleSheet</code>. This is a convenience
0281 * method for the constructor
0282 * <code>HTMLDocument(Content, StyleSheet)</code>.
0283 */
0284 public HTMLDocument() {
0285 this (new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet());
0286 }
0287
0288 /**
0289 * Constructs an HTML document with the default content
0290 * storage implementation and the specified style/attribute
0291 * storage mechanism. This is a convenience method for the
0292 * constructor
0293 * <code>HTMLDocument(Content, StyleSheet)</code>.
0294 *
0295 * @param styles the styles
0296 */
0297 public HTMLDocument(StyleSheet styles) {
0298 this (new GapContent(BUFFER_SIZE_DEFAULT), styles);
0299 }
0300
0301 /**
0302 * Constructs an HTML document with the given content
0303 * storage implementation and the given style/attribute
0304 * storage mechanism.
0305 *
0306 * @param c the container for the content
0307 * @param styles the styles
0308 */
0309 public HTMLDocument(Content c, StyleSheet styles) {
0310 super (c, styles);
0311 }
0312
0313 /**
0314 * Fetches the reader for the parser to use when loading the document
0315 * with HTML. This is implemented to return an instance of
0316 * <code>HTMLDocument.HTMLReader</code>.
0317 * Subclasses can reimplement this
0318 * method to change how the document gets structured if desired.
0319 * (For example, to handle custom tags, or structurally represent character
0320 * style elements.)
0321 *
0322 * @param pos the starting position
0323 * @return the reader used by the parser to load the document
0324 */
0325 public HTMLEditorKit.ParserCallback getReader(int pos) {
0326 Object desc = getProperty(Document.StreamDescriptionProperty);
0327 if (desc instanceof URL) {
0328 setBase((URL) desc);
0329 }
0330 HTMLReader reader = new HTMLReader(pos);
0331 return reader;
0332 }
0333
0334 /**
0335 * Returns the reader for the parser to use to load the document
0336 * with HTML. This is implemented to return an instance of
0337 * <code>HTMLDocument.HTMLReader</code>.
0338 * Subclasses can reimplement this
0339 * method to change how the document gets structured if desired.
0340 * (For example, to handle custom tags, or structurally represent character
0341 * style elements.)
0342 * <p>This is a convenience method for
0343 * <code>getReader(int, int, int, HTML.Tag, TRUE)</code>.
0344 *
0345 * @param popDepth the number of <code>ElementSpec.EndTagTypes</code>
0346 * to generate before inserting
0347 * @param pushDepth the number of <code>ElementSpec.StartTagTypes</code>
0348 * with a direction of <code>ElementSpec.JoinNextDirection</code>
0349 * that should be generated before inserting,
0350 * but after the end tags have been generated
0351 * @param insertTag the first tag to start inserting into document
0352 * @return the reader used by the parser to load the document
0353 */
0354 public HTMLEditorKit.ParserCallback getReader(int pos,
0355 int popDepth, int pushDepth, HTML.Tag insertTag) {
0356 return getReader(pos, popDepth, pushDepth, insertTag, true);
0357 }
0358
0359 /**
0360 * Fetches the reader for the parser to use to load the document
0361 * with HTML. This is implemented to return an instance of
0362 * HTMLDocument.HTMLReader. Subclasses can reimplement this
0363 * method to change how the document get structured if desired
0364 * (e.g. to handle custom tags, structurally represent character
0365 * style elements, etc.).
0366 *
0367 * @param popDepth the number of <code>ElementSpec.EndTagTypes</code>
0368 * to generate before inserting
0369 * @param pushDepth the number of <code>ElementSpec.StartTagTypes</code>
0370 * with a direction of <code>ElementSpec.JoinNextDirection</code>
0371 * that should be generated before inserting,
0372 * but after the end tags have been generated
0373 * @param insertTag the first tag to start inserting into document
0374 * @param insertInsertTag false if all the Elements after insertTag should
0375 * be inserted; otherwise insertTag will be inserted
0376 * @return the reader used by the parser to load the document
0377 */
0378 HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
0379 int pushDepth, HTML.Tag insertTag, boolean insertInsertTag) {
0380 Object desc = getProperty(Document.StreamDescriptionProperty);
0381 if (desc instanceof URL) {
0382 setBase((URL) desc);
0383 }
0384 HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth,
0385 insertTag, insertInsertTag, false, true);
0386 return reader;
0387 }
0388
0389 /**
0390 * Returns the location to resolve relative URLs against. By
0391 * default this will be the document's URL if the document
0392 * was loaded from a URL. If a base tag is found and
0393 * can be parsed, it will be used as the base location.
0394 *
0395 * @return the base location
0396 */
0397 public URL getBase() {
0398 return base;
0399 }
0400
0401 /**
0402 * Sets the location to resolve relative URLs against. By
0403 * default this will be the document's URL if the document
0404 * was loaded from a URL. If a base tag is found and
0405 * can be parsed, it will be used as the base location.
0406 * <p>This also sets the base of the <code>StyleSheet</code>
0407 * to be <code>u</code> as well as the base of the document.
0408 *
0409 * @param u the desired base URL
0410 */
0411 public void setBase(URL u) {
0412 base = u;
0413 getStyleSheet().setBase(u);
0414 }
0415
0416 /**
0417 * Inserts new elements in bulk. This is how elements get created
0418 * in the document. The parsing determines what structure is needed
0419 * and creates the specification as a set of tokens that describe the
0420 * edit while leaving the document free of a write-lock. This method
0421 * can then be called in bursts by the reader to acquire a write-lock
0422 * for a shorter duration (i.e. while the document is actually being
0423 * altered).
0424 *
0425 * @param offset the starting offset
0426 * @param data the element data
0427 * @exception BadLocationException if the given position does not
0428 * represent a valid location in the associated document.
0429 */
0430 protected void insert(int offset, ElementSpec[] data)
0431 throws BadLocationException {
0432 super .insert(offset, data);
0433 }
0434
0435 /**
0436 * Updates document structure as a result of text insertion. This
0437 * will happen within a write lock. This implementation simply
0438 * parses the inserted content for line breaks and builds up a set
0439 * of instructions for the element buffer.
0440 *
0441 * @param chng a description of the document change
0442 * @param attr the attributes
0443 */
0444 protected void insertUpdate(DefaultDocumentEvent chng,
0445 AttributeSet attr) {
0446 if (attr == null) {
0447 attr = contentAttributeSet;
0448 }
0449
0450 // If this is the composed text element, merge the content attribute to it
0451 else if (attr.isDefined(StyleConstants.ComposedTextAttribute)) {
0452 ((MutableAttributeSet) attr)
0453 .addAttributes(contentAttributeSet);
0454 }
0455
0456 if (attr.isDefined(IMPLIED_CR)) {
0457 ((MutableAttributeSet) attr).removeAttribute(IMPLIED_CR);
0458 }
0459
0460 super .insertUpdate(chng, attr);
0461 }
0462
0463 /**
0464 * Replaces the contents of the document with the given
0465 * element specifications. This is called before insert if
0466 * the loading is done in bursts. This is the only method called
0467 * if loading the document entirely in one burst.
0468 *
0469 * @param data the new contents of the document
0470 */
0471 protected void create(ElementSpec[] data) {
0472 super .create(data);
0473 }
0474
0475 /**
0476 * Sets attributes for a paragraph.
0477 * <p>
0478 * This method is thread safe, although most Swing methods
0479 * are not. Please see
0480 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0481 * to Use Threads</A> for more information.
0482 *
0483 * @param offset the offset into the paragraph (must be at least 0)
0484 * @param length the number of characters affected (must be at least 0)
0485 * @param s the attributes
0486 * @param replace whether to replace existing attributes, or merge them
0487 */
0488 public void setParagraphAttributes(int offset, int length,
0489 AttributeSet s, boolean replace) {
0490 try {
0491 writeLock();
0492 // Make sure we send out a change for the length of the paragraph.
0493 int end = Math.min(offset + length, getLength());
0494 Element e = getParagraphElement(offset);
0495 offset = e.getStartOffset();
0496 e = getParagraphElement(end);
0497 length = Math.max(0, e.getEndOffset() - offset);
0498 DefaultDocumentEvent changes = new DefaultDocumentEvent(
0499 offset, length, DocumentEvent.EventType.CHANGE);
0500 AttributeSet sCopy = s.copyAttributes();
0501 int lastEnd = Integer.MAX_VALUE;
0502 for (int pos = offset; pos <= end; pos = lastEnd) {
0503 Element paragraph = getParagraphElement(pos);
0504 if (lastEnd == paragraph.getEndOffset()) {
0505 lastEnd++;
0506 } else {
0507 lastEnd = paragraph.getEndOffset();
0508 }
0509 MutableAttributeSet attr = (MutableAttributeSet) paragraph
0510 .getAttributes();
0511 changes.addEdit(new AttributeUndoableEdit(paragraph,
0512 sCopy, replace));
0513 if (replace) {
0514 attr.removeAttributes(attr);
0515 }
0516 attr.addAttributes(s);
0517 }
0518 changes.end();
0519 fireChangedUpdate(changes);
0520 fireUndoableEditUpdate(new UndoableEditEvent(this , changes));
0521 } finally {
0522 writeUnlock();
0523 }
0524 }
0525
0526 /**
0527 * Fetches the <code>StyleSheet</code> with the document-specific display
0528 * rules (CSS) that were specified in the HTML document itself.
0529 *
0530 * @return the <code>StyleSheet</code>
0531 */
0532 public StyleSheet getStyleSheet() {
0533 return (StyleSheet) getAttributeContext();
0534 }
0535
0536 /**
0537 * Fetches an iterator for the specified HTML tag.
0538 * This can be used for things like iterating over the
0539 * set of anchors contained, or iterating over the input
0540 * elements.
0541 *
0542 * @param t the requested <code>HTML.Tag</code>
0543 * @return the <code>Iterator</code> for the given HTML tag
0544 * @see javax.swing.text.html.HTML.Tag
0545 */
0546 public Iterator getIterator(HTML.Tag t) {
0547 if (t.isBlock()) {
0548 // TBD
0549 return null;
0550 }
0551 return new LeafIterator(t, this );
0552 }
0553
0554 /**
0555 * Creates a document leaf element that directly represents
0556 * text (doesn't have any children). This is implemented
0557 * to return an element of type
0558 * <code>HTMLDocument.RunElement</code>.
0559 *
0560 * @param parent the parent element
0561 * @param a the attributes for the element
0562 * @param p0 the beginning of the range (must be at least 0)
0563 * @param p1 the end of the range (must be at least p0)
0564 * @return the new element
0565 */
0566 protected Element createLeafElement(Element parent, AttributeSet a,
0567 int p0, int p1) {
0568 return new RunElement(parent, a, p0, p1);
0569 }
0570
0571 /**
0572 * Creates a document branch element, that can contain other elements.
0573 * This is implemented to return an element of type
0574 * <code>HTMLDocument.BlockElement</code>.
0575 *
0576 * @param parent the parent element
0577 * @param a the attributes
0578 * @return the element
0579 */
0580 protected Element createBranchElement(Element parent, AttributeSet a) {
0581 return new BlockElement(parent, a);
0582 }
0583
0584 /**
0585 * Creates the root element to be used to represent the
0586 * default document structure.
0587 *
0588 * @return the element base
0589 */
0590 protected AbstractElement createDefaultRoot() {
0591 // grabs a write-lock for this initialization and
0592 // abandon it during initialization so in normal
0593 // operation we can detect an illegitimate attempt
0594 // to mutate attributes.
0595 writeLock();
0596 MutableAttributeSet a = new SimpleAttributeSet();
0597 a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.HTML);
0598 BlockElement html = new BlockElement(null, a.copyAttributes());
0599 a.removeAttributes(a);
0600 a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.BODY);
0601 BlockElement body = new BlockElement(html, a.copyAttributes());
0602 a.removeAttributes(a);
0603 a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.P);
0604 getStyleSheet().addCSSAttributeFromHTML(a,
0605 CSS.Attribute.MARGIN_TOP, "0");
0606 BlockElement paragraph = new BlockElement(body, a
0607 .copyAttributes());
0608 a.removeAttributes(a);
0609 a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
0610 RunElement brk = new RunElement(paragraph, a, 0, 1);
0611 Element[] buff = new Element[1];
0612 buff[0] = brk;
0613 paragraph.replace(0, 0, buff);
0614 buff[0] = paragraph;
0615 body.replace(0, 0, buff);
0616 buff[0] = body;
0617 html.replace(0, 0, buff);
0618 writeUnlock();
0619 return html;
0620 }
0621
0622 /**
0623 * Sets the number of tokens to buffer before trying to update
0624 * the documents element structure.
0625 *
0626 * @param n the number of tokens to buffer
0627 */
0628 public void setTokenThreshold(int n) {
0629 putProperty(TokenThreshold, new Integer(n));
0630 }
0631
0632 /**
0633 * Gets the number of tokens to buffer before trying to update
0634 * the documents element structure. The default value is
0635 * <code>Integer.MAX_VALUE</code>.
0636 *
0637 * @return the number of tokens to buffer
0638 */
0639 public int getTokenThreshold() {
0640 Integer i = (Integer) getProperty(TokenThreshold);
0641 if (i != null) {
0642 return i.intValue();
0643 }
0644 return Integer.MAX_VALUE;
0645 }
0646
0647 /**
0648 * Determines how unknown tags are handled by the parser.
0649 * If set to true, unknown
0650 * tags are put in the model, otherwise they are dropped.
0651 *
0652 * @param preservesTags true if unknown tags should be
0653 * saved in the model, otherwise tags are dropped
0654 * @see javax.swing.text.html.HTML.Tag
0655 */
0656 public void setPreservesUnknownTags(boolean preservesTags) {
0657 preservesUnknownTags = preservesTags;
0658 }
0659
0660 /**
0661 * Returns the behavior the parser observes when encountering
0662 * unknown tags.
0663 *
0664 * @see javax.swing.text.html.HTML.Tag
0665 * @return true if unknown tags are to be preserved when parsing
0666 */
0667 public boolean getPreservesUnknownTags() {
0668 return preservesUnknownTags;
0669 }
0670
0671 /**
0672 * Processes <code>HyperlinkEvents</code> that
0673 * are generated by documents in an HTML frame.
0674 * The <code>HyperlinkEvent</code> type, as the parameter suggests,
0675 * is <code>HTMLFrameHyperlinkEvent</code>.
0676 * In addition to the typical information contained in a
0677 * <code>HyperlinkEvent</code>,
0678 * this event contains the element that corresponds to the frame in
0679 * which the click happened (the source element) and the
0680 * target name. The target name has 4 possible values:
0681 * <ul>
0682 * <li> _self
0683 * <li> _parent
0684 * <li> _top
0685 * <li> a named frame
0686 * </ul>
0687 *
0688 * If target is _self, the action is to change the value of the
0689 * <code>HTML.Attribute.SRC</code> attribute and fires a
0690 * <code>ChangedUpdate</code> event.
0691 *<p>
0692 * If the target is _parent, then it deletes the parent element,
0693 * which is a <FRAMESET> element, and inserts a new <FRAME>
0694 * element, and sets its <code>HTML.Attribute.SRC</code> attribute
0695 * to have a value equal to the destination URL and fire a
0696 * <code>RemovedUpdate</code> and <code>InsertUpdate</code>.
0697 *<p>
0698 * If the target is _top, this method does nothing. In the implementation
0699 * of the view for a frame, namely the <code>FrameView</code>,
0700 * the processing of _top is handled. Given that _top implies
0701 * replacing the entire document, it made sense to handle this outside
0702 * of the document that it will replace.
0703 *<p>
0704 * If the target is a named frame, then the element hierarchy is searched
0705 * for an element with a name equal to the target, its
0706 * <code>HTML.Attribute.SRC</code> attribute is updated and a
0707 * <code>ChangedUpdate</code> event is fired.
0708 *
0709 * @param e the event
0710 */
0711 public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent e) {
0712 String frameName = e.getTarget();
0713 Element element = e.getSourceElement();
0714 String urlStr = e.getURL().toString();
0715
0716 if (frameName.equals("_self")) {
0717 /*
0718 The source and destination elements
0719 are the same.
0720 */
0721 updateFrame(element, urlStr);
0722 } else if (frameName.equals("_parent")) {
0723 /*
0724 The destination is the parent of the frame.
0725 */
0726 updateFrameSet(element.getParentElement(), urlStr);
0727 } else {
0728 /*
0729 locate a named frame
0730 */
0731 Element targetElement = findFrame(frameName);
0732 if (targetElement != null) {
0733 updateFrame(targetElement, urlStr);
0734 }
0735 }
0736 }
0737
0738 /**
0739 * Searches the element hierarchy for an FRAME element
0740 * that has its name attribute equal to the <code>frameName</code>.
0741 *
0742 * @param frameName
0743 * @return the element whose NAME attribute has a value of
0744 * <code>frameName</code>; returns <code>null</code>
0745 * if not found
0746 */
0747 private Element findFrame(String frameName) {
0748 ElementIterator it = new ElementIterator(this );
0749 Element next = null;
0750
0751 while ((next = it.next()) != null) {
0752 AttributeSet attr = next.getAttributes();
0753 if (matchNameAttribute(attr, HTML.Tag.FRAME)) {
0754 String frameTarget = (String) attr
0755 .getAttribute(HTML.Attribute.NAME);
0756 if (frameTarget != null
0757 && frameTarget.equals(frameName)) {
0758 break;
0759 }
0760 }
0761 }
0762 return next;
0763 }
0764
0765 /**
0766 * Returns true if <code>StyleConstants.NameAttribute</code> is
0767 * equal to the tag that is passed in as a parameter.
0768 *
0769 * @param attr the attributes to be matched
0770 * @param tag the value to be matched
0771 * @return true if there is a match, false otherwise
0772 * @see javax.swing.text.html.HTML.Attribute
0773 */
0774 static boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
0775 Object o = attr.getAttribute(StyleConstants.NameAttribute);
0776 if (o instanceof HTML.Tag) {
0777 HTML.Tag name = (HTML.Tag) o;
0778 if (name == tag) {
0779 return true;
0780 }
0781 }
0782 return false;
0783 }
0784
0785 /**
0786 * Replaces a frameset branch Element with a frame leaf element.
0787 *
0788 * @param element the frameset element to remove
0789 * @param url the value for the SRC attribute for the
0790 * new frame that will replace the frameset
0791 */
0792 private void updateFrameSet(Element element, String url) {
0793 try {
0794 int startOffset = element.getStartOffset();
0795 int endOffset = Math.min(getLength(), element
0796 .getEndOffset());
0797 String html = "<frame";
0798 if (url != null) {
0799 html += " src=\"" + url + "\"";
0800 }
0801 html += ">";
0802 installParserIfNecessary();
0803 setOuterHTML(element, html);
0804 } catch (BadLocationException e1) {
0805 // Should handle this better
0806 } catch (IOException ioe) {
0807 // Should handle this better
0808 }
0809 }
0810
0811 /**
0812 * Updates the Frame elements <code>HTML.Attribute.SRC attribute</code>
0813 * and fires a <code>ChangedUpdate</code> event.
0814 *
0815 * @param element a FRAME element whose SRC attribute will be updated
0816 * @param url a string specifying the new value for the SRC attribute
0817 */
0818 private void updateFrame(Element element, String url) {
0819
0820 try {
0821 writeLock();
0822 DefaultDocumentEvent changes = new DefaultDocumentEvent(
0823 element.getStartOffset(), 1,
0824 DocumentEvent.EventType.CHANGE);
0825 AttributeSet sCopy = element.getAttributes()
0826 .copyAttributes();
0827 MutableAttributeSet attr = (MutableAttributeSet) element
0828 .getAttributes();
0829 changes.addEdit(new AttributeUndoableEdit(element, sCopy,
0830 false));
0831 attr.removeAttribute(HTML.Attribute.SRC);
0832 attr.addAttribute(HTML.Attribute.SRC, url);
0833 changes.end();
0834 fireChangedUpdate(changes);
0835 fireUndoableEditUpdate(new UndoableEditEvent(this , changes));
0836 } finally {
0837 writeUnlock();
0838 }
0839 }
0840
0841 /**
0842 * Returns true if the document will be viewed in a frame.
0843 * @return true if document will be viewed in a frame, otherwise false
0844 */
0845 boolean isFrameDocument() {
0846 return frameDocument;
0847 }
0848
0849 /**
0850 * Sets a boolean state about whether the document will be
0851 * viewed in a frame.
0852 * @param frameDoc true if the document will be viewed in a frame,
0853 * otherwise false
0854 */
0855 void setFrameDocumentState(boolean frameDoc) {
0856 this .frameDocument = frameDoc;
0857 }
0858
0859 /**
0860 * Adds the specified map, this will remove a Map that has been
0861 * previously registered with the same name.
0862 *
0863 * @param map the <code>Map</code> to be registered
0864 */
0865 void addMap(Map map) {
0866 String name = map.getName();
0867
0868 if (name != null) {
0869 Object maps = getProperty(MAP_PROPERTY);
0870
0871 if (maps == null) {
0872 maps = new Hashtable(11);
0873 putProperty(MAP_PROPERTY, maps);
0874 }
0875 if (maps instanceof Hashtable) {
0876 ((Hashtable) maps).put("#" + name, map);
0877 }
0878 }
0879 }
0880
0881 /**
0882 * Removes a previously registered map.
0883 * @param map the <code>Map</code> to be removed
0884 */
0885 void removeMap(Map map) {
0886 String name = map.getName();
0887
0888 if (name != null) {
0889 Object maps = getProperty(MAP_PROPERTY);
0890
0891 if (maps instanceof Hashtable) {
0892 ((Hashtable) maps).remove("#" + name);
0893 }
0894 }
0895 }
0896
0897 /**
0898 * Returns the Map associated with the given name.
0899 * @param the name of the desired <code>Map</code>
0900 * @return the <code>Map</code> or <code>null</code> if it can't
0901 * be found, or if <code>name</code> is <code>null</code>
0902 */
0903 Map getMap(String name) {
0904 if (name != null) {
0905 Object maps = getProperty(MAP_PROPERTY);
0906
0907 if (maps != null && (maps instanceof Hashtable)) {
0908 return (Map) ((Hashtable) maps).get(name);
0909 }
0910 }
0911 return null;
0912 }
0913
0914 /**
0915 * Returns an <code>Enumeration</code> of the possible Maps.
0916 * @return the enumerated list of maps, or <code>null</code>
0917 * if the maps are not an instance of <code>Hashtable</code>
0918 */
0919 Enumeration getMaps() {
0920 Object maps = getProperty(MAP_PROPERTY);
0921
0922 if (maps instanceof Hashtable) {
0923 return ((Hashtable) maps).elements();
0924 }
0925 return null;
0926 }
0927
0928 /**
0929 * Sets the content type language used for style sheets that do not
0930 * explicitly specify the type. The default is text/css.
0931 * @param contentType the content type language for the style sheets
0932 */
0933 /* public */
0934 void setDefaultStyleSheetType(String contentType) {
0935 putProperty(StyleType, contentType);
0936 }
0937
0938 /**
0939 * Returns the content type language used for style sheets. The default
0940 * is text/css.
0941 * @return the content type language used for the style sheets
0942 */
0943 /* public */
0944 String getDefaultStyleSheetType() {
0945 String retValue = (String) getProperty(StyleType);
0946 if (retValue == null) {
0947 return "text/css";
0948 }
0949 return retValue;
0950 }
0951
0952 /**
0953 * Sets the parser that is used by the methods that insert html
0954 * into the existing document, such as <code>setInnerHTML</code>,
0955 * and <code>setOuterHTML</code>.
0956 * <p>
0957 * <code>HTMLEditorKit.createDefaultDocument</code> will set the parser
0958 * for you. If you create an <code>HTMLDocument</code> by hand,
0959 * be sure and set the parser accordingly.
0960 * @param parser the parser to be used for text insertion
0961 *
0962 * @since 1.3
0963 */
0964 public void setParser(HTMLEditorKit.Parser parser) {
0965 this .parser = parser;
0966 putProperty("__PARSER__", null);
0967 }
0968
0969 /**
0970 * Returns the parser that is used when inserting HTML into the existing
0971 * document.
0972 * @return the parser used for text insertion
0973 *
0974 * @since 1.3
0975 */
0976 public HTMLEditorKit.Parser getParser() {
0977 Object p = getProperty("__PARSER__");
0978
0979 if (p instanceof HTMLEditorKit.Parser) {
0980 return (HTMLEditorKit.Parser) p;
0981 }
0982 return parser;
0983 }
0984
0985 /**
0986 * Replaces the children of the given element with the contents
0987 * specified as an HTML string.
0988 *
0989 * <p>This will be seen as at least two events, n inserts followed by
0990 * a remove.</p>
0991 *
0992 * <p>Consider the following structure (the <code>elem</code>
0993 * parameter is <b>in bold</b>).</p>
0994 *
0995 * <pre>
0996 * <body>
0997 * |
0998 * <b><div></b>
0999 * / \
1000 * <p> <p>
1001 * </pre>
1002 *
1003 * <p>Invoking <code>setInnerHTML(elem, "<ul><li>")</code>
1004 * results in the following structure (new elements are <font
1005 * color="red">in red</font>).</p>
1006 *
1007 * <pre>
1008 * <body>
1009 * |
1010 * <b><div></b>
1011 * \
1012 * <font color="red"><ul></font>
1013 * \
1014 * <font color="red"><li></font>
1015 * </pre>
1016 *
1017 * <p>Parameter <code>elem</code> must not be a leaf element,
1018 * otherwise an <code>IllegalArgumentException</code> is thrown.
1019 * If either <code>elem</code> or <code>htmlText</code> parameter
1020 * is <code>null</code>, no changes are made to the document.</p>
1021 *
1022 * <p>For this to work correcty, the document must have an
1023 * <code>HTMLEditorKit.Parser</code> set. This will be the case
1024 * if the document was created from an HTMLEditorKit via the
1025 * <code>createDefaultDocument</code> method.</p>
1026 *
1027 * @param elem the branch element whose children will be replaced
1028 * @param htmlText the string to be parsed and assigned to <code>elem</code>
1029 * @throws IllegalArgumentException if <code>elem</code> is a leaf
1030 * @throws IllegalStateException if an <code>HTMLEditorKit.Parser</code>
1031 * has not been defined
1032 * @since 1.3
1033 */
1034 public void setInnerHTML(Element elem, String htmlText)
1035 throws BadLocationException, IOException {
1036 verifyParser();
1037 if (elem != null && elem.isLeaf()) {
1038 throw new IllegalArgumentException(
1039 "Can not set inner HTML of a leaf");
1040 }
1041 if (elem != null && htmlText != null) {
1042 int oldCount = elem.getElementCount();
1043 int insertPosition = elem.getStartOffset();
1044 insertHTML(elem, elem.getStartOffset(), htmlText, true);
1045 if (elem.getElementCount() > oldCount) {
1046 // Elements were inserted, do the cleanup.
1047 removeElements(elem, elem.getElementCount() - oldCount,
1048 oldCount);
1049 }
1050 }
1051 }
1052
1053 /**
1054 * Replaces the given element in the parent with the contents
1055 * specified as an HTML string.
1056 *
1057 * <p>This will be seen as at least two events, n inserts followed by
1058 * a remove.</p>
1059 *
1060 * <p>When replacing a leaf this will attempt to make sure there is
1061 * a newline present if one is needed. This may result in an additional
1062 * element being inserted. Consider, if you were to replace a character
1063 * element that contained a newline with <img> this would create
1064 * two elements, one for the image, ane one for the newline.</p>
1065 *
1066 * <p>If you try to replace the element at length you will most
1067 * likely end up with two elements, eg
1068 * <code>setOuterHTML(getCharacterElement (getLength()),
1069 * "blah")</code> will result in two leaf elements at the end, one
1070 * representing 'blah', and the other representing the end
1071 * element.</p>
1072 *
1073 * <p>Consider the following structure (the <code>elem</code>
1074 * parameter is <b>in bold</b>).</p>
1075 *
1076 * <pre>
1077 * <body>
1078 * |
1079 * <b><div></b>
1080 * / \
1081 * <p> <p>
1082 * </pre>
1083 *
1084 * <p>Invoking <code>setOuterHTML(elem, "<ul><li>")</code>
1085 * results in the following structure (new elements are <font
1086 * color="red">in red</font>).</p>
1087 *
1088 * <pre>
1089 * <body>
1090 * |
1091 * <font color="red"><ul></font>
1092 * \
1093 * <font color="red"><li></font>
1094 * </pre>
1095 *
1096 * <p>If either <code>elem</code> or <code>htmlText</code>
1097 * parameter is <code>null</code>, no changes are made to the
1098 * document.</p>
1099 *
1100 * <p>For this to work correcty, the document must have an
1101 * HTMLEditorKit.Parser set. This will be the case if the document
1102 * was created from an HTMLEditorKit via the
1103 * <code>createDefaultDocument</code> method.</p>
1104 *
1105 * @param elem the element to replace
1106 * @param htmlText the string to be parsed and inserted in place of <code>elem</code>
1107 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1108 * been set
1109 * @since 1.3
1110 */
1111 public void setOuterHTML(Element elem, String htmlText)
1112 throws BadLocationException, IOException {
1113 verifyParser();
1114 if (elem != null && elem.getParentElement() != null
1115 && htmlText != null) {
1116 int start = elem.getStartOffset();
1117 int end = elem.getEndOffset();
1118 int startLength = getLength();
1119 // We don't want a newline if elem is a leaf, and doesn't contain
1120 // a newline.
1121 boolean wantsNewline = !elem.isLeaf();
1122 if (!wantsNewline
1123 && (end > startLength || getText(end - 1, 1)
1124 .charAt(0) == NEWLINE[0])) {
1125 wantsNewline = true;
1126 }
1127 Element parent = elem.getParentElement();
1128 int oldCount = parent.getElementCount();
1129 insertHTML(parent, start, htmlText, wantsNewline);
1130 // Remove old.
1131 int newLength = getLength();
1132 if (oldCount != parent.getElementCount()) {
1133 int removeIndex = parent.getElementIndex(start
1134 + newLength - startLength);
1135 removeElements(parent, removeIndex, 1);
1136 }
1137 }
1138 }
1139
1140 /**
1141 * Inserts the HTML specified as a string at the start
1142 * of the element.
1143 *
1144 * <p>Consider the following structure (the <code>elem</code>
1145 * parameter is <b>in bold</b>).</p>
1146 *
1147 * <pre>
1148 * <body>
1149 * |
1150 * <b><div></b>
1151 * / \
1152 * <p> <p>
1153 * </pre>
1154 *
1155 * <p>Invoking <code>insertAfterStart(elem,
1156 * "<ul><li>")</code> results in the following structure
1157 * (new elements are <font color="red">in red</font>).</p>
1158 *
1159 * <pre>
1160 * <body>
1161 * |
1162 * <b><div></b>
1163 * / | \
1164 * <font color="red"><ul></font> <p> <p>
1165 * /
1166 * <font color="red"><li></font>
1167 * </pre>
1168 *
1169 * <p>Unlike the <code>insertBeforeStart</code> method, new
1170 * elements become <em>children</em> of the specified element,
1171 * not siblings.</p>
1172 *
1173 * <p>Parameter <code>elem</code> must not be a leaf element,
1174 * otherwise an <code>IllegalArgumentException</code> is thrown.
1175 * If either <code>elem</code> or <code>htmlText</code> parameter
1176 * is <code>null</code>, no changes are made to the document.</p>
1177 *
1178 * <p>For this to work correcty, the document must have an
1179 * <code>HTMLEditorKit.Parser</code> set. This will be the case
1180 * if the document was created from an HTMLEditorKit via the
1181 * <code>createDefaultDocument</code> method.</p>
1182 *
1183 * @param elem the branch element to be the root for the new text
1184 * @param htmlText the string to be parsed and assigned to <code>elem</code>
1185 * @throws IllegalArgumentException if <code>elem</code> is a leaf
1186 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1187 * been set on the document
1188 * @since 1.3
1189 */
1190 public void insertAfterStart(Element elem, String htmlText)
1191 throws BadLocationException, IOException {
1192 verifyParser();
1193 if (elem != null && elem.isLeaf()) {
1194 throw new IllegalArgumentException(
1195 "Can not insert HTML after start of a leaf");
1196 }
1197 insertHTML(elem, elem.getStartOffset(), htmlText, false);
1198 }
1199
1200 /**
1201 * Inserts the HTML specified as a string at the end of
1202 * the element.
1203 *
1204 * <p> If <code>elem</code>'s children are leaves, and the
1205 * character at a <code>elem.getEndOffset() - 1</code> is a newline,
1206 * this will insert before the newline so that there isn't text after
1207 * the newline.</p>
1208 *
1209 * <p>Consider the following structure (the <code>elem</code>
1210 * parameter is <b>in bold</b>).</p>
1211 *
1212 * <pre>
1213 * <body>
1214 * |
1215 * <b><div></b>
1216 * / \
1217 * <p> <p>
1218 * </pre>
1219 *
1220 * <p>Invoking <code>insertBeforeEnd(elem, "<ul><li>")</code>
1221 * results in the following structure (new elements are <font
1222 * color="red">in red</font>).</p>
1223 *
1224 * <pre>
1225 * <body>
1226 * |
1227 * <b><div></b>
1228 * / | \
1229 * <p> <p> <font color="red"><ul></font>
1230 * \
1231 * <font color="red"><li></font>
1232 * </pre>
1233 *
1234 * <p>Unlike the <code>insertAfterEnd</code> method, new elements
1235 * become <em>children</em> of the specified element, not
1236 * siblings.</p>
1237 *
1238 * <p>Parameter <code>elem</code> must not be a leaf element,
1239 * otherwise an <code>IllegalArgumentException</code> is thrown.
1240 * If either <code>elem</code> or <code>htmlText</code> parameter
1241 * is <code>null</code>, no changes are made to the document.</p>
1242 *
1243 * <p>For this to work correcty, the document must have an
1244 * <code>HTMLEditorKit.Parser</code> set. This will be the case
1245 * if the document was created from an HTMLEditorKit via the
1246 * <code>createDefaultDocument</code> method.</p>
1247 *
1248 * @param elem the element to be the root for the new text
1249 * @param htmlText the string to be parsed and assigned to <code>elem</code>
1250 * @throws IllegalArgumentException if <code>elem</code> is a leaf
1251 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1252 * been set on the document
1253 * @since 1.3
1254 */
1255 public void insertBeforeEnd(Element elem, String htmlText)
1256 throws BadLocationException, IOException {
1257 verifyParser();
1258 if (elem != null && elem.isLeaf()) {
1259 throw new IllegalArgumentException(
1260 "Can not set inner HTML before end of leaf");
1261 }
1262 if (elem != null) {
1263 int offset = elem.getEndOffset();
1264 if (elem.getElement(elem.getElementIndex(offset - 1))
1265 .isLeaf()
1266 && getText(offset - 1, 1).charAt(0) == NEWLINE[0]) {
1267 offset--;
1268 }
1269 insertHTML(elem, offset, htmlText, false);
1270 }
1271 }
1272
1273 /**
1274 * Inserts the HTML specified as a string before the start of
1275 * the given element.
1276 *
1277 * <p>Consider the following structure (the <code>elem</code>
1278 * parameter is <b>in bold</b>).</p>
1279 *
1280 * <pre>
1281 * <body>
1282 * |
1283 * <b><div></b>
1284 * / \
1285 * <p> <p>
1286 * </pre>
1287 *
1288 * <p>Invoking <code>insertBeforeStart(elem,
1289 * "<ul><li>")</code> results in the following structure
1290 * (new elements are <font color="red">in red</font>).</p>
1291 *
1292 * <pre>
1293 * <body>
1294 * / \
1295 * <font color="red"><ul></font> <b><div></b>
1296 * / / \
1297 * <font color="red"><li></font> <p> <p>
1298 * </pre>
1299 *
1300 * <p>Unlike the <code>insertAfterStart</code> method, new
1301 * elements become <em>siblings</em> of the specified element, not
1302 * children.</p>
1303 *
1304 * <p>If either <code>elem</code> or <code>htmlText</code>
1305 * parameter is <code>null</code>, no changes are made to the
1306 * document.</p>
1307 *
1308 * <p>For this to work correcty, the document must have an
1309 * <code>HTMLEditorKit.Parser</code> set. This will be the case
1310 * if the document was created from an HTMLEditorKit via the
1311 * <code>createDefaultDocument</code> method.</p>
1312 *
1313 * @param elem the element the content is inserted before
1314 * @param htmlText the string to be parsed and inserted before <code>elem</code>
1315 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1316 * been set on the document
1317 * @since 1.3
1318 */
1319 public void insertBeforeStart(Element elem, String htmlText)
1320 throws BadLocationException, IOException {
1321 verifyParser();
1322 if (elem != null) {
1323 Element parent = elem.getParentElement();
1324
1325 if (parent != null) {
1326 insertHTML(parent, elem.getStartOffset(), htmlText,
1327 false);
1328 }
1329 }
1330 }
1331
1332 /**
1333 * Inserts the HTML specified as a string after the the end of the
1334 * given element.
1335 *
1336 * <p>Consider the following structure (the <code>elem</code>
1337 * parameter is <b>in bold</b>).</p>
1338 *
1339 * <pre>
1340 * <body>
1341 * |
1342 * <b><div></b>
1343 * / \
1344 * <p> <p>
1345 * </pre>
1346 *
1347 * <p>Invoking <code>insertAfterEnd(elem, "<ul><li>")</code>
1348 * results in the following structure (new elements are <font
1349 * color="red">in red</font>).</p>
1350 *
1351 * <pre>
1352 * <body>
1353 * / \
1354 * <b><div></b> <font color="red"><ul></font>
1355 * / \ \
1356 * <p> <p> <font color="red"><li></font>
1357 * </pre>
1358 *
1359 * <p>Unlike the <code>insertBeforeEnd</code> method, new elements
1360 * become <em>siblings</em> of the specified element, not
1361 * children.</p>
1362 *
1363 * <p>If either <code>elem</code> or <code>htmlText</code>
1364 * parameter is <code>null</code>, no changes are made to the
1365 * document.</p>
1366 *
1367 * <p>For this to work correcty, the document must have an
1368 * <code>HTMLEditorKit.Parser</code> set. This will be the case
1369 * if the document was created from an HTMLEditorKit via the
1370 * <code>createDefaultDocument</code> method.</p>
1371 *
1372 * @param elem the element the content is inserted after
1373 * @param htmlText the string to be parsed and inserted after <code>elem</code>
1374 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1375 * been set on the document
1376 * @since 1.3
1377 */
1378 public void insertAfterEnd(Element elem, String htmlText)
1379 throws BadLocationException, IOException {
1380 verifyParser();
1381 if (elem != null) {
1382 Element parent = elem.getParentElement();
1383
1384 if (parent != null) {
1385 int offset = elem.getEndOffset();
1386 if (offset > getLength()) {
1387 offset--;
1388 } else if (elem.isLeaf()
1389 && getText(offset - 1, 1).charAt(0) == NEWLINE[0]) {
1390 offset--;
1391 }
1392 insertHTML(parent, offset, htmlText, false);
1393 }
1394 }
1395 }
1396
1397 /**
1398 * Returns the element that has the given id <code>Attribute</code>.
1399 * If the element can't be found, <code>null</code> is returned.
1400 * Note that this method works on an <code>Attribute</code>,
1401 * <i>not</i> a character tag. In the following HTML snippet:
1402 * <code><a id="HelloThere"></code> the attribute is
1403 * 'id' and the character tag is 'a'.
1404 * This is a convenience method for
1405 * <code>getElement(RootElement, HTML.Attribute.id, id)</code>.
1406 * This is not thread-safe.
1407 *
1408 * @param id the string representing the desired <code>Attribute</code>
1409 * @return the element with the specified <code>Attribute</code>
1410 * or <code>null</code> if it can't be found,
1411 * or <code>null</code> if <code>id</code> is <code>null</code>
1412 * @see javax.swing.text.html.HTML.Attribute
1413 * @since 1.3
1414 */
1415 public Element getElement(String id) {
1416 if (id == null) {
1417 return null;
1418 }
1419 return getElement(getDefaultRootElement(), HTML.Attribute.ID,
1420 id, true);
1421 }
1422
1423 /**
1424 * Returns the child element of <code>e</code> that contains the
1425 * attribute, <code>attribute</code> with value <code>value</code>, or
1426 * <code>null</code> if one isn't found. This is not thread-safe.
1427 *
1428 * @param e the root element where the search begins
1429 * @param attribute the desired <code>Attribute</code>
1430 * @param value the values for the specified <code>Attribute</code>
1431 * @return the element with the specified <code>Attribute</code>
1432 * and the specified <code>value</code>, or <code>null</code>
1433 * if it can't be found
1434 * @see javax.swing.text.html.HTML.Attribute
1435 * @since 1.3
1436 */
1437 public Element getElement(Element e, Object attribute, Object value) {
1438 return getElement(e, attribute, value, true);
1439 }
1440
1441 /**
1442 * Returns the child element of <code>e</code> that contains the
1443 * attribute, <code>attribute</code> with value <code>value</code>, or
1444 * <code>null</code> if one isn't found. This is not thread-safe.
1445 * <p>
1446 * If <code>searchLeafAttributes</code> is true, and <code>e</code> is
1447 * a leaf, any attributes that are instances of <code>HTML.Tag</code>
1448 * with a value that is an <code>AttributeSet</code> will also be checked.
1449 *
1450 * @param e the root element where the search begins
1451 * @param attribute the desired <code>Attribute</code>
1452 * @param value the values for the specified <code>Attribute</code>
1453 * @return the element with the specified <code>Attribute</code>
1454 * and the specified <code>value</code>, or <code>null</code>
1455 * if it can't be found
1456 * @see javax.swing.text.html.HTML.Attribute
1457 */
1458 private Element getElement(Element e, Object attribute,
1459 Object value, boolean searchLeafAttributes) {
1460 AttributeSet attr = e.getAttributes();
1461
1462 if (attr != null && attr.isDefined(attribute)) {
1463 if (value.equals(attr.getAttribute(attribute))) {
1464 return e;
1465 }
1466 }
1467 if (!e.isLeaf()) {
1468 for (int counter = 0, maxCounter = e.getElementCount(); counter < maxCounter; counter++) {
1469 Element retValue = getElement(e.getElement(counter),
1470 attribute, value, searchLeafAttributes);
1471
1472 if (retValue != null) {
1473 return retValue;
1474 }
1475 }
1476 } else if (searchLeafAttributes && attr != null) {
1477 // For some leaf elements we store the actual attributes inside
1478 // the AttributeSet of the Element (such as anchors).
1479 Enumeration names = attr.getAttributeNames();
1480 if (names != null) {
1481 while (names.hasMoreElements()) {
1482 Object name = names.nextElement();
1483 if ((name instanceof HTML.Tag)
1484 && (attr.getAttribute(name) instanceof AttributeSet)) {
1485
1486 AttributeSet check = (AttributeSet) attr
1487 .getAttribute(name);
1488 if (check.isDefined(attribute)
1489 && value.equals(check
1490 .getAttribute(attribute))) {
1491 return e;
1492 }
1493 }
1494 }
1495 }
1496 }
1497 return null;
1498 }
1499
1500 /**
1501 * Verifies the document has an <code>HTMLEditorKit.Parser</code> set.
1502 * If <code>getParser</code> returns <code>null</code>, this will throw an
1503 * IllegalStateException.
1504 *
1505 * @throws IllegalStateException if the document does not have a Parser
1506 */
1507 private void verifyParser() {
1508 if (getParser() == null) {
1509 throw new IllegalStateException("No HTMLEditorKit.Parser");
1510 }
1511 }
1512
1513 /**
1514 * Installs a default Parser if one has not been installed yet.
1515 */
1516 private void installParserIfNecessary() {
1517 if (getParser() == null) {
1518 setParser(new HTMLEditorKit().getParser());
1519 }
1520 }
1521
1522 /**
1523 * Inserts a string of HTML into the document at the given position.
1524 * <code>parent</code> is used to identify the location to insert the
1525 * <code>html</code>. If <code>parent</code> is a leaf this can have
1526 * unexpected results.
1527 */
1528 private void insertHTML(Element parent, int offset, String html,
1529 boolean wantsTrailingNewline) throws BadLocationException,
1530 IOException {
1531 if (parent != null && html != null) {
1532 HTMLEditorKit.Parser parser = getParser();
1533 if (parser != null) {
1534 int lastOffset = Math.max(0, offset - 1);
1535 Element charElement = getCharacterElement(lastOffset);
1536 Element commonParent = parent;
1537 int pop = 0;
1538 int push = 0;
1539
1540 if (parent.getStartOffset() > lastOffset) {
1541 while (commonParent != null
1542 && commonParent.getStartOffset() > lastOffset) {
1543 commonParent = commonParent.getParentElement();
1544 push++;
1545 }
1546 if (commonParent == null) {
1547 throw new BadLocationException(
1548 "No common parent", offset);
1549 }
1550 }
1551 while (charElement != null
1552 && charElement != commonParent) {
1553 pop++;
1554 charElement = charElement.getParentElement();
1555 }
1556 if (charElement != null) {
1557 // Found it, do the insert.
1558 HTMLReader reader = new HTMLReader(offset, pop - 1,
1559 push, null, false, true,
1560 wantsTrailingNewline);
1561
1562 parser.parse(new StringReader(html), reader, true);
1563 reader.flush();
1564 }
1565 }
1566 }
1567 }
1568
1569 /**
1570 * Removes child Elements of the passed in Element <code>e</code>. This
1571 * will do the necessary cleanup to ensure the element representing the
1572 * end character is correctly created.
1573 * <p>This is not a general purpose method, it assumes that <code>e</code>
1574 * will still have at least one child after the remove, and it assumes
1575 * the character at <code>e.getStartOffset() - 1</code> is a newline and
1576 * is of length 1.
1577 */
1578 private void removeElements(Element e, int index, int count)
1579 throws BadLocationException {
1580 writeLock();
1581 try {
1582 int start = e.getElement(index).getStartOffset();
1583 int end = e.getElement(index + count - 1).getEndOffset();
1584 if (end > getLength()) {
1585 removeElementsAtEnd(e, index, count, start, end);
1586 } else {
1587 removeElements(e, index, count, start, end);
1588 }
1589 } finally {
1590 writeUnlock();
1591 }
1592 }
1593
1594 /**
1595 * Called to remove child elements of <code>e</code> when one of the
1596 * elements to remove is representing the end character.
1597 * <p>Since the Content will not allow a removal to the end character
1598 * this will do a remove from <code>start - 1</code> to <code>end</code>.
1599 * The end Element(s) will be removed, and the element representing
1600 * <code>start - 1</code> to <code>start</code> will be recreated. This
1601 * Element has to be recreated as after the content removal its offsets
1602 * become <code>start - 1</code> to <code>start - 1</code>.
1603 */
1604 private void removeElementsAtEnd(Element e, int index, int count,
1605 int start, int end) throws BadLocationException {
1606 // index must be > 0 otherwise no insert would have happened.
1607 boolean isLeaf = (e.getElement(index - 1).isLeaf());
1608 DefaultDocumentEvent dde = new DefaultDocumentEvent(start - 1,
1609 end - start + 1, DocumentEvent.EventType.REMOVE);
1610
1611 if (isLeaf) {
1612 Element endE = getCharacterElement(getLength());
1613 // e.getElement(index - 1) should represent the newline.
1614 index--;
1615 if (endE.getParentElement() != e) {
1616 // The hiearchies don't match, we'll have to manually
1617 // recreate the leaf at e.getElement(index - 1)
1618 replace(dde, e, index, ++count, start, end, true, true);
1619 } else {
1620 // The hierarchies for the end Element and
1621 // e.getElement(index - 1), match, we can safely remove
1622 // the Elements and the end content will be aligned
1623 // appropriately.
1624 replace(dde, e, index, count, start, end, true, false);
1625 }
1626 } else {
1627 // Not a leaf, descend until we find the leaf representing
1628 // start - 1 and remove it.
1629 Element newLineE = e.getElement(index - 1);
1630 while (!newLineE.isLeaf()) {
1631 newLineE = newLineE.getElement(newLineE
1632 .getElementCount() - 1);
1633 }
1634 newLineE = newLineE.getParentElement();
1635 replace(dde, e, index, count, start, end, false, false);
1636 replace(dde, newLineE, newLineE.getElementCount() - 1, 1,
1637 start, end, true, true);
1638 }
1639 postRemoveUpdate(dde);
1640 dde.end();
1641 fireRemoveUpdate(dde);
1642 fireUndoableEditUpdate(new UndoableEditEvent(this , dde));
1643 }
1644
1645 /**
1646 * This is used by <code>removeElementsAtEnd</code>, it removes
1647 * <code>count</code> elements starting at <code>start</code> from
1648 * <code>e</code>. If <code>remove</code> is true text of length
1649 * <code>start - 1</code> to <code>end - 1</code> is removed. If
1650 * <code>create</code> is true a new leaf is created of length 1.
1651 */
1652 private void replace(DefaultDocumentEvent dde, Element e,
1653 int index, int count, int start, int end, boolean remove,
1654 boolean create) throws BadLocationException {
1655 Element[] added;
1656 AttributeSet attrs = e.getElement(index).getAttributes();
1657 Element[] removed = new Element[count];
1658
1659 for (int counter = 0; counter < count; counter++) {
1660 removed[counter] = e.getElement(counter + index);
1661 }
1662 if (remove) {
1663 UndoableEdit u = getContent()
1664 .remove(start - 1, end - start);
1665 if (u != null) {
1666 dde.addEdit(u);
1667 }
1668 }
1669 if (create) {
1670 added = new Element[1];
1671 added[0] = createLeafElement(e, attrs, start - 1, start);
1672 } else {
1673 added = new Element[0];
1674 }
1675 dde.addEdit(new ElementEdit(e, index, removed, added));
1676 ((AbstractDocument.BranchElement) e).replace(index,
1677 removed.length, added);
1678 }
1679
1680 /**
1681 * Called to remove child Elements when the end is not touched.
1682 */
1683 private void removeElements(Element e, int index, int count,
1684 int start, int end) throws BadLocationException {
1685 Element[] removed = new Element[count];
1686 Element[] added = new Element[0];
1687 for (int counter = 0; counter < count; counter++) {
1688 removed[counter] = e.getElement(counter + index);
1689 }
1690 DefaultDocumentEvent dde = new DefaultDocumentEvent(start, end
1691 - start, DocumentEvent.EventType.REMOVE);
1692 ((AbstractDocument.BranchElement) e).replace(index,
1693 removed.length, added);
1694 dde.addEdit(new ElementEdit(e, index, removed, added));
1695 UndoableEdit u = getContent().remove(start, end - start);
1696 if (u != null) {
1697 dde.addEdit(u);
1698 }
1699 postRemoveUpdate(dde);
1700 dde.end();
1701 fireRemoveUpdate(dde);
1702 if (u != null) {
1703 fireUndoableEditUpdate(new UndoableEditEvent(this , dde));
1704 }
1705 }
1706
1707 // These two are provided for inner class access. The are named different
1708 // than the super class as the super class implementations are final.
1709 void obtainLock() {
1710 writeLock();
1711 }
1712
1713 void releaseLock() {
1714 writeUnlock();
1715 }
1716
1717 //
1718 // Provided for inner class access.
1719 //
1720
1721 /**
1722 * Notifies all listeners that have registered interest for
1723 * notification on this event type. The event instance
1724 * is lazily created using the parameters passed into
1725 * the fire method.
1726 *
1727 * @param e the event
1728 * @see EventListenerList
1729 */
1730 protected void fireChangedUpdate(DocumentEvent e) {
1731 super .fireChangedUpdate(e);
1732 }
1733
1734 /**
1735 * Notifies all listeners that have registered interest for
1736 * notification on this event type. The event instance
1737 * is lazily created using the parameters passed into
1738 * the fire method.
1739 *
1740 * @param e the event
1741 * @see EventListenerList
1742 */
1743 protected void fireUndoableEditUpdate(UndoableEditEvent e) {
1744 super .fireUndoableEditUpdate(e);
1745 }
1746
1747 boolean hasBaseTag() {
1748 return hasBaseTag;
1749 }
1750
1751 String getBaseTarget() {
1752 return baseTarget;
1753 }
1754
1755 /*
1756 * state defines whether the document is a frame document
1757 * or not.
1758 */
1759 private boolean frameDocument = false;
1760 private boolean preservesUnknownTags = true;
1761
1762 /*
1763 * Used to store button groups for radio buttons in
1764 * a form.
1765 */
1766 private HashMap radioButtonGroupsMap;
1767
1768 /**
1769 * Document property for the number of tokens to buffer
1770 * before building an element subtree to represent them.
1771 */
1772 static final String TokenThreshold = "token threshold";
1773
1774 private static final int MaxThreshold = 10000;
1775
1776 private static final int StepThreshold = 5;
1777
1778 /**
1779 * Document property key value. The value for the key will be a Vector
1780 * of Strings that are comments not found in the body.
1781 */
1782 public static final String AdditionalComments = "AdditionalComments";
1783
1784 /**
1785 * Document property key value. The value for the key will be a
1786 * String indicating the default type of stylesheet links.
1787 */
1788 /* public */static final String StyleType = "StyleType";
1789
1790 /**
1791 * The location to resolve relative URLs against. By
1792 * default this will be the document's URL if the document
1793 * was loaded from a URL. If a base tag is found and
1794 * can be parsed, it will be used as the base location.
1795 */
1796 URL base;
1797
1798 /**
1799 * does the document have base tag
1800 */
1801 boolean hasBaseTag = false;
1802
1803 /**
1804 * BASE tag's TARGET attribute value
1805 */
1806 private String baseTarget = null;
1807
1808 /**
1809 * The parser that is used when inserting html into the existing
1810 * document.
1811 */
1812 private HTMLEditorKit.Parser parser;
1813
1814 /**
1815 * Used for inserts when a null AttributeSet is supplied.
1816 */
1817 private static AttributeSet contentAttributeSet;
1818
1819 /**
1820 * Property Maps are registered under, will be a Hashtable.
1821 */
1822 static String MAP_PROPERTY = "__MAP__";
1823
1824 private static char[] NEWLINE;
1825 private static final String IMPLIED_CR = "CR";
1826
1827 /**
1828 * I18N property key.
1829 *
1830 * @see AbstractDocument.I18NProperty
1831 */
1832 private static final String I18NProperty = "i18n";
1833
1834 static {
1835 contentAttributeSet = new SimpleAttributeSet();
1836 ((MutableAttributeSet) contentAttributeSet).addAttribute(
1837 StyleConstants.NameAttribute, HTML.Tag.CONTENT);
1838 NEWLINE = new char[1];
1839 NEWLINE[0] = '\n';
1840 }
1841
1842 /**
1843 * An iterator to iterate over a particular type of
1844 * tag. The iterator is not thread safe. If reliable
1845 * access to the document is not already ensured by
1846 * the context under which the iterator is being used,
1847 * its use should be performed under the protection of
1848 * Document.render.
1849 */
1850 public static abstract class Iterator {
1851
1852 /**
1853 * Return the attributes for this tag.
1854 * @return the <code>AttributeSet</code> for this tag, or
1855 * <code>null</code> if none can be found
1856 */
1857 public abstract AttributeSet getAttributes();
1858
1859 /**
1860 * Returns the start of the range for which the current occurrence of
1861 * the tag is defined and has the same attributes.
1862 *
1863 * @return the start of the range, or -1 if it can't be found
1864 */
1865 public abstract int getStartOffset();
1866
1867 /**
1868 * Returns the end of the range for which the current occurrence of
1869 * the tag is defined and has the same attributes.
1870 *
1871 * @return the end of the range
1872 */
1873 public abstract int getEndOffset();
1874
1875 /**
1876 * Move the iterator forward to the next occurrence
1877 * of the tag it represents.
1878 */
1879 public abstract void next();
1880
1881 /**
1882 * Indicates if the iterator is currently
1883 * representing an occurrence of a tag. If
1884 * false there are no more tags for this iterator.
1885 * @return true if the iterator is currently representing an
1886 * occurrence of a tag, otherwise returns false
1887 */
1888 public abstract boolean isValid();
1889
1890 /**
1891 * Type of tag this iterator represents.
1892 */
1893 public abstract HTML.Tag getTag();
1894 }
1895
1896 /**
1897 * An iterator to iterate over a particular type of tag.
1898 */
1899 static class LeafIterator extends Iterator {
1900
1901 LeafIterator(HTML.Tag t, Document doc) {
1902 tag = t;
1903 pos = new ElementIterator(doc);
1904 endOffset = 0;
1905 next();
1906 }
1907
1908 /**
1909 * Returns the attributes for this tag.
1910 * @return the <code>AttributeSet</code> for this tag,
1911 * or <code>null</code> if none can be found
1912 */
1913 public AttributeSet getAttributes() {
1914 Element elem = pos.current();
1915 if (elem != null) {
1916 AttributeSet a = (AttributeSet) elem.getAttributes()
1917 .getAttribute(tag);
1918 if (a == null) {
1919 a = (AttributeSet) elem.getAttributes();
1920 }
1921 return a;
1922 }
1923 return null;
1924 }
1925
1926 /**
1927 * Returns the start of the range for which the current occurrence of
1928 * the tag is defined and has the same attributes.
1929 *
1930 * @return the start of the range, or -1 if it can't be found
1931 */
1932 public int getStartOffset() {
1933 Element elem = pos.current();
1934 if (elem != null) {
1935 return elem.getStartOffset();
1936 }
1937 return -1;
1938 }
1939
1940 /**
1941 * Returns the end of the range for which the current occurrence of
1942 * the tag is defined and has the same attributes.
1943 *
1944 * @return the end of the range
1945 */
1946 public int getEndOffset() {
1947 return endOffset;
1948 }
1949
1950 /**
1951 * Moves the iterator forward to the next occurrence
1952 * of the tag it represents.
1953 */
1954 public void next() {
1955 for (nextLeaf(pos); isValid(); nextLeaf(pos)) {
1956 Element elem = pos.current();
1957 if (elem.getStartOffset() >= endOffset) {
1958 AttributeSet a = pos.current().getAttributes();
1959
1960 if (a.isDefined(tag)
1961 || a
1962 .getAttribute(StyleConstants.NameAttribute) == tag) {
1963
1964 // we found the next one
1965 setEndOffset();
1966 break;
1967 }
1968 }
1969 }
1970 }
1971
1972 /**
1973 * Returns the type of tag this iterator represents.
1974 *
1975 * @return the <code>HTML.Tag</code> that this iterator represents.
1976 * @see javax.swing.text.html.HTML.Tag
1977 */
1978 public HTML.Tag getTag() {
1979 return tag;
1980 }
1981
1982 /**
1983 * Returns true if the current position is not <code>null</code>.
1984 * @return true if current position is not <code>null</code>,
1985 * otherwise returns false
1986 */
1987 public boolean isValid() {
1988 return (pos.current() != null);
1989 }
1990
1991 /**
1992 * Moves the given iterator to the next leaf element.
1993 * @param iter the iterator to be scanned
1994 */
1995 void nextLeaf(ElementIterator iter) {
1996 for (iter.next(); iter.current() != null; iter.next()) {
1997 Element e = iter.current();
1998 if (e.isLeaf()) {
1999 break;
2000 }
2001 }
2002 }
2003
2004 /**
2005 * Marches a cloned iterator forward to locate the end
2006 * of the run. This sets the value of <code>endOffset</code>.
2007 */
2008 void setEndOffset() {
2009 AttributeSet a0 = getAttributes();
2010 endOffset = pos.current().getEndOffset();
2011 ElementIterator fwd = (ElementIterator) pos.clone();
2012 for (nextLeaf(fwd); fwd.current() != null; nextLeaf(fwd)) {
2013 Element e = fwd.current();
2014 AttributeSet a1 = (AttributeSet) e.getAttributes()
2015 .getAttribute(tag);
2016 if ((a1 == null) || (!a1.equals(a0))) {
2017 break;
2018 }
2019 endOffset = e.getEndOffset();
2020 }
2021 }
2022
2023 private int endOffset;
2024 private HTML.Tag tag;
2025 private ElementIterator pos;
2026
2027 }
2028
2029 /**
2030 * An HTML reader to load an HTML document with an HTML
2031 * element structure. This is a set of callbacks from
2032 * the parser, implemented to create a set of elements
2033 * tagged with attributes. The parse builds up tokens
2034 * (ElementSpec) that describe the element subtree desired,
2035 * and burst it into the document under the protection of
2036 * a write lock using the insert method on the document
2037 * outer class.
2038 * <p>
2039 * The reader can be configured by registering actions
2040 * (of type <code>HTMLDocument.HTMLReader.TagAction</code>)
2041 * that describe how to handle the action. The idea behind
2042 * the actions provided is that the most natural text editing
2043 * operations can be provided if the element structure boils
2044 * down to paragraphs with runs of some kind of style
2045 * in them. Some things are more naturally specified
2046 * structurally, so arbitrary structure should be allowed
2047 * above the paragraphs, but will need to be edited with structural
2048 * actions. The implication of this is that some of the
2049 * HTML elements specified in the stream being parsed will
2050 * be collapsed into attributes, and in some cases paragraphs
2051 * will be synthesized. When HTML elements have been
2052 * converted to attributes, the attribute key will be of
2053 * type HTML.Tag, and the value will be of type AttributeSet
2054 * so that no information is lost. This enables many of the
2055 * existing actions to work so that the user can type input,
2056 * hit the return key, backspace, delete, etc and have a
2057 * reasonable result. Selections can be created, and attributes
2058 * applied or removed, etc. With this in mind, the work done
2059 * by the reader can be categorized into the following kinds
2060 * of tasks:
2061 * <dl>
2062 * <dt>Block
2063 * <dd>Build the structure like it's specified in the stream.
2064 * This produces elements that contain other elements.
2065 * <dt>Paragraph
2066 * <dd>Like block except that it's expected that the element
2067 * will be used with a paragraph view so a paragraph element
2068 * won't need to be synthesized.
2069 * <dt>Character
2070 * <dd>Contribute the element as an attribute that will start
2071 * and stop at arbitrary text locations. This will ultimately
2072 * be mixed into a run of text, with all of the currently
2073 * flattened HTML character elements.
2074 * <dt>Special
2075 * <dd>Produce an embedded graphical element.
2076 * <dt>Form
2077 * <dd>Produce an element that is like the embedded graphical
2078 * element, except that it also has a component model associated
2079 * with it.
2080 * <dt>Hidden
2081 * <dd>Create an element that is hidden from view when the
2082 * document is being viewed read-only, and visible when the
2083 * document is being edited. This is useful to keep the
2084 * model from losing information, and used to store things
2085 * like comments and unrecognized tags.
2086 *
2087 * </dl>
2088 * <p>
2089 * Currently, <APPLET>, <PARAM>, <MAP>, <AREA>, <LINK>,
2090 * <SCRIPT> and <STYLE> are unsupported.
2091 *
2092 * <p>
2093 * The assignment of the actions described is shown in the
2094 * following table for the tags defined in <code>HTML.Tag</code>.<P>
2095 * <table border=1 summary="HTML tags and assigned actions">
2096 * <tr><th>Tag</th><th>Action</th></tr>
2097 * <tr><td><code>HTML.Tag.A</code> <td>CharacterAction
2098 * <tr><td><code>HTML.Tag.ADDRESS</code> <td>CharacterAction
2099 * <tr><td><code>HTML.Tag.APPLET</code> <td>HiddenAction
2100 * <tr><td><code>HTML.Tag.AREA</code> <td>AreaAction
2101 * <tr><td><code>HTML.Tag.B</code> <td>CharacterAction
2102 * <tr><td><code>HTML.Tag.BASE</code> <td>BaseAction
2103 * <tr><td><code>HTML.Tag.BASEFONT</code> <td>CharacterAction
2104 * <tr><td><code>HTML.Tag.BIG</code> <td>CharacterAction
2105 * <tr><td><code>HTML.Tag.BLOCKQUOTE</code><td>BlockAction
2106 * <tr><td><code>HTML.Tag.BODY</code> <td>BlockAction
2107 * <tr><td><code>HTML.Tag.BR</code> <td>SpecialAction
2108 * <tr><td><code>HTML.Tag.CAPTION</code> <td>BlockAction
2109 * <tr><td><code>HTML.Tag.CENTER</code> <td>BlockAction
2110 * <tr><td><code>HTML.Tag.CITE</code> <td>CharacterAction
2111 * <tr><td><code>HTML.Tag.CODE</code> <td>CharacterAction
2112 * <tr><td><code>HTML.Tag.DD</code> <td>BlockAction
2113 * <tr><td><code>HTML.Tag.DFN</code> <td>CharacterAction
2114 * <tr><td><code>HTML.Tag.DIR</code> <td>BlockAction
2115 * <tr><td><code>HTML.Tag.DIV</code> <td>BlockAction
2116 * <tr><td><code>HTML.Tag.DL</code> <td>BlockAction
2117 * <tr><td><code>HTML.Tag.DT</code> <td>ParagraphAction
2118 * <tr><td><code>HTML.Tag.EM</code> <td>CharacterAction
2119 * <tr><td><code>HTML.Tag.FONT</code> <td>CharacterAction
2120 * <tr><td><code>HTML.Tag.FORM</code> <td>As of 1.4 a BlockAction
2121 * <tr><td><code>HTML.Tag.FRAME</code> <td>SpecialAction
2122 * <tr><td><code>HTML.Tag.FRAMESET</code> <td>BlockAction
2123 * <tr><td><code>HTML.Tag.H1</code> <td>ParagraphAction
2124 * <tr><td><code>HTML.Tag.H2</code> <td>ParagraphAction
2125 * <tr><td><code>HTML.Tag.H3</code> <td>ParagraphAction
2126 * <tr><td><code>HTML.Tag.H4</code> <td>ParagraphAction
2127 * <tr><td><code>HTML.Tag.H5</code> <td>ParagraphAction
2128 * <tr><td><code>HTML.Tag.H6</code> <td>ParagraphAction
2129 * <tr><td><code>HTML.Tag.HEAD</code> <td>HeadAction
2130 * <tr><td><code>HTML.Tag.HR</code> <td>SpecialAction
2131 * <tr><td><code>HTML.Tag.HTML</code> <td>BlockAction
2132 * <tr><td><code>HTML.Tag.I</code> <td>CharacterAction
2133 * <tr><td><code>HTML.Tag.IMG</code> <td>SpecialAction
2134 * <tr><td><code>HTML.Tag.INPUT</code> <td>FormAction
2135 * <tr><td><code>HTML.Tag.ISINDEX</code> <td>IsndexAction
2136 * <tr><td><code>HTML.Tag.KBD</code> <td>CharacterAction
2137 * <tr><td><code>HTML.Tag.LI</code> <td>BlockAction
2138 * <tr><td><code>HTML.Tag.LINK</code> <td>LinkAction
2139 * <tr><td><code>HTML.Tag.MAP</code> <td>MapAction
2140 * <tr><td><code>HTML.Tag.MENU</code> <td>BlockAction
2141 * <tr><td><code>HTML.Tag.META</code> <td>MetaAction
2142 * <tr><td><code>HTML.Tag.NOFRAMES</code> <td>BlockAction
2143 * <tr><td><code>HTML.Tag.OBJECT</code> <td>SpecialAction
2144 * <tr><td><code>HTML.Tag.OL</code> <td>BlockAction
2145 * <tr><td><code>HTML.Tag.OPTION</code> <td>FormAction
2146 * <tr><td><code>HTML.Tag.P</code> <td>ParagraphAction
2147 * <tr><td><code>HTML.Tag.PARAM</code> <td>HiddenAction
2148 * <tr><td><code>HTML.Tag.PRE</code> <td>PreAction
2149 * <tr><td><code>HTML.Tag.SAMP</code> <td>CharacterAction
2150 * <tr><td><code>HTML.Tag.SCRIPT</code> <td>HiddenAction
2151 * <tr><td><code>HTML.Tag.SELECT</code> <td>FormAction
2152 * <tr><td><code>HTML.Tag.SMALL</code> <td>CharacterAction
2153 * <tr><td><code>HTML.Tag.STRIKE</code> <td>CharacterAction
2154 * <tr><td><code>HTML.Tag.S</code> <td>CharacterAction
2155 * <tr><td><code>HTML.Tag.STRONG</code> <td>CharacterAction
2156 * <tr><td><code>HTML.Tag.STYLE</code> <td>StyleAction
2157 * <tr><td><code>HTML.Tag.SUB</code> <td>CharacterAction
2158 * <tr><td><code>HTML.Tag.SUP</code> <td>CharacterAction
2159 * <tr><td><code>HTML.Tag.TABLE</code> <td>BlockAction
2160 * <tr><td><code>HTML.Tag.TD</code> <td>BlockAction
2161 * <tr><td><code>HTML.Tag.TEXTAREA</code> <td>FormAction
2162 * <tr><td><code>HTML.Tag.TH</code> <td>BlockAction
2163 * <tr><td><code>HTML.Tag.TITLE</code> <td>TitleAction
2164 * <tr><td><code>HTML.Tag.TR</code> <td>BlockAction
2165 * <tr><td><code>HTML.Tag.TT</code> <td>CharacterAction
2166 * <tr><td><code>HTML.Tag.U</code> <td>CharacterAction
2167 * <tr><td><code>HTML.Tag.UL</code> <td>BlockAction
2168 * <tr><td><code>HTML.Tag.VAR</code> <td>CharacterAction
2169 * </table>
2170 * <p>
2171 * Once </html> is encountered, the Actions are no longer notified.
2172 */
2173 public class HTMLReader extends HTMLEditorKit.ParserCallback {
2174
2175 public HTMLReader(int offset) {
2176 this (offset, 0, 0, null);
2177 }
2178
2179 public HTMLReader(int offset, int popDepth, int pushDepth,
2180 HTML.Tag insertTag) {
2181 this (offset, popDepth, pushDepth, insertTag, true, false,
2182 true);
2183 }
2184
2185 /**
2186 * Generates a RuntimeException (will eventually generate
2187 * a BadLocationException when API changes are alloced) if inserting
2188 * into non empty document, <code>insertTag</code> is
2189 * non-<code>null</code>, and <code>offset</code> is not in the body.
2190 */
2191 // PENDING(sky): Add throws BadLocationException and remove
2192 // RuntimeException
2193 HTMLReader(int offset, int popDepth, int pushDepth,
2194 HTML.Tag insertTag, boolean insertInsertTag,
2195 boolean insertAfterImplied, boolean wantsTrailingNewline) {
2196 emptyDocument = (getLength() == 0);
2197 isStyleCSS = "text/css".equals(getDefaultStyleSheetType());
2198 this .offset = offset;
2199 threshold = HTMLDocument.this .getTokenThreshold();
2200 tagMap = new Hashtable(57);
2201 TagAction na = new TagAction();
2202 TagAction ba = new BlockAction();
2203 TagAction pa = new ParagraphAction();
2204 TagAction ca = new CharacterAction();
2205 TagAction sa = new SpecialAction();
2206 TagAction fa = new FormAction();
2207 TagAction ha = new HiddenAction();
2208 TagAction conv = new ConvertAction();
2209
2210 // register handlers for the well known tags
2211 tagMap.put(HTML.Tag.A, new AnchorAction());
2212 tagMap.put(HTML.Tag.ADDRESS, ca);
2213 tagMap.put(HTML.Tag.APPLET, ha);
2214 tagMap.put(HTML.Tag.AREA, new AreaAction());
2215 tagMap.put(HTML.Tag.B, conv);
2216 tagMap.put(HTML.Tag.BASE, new BaseAction());
2217 tagMap.put(HTML.Tag.BASEFONT, ca);
2218 tagMap.put(HTML.Tag.BIG, ca);
2219 tagMap.put(HTML.Tag.BLOCKQUOTE, ba);
2220 tagMap.put(HTML.Tag.BODY, ba);
2221 tagMap.put(HTML.Tag.BR, sa);
2222 tagMap.put(HTML.Tag.CAPTION, ba);
2223 tagMap.put(HTML.Tag.CENTER, ba);
2224 tagMap.put(HTML.Tag.CITE, ca);
2225 tagMap.put(HTML.Tag.CODE, ca);
2226 tagMap.put(HTML.Tag.DD, ba);
2227 tagMap.put(HTML.Tag.DFN, ca);
2228 tagMap.put(HTML.Tag.DIR, ba);
2229 tagMap.put(HTML.Tag.DIV, ba);
2230 tagMap.put(HTML.Tag.DL, ba);
2231 tagMap.put(HTML.Tag.DT, pa);
2232 tagMap.put(HTML.Tag.EM, ca);
2233 tagMap.put(HTML.Tag.FONT, conv);
2234 tagMap.put(HTML.Tag.FORM, new FormTagAction());
2235 tagMap.put(HTML.Tag.FRAME, sa);
2236 tagMap.put(HTML.Tag.FRAMESET, ba);
2237 tagMap.put(HTML.Tag.H1, pa);
2238 tagMap.put(HTML.Tag.H2, pa);
2239 tagMap.put(HTML.Tag.H3, pa);
2240 tagMap.put(HTML.Tag.H4, pa);
2241 tagMap.put(HTML.Tag.H5, pa);
2242 tagMap.put(HTML.Tag.H6, pa);
2243 tagMap.put(HTML.Tag.HEAD, new HeadAction());
2244 tagMap.put(HTML.Tag.HR, sa);
2245 tagMap.put(HTML.Tag.HTML, ba);
2246 tagMap.put(HTML.Tag.I, conv);
2247 tagMap.put(HTML.Tag.IMG, sa);
2248 tagMap.put(HTML.Tag.INPUT, fa);
2249 tagMap.put(HTML.Tag.ISINDEX, new IsindexAction());
2250 tagMap.put(HTML.Tag.KBD, ca);
2251 tagMap.put(HTML.Tag.LI, ba);
2252 tagMap.put(HTML.Tag.LINK, new LinkAction());
2253 tagMap.put(HTML.Tag.MAP, new MapAction());
2254 tagMap.put(HTML.Tag.MENU, ba);
2255 tagMap.put(HTML.Tag.META, new MetaAction());
2256 tagMap.put(HTML.Tag.NOBR, ca);
2257 tagMap.put(HTML.Tag.NOFRAMES, ba);
2258 tagMap.put(HTML.Tag.OBJECT, sa);
2259 tagMap.put(HTML.Tag.OL, ba);
2260 tagMap.put(HTML.Tag.OPTION, fa);
2261 tagMap.put(HTML.Tag.P, pa);
2262 tagMap.put(HTML.Tag.PARAM, new ObjectAction());
2263 tagMap.put(HTML.Tag.PRE, new PreAction());
2264 tagMap.put(HTML.Tag.SAMP, ca);
2265 tagMap.put(HTML.Tag.SCRIPT, ha);
2266 tagMap.put(HTML.Tag.SELECT, fa);
2267 tagMap.put(HTML.Tag.SMALL, ca);
2268 tagMap.put(HTML.Tag.SPAN, ca);
2269 tagMap.put(HTML.Tag.STRIKE, conv);
2270 tagMap.put(HTML.Tag.S, ca);
2271 tagMap.put(HTML.Tag.STRONG, ca);
2272 tagMap.put(HTML.Tag.STYLE, new StyleAction());
2273 tagMap.put(HTML.Tag.SUB, conv);
2274 tagMap.put(HTML.Tag.SUP, conv);
2275 tagMap.put(HTML.Tag.TABLE, ba);
2276 tagMap.put(HTML.Tag.TD, ba);
2277 tagMap.put(HTML.Tag.TEXTAREA, fa);
2278 tagMap.put(HTML.Tag.TH, ba);
2279 tagMap.put(HTML.Tag.TITLE, new TitleAction());
2280 tagMap.put(HTML.Tag.TR, ba);
2281 tagMap.put(HTML.Tag.TT, ca);
2282 tagMap.put(HTML.Tag.U, conv);
2283 tagMap.put(HTML.Tag.UL, ba);
2284 tagMap.put(HTML.Tag.VAR, ca);
2285
2286 if (insertTag != null) {
2287 this .insertTag = insertTag;
2288 this .popDepth = popDepth;
2289 this .pushDepth = pushDepth;
2290 this .insertInsertTag = insertInsertTag;
2291 foundInsertTag = false;
2292 } else {
2293 foundInsertTag = true;
2294 }
2295 if (insertAfterImplied) {
2296 this .popDepth = popDepth;
2297 this .pushDepth = pushDepth;
2298 this .insertAfterImplied = true;
2299 foundInsertTag = false;
2300 midInsert = false;
2301 this .insertInsertTag = true;
2302 this .wantsTrailingNewline = wantsTrailingNewline;
2303 } else {
2304 midInsert = (!emptyDocument && insertTag == null);
2305 if (midInsert) {
2306 generateEndsSpecsForMidInsert();
2307 }
2308 }
2309
2310 /**
2311 * This block initializes the <code>inParagraph</code> flag.
2312 * It is left in <code>false</code> value automatically
2313 * if the target document is empty or future inserts
2314 * were positioned into the 'body' tag.
2315 */
2316 if (!emptyDocument && !midInsert) {
2317 int targetOffset = Math.max(this .offset - 1, 0);
2318 Element elem = HTMLDocument.this
2319 .getCharacterElement(targetOffset);
2320 /* Going up by the left document structure path */
2321 for (int i = 0; i <= this .popDepth; i++) {
2322 elem = elem.getParentElement();
2323 }
2324 /* Going down by the right document structure path */
2325 for (int i = 0; i < this .pushDepth; i++) {
2326 int index = elem.getElementIndex(this .offset);
2327 elem = elem.getElement(index);
2328 }
2329 AttributeSet attrs = elem.getAttributes();
2330 if (attrs != null) {
2331 HTML.Tag tagToInsertInto = (HTML.Tag) attrs
2332 .getAttribute(StyleConstants.NameAttribute);
2333 if (tagToInsertInto != null) {
2334 this .inParagraph = tagToInsertInto
2335 .isParagraph();
2336 }
2337 }
2338 }
2339 }
2340
2341 /**
2342 * Generates an initial batch of end <code>ElementSpecs</code>
2343 * in parseBuffer to position future inserts into the body.
2344 */
2345 private void generateEndsSpecsForMidInsert() {
2346 int count = heightToElementWithName(HTML.Tag.BODY, Math
2347 .max(0, offset - 1));
2348 boolean joinNext = false;
2349
2350 if (count == -1 && offset > 0) {
2351 count = heightToElementWithName(HTML.Tag.BODY, offset);
2352 if (count != -1) {
2353 // Previous isn't in body, but current is. Have to
2354 // do some end specs, followed by join next.
2355 count = depthTo(offset - 1) - 1;
2356 joinNext = true;
2357 }
2358 }
2359 if (count == -1) {
2360 throw new RuntimeException(
2361 "Must insert new content into body element-");
2362 }
2363 if (count != -1) {
2364 // Insert a newline, if necessary.
2365 try {
2366 if (!joinNext && offset > 0
2367 && !getText(offset - 1, 1).equals("\n")) {
2368 SimpleAttributeSet newAttrs = new SimpleAttributeSet();
2369 newAttrs.addAttribute(
2370 StyleConstants.NameAttribute,
2371 HTML.Tag.CONTENT);
2372 ElementSpec spec = new ElementSpec(newAttrs,
2373 ElementSpec.ContentType, NEWLINE, 0, 1);
2374 parseBuffer.addElement(spec);
2375 }
2376 // Should never throw, but will catch anyway.
2377 } catch (BadLocationException ble) {
2378 }
2379 while (count-- > 0) {
2380 parseBuffer.addElement(new ElementSpec(null,
2381 ElementSpec.EndTagType));
2382 }
2383 if (joinNext) {
2384 ElementSpec spec = new ElementSpec(null,
2385 ElementSpec.StartTagType);
2386
2387 spec.setDirection(ElementSpec.JoinNextDirection);
2388 parseBuffer.addElement(spec);
2389 }
2390 }
2391 // We should probably throw an exception if (count == -1)
2392 // Or look for the body and reset the offset.
2393 }
2394
2395 /**
2396 * @return number of parents to reach the child at offset.
2397 */
2398 private int depthTo(int offset) {
2399 Element e = getDefaultRootElement();
2400 int count = 0;
2401
2402 while (!e.isLeaf()) {
2403 count++;
2404 e = e.getElement(e.getElementIndex(offset));
2405 }
2406 return count;
2407 }
2408
2409 /**
2410 * @return number of parents of the leaf at <code>offset</code>
2411 * until a parent with name, <code>name</code> has been
2412 * found. -1 indicates no matching parent with
2413 * <code>name</code>.
2414 */
2415 private int heightToElementWithName(Object name, int offset) {
2416 Element e = getCharacterElement(offset).getParentElement();
2417 int count = 0;
2418
2419 while (e != null
2420 && e.getAttributes().getAttribute(
2421 StyleConstants.NameAttribute) != name) {
2422 count++;
2423 e = e.getParentElement();
2424 }
2425 return (e == null) ? -1 : count;
2426 }
2427
2428 /**
2429 * This will make sure there aren't two BODYs (the second is
2430 * typically created when you do a remove all, and then an insert).
2431 */
2432 private void adjustEndElement() {
2433 int length = getLength();
2434 if (length == 0) {
2435 return;
2436 }
2437 obtainLock();
2438 try {
2439 Element[] pPath = getPathTo(length - 1);
2440 int pLength = pPath.length;
2441 if (pLength > 1
2442 && pPath[1].getAttributes().getAttribute(
2443 StyleConstants.NameAttribute) == HTML.Tag.BODY
2444 && pPath[1].getEndOffset() == length) {
2445 String lastText = getText(length - 1, 1);
2446 DefaultDocumentEvent event = null;
2447 Element[] added;
2448 Element[] removed;
2449 int index;
2450 // Remove the fake second body.
2451 added = new Element[0];
2452 removed = new Element[1];
2453 index = pPath[0].getElementIndex(length);
2454 removed[0] = pPath[0].getElement(index);
2455 ((BranchElement) pPath[0]).replace(index, 1, added);
2456 ElementEdit firstEdit = new ElementEdit(pPath[0],
2457 index, removed, added);
2458
2459 // Insert a new element to represent the end that the
2460 // second body was representing.
2461 SimpleAttributeSet sas = new SimpleAttributeSet();
2462 sas.addAttribute(StyleConstants.NameAttribute,
2463 HTML.Tag.CONTENT);
2464 sas.addAttribute(IMPLIED_CR, Boolean.TRUE);
2465 added = new Element[1];
2466 added[0] = createLeafElement(pPath[pLength - 1],
2467 sas, length, length + 1);
2468 index = pPath[pLength - 1].getElementCount();
2469 ((BranchElement) pPath[pLength - 1]).replace(index,
2470 0, added);
2471 event = new DefaultDocumentEvent(length, 1,
2472 DocumentEvent.EventType.CHANGE);
2473 event.addEdit(new ElementEdit(pPath[pLength - 1],
2474 index, new Element[0], added));
2475 event.addEdit(firstEdit);
2476 event.end();
2477 fireChangedUpdate(event);
2478 fireUndoableEditUpdate(new UndoableEditEvent(this ,
2479 event));
2480
2481 if (lastText.equals("\n")) {
2482 // We now have two \n's, one part of the Document.
2483 // We need to remove one
2484 event = new DefaultDocumentEvent(length - 1, 1,
2485 DocumentEvent.EventType.REMOVE);
2486 removeUpdate(event);
2487 UndoableEdit u = getContent().remove(
2488 length - 1, 1);
2489 if (u != null) {
2490 event.addEdit(u);
2491 }
2492 postRemoveUpdate(event);
2493 // Mark the edit as done.
2494 event.end();
2495 fireRemoveUpdate(event);
2496 fireUndoableEditUpdate(new UndoableEditEvent(
2497 this , event));
2498 }
2499 }
2500 } catch (BadLocationException ble) {
2501 } finally {
2502 releaseLock();
2503 }
2504 }
2505
2506 private Element[] getPathTo(int offset) {
2507 Stack elements = new Stack();
2508 Element e = getDefaultRootElement();
2509 int index;
2510 while (!e.isLeaf()) {
2511 elements.push(e);
2512 e = e.getElement(e.getElementIndex(offset));
2513 }
2514 Element[] retValue = new Element[elements.size()];
2515 elements.copyInto(retValue);
2516 return retValue;
2517 }
2518
2519 // -- HTMLEditorKit.ParserCallback methods --------------------
2520
2521 /**
2522 * The last method called on the reader. It allows
2523 * any pending changes to be flushed into the document.
2524 * Since this is currently loading synchronously, the entire
2525 * set of changes are pushed in at this point.
2526 */
2527 public void flush() throws BadLocationException {
2528 if (emptyDocument && !insertAfterImplied) {
2529 if (HTMLDocument.this .getLength() > 0
2530 || parseBuffer.size() > 0) {
2531 flushBuffer(true);
2532 adjustEndElement();
2533 }
2534 // We won't insert when
2535 } else {
2536 flushBuffer(true);
2537 }
2538 }
2539
2540 /**
2541 * Called by the parser to indicate a block of text was
2542 * encountered.
2543 */
2544 public void handleText(char[] data, int pos) {
2545 if (receivedEndHTML || (midInsert && !inBody)) {
2546 return;
2547 }
2548
2549 // see if complex glyph layout support is needed
2550 if (HTMLDocument.this .getProperty(I18NProperty).equals(
2551 Boolean.FALSE)) {
2552 // if a default direction of right-to-left has been specified,
2553 // we want complex layout even if the text is all left to right.
2554 Object d = getProperty(TextAttribute.RUN_DIRECTION);
2555 if ((d != null)
2556 && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
2557 HTMLDocument.this .putProperty(I18NProperty,
2558 Boolean.TRUE);
2559 } else {
2560 if (SwingUtilities2.isComplexLayout(data, 0,
2561 data.length)) {
2562 HTMLDocument.this .putProperty(I18NProperty,
2563 Boolean.TRUE);
2564 }
2565 }
2566 }
2567
2568 if (inTextArea) {
2569 textAreaContent(data);
2570 } else if (inPre) {
2571 preContent(data);
2572 } else if (inTitle) {
2573 putProperty(Document.TitleProperty, new String(data));
2574 } else if (option != null) {
2575 option.setLabel(new String(data));
2576 } else if (inStyle) {
2577 if (styles != null) {
2578 styles.addElement(new String(data));
2579 }
2580 } else if (inBlock > 0) {
2581 if (!foundInsertTag && insertAfterImplied) {
2582 // Assume content should be added.
2583 foundInsertTag(false);
2584 foundInsertTag = true;
2585 inParagraph = impliedP = true;
2586 }
2587 if (data.length >= 1) {
2588 addContent(data, 0, data.length);
2589 }
2590 }
2591 }
2592
2593 /**
2594 * Callback from the parser. Route to the appropriate
2595 * handler for the tag.
2596 */
2597 public void handleStartTag(HTML.Tag t, MutableAttributeSet a,
2598 int pos) {
2599 if (receivedEndHTML) {
2600 return;
2601 }
2602 if (midInsert && !inBody) {
2603 if (t == HTML.Tag.BODY) {
2604 inBody = true;
2605 // Increment inBlock since we know we are in the body,
2606 // this is needed incase an implied-p is needed. If
2607 // inBlock isn't incremented, and an implied-p is
2608 // encountered, addContent won't be called!
2609 inBlock++;
2610 }
2611 return;
2612 }
2613 if (!inBody && t == HTML.Tag.BODY) {
2614 inBody = true;
2615 }
2616 if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
2617 // Map the style attributes.
2618 String decl = (String) a
2619 .getAttribute(HTML.Attribute.STYLE);
2620 a.removeAttribute(HTML.Attribute.STYLE);
2621 styleAttributes = getStyleSheet().getDeclaration(decl);
2622 a.addAttributes(styleAttributes);
2623 } else {
2624 styleAttributes = null;
2625 }
2626 TagAction action = (TagAction) tagMap.get(t);
2627
2628 if (action != null) {
2629 action.start(t, a);
2630 }
2631 }
2632
2633 public void handleComment(char[] data, int pos) {
2634 if (receivedEndHTML) {
2635 addExternalComment(new String(data));
2636 return;
2637 }
2638 if (inStyle) {
2639 if (styles != null) {
2640 styles.addElement(new String(data));
2641 }
2642 } else if (getPreservesUnknownTags()) {
2643 if (inBlock == 0
2644 && (foundInsertTag || insertTag != HTML.Tag.COMMENT)) {
2645 // Comment outside of body, will not be able to show it,
2646 // but can add it as a property on the Document.
2647 addExternalComment(new String(data));
2648 return;
2649 }
2650 SimpleAttributeSet sas = new SimpleAttributeSet();
2651 sas.addAttribute(HTML.Attribute.COMMENT, new String(
2652 data));
2653 addSpecialElement(HTML.Tag.COMMENT, sas);
2654 }
2655
2656 TagAction action = (TagAction) tagMap.get(HTML.Tag.COMMENT);
2657 if (action != null) {
2658 action
2659 .start(HTML.Tag.COMMENT,
2660 new SimpleAttributeSet());
2661 action.end(HTML.Tag.COMMENT);
2662 }
2663 }
2664
2665 /**
2666 * Adds the comment <code>comment</code> to the set of comments
2667 * maintained outside of the scope of elements.
2668 */
2669 private void addExternalComment(String comment) {
2670 Object comments = getProperty(AdditionalComments);
2671 if (comments != null && !(comments instanceof Vector)) {
2672 // No place to put comment.
2673 return;
2674 }
2675 if (comments == null) {
2676 comments = new Vector();
2677 putProperty(AdditionalComments, comments);
2678 }
2679 ((Vector) comments).addElement(comment);
2680 }
2681
2682 /**
2683 * Callback from the parser. Route to the appropriate
2684 * handler for the tag.
2685 */
2686 public void handleEndTag(HTML.Tag t, int pos) {
2687 if (receivedEndHTML || (midInsert && !inBody)) {
2688 return;
2689 }
2690 if (t == HTML.Tag.HTML) {
2691 receivedEndHTML = true;
2692 }
2693 if (t == HTML.Tag.BODY) {
2694 inBody = false;
2695 if (midInsert) {
2696 inBlock--;
2697 }
2698 }
2699 TagAction action = (TagAction) tagMap.get(t);
2700 if (action != null) {
2701 action.end(t);
2702 }
2703 }
2704
2705 /**
2706 * Callback from the parser. Route to the appropriate
2707 * handler for the tag.
2708 */
2709 public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a,
2710 int pos) {
2711 if (receivedEndHTML || (midInsert && !inBody)) {
2712 return;
2713 }
2714
2715 if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
2716 // Map the style attributes.
2717 String decl = (String) a
2718 .getAttribute(HTML.Attribute.STYLE);
2719 a.removeAttribute(HTML.Attribute.STYLE);
2720 styleAttributes = getStyleSheet().getDeclaration(decl);
2721 a.addAttributes(styleAttributes);
2722 } else {
2723 styleAttributes = null;
2724 }
2725
2726 TagAction action = (TagAction) tagMap.get(t);
2727 if (action != null) {
2728 action.start(t, a);
2729 action.end(t);
2730 } else if (getPreservesUnknownTags()) {
2731 // unknown tag, only add if should preserve it.
2732 addSpecialElement(t, a);
2733 }
2734 }
2735
2736 /**
2737 * This is invoked after the stream has been parsed, but before
2738 * <code>flush</code>. <code>eol</code> will be one of \n, \r
2739 * or \r\n, which ever is encountered the most in parsing the
2740 * stream.
2741 *
2742 * @since 1.3
2743 */
2744 public void handleEndOfLineString(String eol) {
2745 if (emptyDocument && eol != null) {
2746 putProperty(DefaultEditorKit.EndOfLineStringProperty,
2747 eol);
2748 }
2749 }
2750
2751 // ---- tag handling support ------------------------------
2752
2753 /**
2754 * Registers a handler for the given tag. By default
2755 * all of the well-known tags will have been registered.
2756 * This can be used to change the handling of a particular
2757 * tag or to add support for custom tags.
2758 */
2759 protected void registerTag(HTML.Tag t, TagAction a) {
2760 tagMap.put(t, a);
2761 }
2762
2763 /**
2764 * An action to be performed in response
2765 * to parsing a tag. This allows customization
2766 * of how each tag is handled and avoids a large
2767 * switch statement.
2768 */
2769 public class TagAction {
2770
2771 /**
2772 * Called when a start tag is seen for the
2773 * type of tag this action was registered
2774 * to. The tag argument indicates the actual
2775 * tag for those actions that are shared across
2776 * many tags. By default this does nothing and
2777 * completely ignores the tag.
2778 */
2779 public void start(HTML.Tag t, MutableAttributeSet a) {
2780 }
2781
2782 /**
2783 * Called when an end tag is seen for the
2784 * type of tag this action was registered
2785 * to. The tag argument indicates the actual
2786 * tag for those actions that are shared across
2787 * many tags. By default this does nothing and
2788 * completely ignores the tag.
2789 */
2790 public void end(HTML.Tag t) {
2791 }
2792
2793 }
2794
2795 public class BlockAction extends TagAction {
2796
2797 public void start(HTML.Tag t, MutableAttributeSet attr) {
2798 blockOpen(t, attr);
2799 }
2800
2801 public void end(HTML.Tag t) {
2802 blockClose(t);
2803 }
2804 }
2805
2806 /**
2807 * Action used for the actual element form tag. This is named such
2808 * as there was already a public class named FormAction.
2809 */
2810 private class FormTagAction extends BlockAction {
2811 public void start(HTML.Tag t, MutableAttributeSet attr) {
2812 super .start(t, attr);
2813 // initialize a ButtonGroupsMap when
2814 // FORM tag is encountered. This will
2815 // be used for any radio buttons that
2816 // might be defined in the FORM.
2817 // for new group new ButtonGroup will be created (fix for 4529702)
2818 // group name is a key in radioButtonGroupsMap
2819 radioButtonGroupsMap = new HashMap();
2820 }
2821
2822 public void end(HTML.Tag t) {
2823 super .end(t);
2824 // reset the button group to null since
2825 // the form has ended.
2826 radioButtonGroupsMap = null;
2827 }
2828 }
2829
2830 public class ParagraphAction extends BlockAction {
2831
2832 public void start(HTML.Tag t, MutableAttributeSet a) {
2833 super .start(t, a);
2834 inParagraph = true;
2835 }
2836
2837 public void end(HTML.Tag t) {
2838 super .end(t);
2839 inParagraph = false;
2840 }
2841 }
2842
2843 public class SpecialAction extends TagAction {
2844
2845 public void start(HTML.Tag t, MutableAttributeSet a) {
2846 addSpecialElement(t, a);
2847 }
2848
2849 }
2850
2851 public class IsindexAction extends TagAction {
2852
2853 public void start(HTML.Tag t, MutableAttributeSet a) {
2854 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
2855 addSpecialElement(t, a);
2856 blockClose(HTML.Tag.IMPLIED);
2857 }
2858
2859 }
2860
2861 public class HiddenAction extends TagAction {
2862
2863 public void start(HTML.Tag t, MutableAttributeSet a) {
2864 addSpecialElement(t, a);
2865 }
2866
2867 public void end(HTML.Tag t) {
2868 if (!isEmpty(t)) {
2869 MutableAttributeSet a = new SimpleAttributeSet();
2870 a.addAttribute(HTML.Attribute.ENDTAG, "true");
2871 addSpecialElement(t, a);
2872 }
2873 }
2874
2875 boolean isEmpty(HTML.Tag t) {
2876 if (t == HTML.Tag.APPLET || t == HTML.Tag.SCRIPT) {
2877 return false;
2878 }
2879 return true;
2880 }
2881 }
2882
2883 /**
2884 * Subclass of HiddenAction to set the content type for style sheets,
2885 * and to set the name of the default style sheet.
2886 */
2887 class MetaAction extends HiddenAction {
2888
2889 public void start(HTML.Tag t, MutableAttributeSet a) {
2890 Object equiv = a.getAttribute(HTML.Attribute.HTTPEQUIV);
2891 if (equiv != null) {
2892 equiv = ((String) equiv).toLowerCase();
2893 if (equiv.equals("content-style-type")) {
2894 String value = (String) a
2895 .getAttribute(HTML.Attribute.CONTENT);
2896 setDefaultStyleSheetType(value);
2897 isStyleCSS = "text/css"
2898 .equals(getDefaultStyleSheetType());
2899 } else if (equiv.equals("default-style")) {
2900 defaultStyle = (String) a
2901 .getAttribute(HTML.Attribute.CONTENT);
2902 }
2903 }
2904 super .start(t, a);
2905 }
2906
2907 boolean isEmpty(HTML.Tag t) {
2908 return true;
2909 }
2910 }
2911
2912 /**
2913 * End if overridden to create the necessary stylesheets that
2914 * are referenced via the link tag. It is done in this manner
2915 * as the meta tag can be used to specify an alternate style sheet,
2916 * and is not guaranteed to come before the link tags.
2917 */
2918 class HeadAction extends BlockAction {
2919
2920 public void start(HTML.Tag t, MutableAttributeSet a) {
2921 inHead = true;
2922 // This check of the insertTag is put in to avoid considering
2923 // the implied-p that is generated for the head. This allows
2924 // inserts for HR to work correctly.
2925 if ((insertTag == null && !insertAfterImplied)
2926 || (insertTag == HTML.Tag.HEAD)
2927 || (insertAfterImplied && (foundInsertTag || !a
2928 .isDefined(IMPLIED)))) {
2929 super .start(t, a);
2930 }
2931 }
2932
2933 public void end(HTML.Tag t) {
2934 inHead = inStyle = false;
2935 // See if there is a StyleSheet to link to.
2936 if (styles != null) {
2937 boolean isDefaultCSS = isStyleCSS;
2938 for (int counter = 0, maxCounter = styles.size(); counter < maxCounter;) {
2939 Object value = styles.elementAt(counter);
2940 if (value == HTML.Tag.LINK) {
2941 handleLink((AttributeSet) styles
2942 .elementAt(++counter));
2943 counter++;
2944 } else {
2945 // Rule.
2946 // First element gives type.
2947 String type = (String) styles
2948 .elementAt(++counter);
2949 boolean isCSS = (type == null) ? isDefaultCSS
2950 : type.equals("text/css");
2951 while (++counter < maxCounter
2952 && (styles.elementAt(counter) instanceof String)) {
2953 if (isCSS) {
2954 addCSSRules((String) styles
2955 .elementAt(counter));
2956 }
2957 }
2958 }
2959 }
2960 }
2961 if ((insertTag == null && !insertAfterImplied)
2962 || insertTag == HTML.Tag.HEAD
2963 || (insertAfterImplied && foundInsertTag)) {
2964 super .end(t);
2965 }
2966 }
2967
2968 boolean isEmpty(HTML.Tag t) {
2969 return false;
2970 }
2971
2972 private void handleLink(AttributeSet attr) {
2973 // Link.
2974 String type = (String) attr
2975 .getAttribute(HTML.Attribute.TYPE);
2976 if (type == null) {
2977 type = getDefaultStyleSheetType();
2978 }
2979 // Only choose if type==text/css
2980 // Select link if rel==stylesheet.
2981 // Otherwise if rel==alternate stylesheet and
2982 // title matches default style.
2983 if (type.equals("text/css")) {
2984 String rel = (String) attr
2985 .getAttribute(HTML.Attribute.REL);
2986 String title = (String) attr
2987 .getAttribute(HTML.Attribute.TITLE);
2988 String media = (String) attr
2989 .getAttribute(HTML.Attribute.MEDIA);
2990 if (media == null) {
2991 media = "all";
2992 } else {
2993 media = media.toLowerCase();
2994 }
2995 if (rel != null) {
2996 rel = rel.toLowerCase();
2997 if ((media.indexOf("all") != -1 || media
2998 .indexOf("screen") != -1)
2999 && (rel.equals("stylesheet") || (rel
3000 .equals("alternate stylesheet") && title
3001 .equals(defaultStyle)))) {
3002 linkCSSStyleSheet((String) attr
3003 .getAttribute(HTML.Attribute.HREF));
3004 }
3005 }
3006 }
3007 }
3008 }
3009
3010 /**
3011 * A subclass to add the AttributeSet to styles if the
3012 * attributes contains an attribute for 'rel' with value
3013 * 'stylesheet' or 'alternate stylesheet'.
3014 */
3015 class LinkAction extends HiddenAction {
3016
3017 public void start(HTML.Tag t, MutableAttributeSet a) {
3018 String rel = (String) a
3019 .getAttribute(HTML.Attribute.REL);
3020 if (rel != null) {
3021 rel = rel.toLowerCase();
3022 if (rel.equals("stylesheet")
3023 || rel.equals("alternate stylesheet")) {
3024 if (styles == null) {
3025 styles = new Vector(3);
3026 }
3027 styles.addElement(t);
3028 styles.addElement(a.copyAttributes());
3029 }
3030 }
3031 super .start(t, a);
3032 }
3033 }
3034
3035 class MapAction extends TagAction {
3036
3037 public void start(HTML.Tag t, MutableAttributeSet a) {
3038 lastMap = new Map((String) a
3039 .getAttribute(HTML.Attribute.NAME));
3040 addMap(lastMap);
3041 }
3042
3043 public void end(HTML.Tag t) {
3044 }
3045 }
3046
3047 class AreaAction extends TagAction {
3048
3049 public void start(HTML.Tag t, MutableAttributeSet a) {
3050 if (lastMap != null) {
3051 lastMap.addArea(a.copyAttributes());
3052 }
3053 }
3054
3055 public void end(HTML.Tag t) {
3056 }
3057 }
3058
3059 class StyleAction extends TagAction {
3060
3061 public void start(HTML.Tag t, MutableAttributeSet a) {
3062 if (inHead) {
3063 if (styles == null) {
3064 styles = new Vector(3);
3065 }
3066 styles.addElement(t);
3067 styles.addElement(a
3068 .getAttribute(HTML.Attribute.TYPE));
3069 inStyle = true;
3070 }
3071 }
3072
3073 public void end(HTML.Tag t) {
3074 inStyle = false;
3075 }
3076
3077 boolean isEmpty(HTML.Tag t) {
3078 return false;
3079 }
3080 }
3081
3082 public class PreAction extends BlockAction {
3083
3084 public void start(HTML.Tag t, MutableAttributeSet attr) {
3085 inPre = true;
3086 blockOpen(t, attr);
3087 attr.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
3088 blockOpen(HTML.Tag.IMPLIED, attr);
3089 }
3090
3091 public void end(HTML.Tag t) {
3092 blockClose(HTML.Tag.IMPLIED);
3093 // set inPre to false after closing, so that if a newline
3094 // is added it won't generate a blockOpen.
3095 inPre = false;
3096 blockClose(t);
3097 }
3098 }
3099
3100 public class CharacterAction extends TagAction {
3101
3102 public void start(HTML.Tag t, MutableAttributeSet attr) {
3103 pushCharacterStyle();
3104 if (!foundInsertTag) {
3105 // Note that the third argument should really be based off
3106 // inParagraph and impliedP. If we're wrong (that is
3107 // insertTagDepthDelta shouldn't be changed), we'll end up
3108 // removing an extra EndSpec, which won't matter anyway.
3109 boolean insert = canInsertTag(t, attr, false);
3110 if (foundInsertTag) {
3111 if (!inParagraph) {
3112 inParagraph = impliedP = true;
3113 }
3114 }
3115 if (!insert) {
3116 return;
3117 }
3118 }
3119 if (attr.isDefined(IMPLIED)) {
3120 attr.removeAttribute(IMPLIED);
3121 }
3122 charAttr.addAttribute(t, attr.copyAttributes());
3123 if (styleAttributes != null) {
3124 charAttr.addAttributes(styleAttributes);
3125 }
3126 }
3127
3128 public void end(HTML.Tag t) {
3129 popCharacterStyle();
3130 }
3131 }
3132
3133 /**
3134 * Provides conversion of HTML tag/attribute
3135 * mappings that have a corresponding StyleConstants
3136 * and CSS mapping. The conversion is to CSS attributes.
3137 */
3138 class ConvertAction extends TagAction {
3139
3140 public void start(HTML.Tag t, MutableAttributeSet attr) {
3141 pushCharacterStyle();
3142 if (!foundInsertTag) {
3143 // Note that the third argument should really be based off
3144 // inParagraph and impliedP. If we're wrong (that is
3145 // insertTagDepthDelta shouldn't be changed), we'll end up
3146 // removing an extra EndSpec, which won't matter anyway.
3147 boolean insert = canInsertTag(t, attr, false);
3148 if (foundInsertTag) {
3149 if (!inParagraph) {
3150 inParagraph = impliedP = true;
3151 }
3152 }
3153 if (!insert) {
3154 return;
3155 }
3156 }
3157 if (attr.isDefined(IMPLIED)) {
3158 attr.removeAttribute(IMPLIED);
3159 }
3160 if (styleAttributes != null) {
3161 charAttr.addAttributes(styleAttributes);
3162 }
3163 // We also need to add attr, otherwise we lose custom
3164 // attributes, including class/id for style lookups, and
3165 // further confuse style lookup (doesn't have tag).
3166 charAttr.addAttribute(t, attr.copyAttributes());
3167 StyleSheet sheet = getStyleSheet();
3168 if (t == HTML.Tag.B) {
3169 sheet.addCSSAttribute(charAttr,
3170 CSS.Attribute.FONT_WEIGHT, "bold");
3171 } else if (t == HTML.Tag.I) {
3172 sheet.addCSSAttribute(charAttr,
3173 CSS.Attribute.FONT_STYLE, "italic");
3174 } else if (t == HTML.Tag.U) {
3175 Object v = charAttr
3176 .getAttribute(CSS.Attribute.TEXT_DECORATION);
3177 String value = "underline";
3178 value = (v != null) ? value + "," + v.toString()
3179 : value;
3180 sheet.addCSSAttribute(charAttr,
3181 CSS.Attribute.TEXT_DECORATION, value);
3182 } else if (t == HTML.Tag.STRIKE) {
3183 Object v = charAttr
3184 .getAttribute(CSS.Attribute.TEXT_DECORATION);
3185 String value = "line-through";
3186 value = (v != null) ? value + "," + v.toString()
3187 : value;
3188 sheet.addCSSAttribute(charAttr,
3189 CSS.Attribute.TEXT_DECORATION, value);
3190 } else if (t == HTML.Tag.SUP) {
3191 Object v = charAttr
3192 .getAttribute(CSS.Attribute.VERTICAL_ALIGN);
3193 String value = "sup";
3194 value = (v != null) ? value + "," + v.toString()
3195 : value;
3196 sheet.addCSSAttribute(charAttr,
3197 CSS.Attribute.VERTICAL_ALIGN, value);
3198 } else if (t == HTML.Tag.SUB) {
3199 Object v = charAttr
3200 .getAttribute(CSS.Attribute.VERTICAL_ALIGN);
3201 String value = "sub";
3202 value = (v != null) ? value + "," + v.toString()
3203 : value;
3204 sheet.addCSSAttribute(charAttr,
3205 CSS.Attribute.VERTICAL_ALIGN, value);
3206 } else if (t == HTML.Tag.FONT) {
3207 String color = (String) attr
3208 .getAttribute(HTML.Attribute.COLOR);
3209 if (color != null) {
3210 sheet.addCSSAttribute(charAttr,
3211 CSS.Attribute.COLOR, color);
3212 }
3213 String face = (String) attr
3214 .getAttribute(HTML.Attribute.FACE);
3215 if (face != null) {
3216 sheet.addCSSAttribute(charAttr,
3217 CSS.Attribute.FONT_FAMILY, face);
3218 }
3219 String size = (String) attr
3220 .getAttribute(HTML.Attribute.SIZE);
3221 if (size != null) {
3222 sheet.addCSSAttributeFromHTML(charAttr,
3223 CSS.Attribute.FONT_SIZE, size);
3224 }
3225 }
3226 }
3227
3228 public void end(HTML.Tag t) {
3229 popCharacterStyle();
3230 }
3231
3232 }
3233
3234 class AnchorAction extends CharacterAction {
3235
3236 public void start(HTML.Tag t, MutableAttributeSet attr) {
3237 // set flag to catch empty anchors
3238 emptyAnchor = true;
3239 super .start(t, attr);
3240 }
3241
3242 public void end(HTML.Tag t) {
3243 if (emptyAnchor) {
3244 // if the anchor was empty it was probably a
3245 // named anchor point and we don't want to throw
3246 // it away.
3247 char[] one = new char[1];
3248 one[0] = '\n';
3249 addContent(one, 0, 1);
3250 }
3251 super .end(t);
3252 }
3253 }
3254
3255 class TitleAction extends HiddenAction {
3256
3257 public void start(HTML.Tag t, MutableAttributeSet attr) {
3258 inTitle = true;
3259 super .start(t, attr);
3260 }
3261
3262 public void end(HTML.Tag t) {
3263 inTitle = false;
3264 super .end(t);
3265 }
3266
3267 boolean isEmpty(HTML.Tag t) {
3268 return false;
3269 }
3270 }
3271
3272 class BaseAction extends TagAction {
3273
3274 public void start(HTML.Tag t, MutableAttributeSet attr) {
3275 String href = (String) attr
3276 .getAttribute(HTML.Attribute.HREF);
3277 if (href != null) {
3278 try {
3279 URL newBase = new URL(base, href);
3280 setBase(newBase);
3281 hasBaseTag = true;
3282 } catch (MalformedURLException ex) {
3283 }
3284 }
3285 baseTarget = (String) attr
3286 .getAttribute(HTML.Attribute.TARGET);
3287 }
3288 }
3289
3290 class ObjectAction extends SpecialAction {
3291
3292 public void start(HTML.Tag t, MutableAttributeSet a) {
3293 if (t == HTML.Tag.PARAM) {
3294 addParameter(a);
3295 } else {
3296 super .start(t, a);
3297 }
3298 }
3299
3300 public void end(HTML.Tag t) {
3301 if (t != HTML.Tag.PARAM) {
3302 super .end(t);
3303 }
3304 }
3305
3306 void addParameter(AttributeSet a) {
3307 String name = (String) a
3308 .getAttribute(HTML.Attribute.NAME);
3309 String value = (String) a
3310 .getAttribute(HTML.Attribute.VALUE);
3311 if ((name != null) && (value != null)) {
3312 ElementSpec objSpec = (ElementSpec) parseBuffer
3313 .lastElement();
3314 MutableAttributeSet objAttr = (MutableAttributeSet) objSpec
3315 .getAttributes();
3316 objAttr.addAttribute(name, value);
3317 }
3318 }
3319 }
3320
3321 /**
3322 * Action to support forms by building all of the elements
3323 * used to represent form controls. This will process
3324 * the <INPUT>, <TEXTAREA>, <SELECT>,
3325 * and <OPTION> tags. The element created by
3326 * this action is expected to have the attribute
3327 * <code>StyleConstants.ModelAttribute</code> set to
3328 * the model that holds the state for the form control.
3329 * This enables multiple views, and allows document to
3330 * be iterated over picking up the data of the form.
3331 * The following are the model assignments for the
3332 * various type of form elements.
3333 * <table summary="model assignments for the various types of form elements">
3334 * <tr>
3335 * <th>Element Type
3336 * <th>Model Type
3337 * <tr>
3338 * <td>input, type button
3339 * <td>{@link DefaultButtonModel}
3340 * <tr>
3341 * <td>input, type checkbox
3342 * <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
3343 * <tr>
3344 * <td>input, type image
3345 * <td>{@link DefaultButtonModel}
3346 * <tr>
3347 * <td>input, type password
3348 * <td>{@link PlainDocument}
3349 * <tr>
3350 * <td>input, type radio
3351 * <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
3352 * <tr>
3353 * <td>input, type reset
3354 * <td>{@link DefaultButtonModel}
3355 * <tr>
3356 * <td>input, type submit
3357 * <td>{@link DefaultButtonModel}
3358 * <tr>
3359 * <td>input, type text or type is null.
3360 * <td>{@link PlainDocument}
3361 * <tr>
3362 * <td>select
3363 * <td>{@link DefaultComboBoxModel} or an {@link DefaultListModel}, with an item type of Option
3364 * <tr>
3365 * <td>textarea
3366 * <td>{@link PlainDocument}
3367 * </table>
3368 *
3369 */
3370 public class FormAction extends SpecialAction {
3371
3372 public void start(HTML.Tag t, MutableAttributeSet attr) {
3373 if (t == HTML.Tag.INPUT) {
3374 String type = (String) attr
3375 .getAttribute(HTML.Attribute.TYPE);
3376 /*
3377 * if type is not defined teh default is
3378 * assumed to be text.
3379 */
3380 if (type == null) {
3381 type = "text";
3382 attr.addAttribute(HTML.Attribute.TYPE, "text");
3383 }
3384 setModel(type, attr);
3385 } else if (t == HTML.Tag.TEXTAREA) {
3386 inTextArea = true;
3387 textAreaDocument = new TextAreaDocument();
3388 attr.addAttribute(StyleConstants.ModelAttribute,
3389 textAreaDocument);
3390 } else if (t == HTML.Tag.SELECT) {
3391 int size = HTML.getIntegerAttributeValue(attr,
3392 HTML.Attribute.SIZE, 1);
3393 boolean multiple = ((String) attr
3394 .getAttribute(HTML.Attribute.MULTIPLE) != null);
3395 if ((size > 1) || multiple) {
3396 OptionListModel m = new OptionListModel();
3397 if (multiple) {
3398 m
3399 .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
3400 }
3401 selectModel = m;
3402 } else {
3403 selectModel = new OptionComboBoxModel();
3404 }
3405 attr.addAttribute(StyleConstants.ModelAttribute,
3406 selectModel);
3407
3408 }
3409
3410 // build the element, unless this is an option.
3411 if (t == HTML.Tag.OPTION) {
3412 option = new Option(attr);
3413
3414 if (selectModel instanceof OptionListModel) {
3415 OptionListModel m = (OptionListModel) selectModel;
3416 m.addElement(option);
3417 if (option.isSelected()) {
3418 m.addSelectionInterval(optionCount,
3419 optionCount);
3420 m.setInitialSelection(optionCount);
3421 }
3422 } else if (selectModel instanceof OptionComboBoxModel) {
3423 OptionComboBoxModel m = (OptionComboBoxModel) selectModel;
3424 m.addElement(option);
3425 if (option.isSelected()) {
3426 m.setSelectedItem(option);
3427 m.setInitialSelection(option);
3428 }
3429 }
3430 optionCount++;
3431 } else {
3432 super .start(t, attr);
3433 }
3434 }
3435
3436 public void end(HTML.Tag t) {
3437 if (t == HTML.Tag.OPTION) {
3438 option = null;
3439 } else {
3440 if (t == HTML.Tag.SELECT) {
3441 selectModel = null;
3442 optionCount = 0;
3443 } else if (t == HTML.Tag.TEXTAREA) {
3444 inTextArea = false;
3445
3446 /* Now that the textarea has ended,
3447 * store the entire initial text
3448 * of the text area. This will
3449 * enable us to restore the initial
3450 * state if a reset is requested.
3451 */
3452 textAreaDocument.storeInitialText();
3453 }
3454 super .end(t);
3455 }
3456 }
3457
3458 void setModel(String type, MutableAttributeSet attr) {
3459 if (type.equals("submit") || type.equals("reset")
3460 || type.equals("image")) {
3461
3462 // button model
3463 attr.addAttribute(StyleConstants.ModelAttribute,
3464 new DefaultButtonModel());
3465 } else if (type.equals("text")
3466 || type.equals("password")) {
3467 // plain text model
3468 int maxLength = HTML.getIntegerAttributeValue(attr,
3469 HTML.Attribute.MAXLENGTH, -1);
3470 Document doc;
3471
3472 if (maxLength > 0) {
3473 doc = new FixedLengthDocument(maxLength);
3474 } else {
3475 doc = new PlainDocument();
3476 }
3477 String value = (String) attr
3478 .getAttribute(HTML.Attribute.VALUE);
3479 try {
3480 doc.insertString(0, value, null);
3481 } catch (BadLocationException e) {
3482 }
3483 attr.addAttribute(StyleConstants.ModelAttribute,
3484 doc);
3485 } else if (type.equals("file")) {
3486 // plain text model
3487 attr.addAttribute(StyleConstants.ModelAttribute,
3488 new PlainDocument());
3489 } else if (type.equals("checkbox")
3490 || type.equals("radio")) {
3491 JToggleButton.ToggleButtonModel model = new JToggleButton.ToggleButtonModel();
3492 if (type.equals("radio")) {
3493 String name = (String) attr
3494 .getAttribute(HTML.Attribute.NAME);
3495 if (radioButtonGroupsMap == null) { //fix for 4772743
3496 radioButtonGroupsMap = new HashMap();
3497 }
3498 ButtonGroup radioButtonGroup = (ButtonGroup) radioButtonGroupsMap
3499 .get(name);
3500 if (radioButtonGroup == null) {
3501 radioButtonGroup = new ButtonGroup();
3502 radioButtonGroupsMap.put(name,
3503 radioButtonGroup);
3504 }
3505 model.setGroup(radioButtonGroup);
3506 }
3507 boolean checked = (attr
3508 .getAttribute(HTML.Attribute.CHECKED) != null);
3509 model.setSelected(checked);
3510 attr.addAttribute(StyleConstants.ModelAttribute,
3511 model);
3512 }
3513 }
3514
3515 /**
3516 * If a <SELECT> tag is being processed, this
3517 * model will be a reference to the model being filled
3518 * with the <OPTION> elements (which produce
3519 * objects of type <code>Option</code>.
3520 */
3521 Object selectModel;
3522 int optionCount;
3523 }
3524
3525 // --- utility methods used by the reader ------------------
3526
3527 /**
3528 * Pushes the current character style on a stack in preparation
3529 * for forming a new nested character style.
3530 */
3531 protected void pushCharacterStyle() {
3532 charAttrStack.push(charAttr.copyAttributes());
3533 }
3534
3535 /**
3536 * Pops a previously pushed character style off the stack
3537 * to return to a previous style.
3538 */
3539 protected void popCharacterStyle() {
3540 if (!charAttrStack.empty()) {
3541 charAttr = (MutableAttributeSet) charAttrStack.peek();
3542 charAttrStack.pop();
3543 }
3544 }
3545
3546 /**
3547 * Adds the given content to the textarea document.
3548 * This method gets called when we are in a textarea
3549 * context. Therefore all text that is seen belongs
3550 * to the text area and is hence added to the
3551 * TextAreaDocument associated with the text area.
3552 */
3553 protected void textAreaContent(char[] data) {
3554 try {
3555 textAreaDocument.insertString(textAreaDocument
3556 .getLength(), new String(data), null);
3557 } catch (BadLocationException e) {
3558 // Should do something reasonable
3559 }
3560 }
3561
3562 /**
3563 * Adds the given content that was encountered in a
3564 * PRE element. This synthesizes lines to hold the
3565 * runs of text, and makes calls to addContent to
3566 * actually add the text.
3567 */
3568 protected void preContent(char[] data) {
3569 int last = 0;
3570 for (int i = 0; i < data.length; i++) {
3571 if (data[i] == '\n') {
3572 addContent(data, last, i - last + 1);
3573 blockClose(HTML.Tag.IMPLIED);
3574 MutableAttributeSet a = new SimpleAttributeSet();
3575 a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
3576 blockOpen(HTML.Tag.IMPLIED, a);
3577 last = i + 1;
3578 }
3579 }
3580 if (last < data.length) {
3581 addContent(data, last, data.length - last);
3582 }
3583 }
3584
3585 /**
3586 * Adds an instruction to the parse buffer to create a
3587 * block element with the given attributes.
3588 */
3589 protected void blockOpen(HTML.Tag t, MutableAttributeSet attr) {
3590 if (impliedP) {
3591 blockClose(HTML.Tag.IMPLIED);
3592 }
3593
3594 inBlock++;
3595
3596 if (!canInsertTag(t, attr, true)) {
3597 return;
3598 }
3599 if (attr.isDefined(IMPLIED)) {
3600 attr.removeAttribute(IMPLIED);
3601 }
3602 lastWasNewline = false;
3603 attr.addAttribute(StyleConstants.NameAttribute, t);
3604 ElementSpec es = new ElementSpec(attr.copyAttributes(),
3605 ElementSpec.StartTagType);
3606 parseBuffer.addElement(es);
3607 }
3608
3609 /**
3610 * Adds an instruction to the parse buffer to close out
3611 * a block element of the given type.
3612 */
3613 protected void blockClose(HTML.Tag t) {
3614 inBlock--;
3615
3616 if (!foundInsertTag) {
3617 return;
3618 }
3619
3620 // Add a new line, if the last character wasn't one. This is
3621 // needed for proper positioning of the cursor. addContent
3622 // with true will force an implied paragraph to be generated if
3623 // there isn't one. This may result in a rather bogus structure
3624 // (perhaps a table with a child pargraph), but the paragraph
3625 // is needed for proper positioning and display.
3626 if (!lastWasNewline) {
3627 pushCharacterStyle();
3628 charAttr.addAttribute(IMPLIED_CR, Boolean.TRUE);
3629 addContent(NEWLINE, 0, 1, true);
3630 popCharacterStyle();
3631 lastWasNewline = true;
3632 }
3633
3634 if (impliedP) {
3635 impliedP = false;
3636 inParagraph = false;
3637 if (t != HTML.Tag.IMPLIED) {
3638 blockClose(HTML.Tag.IMPLIED);
3639 }
3640 }
3641 // an open/close with no content will be removed, so we
3642 // add a space of content to keep the element being formed.
3643 ElementSpec prev = (parseBuffer.size() > 0) ? (ElementSpec) parseBuffer
3644 .lastElement()
3645 : null;
3646 if (prev != null
3647 && prev.getType() == ElementSpec.StartTagType) {
3648 char[] one = new char[1];
3649 one[0] = ' ';
3650 addContent(one, 0, 1);
3651 }
3652 ElementSpec es = new ElementSpec(null,
3653 ElementSpec.EndTagType);
3654 parseBuffer.addElement(es);
3655 }
3656
3657 /**
3658 * Adds some text with the current character attributes.
3659 *
3660 * @param data the content to add
3661 * @param offs the initial offset
3662 * @param length the length
3663 */
3664 protected void addContent(char[] data, int offs, int length) {
3665 addContent(data, offs, length, true);
3666 }
3667
3668 /**
3669 * Adds some text with the current character attributes.
3670 *
3671 * @param data the content to add
3672 * @param offs the initial offset
3673 * @param length the length
3674 * @param generateImpliedPIfNecessary whether to generate implied
3675 * paragraphs
3676 */
3677 protected void addContent(char[] data, int offs, int length,
3678 boolean generateImpliedPIfNecessary) {
3679 if (!foundInsertTag) {
3680 return;
3681 }
3682
3683 if (generateImpliedPIfNecessary && (!inParagraph)
3684 && (!inPre)) {
3685 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
3686 inParagraph = true;
3687 impliedP = true;
3688 }
3689 emptyAnchor = false;
3690 charAttr.addAttribute(StyleConstants.NameAttribute,
3691 HTML.Tag.CONTENT);
3692 AttributeSet a = charAttr.copyAttributes();
3693 ElementSpec es = new ElementSpec(a,
3694 ElementSpec.ContentType, data, offs, length);
3695 parseBuffer.addElement(es);
3696
3697 if (parseBuffer.size() > threshold) {
3698 if (threshold <= MaxThreshold) {
3699 threshold *= StepThreshold;
3700 }
3701 try {
3702 flushBuffer(false);
3703 } catch (BadLocationException ble) {
3704 }
3705 }
3706 if (length > 0) {
3707 lastWasNewline = (data[offs + length - 1] == '\n');
3708 }
3709 }
3710
3711 /**
3712 * Adds content that is basically specified entirely
3713 * in the attribute set.
3714 */
3715 protected void addSpecialElement(HTML.Tag t,
3716 MutableAttributeSet a) {
3717 if ((t != HTML.Tag.FRAME) && (!inParagraph) && (!inPre)) {
3718 nextTagAfterPImplied = t;
3719 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
3720 nextTagAfterPImplied = null;
3721 inParagraph = true;
3722 impliedP = true;
3723 }
3724 if (!canInsertTag(t, a, t.isBlock())) {
3725 return;
3726 }
3727 if (a.isDefined(IMPLIED)) {
3728 a.removeAttribute(IMPLIED);
3729 }
3730 emptyAnchor = false;
3731 a.addAttributes(charAttr);
3732 a.addAttribute(StyleConstants.NameAttribute, t);
3733 char[] one = new char[1];
3734 one[0] = ' ';
3735 ElementSpec es = new ElementSpec(a.copyAttributes(),
3736 ElementSpec.ContentType, one, 0, 1);
3737 parseBuffer.addElement(es);
3738 // Set this to avoid generating a newline for frames, frames
3739 // shouldn't have any content, and shouldn't need a newline.
3740 if (t == HTML.Tag.FRAME) {
3741 lastWasNewline = true;
3742 }
3743 }
3744
3745 /**
3746 * Flushes the current parse buffer into the document.
3747 * @param endOfStream true if there is no more content to parser
3748 */
3749 void flushBuffer(boolean endOfStream)
3750 throws BadLocationException {
3751 int oldLength = HTMLDocument.this .getLength();
3752 int size = parseBuffer.size();
3753 if (endOfStream
3754 && (insertTag != null || insertAfterImplied)
3755 && size > 0) {
3756 adjustEndSpecsForPartialInsert();
3757 size = parseBuffer.size();
3758 }
3759 ElementSpec[] spec = new ElementSpec[size];
3760 parseBuffer.copyInto(spec);
3761
3762 if (oldLength == 0
3763 && (insertTag == null && !insertAfterImplied)) {
3764 create(spec);
3765 } else {
3766 insert(offset, spec);
3767 }
3768 parseBuffer.removeAllElements();
3769 offset += HTMLDocument.this .getLength() - oldLength;
3770 flushCount++;
3771 }
3772
3773 /**
3774 * This will be invoked for the last flush, if <code>insertTag</code>
3775 * is non null.
3776 */
3777 private void adjustEndSpecsForPartialInsert() {
3778 int size = parseBuffer.size();
3779 if (insertTagDepthDelta < 0) {
3780 // When inserting via an insertTag, the depths (of the tree
3781 // being read in, and existing hiearchy) may not match up.
3782 // This attemps to clean it up.
3783 int removeCounter = insertTagDepthDelta;
3784 while (removeCounter < 0
3785 && size >= 0
3786 && ((ElementSpec) parseBuffer
3787 .elementAt(size - 1)).getType() == ElementSpec.EndTagType) {
3788 parseBuffer.removeElementAt(--size);
3789 removeCounter++;
3790 }
3791 }
3792 if (flushCount == 0
3793 && (!insertAfterImplied || !wantsTrailingNewline)) {
3794 // If this starts with content (or popDepth > 0 &&
3795 // pushDepth > 0) and ends with EndTagTypes, make sure
3796 // the last content isn't a \n, otherwise will end up with
3797 // an extra \n in the middle of content.
3798 int index = 0;
3799 if (pushDepth > 0) {
3800 if (((ElementSpec) parseBuffer.elementAt(0))
3801 .getType() == ElementSpec.ContentType) {
3802 index++;
3803 }
3804 }
3805 index += (popDepth + pushDepth);
3806 int cCount = 0;
3807 int cStart = index;
3808 while (index < size
3809 && ((ElementSpec) parseBuffer.elementAt(index))
3810 .getType() == ElementSpec.ContentType) {
3811 index++;
3812 cCount++;
3813 }
3814 if (cCount > 1) {
3815 while (index < size
3816 && ((ElementSpec) parseBuffer
3817 .elementAt(index)).getType() == ElementSpec.EndTagType) {
3818 index++;
3819 }
3820 if (index == size) {
3821 char[] lastText = ((ElementSpec) parseBuffer
3822 .elementAt(cStart + cCount - 1))
3823 .getArray();
3824 if (lastText.length == 1
3825 && lastText[0] == NEWLINE[0]) {
3826 index = cStart + cCount - 1;
3827 while (size > index) {
3828 parseBuffer.removeElementAt(--size);
3829 }
3830 }
3831 }
3832 }
3833 }
3834 if (wantsTrailingNewline) {
3835 // Make sure there is in fact a newline
3836 for (int counter = parseBuffer.size() - 1; counter >= 0; counter--) {
3837 ElementSpec spec = (ElementSpec) parseBuffer
3838 .elementAt(counter);
3839 if (spec.getType() == ElementSpec.ContentType) {
3840 if (spec.getArray()[spec.getLength() - 1] != '\n') {
3841 SimpleAttributeSet attrs = new SimpleAttributeSet();
3842
3843 attrs.addAttribute(
3844 StyleConstants.NameAttribute,
3845 HTML.Tag.CONTENT);
3846 parseBuffer
3847 .insertElementAt(new ElementSpec(
3848 attrs,
3849 ElementSpec.ContentType,
3850 NEWLINE, 0, 1), counter + 1);
3851 }
3852 break;
3853 }
3854 }
3855 }
3856 }
3857
3858 /**
3859 * Adds the CSS rules in <code>rules</code>.
3860 */
3861 void addCSSRules(String rules) {
3862 StyleSheet ss = getStyleSheet();
3863 ss.addRule(rules);
3864 }
3865
3866 /**
3867 * Adds the CSS stylesheet at <code>href</code> to the known list
3868 * of stylesheets.
3869 */
3870 void linkCSSStyleSheet(String href) {
3871 URL url = null;
3872 try {
3873 url = new URL(base, href);
3874 } catch (MalformedURLException mfe) {
3875 try {
3876 url = new URL(href);
3877 } catch (MalformedURLException mfe2) {
3878 url = null;
3879 }
3880 }
3881 if (url != null) {
3882 getStyleSheet().importStyleSheet(url);
3883 }
3884 }
3885
3886 /**
3887 * Returns true if can insert starting at <code>t</code>. This
3888 * will return false if the insert tag is set, and hasn't been found
3889 * yet.
3890 */
3891 private boolean canInsertTag(HTML.Tag t, AttributeSet attr,
3892 boolean isBlockTag) {
3893 if (!foundInsertTag) {
3894 boolean needPImplied = ((t == HTML.Tag.IMPLIED)
3895 && (!inParagraph) && (!inPre));
3896 if (needPImplied && (nextTagAfterPImplied != null)) {
3897
3898 /*
3899 * If insertTag == null then just proceed to
3900 * foundInsertTag() call below and return true.
3901 */
3902 if (insertTag != null) {
3903 boolean nextTagIsInsertTag = isInsertTag(nextTagAfterPImplied);
3904 if ((!nextTagIsInsertTag) || (!insertInsertTag)) {
3905 return false;
3906 }
3907 }
3908 /*
3909 * Proceed to foundInsertTag() call...
3910 */
3911 } else if ((insertTag != null && !isInsertTag(t))
3912 || (insertAfterImplied && (attr == null
3913 || attr.isDefined(IMPLIED) || t == HTML.Tag.IMPLIED))) {
3914 return false;
3915 }
3916
3917 // Allow the insert if t matches the insert tag, or
3918 // insertAfterImplied is true and the element is implied.
3919 foundInsertTag(isBlockTag);
3920 if (!insertInsertTag) {
3921 return false;
3922 }
3923 }
3924 return true;
3925 }
3926
3927 private boolean isInsertTag(HTML.Tag tag) {
3928 return (insertTag == tag);
3929 }
3930
3931 private void foundInsertTag(boolean isBlockTag) {
3932 foundInsertTag = true;
3933 if (!insertAfterImplied && (popDepth > 0 || pushDepth > 0)) {
3934 try {
3935 if (offset == 0
3936 || !getText(offset - 1, 1).equals("\n")) {
3937 // Need to insert a newline.
3938 AttributeSet newAttrs = null;
3939 boolean joinP = true;
3940
3941 if (offset != 0) {
3942 // Determine if we can use JoinPrevious, we can't
3943 // if the Element has some attributes that are
3944 // not meant to be duplicated.
3945 Element charElement = getCharacterElement(offset - 1);
3946 AttributeSet attrs = charElement
3947 .getAttributes();
3948
3949 if (attrs
3950 .isDefined(StyleConstants.ComposedTextAttribute)) {
3951 joinP = false;
3952 } else {
3953 Object name = attrs
3954 .getAttribute(StyleConstants.NameAttribute);
3955 if (name instanceof HTML.Tag) {
3956 HTML.Tag tag = (HTML.Tag) name;
3957 if (tag == HTML.Tag.IMG
3958 || tag == HTML.Tag.HR
3959 || tag == HTML.Tag.COMMENT
3960 || (tag instanceof HTML.UnknownTag)) {
3961 joinP = false;
3962 }
3963 }
3964 }
3965 }
3966 if (!joinP) {
3967 // If not joining with the previous element, be
3968 // sure and set the name (otherwise it will be
3969 // inherited).
3970 newAttrs = new SimpleAttributeSet();
3971 ((SimpleAttributeSet) newAttrs)
3972 .addAttribute(
3973 StyleConstants.NameAttribute,
3974 HTML.Tag.CONTENT);
3975 }
3976 ElementSpec es = new ElementSpec(newAttrs,
3977 ElementSpec.ContentType, NEWLINE, 0,
3978 NEWLINE.length);
3979 if (joinP) {
3980 es
3981 .setDirection(ElementSpec.JoinPreviousDirection);
3982 }
3983 parseBuffer.addElement(es);
3984 }
3985 } catch (BadLocationException ble) {
3986 }
3987 }
3988 // pops
3989 for (int counter = 0; counter < popDepth; counter++) {
3990 parseBuffer.addElement(new ElementSpec(null,
3991 ElementSpec.EndTagType));
3992 }
3993 // pushes
3994 for (int counter = 0; counter < pushDepth; counter++) {
3995 ElementSpec es = new ElementSpec(null,
3996 ElementSpec.StartTagType);
3997 es.setDirection(ElementSpec.JoinNextDirection);
3998 parseBuffer.addElement(es);
3999 }
4000 insertTagDepthDelta = depthTo(Math.max(0, offset - 1))
4001 - popDepth + pushDepth - inBlock;
4002 if (isBlockTag) {
4003 // A start spec will be added (for this tag), so we account
4004 // for it here.
4005 insertTagDepthDelta++;
4006 } else {
4007 // An implied paragraph close (end spec) is going to be added,
4008 // so we account for it here.
4009 insertTagDepthDelta--;
4010 inParagraph = true;
4011 lastWasNewline = false;
4012 }
4013 }
4014
4015 /**
4016 * This is set to true when and end is invoked for <html>.
4017 */
4018 private boolean receivedEndHTML;
4019 /** Number of times <code>flushBuffer</code> has been invoked. */
4020 private int flushCount;
4021 /** If true, behavior is similiar to insertTag, but instead of
4022 * waiting for insertTag will wait for first Element without
4023 * an 'implied' attribute and begin inserting then. */
4024 private boolean insertAfterImplied;
4025 /** This is only used if insertAfterImplied is true. If false, only
4026 * inserting content, and there is a trailing newline it is removed. */
4027 private boolean wantsTrailingNewline;
4028 int threshold;
4029 int offset;
4030 boolean inParagraph = false;
4031 boolean impliedP = false;
4032 boolean inPre = false;
4033 boolean inTextArea = false;
4034 TextAreaDocument textAreaDocument = null;
4035 boolean inTitle = false;
4036 boolean lastWasNewline = true;
4037 boolean emptyAnchor;
4038 /** True if (!emptyDocument && insertTag == null), this is used so
4039 * much it is cached. */
4040 boolean midInsert;
4041 /** True when the body has been encountered. */
4042 boolean inBody;
4043 /** If non null, gives parent Tag that insert is to happen at. */
4044 HTML.Tag insertTag;
4045 /** If true, the insertTag is inserted, otherwise elements after
4046 * the insertTag is found are inserted. */
4047 boolean insertInsertTag;
4048 /** Set to true when insertTag has been found. */
4049 boolean foundInsertTag;
4050 /** When foundInsertTag is set to true, this will be updated to
4051 * reflect the delta between the two structures. That is, it
4052 * will be the depth the inserts are happening at minus the
4053 * depth of the tags being passed in. A value of 0 (the common
4054 * case) indicates the structures match, a value greater than 0 indicates
4055 * the insert is happening at a deeper depth than the stream is
4056 * parsing, and a value less than 0 indicates the insert is happening earlier
4057 * in the tree that the parser thinks and that we will need to remove
4058 * EndTagType specs in the flushBuffer method.
4059 */
4060 int insertTagDepthDelta;
4061 /** How many parents to ascend before insert new elements. */
4062 int popDepth;
4063 /** How many parents to descend (relative to popDepth) before
4064 * inserting. */
4065 int pushDepth;
4066 /** Last Map that was encountered. */
4067 Map lastMap;
4068 /** Set to true when a style element is encountered. */
4069 boolean inStyle = false;
4070 /** Name of style to use. Obtained from Meta tag. */
4071 String defaultStyle;
4072 /** Vector describing styles that should be include. Will consist
4073 * of a bunch of HTML.Tags, which will either be:
4074 * <p>LINK: in which case it is followed by an AttributeSet
4075 * <p>STYLE: in which case the following element is a String
4076 * indicating the type (may be null), and the elements following
4077 * it until the next HTML.Tag are the rules as Strings.
4078 */
4079 Vector styles;
4080 /** True if inside the head tag. */
4081 boolean inHead = false;
4082 /** Set to true if the style language is text/css. Since this is
4083 * used alot, it is cached. */
4084 boolean isStyleCSS;
4085 /** True if inserting into an empty document. */
4086 boolean emptyDocument;
4087 /** Attributes from a style Attribute. */
4088 AttributeSet styleAttributes;
4089
4090 /**
4091 * Current option, if in an option element (needed to
4092 * load the label.
4093 */
4094 Option option;
4095
4096 protected Vector<ElementSpec> parseBuffer = new Vector(); // Vector<ElementSpec>
4097 protected MutableAttributeSet charAttr = new TaggedAttributeSet();
4098 Stack charAttrStack = new Stack();
4099 Hashtable tagMap;
4100 int inBlock = 0;
4101
4102 /**
4103 * This attribute is sometimes used to refer to next tag
4104 * to be handled after p-implied when the latter is
4105 * the current tag which is being handled.
4106 */
4107 private HTML.Tag nextTagAfterPImplied = null;
4108 }
4109
4110 /**
4111 * Used by StyleSheet to determine when to avoid removing HTML.Tags
4112 * matching StyleConstants.
4113 */
4114 static class TaggedAttributeSet extends SimpleAttributeSet {
4115 TaggedAttributeSet() {
4116 super ();
4117 }
4118 }
4119
4120 /**
4121 * An element that represents a chunk of text that has
4122 * a set of HTML character level attributes assigned to
4123 * it.
4124 */
4125 public class RunElement extends LeafElement {
4126
4127 /**
4128 * Constructs an element that represents content within the
4129 * document (has no children).
4130 *
4131 * @param parent the parent element
4132 * @param a the element attributes
4133 * @param offs0 the start offset (must be at least 0)
4134 * @param offs1 the end offset (must be at least offs0)
4135 * @since 1.4
4136 */
4137 public RunElement(Element parent, AttributeSet a, int offs0,
4138 int offs1) {
4139 super (parent, a, offs0, offs1);
4140 }
4141
4142 /**
4143 * Gets the name of the element.
4144 *
4145 * @return the name, null if none
4146 */
4147 public String getName() {
4148 Object o = getAttribute(StyleConstants.NameAttribute);
4149 if (o != null) {
4150 return o.toString();
4151 }
4152 return super .getName();
4153 }
4154
4155 /**
4156 * Gets the resolving parent. HTML attributes are not inherited
4157 * at the model level so we override this to return null.
4158 *
4159 * @return null, there are none
4160 * @see AttributeSet#getResolveParent
4161 */
4162 public AttributeSet getResolveParent() {
4163 return null;
4164 }
4165 }
4166
4167 /**
4168 * An element that represents a structural <em>block</em> of
4169 * HTML.
4170 */
4171 public class BlockElement extends BranchElement {
4172
4173 /**
4174 * Constructs a composite element that initially contains
4175 * no children.
4176 *
4177 * @param parent the parent element
4178 * @param a the attributes for the element
4179 * @since 1.4
4180 */
4181 public BlockElement(Element parent, AttributeSet a) {
4182 super (parent, a);
4183 }
4184
4185 /**
4186 * Gets the name of the element.
4187 *
4188 * @return the name, null if none
4189 */
4190 public String getName() {
4191 Object o = getAttribute(StyleConstants.NameAttribute);
4192 if (o != null) {
4193 return o.toString();
4194 }
4195 return super .getName();
4196 }
4197
4198 /**
4199 * Gets the resolving parent. HTML attributes are not inherited
4200 * at the model level so we override this to return null.
4201 *
4202 * @return null, there are none
4203 * @see AttributeSet#getResolveParent
4204 */
4205 public AttributeSet getResolveParent() {
4206 return null;
4207 }
4208
4209 }
4210
4211 /**
4212 * Document that allows you to set the maximum length of the text.
4213 */
4214 private static class FixedLengthDocument extends PlainDocument {
4215 private int maxLength;
4216
4217 public FixedLengthDocument(int maxLength) {
4218 this .maxLength = maxLength;
4219 }
4220
4221 public void insertString(int offset, String str, AttributeSet a)
4222 throws BadLocationException {
4223 if (str != null && str.length() + getLength() <= maxLength) {
4224 super.insertString(offset, str, a);
4225 }
4226 }
4227 }
4228 }
|