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