1 /*
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 */
25 package javax.swing.text.html;
26
27 import java.awt.Color;
28 import java.awt.Component;
29 import java.awt.font.TextAttribute;
30 import java.util;
31 import java.net.URL;
32 import java.net.URLEncoder;
33 import java.net.MalformedURLException;
34 import java.io;
35 import javax.swing;
36 import javax.swing.event;
37 import javax.swing.text;
38 import javax.swing.undo;
39 import java.text.Bidi;
40 import 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 <BASE> 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 * <html>
114 * <head>
115 * <title>An example HTMLDocument</title>
116 * <style type="text/css">
117 * div { background-color: silver; }
118 * ul { color: red; }
119 * </style>
120 * </head>
121 * <body>
122 * <div id="BOX">
123 * <p>Paragraph 1</p>
124 * <p>Paragraph 2</p>
125 * </div>
126 * </body>
127 * </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, "<ul><li>List
156 * Item</li></ul>")</code> inserts the list before the first
157 * paragraph, and <code>d.insertBeforeEnd(e, "<ul><li>List
158 * Item</li></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 * "<ul><li>List Item</li></ul>")</code> inserts the list
167 * before the <code>DIV</code> element, and <code>d.insertAfterEnd(e,
168 * "<ul><li>List Item</li></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, "<ul><li>List
178 * Item</li></ul>")</code> replaces all children paragraphs with
179 * the list, and <code>d.setOuterHTML(e, "<ul><li>List
180 * Item</li></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 */
276 public 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 <FRAMESET> element, and inserts a new <FRAME>
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 * <body>
992 * |
993 * <b><div></b>
994 * / \
995 * <p> <p>
996 * </pre>
997 *
998 * <p>Invoking <code>setInnerHTML(elem, "<ul><li>")</code>
999 * results in the following structure (new elements are <font
1000 * color="red">in red</font>).</p>
1001 *
1002 * <pre>
1003 * <body>
1004 * |
1005 * <b><div></b>
1006 * \
1007 * <font color="red"><ul></font>
1008 * \
1009 * <font color="red"><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 <img> 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 * <body>
1073 * |
1074 * <b><div></b>
1075 * / \
1076 * <p> <p>
1077 * </pre>
1078 *
1079 * <p>Invoking <code>setOuterHTML(elem, "<ul><li>")</code>
1080 * results in the following structure (new elements are <font
1081 * color="red">in red</font>).</p>
1082 *
1083 * <pre>
1084 * <body>
1085 * |
1086 * <font color="red"><ul></font>
1087 * \
1088 * <font color="red"><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 * <body>
1143 * |
1144 * <b><div></b>
1145 * / \
1146 * <p> <p>
1147 * </pre>
1148 *
1149 * <p>Invoking <code>insertAfterStart(elem,
1150 * "<ul><li>")</code> results in the following structure
1151 * (new elements are <font color="red">in red</font>).</p>
1152 *
1153 * <pre>
1154 * <body>
1155 * |
1156 * <b><div></b>
1157 * / | \
1158 * <font color="red"><ul></font> <p> <p>
1159 * /
1160 * <font color="red"><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 * <body>
1208 * |
1209 * <b><div></b>
1210 * / \
1211 * <p> <p>
1212 * </pre>
1213 *
1214 * <p>Invoking <code>insertBeforeEnd(elem, "<ul><li>")</code>
1215 * results in the following structure (new elements are <font
1216 * color="red">in red</font>).</p>
1217 *
1218 * <pre>
1219 * <body>
1220 * |
1221 * <b><div></b>
1222 * / | \
1223 * <p> <p> <font color="red"><ul></font>
1224 * \
1225 * <font color="red"><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 * <body>
1275 * |
1276 * <b><div></b>
1277 * / \
1278 * <p> <p>
1279 * </pre>
1280 *
1281 * <p>Invoking <code>insertBeforeStart(elem,
1282 * "<ul><li>")</code> results in the following structure
1283 * (new elements are <font color="red">in red</font>).</p>
1284 *
1285 * <pre>
1286 * <body>
1287 * / \
1288 * <font color="red"><ul></font> <b><div></b>
1289 * / / \
1290 * <font color="red"><li></font> <p> <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 * <body>
1333 * |
1334 * <b><div></b>
1335 * / \
1336 * <p> <p>
1337 * </pre>
1338 *
1339 * <p>Invoking <code>insertAfterEnd(elem, "<ul><li>")</code>
1340 * results in the following structure (new elements are <font
1341 * color="red">in red</font>).</p>
1342 *
1343 * <pre>
1344 * <body>
1345 * / \
1346 * <b><div></b> <font color="red"><ul></font>
1347 * / \ \
1348 * <p> <p> <font color="red"><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><a id="HelloThere"></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, <APPLET>, <PARAM>, <MAP>, <AREA>, <LINK>,
2087 * <SCRIPT> and <STYLE> 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 </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