History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/text/html/HTMLDocument.java (1 of 18352 files). Show all

Unified · Split

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