History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

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

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

Unified · Split

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