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 <BASE> 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 * <html>
111 * <head>
112 * <title>An example HTMLDocument</title>
113 * <style type="text/css">
114 * div { background-color: silver; }
115 * ul { color: red; }
116 * </style>
117 * </head>
118 * <body>
119 * <div id="BOX">
120 * <p>Paragraph 1</p>
121 * <p>Paragraph 2</p>
122 * </div>
123 * </body>
124 * </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, "<ul><li>List
153 * Item</li></ul>")</code> inserts the list before the first
154 * paragraph, and <code>d.insertBeforeEnd(e, "<ul><li>List
155 * Item</li></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 * "<ul><li>List Item</li></ul>")</code> inserts the list
164 * before the <code>DIV</code> element, and <code>d.insertAfterEnd(e,
165 * "<ul><li>List Item</li></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, "<ul><li>List
175 * Item</li></ul>")</code> replaces all children paragraphs with
176 * the list, and <code>d.setOuterHTML(e, "<ul><li>List
177 * Item</li></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 summary="HTML Content of example above">
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 style="white-space: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 style="white-space: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 style="white-space:nowrap">
246 * <ul style="color: red;">
247 * <li>List Item</li>
248 * </ul>
249 * </div>
250 * </td>
251 * <!--setOuterHTML-->
252 * <td style="white-space: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™
265 * 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 @SuppressWarnings("serial") // Same-version serialization only
274 public class HTMLDocument extends DefaultStyledDocument {
275 /**
276 * Constructs an HTML document using the default buffer size
277 * and a default <code>StyleSheet</code>. This is a convenience
278 * method for the constructor
279 * <code>HTMLDocument(Content, StyleSheet)</code>.
280 */
281 public HTMLDocument() {
282 this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet());
283 }
284
285 /**
286 * Constructs an HTML document with the default content
287 * storage implementation and the specified style/attribute
288 * storage mechanism. This is a convenience method for the
289 * constructor
290 * <code>HTMLDocument(Content, StyleSheet)</code>.
291 *
292 * @param styles the styles
293 */
294 public HTMLDocument(StyleSheet styles) {
295 this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
296 }
297
298 /**
299 * Constructs an HTML document with the given content
300 * storage implementation and the given style/attribute
301 * storage mechanism.
302 *
303 * @param c the container for the content
304 * @param styles the styles
305 */
306 public HTMLDocument(Content c, StyleSheet styles) {
307 super(c, styles);
308 }
309
310 /**
311 * Fetches the reader for the parser to use when loading the document
312 * with HTML. This is implemented to return an instance of
313 * <code>HTMLDocument.HTMLReader</code>.
314 * Subclasses can reimplement this
315 * method to change how the document gets structured if desired.
316 * (For example, to handle custom tags, or structurally represent character
317 * style elements.)
318 *
319 * @param pos the starting position
320 * @return the reader used by the parser to load the document
321 */
322 public HTMLEditorKit.ParserCallback getReader(int pos) {
323 Object desc = getProperty(Document.StreamDescriptionProperty);
324 if (desc instanceof URL) {
325 setBase((URL)desc);
326 }
327 HTMLReader reader = new HTMLReader(pos);
328 return reader;
329 }
330
331 /**
332 * Returns the reader for the parser to use to load the document
333 * with HTML. This is implemented to return an instance of
334 * <code>HTMLDocument.HTMLReader</code>.
335 * Subclasses can reimplement this
336 * method to change how the document gets structured if desired.
337 * (For example, to handle custom tags, or structurally represent character
338 * style elements.)
339 * <p>This is a convenience method for
340 * <code>getReader(int, int, int, HTML.Tag, TRUE)</code>.
341 *
342 * @param pos the starting position
343 * @param popDepth the number of <code>ElementSpec.EndTagTypes</code>
344 * to generate before inserting
345 * @param pushDepth the number of <code>ElementSpec.StartTagTypes</code>
346 * with a direction of <code>ElementSpec.JoinNextDirection</code>
347 * that should be generated before inserting,
348 * but after the end tags have been generated
349 * @param insertTag the first tag to start inserting into document
350 * @return the reader used by the parser to load the document
351 */
352 public HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
353 int pushDepth,
354 HTML.Tag insertTag) {
355 return getReader(pos, popDepth, pushDepth, insertTag, true);
356 }
357
358 /**
359 * Fetches the reader for the parser to use to load the document
360 * with HTML. This is implemented to return an instance of
361 * HTMLDocument.HTMLReader. Subclasses can reimplement this
362 * method to change how the document get structured if desired
363 * (e.g. to handle custom tags, structurally represent character
364 * style elements, etc.).
365 *
366 * @param popDepth the number of <code>ElementSpec.EndTagTypes</code>
367 * to generate before inserting
368 * @param pushDepth the number of <code>ElementSpec.StartTagTypes</code>
369 * with a direction of <code>ElementSpec.JoinNextDirection</code>
370 * that should be generated before inserting,
371 * but after the end tags have been generated
372 * @param insertTag the first tag to start inserting into document
373 * @param insertInsertTag false if all the Elements after insertTag should
374 * be inserted; otherwise insertTag will be inserted
375 * @return the reader used by the parser to load the document
376 */
377 HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
378 int pushDepth,
379 HTML.Tag insertTag,
380 boolean insertInsertTag) {
381 Object desc = getProperty(Document.StreamDescriptionProperty);
382 if (desc instanceof URL) {
383 setBase((URL)desc);
384 }
385 HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth,
386 insertTag, insertInsertTag, false,
387 true);
388 return reader;
389 }
390
391 /**
392 * Returns the location to resolve relative URLs against. By
393 * default this will be the document's URL if the document
394 * was loaded from a URL. If a base tag is found and
395 * can be parsed, it will be used as the base location.
396 *
397 * @return the base location
398 */
399 public URL getBase() {
400 return base;
401 }
402
403 /**
404 * Sets the location to resolve relative URLs against. By
405 * default this will be the document's URL if the document
406 * was loaded from a URL. If a base tag is found and
407 * can be parsed, it will be used as the base location.
408 * <p>This also sets the base of the <code>StyleSheet</code>
409 * to be <code>u</code> as well as the base of the document.
410 *
411 * @param u the desired base URL
412 */
413 public void setBase(URL u) {
414 base = u;
415 getStyleSheet().setBase(u);
416 }
417
418 /**
419 * Inserts new elements in bulk. This is how elements get created
420 * in the document. The parsing determines what structure is needed
421 * and creates the specification as a set of tokens that describe the
422 * edit while leaving the document free of a write-lock. This method
423 * can then be called in bursts by the reader to acquire a write-lock
424 * for a shorter duration (i.e. while the document is actually being
425 * altered).
426 *
427 * @param offset the starting offset
428 * @param data the element data
429 * @exception BadLocationException if the given position does not
507 else {
508 lastEnd = paragraph.getEndOffset();
509 }
510 MutableAttributeSet attr =
511 (MutableAttributeSet) paragraph.getAttributes();
512 changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
513 if (replace) {
514 attr.removeAttributes(attr);
515 }
516 attr.addAttributes(s);
517 }
518 changes.end();
519 fireChangedUpdate(changes);
520 fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
521 } finally {
522 writeUnlock();
523 }
524 }
525
526 /**
527 * Fetches the <code>StyleSheet</code> with the document-specific display
528 * rules (CSS) that were specified in the HTML document itself.
529 *
530 * @return the <code>StyleSheet</code>
531 */
532 public StyleSheet getStyleSheet() {
533 return (StyleSheet) getAttributeContext();
534 }
535
536 /**
537 * Fetches an iterator for the specified HTML tag.
538 * This can be used for things like iterating over the
539 * set of anchors contained, or iterating over the input
540 * elements.
541 *
542 * @param t the requested <code>HTML.Tag</code>
543 * @return the <code>Iterator</code> for the given HTML tag
544 * @see javax.swing.text.html.HTML.Tag
545 */
546 public Iterator getIterator(HTML.Tag t) {
547 if (t.isBlock()) {
548 // TBD
549 return null;
550 }
551 return new LeafIterator(t, this);
552 }
553
554 /**
555 * Creates a document leaf element that directly represents
556 * text (doesn't have any children). This is implemented
557 * to return an element of type
558 * <code>HTMLDocument.RunElement</code>.
559 *
560 * @param parent the parent element
561 * @param a the attributes for the element
562 * @param p0 the beginning of the range (must be at least 0)
563 * @param p1 the end of the range (must be at least p0)
564 * @return the new element
565 */
566 protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
567 return new RunElement(parent, a, p0, p1);
568 }
569
570 /**
571 * Creates a document branch element, that can contain other elements.
572 * This is implemented to return an element of type
573 * <code>HTMLDocument.BlockElement</code>.
574 *
575 * @param parent the parent element
576 * @param a the attributes
577 * @return the element
578 */
579 protected Element createBranchElement(Element parent, AttributeSet a) {
580 return new BlockElement(parent, a);
581 }
582
583 /**
584 * Creates the root element to be used to represent the
585 * default document structure.
586 *
587 * @return the element base
588 */
589 protected AbstractElement createDefaultRoot() {
590 // grabs a write-lock for this initialization and
591 // abandon it during initialization so in normal
592 // operation we can detect an illegitimate attempt
593 // to mutate attributes.
612 body.replace(0, 0, buff);
613 buff[0] = body;
614 html.replace(0, 0, buff);
615 writeUnlock();
616 return html;
617 }
618
619 /**
620 * Sets the number of tokens to buffer before trying to update
621 * the documents element structure.
622 *
623 * @param n the number of tokens to buffer
624 */
625 public void setTokenThreshold(int n) {
626 putProperty(TokenThreshold, n);
627 }
628
629 /**
630 * Gets the number of tokens to buffer before trying to update
631 * the documents element structure. The default value is
632 * <code>Integer.MAX_VALUE</code>.
633 *
634 * @return the number of tokens to buffer
635 */
636 public int getTokenThreshold() {
637 Integer i = (Integer) getProperty(TokenThreshold);
638 if (i != null) {
639 return i.intValue();
640 }
641 return Integer.MAX_VALUE;
642 }
643
644 /**
645 * Determines how unknown tags are handled by the parser.
646 * If set to true, unknown
647 * tags are put in the model, otherwise they are dropped.
648 *
649 * @param preservesTags true if unknown tags should be
650 * saved in the model, otherwise tags are dropped
651 * @see javax.swing.text.html.HTML.Tag
652 */
653 public void setPreservesUnknownTags(boolean preservesTags) {
654 preservesUnknownTags = preservesTags;
655 }
656
657 /**
658 * Returns the behavior the parser observes when encountering
659 * unknown tags.
660 *
661 * @see javax.swing.text.html.HTML.Tag
662 * @return true if unknown tags are to be preserved when parsing
663 */
664 public boolean getPreservesUnknownTags() {
665 return preservesUnknownTags;
666 }
667
668 /**
669 * Processes <code>HyperlinkEvents</code> that
670 * are generated by documents in an HTML frame.
671 * The <code>HyperlinkEvent</code> type, as the parameter suggests,
672 * is <code>HTMLFrameHyperlinkEvent</code>.
673 * In addition to the typical information contained in a
674 * <code>HyperlinkEvent</code>,
675 * this event contains the element that corresponds to the frame in
676 * which the click happened (the source element) and the
677 * target name. The target name has 4 possible values:
678 * <ul>
679 * <li> _self
680 * <li> _parent
681 * <li> _top
682 * <li> a named frame
683 * </ul>
684 *
685 * If target is _self, the action is to change the value of the
686 * <code>HTML.Attribute.SRC</code> attribute and fires a
687 * <code>ChangedUpdate</code> event.
688 *<p>
689 * If the target is _parent, then it deletes the parent element,
690 * which is a <FRAMESET> element, and inserts a new <FRAME>
691 * element, and sets its <code>HTML.Attribute.SRC</code> attribute
692 * to have a value equal to the destination URL and fire a
693 * <code>RemovedUpdate</code> and <code>InsertUpdate</code>.
694 *<p>
695 * If the target is _top, this method does nothing. In the implementation
696 * of the view for a frame, namely the <code>FrameView</code>,
697 * the processing of _top is handled. Given that _top implies
698 * replacing the entire document, it made sense to handle this outside
699 * of the document that it will replace.
700 *<p>
701 * If the target is a named frame, then the element hierarchy is searched
702 * for an element with a name equal to the target, its
703 * <code>HTML.Attribute.SRC</code> attribute is updated and a
704 * <code>ChangedUpdate</code> event is fired.
705 *
706 * @param e the event
707 */
708 public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent e) {
709 String frameName = e.getTarget();
710 Element element = e.getSourceElement();
711 String urlStr = e.getURL().toString();
712
713 if (frameName.equals("_self")) {
714 /*
715 The source and destination elements
716 are the same.
717 */
718 updateFrame(element, urlStr);
719 } else if (frameName.equals("_parent")) {
720 /*
721 The destination is the parent of the frame.
722 */
723 updateFrameSet(element.getParentElement(), urlStr);
724 } else {
725 /*
726 locate a named frame
727 */
728 Element targetElement = findFrame(frameName);
729 if (targetElement != null) {
730 updateFrame(targetElement, urlStr);
731 }
732 }
733 }
734
735
736 /**
737 * Searches the element hierarchy for an FRAME element
738 * that has its name attribute equal to the <code>frameName</code>.
739 *
740 * @param frameName
741 * @return the element whose NAME attribute has a value of
742 * <code>frameName</code>; returns <code>null</code>
743 * if not found
744 */
745 private Element findFrame(String frameName) {
746 ElementIterator it = new ElementIterator(this);
747 Element next;
748
749 while ((next = it.next()) != null) {
750 AttributeSet attr = next.getAttributes();
751 if (matchNameAttribute(attr, HTML.Tag.FRAME)) {
752 String frameTarget = (String)attr.getAttribute(HTML.Attribute.NAME);
753 if (frameTarget != null && frameTarget.equals(frameName)) {
754 break;
755 }
756 }
757 }
758 return next;
759 }
760
761 /**
762 * Returns true if <code>StyleConstants.NameAttribute</code> is
763 * equal to the tag that is passed in as a parameter.
764 *
765 * @param attr the attributes to be matched
766 * @param tag the value to be matched
767 * @return true if there is a match, false otherwise
768 * @see javax.swing.text.html.HTML.Attribute
769 */
770 static boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
771 Object o = attr.getAttribute(StyleConstants.NameAttribute);
772 if (o instanceof HTML.Tag) {
773 HTML.Tag name = (HTML.Tag) o;
774 if (name == tag) {
775 return true;
776 }
777 }
778 return false;
779 }
780
781 /**
782 * Replaces a frameset branch Element with a frame leaf element.
788 private void updateFrameSet(Element element, String url) {
789 try {
790 int startOffset = element.getStartOffset();
791 int endOffset = Math.min(getLength(), element.getEndOffset());
792 String html = "<frame";
793 if (url != null) {
794 html += " src=\"" + url + "\"";
795 }
796 html += ">";
797 installParserIfNecessary();
798 setOuterHTML(element, html);
799 } catch (BadLocationException e1) {
800 // Should handle this better
801 } catch (IOException ioe) {
802 // Should handle this better
803 }
804 }
805
806
807 /**
808 * Updates the Frame elements <code>HTML.Attribute.SRC attribute</code>
809 * and fires a <code>ChangedUpdate</code> event.
810 *
811 * @param element a FRAME element whose SRC attribute will be updated
812 * @param url a string specifying the new value for the SRC attribute
813 */
814 private void updateFrame(Element element, String url) {
815
816 try {
817 writeLock();
818 DefaultDocumentEvent changes = new DefaultDocumentEvent(element.getStartOffset(),
819 1,
820 DocumentEvent.EventType.CHANGE);
821 AttributeSet sCopy = element.getAttributes().copyAttributes();
822 MutableAttributeSet attr = (MutableAttributeSet) element.getAttributes();
823 changes.addEdit(new AttributeUndoableEdit(element, sCopy, false));
824 attr.removeAttribute(HTML.Attribute.SRC);
825 attr.addAttribute(HTML.Attribute.SRC, url);
826 changes.end();
827 fireChangedUpdate(changes);
828 fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
829 } finally {
837 * @return true if document will be viewed in a frame, otherwise false
838 */
839 boolean isFrameDocument() {
840 return frameDocument;
841 }
842
843 /**
844 * Sets a boolean state about whether the document will be
845 * viewed in a frame.
846 * @param frameDoc true if the document will be viewed in a frame,
847 * otherwise false
848 */
849 void setFrameDocumentState(boolean frameDoc) {
850 this.frameDocument = frameDoc;
851 }
852
853 /**
854 * Adds the specified map, this will remove a Map that has been
855 * previously registered with the same name.
856 *
857 * @param map the <code>Map</code> to be registered
858 */
859 void addMap(Map map) {
860 String name = map.getName();
861
862 if (name != null) {
863 Object maps = getProperty(MAP_PROPERTY);
864
865 if (maps == null) {
866 maps = new Hashtable<>(11);
867 putProperty(MAP_PROPERTY, maps);
868 }
869 if (maps instanceof Hashtable) {
870 @SuppressWarnings("unchecked")
871 Hashtable<Object, Object> tmp = (Hashtable)maps;
872 tmp.put("#" + name, map);
873 }
874 }
875 }
876
877 /**
878 * Removes a previously registered map.
879 * @param map the <code>Map</code> to be removed
880 */
881 void removeMap(Map map) {
882 String name = map.getName();
883
884 if (name != null) {
885 Object maps = getProperty(MAP_PROPERTY);
886
887 if (maps instanceof Hashtable) {
888 ((Hashtable)maps).remove("#" + name);
889 }
890 }
891 }
892
893 /**
894 * Returns the Map associated with the given name.
895 * @param name the name of the desired <code>Map</code>
896 * @return the <code>Map</code> or <code>null</code> if it can't
897 * be found, or if <code>name</code> is <code>null</code>
898 */
899 Map getMap(String name) {
900 if (name != null) {
901 Object maps = getProperty(MAP_PROPERTY);
902
903 if (maps != null && (maps instanceof Hashtable)) {
904 return (Map)((Hashtable)maps).get(name);
905 }
906 }
907 return null;
908 }
909
910 /**
911 * Returns an <code>Enumeration</code> of the possible Maps.
912 * @return the enumerated list of maps, or <code>null</code>
913 * if the maps are not an instance of <code>Hashtable</code>
914 */
915 Enumeration<Object> getMaps() {
916 Object maps = getProperty(MAP_PROPERTY);
917
918 if (maps instanceof Hashtable) {
919 @SuppressWarnings("unchecked")
920 Hashtable<Object, Object> tmp = (Hashtable) maps;
921 return tmp.elements();
922 }
923 return null;
924 }
925
926 /**
927 * Sets the content type language used for style sheets that do not
928 * explicitly specify the type. The default is text/css.
929 * @param contentType the content type language for the style sheets
930 */
931 /* public */
932 void setDefaultStyleSheetType(String contentType) {
933 putProperty(StyleType, contentType);
934 }
935
936 /**
937 * Returns the content type language used for style sheets. The default
938 * is text/css.
939 * @return the content type language used for the style sheets
940 */
941 /* public */
942 String getDefaultStyleSheetType() {
943 String retValue = (String)getProperty(StyleType);
944 if (retValue == null) {
945 return "text/css";
946 }
947 return retValue;
948 }
949
950 /**
951 * Sets the parser that is used by the methods that insert html
952 * into the existing document, such as <code>setInnerHTML</code>,
953 * and <code>setOuterHTML</code>.
954 * <p>
955 * <code>HTMLEditorKit.createDefaultDocument</code> will set the parser
956 * for you. If you create an <code>HTMLDocument</code> by hand,
957 * be sure and set the parser accordingly.
958 * @param parser the parser to be used for text insertion
959 *
960 * @since 1.3
961 */
962 public void setParser(HTMLEditorKit.Parser parser) {
963 this.parser = parser;
964 putProperty("__PARSER__", null);
965 }
966
967 /**
968 * Returns the parser that is used when inserting HTML into the existing
969 * document.
970 * @return the parser used for text insertion
971 *
972 * @since 1.3
973 */
974 public HTMLEditorKit.Parser getParser() {
975 Object p = getProperty("__PARSER__");
976
977 if (p instanceof HTMLEditorKit.Parser) {
978 return (HTMLEditorKit.Parser)p;
979 }
980 return parser;
981 }
982
983 /**
984 * Replaces the children of the given element with the contents
985 * specified as an HTML string.
986 *
987 * <p>This will be seen as at least two events, n inserts followed by
988 * a remove.</p>
989 *
990 * <p>Consider the following structure (the <code>elem</code>
991 * parameter is <b>in bold</b>).</p>
992 *
993 * <pre>
994 * <body>
995 * |
996 * <b><div></b>
997 * / \
998 * <p> <p>
999 * </pre>
1000 *
1001 * <p>Invoking <code>setInnerHTML(elem, "<ul><li>")</code>
1002 * results in the following structure (new elements are <font
1003 * style="color: red;">in red</font>).</p>
1004 *
1005 * <pre>
1006 * <body>
1007 * |
1008 * <b><div></b>
1009 * \
1010 * <font style="color: red;"><ul></font>
1011 * \
1012 * <font style="color: red;"><li></font>
1013 * </pre>
1014 *
1015 * <p>Parameter <code>elem</code> must not be a leaf element,
1016 * otherwise an <code>IllegalArgumentException</code> is thrown.
1017 * If either <code>elem</code> or <code>htmlText</code> parameter
1018 * is <code>null</code>, no changes are made to the document.</p>
1019 *
1020 * <p>For this to work correctly, the document must have an
1021 * <code>HTMLEditorKit.Parser</code> set. This will be the case
1022 * if the document was created from an HTMLEditorKit via the
1023 * <code>createDefaultDocument</code> method.</p>
1024 *
1025 * @param elem the branch element whose children will be replaced
1026 * @param htmlText the string to be parsed and assigned to <code>elem</code>
1027 * @throws IllegalArgumentException if <code>elem</code> is a leaf
1028 * @throws IllegalStateException if an <code>HTMLEditorKit.Parser</code>
1029 * has not been defined
1030 * @throws BadLocationException if replacement is impossible because of
1031 * a structural issue
1032 * @throws IOException if an I/O exception occurs
1033 * @since 1.3
1034 */
1035 public void setInnerHTML(Element elem, String htmlText) throws
1036 BadLocationException, IOException {
1037 verifyParser();
1038 if (elem != null && elem.isLeaf()) {
1039 throw new IllegalArgumentException
1040 ("Can not set inner HTML of a leaf");
1041 }
1042 if (elem != null && htmlText != null) {
1043 int oldCount = elem.getElementCount();
1044 int insertPosition = elem.getStartOffset();
1045 insertHTML(elem, elem.getStartOffset(), htmlText, true);
1046 if (elem.getElementCount() > oldCount) {
1047 // Elements were inserted, do the cleanup.
1048 removeElements(elem, elem.getElementCount() - oldCount,
1049 oldCount);
1050 }
1051 }
1052 }
1053
1054 /**
1055 * Replaces the given element in the parent with the contents
1056 * specified as an HTML string.
1057 *
1058 * <p>This will be seen as at least two events, n inserts followed by
1059 * a remove.</p>
1060 *
1061 * <p>When replacing a leaf this will attempt to make sure there is
1062 * a newline present if one is needed. This may result in an additional
1063 * element being inserted. Consider, if you were to replace a character
1064 * element that contained a newline with <img> this would create
1065 * two elements, one for the image, and one for the newline.</p>
1066 *
1067 * <p>If you try to replace the element at length you will most
1068 * likely end up with two elements, eg
1069 * <code>setOuterHTML(getCharacterElement (getLength()),
1070 * "blah")</code> will result in two leaf elements at the end, one
1071 * representing 'blah', and the other representing the end
1072 * element.</p>
1073 *
1074 * <p>Consider the following structure (the <code>elem</code>
1075 * parameter is <b>in bold</b>).</p>
1076 *
1077 * <pre>
1078 * <body>
1079 * |
1080 * <b><div></b>
1081 * / \
1082 * <p> <p>
1083 * </pre>
1084 *
1085 * <p>Invoking <code>setOuterHTML(elem, "<ul><li>")</code>
1086 * results in the following structure (new elements are <font
1087 * style="color: red;">in red</font>).</p>
1088 *
1089 * <pre>
1090 * <body>
1091 * |
1092 * <font style="color: red;"><ul></font>
1093 * \
1094 * <font style="color: red;"><li></font>
1095 * </pre>
1096 *
1097 * <p>If either <code>elem</code> or <code>htmlText</code>
1098 * parameter is <code>null</code>, no changes are made to the
1099 * document.</p>
1100 *
1101 * <p>For this to work correctly, the document must have an
1102 * HTMLEditorKit.Parser set. This will be the case if the document
1103 * was created from an HTMLEditorKit via the
1104 * <code>createDefaultDocument</code> method.</p>
1105 *
1106 * @param elem the element to replace
1107 * @param htmlText the string to be parsed and inserted in place of <code>elem</code>
1108 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1109 * been set
1110 * @throws BadLocationException if replacement is impossible because of
1111 * a structural issue
1112 * @throws IOException if an I/O exception occurs
1113 * @since 1.3
1114 */
1115 public void setOuterHTML(Element elem, String htmlText) throws
1116 BadLocationException, IOException {
1117 verifyParser();
1118 if (elem != null && elem.getParentElement() != null &&
1119 htmlText != null) {
1120 int start = elem.getStartOffset();
1121 int end = elem.getEndOffset();
1122 int startLength = getLength();
1123 // We don't want a newline if elem is a leaf, and doesn't contain
1124 // a newline.
1125 boolean wantsNewline = !elem.isLeaf();
1126 if (!wantsNewline && (end > startLength ||
1127 getText(end - 1, 1).charAt(0) == NEWLINE[0])){
1128 wantsNewline = true;
1129 }
1130 Element parent = elem.getParentElement();
1131 int oldCount = parent.getElementCount();
1132 insertHTML(parent, start, htmlText, wantsNewline);
1133 // Remove old.
1134 int newLength = getLength();
1135 if (oldCount != parent.getElementCount()) {
1136 int removeIndex = parent.getElementIndex(start + newLength -
1137 startLength);
1138 removeElements(parent, removeIndex, 1);
1139 }
1140 }
1141 }
1142
1143 /**
1144 * Inserts the HTML specified as a string at the start
1145 * of the element.
1146 *
1147 * <p>Consider the following structure (the <code>elem</code>
1148 * parameter is <b>in bold</b>).</p>
1149 *
1150 * <pre>
1151 * <body>
1152 * |
1153 * <b><div></b>
1154 * / \
1155 * <p> <p>
1156 * </pre>
1157 *
1158 * <p>Invoking <code>insertAfterStart(elem,
1159 * "<ul><li>")</code> results in the following structure
1160 * (new elements are <font style="color: red;">in red</font>).</p>
1161 *
1162 * <pre>
1163 * <body>
1164 * |
1165 * <b><div></b>
1166 * / | \
1167 * <font style="color: red;"><ul></font> <p> <p>
1168 * /
1169 * <font style="color: red;"><li></font>
1170 * </pre>
1171 *
1172 * <p>Unlike the <code>insertBeforeStart</code> method, new
1173 * elements become <em>children</em> of the specified element,
1174 * not siblings.</p>
1175 *
1176 * <p>Parameter <code>elem</code> must not be a leaf element,
1177 * otherwise an <code>IllegalArgumentException</code> is thrown.
1178 * If either <code>elem</code> or <code>htmlText</code> parameter
1179 * is <code>null</code>, no changes are made to the document.</p>
1180 *
1181 * <p>For this to work correctly, the document must have an
1182 * <code>HTMLEditorKit.Parser</code> set. This will be the case
1183 * if the document was created from an HTMLEditorKit via the
1184 * <code>createDefaultDocument</code> method.</p>
1185 *
1186 * @param elem the branch element to be the root for the new text
1187 * @param htmlText the string to be parsed and assigned to <code>elem</code>
1188 * @throws IllegalArgumentException if <code>elem</code> is a leaf
1189 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1190 * been set on the document
1191 * @throws BadLocationException if insertion is impossible because of
1192 * a structural issue
1193 * @throws IOException if an I/O exception occurs
1194 * @since 1.3
1195 */
1196 public void insertAfterStart(Element elem, String htmlText) throws
1197 BadLocationException, IOException {
1198 verifyParser();
1199
1200 if (elem == null || htmlText == null) {
1201 return;
1202 }
1203
1204 if (elem.isLeaf()) {
1205 throw new IllegalArgumentException
1206 ("Can not insert HTML after start of a leaf");
1207 }
1208 insertHTML(elem, elem.getStartOffset(), htmlText, false);
1209 }
1210
1211 /**
1212 * Inserts the HTML specified as a string at the end of
1213 * the element.
1214 *
1215 * <p> If <code>elem</code>'s children are leaves, and the
1216 * character at a <code>elem.getEndOffset() - 1</code> is a newline,
1217 * this will insert before the newline so that there isn't text after
1218 * the newline.</p>
1219 *
1220 * <p>Consider the following structure (the <code>elem</code>
1221 * parameter is <b>in bold</b>).</p>
1222 *
1223 * <pre>
1224 * <body>
1225 * |
1226 * <b><div></b>
1227 * / \
1228 * <p> <p>
1229 * </pre>
1230 *
1231 * <p>Invoking <code>insertBeforeEnd(elem, "<ul><li>")</code>
1232 * results in the following structure (new elements are <font
1233 * style="color: red;">in red</font>).</p>
1234 *
1235 * <pre>
1236 * <body>
1237 * |
1238 * <b><div></b>
1239 * / | \
1240 * <p> <p> <font style="color: red;"><ul></font>
1241 * \
1242 * <font style="color: red;"><li></font>
1243 * </pre>
1244 *
1245 * <p>Unlike the <code>insertAfterEnd</code> method, new elements
1246 * become <em>children</em> of the specified element, not
1247 * siblings.</p>
1248 *
1249 * <p>Parameter <code>elem</code> must not be a leaf element,
1250 * otherwise an <code>IllegalArgumentException</code> is thrown.
1251 * If either <code>elem</code> or <code>htmlText</code> parameter
1252 * is <code>null</code>, no changes are made to the document.</p>
1253 *
1254 * <p>For this to work correctly, the document must have an
1255 * <code>HTMLEditorKit.Parser</code> set. This will be the case
1256 * if the document was created from an HTMLEditorKit via the
1257 * <code>createDefaultDocument</code> method.</p>
1258 *
1259 * @param elem the element to be the root for the new text
1260 * @param htmlText the string to be parsed and assigned to <code>elem</code>
1261 * @throws IllegalArgumentException if <code>elem</code> is a leaf
1262 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1263 * been set on the document
1264 * @throws BadLocationException if insertion is impossible because of
1265 * a structural issue
1266 * @throws IOException if an I/O exception occurs
1267 * @since 1.3
1268 */
1269 public void insertBeforeEnd(Element elem, String htmlText) throws
1270 BadLocationException, IOException {
1271 verifyParser();
1272 if (elem != null && elem.isLeaf()) {
1273 throw new IllegalArgumentException
1274 ("Can not set inner HTML before end of leaf");
1275 }
1276 if (elem != null) {
1277 int offset = elem.getEndOffset();
1278 if (elem.getElement(elem.getElementIndex(offset - 1)).isLeaf() &&
1279 getText(offset - 1, 1).charAt(0) == NEWLINE[0]) {
1280 offset--;
1281 }
1282 insertHTML(elem, offset, htmlText, false);
1283 }
1284 }
1285
1286 /**
1287 * Inserts the HTML specified as a string before the start of
1288 * the given element.
1289 *
1290 * <p>Consider the following structure (the <code>elem</code>
1291 * parameter is <b>in bold</b>).</p>
1292 *
1293 * <pre>
1294 * <body>
1295 * |
1296 * <b><div></b>
1297 * / \
1298 * <p> <p>
1299 * </pre>
1300 *
1301 * <p>Invoking <code>insertBeforeStart(elem,
1302 * "<ul><li>")</code> results in the following structure
1303 * (new elements are <font style="color: red;">in red</font>).</p>
1304 *
1305 * <pre>
1306 * <body>
1307 * / \
1308 * <font style="color: red;"><ul></font> <b><div></b>
1309 * / / \
1310 * <font style="color: red;"><li></font> <p> <p>
1311 * </pre>
1312 *
1313 * <p>Unlike the <code>insertAfterStart</code> method, new
1314 * elements become <em>siblings</em> of the specified element, not
1315 * children.</p>
1316 *
1317 * <p>If either <code>elem</code> or <code>htmlText</code>
1318 * parameter is <code>null</code>, no changes are made to the
1319 * document.</p>
1320 *
1321 * <p>For this to work correctly, the document must have an
1322 * <code>HTMLEditorKit.Parser</code> set. This will be the case
1323 * if the document was created from an HTMLEditorKit via the
1324 * <code>createDefaultDocument</code> method.</p>
1325 *
1326 * @param elem the element the content is inserted before
1327 * @param htmlText the string to be parsed and inserted before <code>elem</code>
1328 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1329 * been set on the document
1330 * @throws BadLocationException if insertion is impossible because of
1331 * a structural issue
1332 * @throws IOException if an I/O exception occurs
1333 * @since 1.3
1334 */
1335 public void insertBeforeStart(Element elem, String htmlText) throws
1336 BadLocationException, IOException {
1337 verifyParser();
1338 if (elem != null) {
1339 Element parent = elem.getParentElement();
1340
1341 if (parent != null) {
1342 insertHTML(parent, elem.getStartOffset(), htmlText, false);
1343 }
1344 }
1345 }
1346
1347 /**
1348 * Inserts the HTML specified as a string after the end of the
1349 * given element.
1350 *
1351 * <p>Consider the following structure (the <code>elem</code>
1352 * parameter is <b>in bold</b>).</p>
1353 *
1354 * <pre>
1355 * <body>
1356 * |
1357 * <b><div></b>
1358 * / \
1359 * <p> <p>
1360 * </pre>
1361 *
1362 * <p>Invoking <code>insertAfterEnd(elem, "<ul><li>")</code>
1363 * results in the following structure (new elements are <font
1364 * style="color: red;">in red</font>).</p>
1365 *
1366 * <pre>
1367 * <body>
1368 * / \
1369 * <b><div></b> <font style="color: red;"><ul></font>
1370 * / \ \
1371 * <p> <p> <font style="color: red;"><li></font>
1372 * </pre>
1373 *
1374 * <p>Unlike the <code>insertBeforeEnd</code> method, new elements
1375 * become <em>siblings</em> of the specified element, not
1376 * children.</p>
1377 *
1378 * <p>If either <code>elem</code> or <code>htmlText</code>
1379 * parameter is <code>null</code>, no changes are made to the
1380 * document.</p>
1381 *
1382 * <p>For this to work correctly, the document must have an
1383 * <code>HTMLEditorKit.Parser</code> set. This will be the case
1384 * if the document was created from an HTMLEditorKit via the
1385 * <code>createDefaultDocument</code> method.</p>
1386 *
1387 * @param elem the element the content is inserted after
1388 * @param htmlText the string to be parsed and inserted after <code>elem</code>
1389 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1390 * been set on the document
1391 * @throws BadLocationException if insertion is impossible because of
1392 * a structural issue
1393 * @throws IOException if an I/O exception occurs
1394 * @since 1.3
1395 */
1396 public void insertAfterEnd(Element elem, String htmlText) throws
1397 BadLocationException, IOException {
1398 verifyParser();
1399 if (elem != null) {
1400 Element parent = elem.getParentElement();
1401
1402 if (parent != null) {
1403 // If we are going to insert the string into the body
1404 // section, it is necessary to set the corrsponding flag.
1405 if (HTML.Tag.BODY.name.equals(parent.getName())) {
1406 insertInBody = true;
1407 }
1408 int offset = elem.getEndOffset();
1409 if (offset > (getLength() + 1)) {
1410 offset--;
1411 }
1412 else if (elem.isLeaf() && getText(offset - 1, 1).
1413 charAt(0) == NEWLINE[0]) {
1414 offset--;
1415 }
1416 insertHTML(parent, offset, htmlText, false);
1417 // Cleanup the flag, if any.
1418 if (insertInBody) {
1419 insertInBody = false;
1420 }
1421 }
1422 }
1423 }
1424
1425 /**
1426 * Returns the element that has the given id <code>Attribute</code>.
1427 * If the element can't be found, <code>null</code> is returned.
1428 * Note that this method works on an <code>Attribute</code>,
1429 * <i>not</i> a character tag. In the following HTML snippet:
1430 * <code><a id="HelloThere"></code> the attribute is
1431 * 'id' and the character tag is 'a'.
1432 * This is a convenience method for
1433 * <code>getElement(RootElement, HTML.Attribute.id, id)</code>.
1434 * This is not thread-safe.
1435 *
1436 * @param id the string representing the desired <code>Attribute</code>
1437 * @return the element with the specified <code>Attribute</code>
1438 * or <code>null</code> if it can't be found,
1439 * or <code>null</code> if <code>id</code> is <code>null</code>
1440 * @see javax.swing.text.html.HTML.Attribute
1441 * @since 1.3
1442 */
1443 public Element getElement(String id) {
1444 if (id == null) {
1445 return null;
1446 }
1447 return getElement(getDefaultRootElement(), HTML.Attribute.ID, id,
1448 true);
1449 }
1450
1451 /**
1452 * Returns the child element of <code>e</code> that contains the
1453 * attribute, <code>attribute</code> with value <code>value</code>, or
1454 * <code>null</code> if one isn't found. This is not thread-safe.
1455 *
1456 * @param e the root element where the search begins
1457 * @param attribute the desired <code>Attribute</code>
1458 * @param value the values for the specified <code>Attribute</code>
1459 * @return the element with the specified <code>Attribute</code>
1460 * and the specified <code>value</code>, or <code>null</code>
1461 * if it can't be found
1462 * @see javax.swing.text.html.HTML.Attribute
1463 * @since 1.3
1464 */
1465 public Element getElement(Element e, Object attribute, Object value) {
1466 return getElement(e, attribute, value, true);
1467 }
1468
1469 /**
1470 * Returns the child element of <code>e</code> that contains the
1471 * attribute, <code>attribute</code> with value <code>value</code>, or
1472 * <code>null</code> if one isn't found. This is not thread-safe.
1473 * <p>
1474 * If <code>searchLeafAttributes</code> is true, and <code>e</code> is
1475 * a leaf, any attributes that are instances of <code>HTML.Tag</code>
1476 * with a value that is an <code>AttributeSet</code> will also be checked.
1477 *
1478 * @param e the root element where the search begins
1479 * @param attribute the desired <code>Attribute</code>
1480 * @param value the values for the specified <code>Attribute</code>
1481 * @return the element with the specified <code>Attribute</code>
1482 * and the specified <code>value</code>, or <code>null</code>
1483 * if it can't be found
1484 * @see javax.swing.text.html.HTML.Attribute
1485 */
1486 private Element getElement(Element e, Object attribute, Object value,
1487 boolean searchLeafAttributes) {
1488 AttributeSet attr = e.getAttributes();
1489
1490 if (attr != null && attr.isDefined(attribute)) {
1491 if (value.equals(attr.getAttribute(attribute))) {
1492 return e;
1493 }
1494 }
1495 if (!e.isLeaf()) {
1496 for (int counter = 0, maxCounter = e.getElementCount();
1497 counter < maxCounter; counter++) {
1498 Element retValue = getElement(e.getElement(counter), attribute,
1499 value, searchLeafAttributes);
1500
1501 if (retValue != null) {
1502 return retValue;
1510 if (names != null) {
1511 while (names.hasMoreElements()) {
1512 Object name = names.nextElement();
1513 if ((name instanceof HTML.Tag) &&
1514 (attr.getAttribute(name) instanceof AttributeSet)) {
1515
1516 AttributeSet check = (AttributeSet)attr.
1517 getAttribute(name);
1518 if (check.isDefined(attribute) &&
1519 value.equals(check.getAttribute(attribute))) {
1520 return e;
1521 }
1522 }
1523 }
1524 }
1525 }
1526 return null;
1527 }
1528
1529 /**
1530 * Verifies the document has an <code>HTMLEditorKit.Parser</code> set.
1531 * If <code>getParser</code> returns <code>null</code>, this will throw an
1532 * IllegalStateException.
1533 *
1534 * @throws IllegalStateException if the document does not have a Parser
1535 */
1536 private void verifyParser() {
1537 if (getParser() == null) {
1538 throw new IllegalStateException("No HTMLEditorKit.Parser");
1539 }
1540 }
1541
1542 /**
1543 * Installs a default Parser if one has not been installed yet.
1544 */
1545 private void installParserIfNecessary() {
1546 if (getParser() == null) {
1547 setParser(new HTMLEditorKit().getParser());
1548 }
1549 }
1550
1551 /**
1552 * Inserts a string of HTML into the document at the given position.
1553 * <code>parent</code> is used to identify the location to insert the
1554 * <code>html</code>. If <code>parent</code> is a leaf this can have
1555 * unexpected results.
1556 */
1557 private void insertHTML(Element parent, int offset, String html,
1558 boolean wantsTrailingNewline)
1559 throws BadLocationException, IOException {
1560 if (parent != null && html != null) {
1561 HTMLEditorKit.Parser parser = getParser();
1562 if (parser != null) {
1563 int lastOffset = Math.max(0, offset - 1);
1564 Element charElement = getCharacterElement(lastOffset);
1565 Element commonParent = parent;
1566 int pop = 0;
1567 int push = 0;
1568
1569 if (parent.getStartOffset() > lastOffset) {
1570 while (commonParent != null &&
1571 commonParent.getStartOffset() > lastOffset) {
1572 commonParent = commonParent.getParentElement();
1573 push++;
1574 }
1578 }
1579 }
1580 while (charElement != null && charElement != commonParent) {
1581 pop++;
1582 charElement = charElement.getParentElement();
1583 }
1584 if (charElement != null) {
1585 // Found it, do the insert.
1586 HTMLReader reader = new HTMLReader(offset, pop - 1, push,
1587 null, false, true,
1588 wantsTrailingNewline);
1589
1590 parser.parse(new StringReader(html), reader, true);
1591 reader.flush();
1592 }
1593 }
1594 }
1595 }
1596
1597 /**
1598 * Removes child Elements of the passed in Element <code>e</code>. This
1599 * will do the necessary cleanup to ensure the element representing the
1600 * end character is correctly created.
1601 * <p>This is not a general purpose method, it assumes that <code>e</code>
1602 * will still have at least one child after the remove, and it assumes
1603 * the character at <code>e.getStartOffset() - 1</code> is a newline and
1604 * is of length 1.
1605 */
1606 private void removeElements(Element e, int index, int count) throws BadLocationException {
1607 writeLock();
1608 try {
1609 int start = e.getElement(index).getStartOffset();
1610 int end = e.getElement(index + count - 1).getEndOffset();
1611 if (end > getLength()) {
1612 removeElementsAtEnd(e, index, count, start, end);
1613 }
1614 else {
1615 removeElements(e, index, count, start, end);
1616 }
1617 } finally {
1618 writeUnlock();
1619 }
1620 }
1621
1622 /**
1623 * Called to remove child elements of <code>e</code> when one of the
1624 * elements to remove is representing the end character.
1625 * <p>Since the Content will not allow a removal to the end character
1626 * this will do a remove from <code>start - 1</code> to <code>end</code>.
1627 * The end Element(s) will be removed, and the element representing
1628 * <code>start - 1</code> to <code>start</code> will be recreated. This
1629 * Element has to be recreated as after the content removal its offsets
1630 * become <code>start - 1</code> to <code>start - 1</code>.
1631 */
1632 private void removeElementsAtEnd(Element e, int index, int count,
1633 int start, int end) throws BadLocationException {
1634 // index must be > 0 otherwise no insert would have happened.
1635 boolean isLeaf = (e.getElement(index - 1).isLeaf());
1636 DefaultDocumentEvent dde = new DefaultDocumentEvent(
1637 start - 1, end - start + 1, DocumentEvent.
1638 EventType.REMOVE);
1639
1640 if (isLeaf) {
1641 Element endE = getCharacterElement(getLength());
1642 // e.getElement(index - 1) should represent the newline.
1643 index--;
1644 if (endE.getParentElement() != e) {
1645 // The hiearchies don't match, we'll have to manually
1646 // recreate the leaf at e.getElement(index - 1)
1647 replace(dde, e, index, ++count, start, end, true, true);
1648 }
1649 else {
1650 // The hierarchies for the end Element and
1656 }
1657 else {
1658 // Not a leaf, descend until we find the leaf representing
1659 // start - 1 and remove it.
1660 Element newLineE = e.getElement(index - 1);
1661 while (!newLineE.isLeaf()) {
1662 newLineE = newLineE.getElement(newLineE.getElementCount() - 1);
1663 }
1664 newLineE = newLineE.getParentElement();
1665 replace(dde, e, index, count, start, end, false, false);
1666 replace(dde, newLineE, newLineE.getElementCount() - 1, 1, start,
1667 end, true, true);
1668 }
1669 postRemoveUpdate(dde);
1670 dde.end();
1671 fireRemoveUpdate(dde);
1672 fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
1673 }
1674
1675 /**
1676 * This is used by <code>removeElementsAtEnd</code>, it removes
1677 * <code>count</code> elements starting at <code>start</code> from
1678 * <code>e</code>. If <code>remove</code> is true text of length
1679 * <code>start - 1</code> to <code>end - 1</code> is removed. If
1680 * <code>create</code> is true a new leaf is created of length 1.
1681 */
1682 private void replace(DefaultDocumentEvent dde, Element e, int index,
1683 int count, int start, int end, boolean remove,
1684 boolean create) throws BadLocationException {
1685 Element[] added;
1686 AttributeSet attrs = e.getElement(index).getAttributes();
1687 Element[] removed = new Element[count];
1688
1689 for (int counter = 0; counter < count; counter++) {
1690 removed[counter] = e.getElement(counter + index);
1691 }
1692 if (remove) {
1693 UndoableEdit u = getContent().remove(start - 1, end - start);
1694 if (u != null) {
1695 dde.addEdit(u);
1696 }
1697 }
1698 if (create) {
1699 added = new Element[1];
1700 added[0] = createLeafElement(e, attrs, start - 1, start);
1872 ((MutableAttributeSet)contentAttributeSet).
1873 addAttribute(StyleConstants.NameAttribute,
1874 HTML.Tag.CONTENT);
1875 NEWLINE = new char[1];
1876 NEWLINE[0] = '\n';
1877 }
1878
1879
1880 /**
1881 * An iterator to iterate over a particular type of
1882 * tag. The iterator is not thread safe. If reliable
1883 * access to the document is not already ensured by
1884 * the context under which the iterator is being used,
1885 * its use should be performed under the protection of
1886 * Document.render.
1887 */
1888 public abstract static class Iterator {
1889
1890 /**
1891 * Return the attributes for this tag.
1892 * @return the <code>AttributeSet</code> for this tag, or
1893 * <code>null</code> if none can be found
1894 */
1895 public abstract AttributeSet getAttributes();
1896
1897 /**
1898 * Returns the start of the range for which the current occurrence of
1899 * the tag is defined and has the same attributes.
1900 *
1901 * @return the start of the range, or -1 if it can't be found
1902 */
1903 public abstract int getStartOffset();
1904
1905 /**
1906 * Returns the end of the range for which the current occurrence of
1907 * the tag is defined and has the same attributes.
1908 *
1909 * @return the end of the range
1910 */
1911 public abstract int getEndOffset();
1912
1913 /**
1929 * Type of tag this iterator represents.
1930 * @return the tag
1931 */
1932 public abstract HTML.Tag getTag();
1933 }
1934
1935 /**
1936 * An iterator to iterate over a particular type of tag.
1937 */
1938 static class LeafIterator extends Iterator {
1939
1940 LeafIterator(HTML.Tag t, Document doc) {
1941 tag = t;
1942 pos = new ElementIterator(doc);
1943 endOffset = 0;
1944 next();
1945 }
1946
1947 /**
1948 * Returns the attributes for this tag.
1949 * @return the <code>AttributeSet</code> for this tag,
1950 * or <code>null</code> if none can be found
1951 */
1952 public AttributeSet getAttributes() {
1953 Element elem = pos.current();
1954 if (elem != null) {
1955 AttributeSet a = (AttributeSet)
1956 elem.getAttributes().getAttribute(tag);
1957 if (a == null) {
1958 a = elem.getAttributes();
1959 }
1960 return a;
1961 }
1962 return null;
1963 }
1964
1965 /**
1966 * Returns the start of the range for which the current occurrence of
1967 * the tag is defined and has the same attributes.
1968 *
1969 * @return the start of the range, or -1 if it can't be found
1970 */
1993 public void next() {
1994 for (nextLeaf(pos); isValid(); nextLeaf(pos)) {
1995 Element elem = pos.current();
1996 if (elem.getStartOffset() >= endOffset) {
1997 AttributeSet a = pos.current().getAttributes();
1998
1999 if (a.isDefined(tag) ||
2000 a.getAttribute(StyleConstants.NameAttribute) == tag) {
2001
2002 // we found the next one
2003 setEndOffset();
2004 break;
2005 }
2006 }
2007 }
2008 }
2009
2010 /**
2011 * Returns the type of tag this iterator represents.
2012 *
2013 * @return the <code>HTML.Tag</code> that this iterator represents.
2014 * @see javax.swing.text.html.HTML.Tag
2015 */
2016 public HTML.Tag getTag() {
2017 return tag;
2018 }
2019
2020 /**
2021 * Returns true if the current position is not <code>null</code>.
2022 * @return true if current position is not <code>null</code>,
2023 * otherwise returns false
2024 */
2025 public boolean isValid() {
2026 return (pos.current() != null);
2027 }
2028
2029 /**
2030 * Moves the given iterator to the next leaf element.
2031 * @param iter the iterator to be scanned
2032 */
2033 void nextLeaf(ElementIterator iter) {
2034 for (iter.next(); iter.current() != null; iter.next()) {
2035 Element e = iter.current();
2036 if (e.isLeaf()) {
2037 break;
2038 }
2039 }
2040 }
2041
2042 /**
2043 * Marches a cloned iterator forward to locate the end
2044 * of the run. This sets the value of <code>endOffset</code>.
2045 */
2046 void setEndOffset() {
2047 AttributeSet a0 = getAttributes();
2048 endOffset = pos.current().getEndOffset();
2049 ElementIterator fwd = (ElementIterator) pos.clone();
2050 for (nextLeaf(fwd); fwd.current() != null; nextLeaf(fwd)) {
2051 Element e = fwd.current();
2052 AttributeSet a1 = (AttributeSet) e.getAttributes().getAttribute(tag);
2053 if ((a1 == null) || (! a1.equals(a0))) {
2054 break;
2055 }
2056 endOffset = e.getEndOffset();
2057 }
2058 }
2059
2060 private int endOffset;
2061 private HTML.Tag tag;
2062 private ElementIterator pos;
2063
2064 }
2065
2066 /**
2067 * An HTML reader to load an HTML document with an HTML
2068 * element structure. This is a set of callbacks from
2069 * the parser, implemented to create a set of elements
2070 * tagged with attributes. The parse builds up tokens
2071 * (ElementSpec) that describe the element subtree desired,
2072 * and burst it into the document under the protection of
2073 * a write lock using the insert method on the document
2074 * outer class.
2075 * <p>
2076 * The reader can be configured by registering actions
2077 * (of type <code>HTMLDocument.HTMLReader.TagAction</code>)
2078 * that describe how to handle the action. The idea behind
2079 * the actions provided is that the most natural text editing
2080 * operations can be provided if the element structure boils
2081 * down to paragraphs with runs of some kind of style
2082 * in them. Some things are more naturally specified
2083 * structurally, so arbitrary structure should be allowed
2084 * above the paragraphs, but will need to be edited with structural
2085 * actions. The implication of this is that some of the
2086 * HTML elements specified in the stream being parsed will
2087 * be collapsed into attributes, and in some cases paragraphs
2088 * will be synthesized. When HTML elements have been
2089 * converted to attributes, the attribute key will be of
2090 * type HTML.Tag, and the value will be of type AttributeSet
2091 * so that no information is lost. This enables many of the
2092 * existing actions to work so that the user can type input,
2093 * hit the return key, backspace, delete, etc and have a
2094 * reasonable result. Selections can be created, and attributes
2095 * applied or removed, etc. With this in mind, the work done
2096 * by the reader can be categorized into the following kinds
2097 * of tasks:
2111 * <dt>Special
2112 * <dd>Produce an embedded graphical element.
2113 * <dt>Form
2114 * <dd>Produce an element that is like the embedded graphical
2115 * element, except that it also has a component model associated
2116 * with it.
2117 * <dt>Hidden
2118 * <dd>Create an element that is hidden from view when the
2119 * document is being viewed read-only, and visible when the
2120 * document is being edited. This is useful to keep the
2121 * model from losing information, and used to store things
2122 * like comments and unrecognized tags.
2123 *
2124 * </dl>
2125 * <p>
2126 * Currently, <APPLET>, <PARAM>, <MAP>, <AREA>, <LINK>,
2127 * <SCRIPT> and <STYLE> are unsupported.
2128 *
2129 * <p>
2130 * The assignment of the actions described is shown in the
2131 * following table for the tags defined in <code>HTML.Tag</code>.
2132 * <table border=1 summary="HTML tags and assigned actions">
2133 * <tr><th>Tag</th><th>Action</th></tr>
2134 * <tr><td><code>HTML.Tag.A</code> <td>CharacterAction
2135 * <tr><td><code>HTML.Tag.ADDRESS</code> <td>CharacterAction
2136 * <tr><td><code>HTML.Tag.APPLET</code> <td>HiddenAction
2137 * <tr><td><code>HTML.Tag.AREA</code> <td>AreaAction
2138 * <tr><td><code>HTML.Tag.B</code> <td>CharacterAction
2139 * <tr><td><code>HTML.Tag.BASE</code> <td>BaseAction
2140 * <tr><td><code>HTML.Tag.BASEFONT</code> <td>CharacterAction
2141 * <tr><td><code>HTML.Tag.BIG</code> <td>CharacterAction
2142 * <tr><td><code>HTML.Tag.BLOCKQUOTE</code><td>BlockAction
2143 * <tr><td><code>HTML.Tag.BODY</code> <td>BlockAction
2144 * <tr><td><code>HTML.Tag.BR</code> <td>SpecialAction
2145 * <tr><td><code>HTML.Tag.CAPTION</code> <td>BlockAction
2146 * <tr><td><code>HTML.Tag.CENTER</code> <td>BlockAction
2147 * <tr><td><code>HTML.Tag.CITE</code> <td>CharacterAction
2148 * <tr><td><code>HTML.Tag.CODE</code> <td>CharacterAction
2149 * <tr><td><code>HTML.Tag.DD</code> <td>BlockAction
2150 * <tr><td><code>HTML.Tag.DFN</code> <td>CharacterAction
2151 * <tr><td><code>HTML.Tag.DIR</code> <td>BlockAction
2152 * <tr><td><code>HTML.Tag.DIV</code> <td>BlockAction
2153 * <tr><td><code>HTML.Tag.DL</code> <td>BlockAction
2154 * <tr><td><code>HTML.Tag.DT</code> <td>ParagraphAction
2155 * <tr><td><code>HTML.Tag.EM</code> <td>CharacterAction
2156 * <tr><td><code>HTML.Tag.FONT</code> <td>CharacterAction
2157 * <tr><td><code>HTML.Tag.FORM</code> <td>As of 1.4 a BlockAction
2158 * <tr><td><code>HTML.Tag.FRAME</code> <td>SpecialAction
2159 * <tr><td><code>HTML.Tag.FRAMESET</code> <td>BlockAction
2160 * <tr><td><code>HTML.Tag.H1</code> <td>ParagraphAction
2161 * <tr><td><code>HTML.Tag.H2</code> <td>ParagraphAction
2162 * <tr><td><code>HTML.Tag.H3</code> <td>ParagraphAction
2163 * <tr><td><code>HTML.Tag.H4</code> <td>ParagraphAction
2164 * <tr><td><code>HTML.Tag.H5</code> <td>ParagraphAction
2165 * <tr><td><code>HTML.Tag.H6</code> <td>ParagraphAction
2166 * <tr><td><code>HTML.Tag.HEAD</code> <td>HeadAction
2167 * <tr><td><code>HTML.Tag.HR</code> <td>SpecialAction
2168 * <tr><td><code>HTML.Tag.HTML</code> <td>BlockAction
2169 * <tr><td><code>HTML.Tag.I</code> <td>CharacterAction
2170 * <tr><td><code>HTML.Tag.IMG</code> <td>SpecialAction
2171 * <tr><td><code>HTML.Tag.INPUT</code> <td>FormAction
2172 * <tr><td><code>HTML.Tag.ISINDEX</code> <td>IsndexAction
2173 * <tr><td><code>HTML.Tag.KBD</code> <td>CharacterAction
2174 * <tr><td><code>HTML.Tag.LI</code> <td>BlockAction
2175 * <tr><td><code>HTML.Tag.LINK</code> <td>LinkAction
2176 * <tr><td><code>HTML.Tag.MAP</code> <td>MapAction
2177 * <tr><td><code>HTML.Tag.MENU</code> <td>BlockAction
2178 * <tr><td><code>HTML.Tag.META</code> <td>MetaAction
2179 * <tr><td><code>HTML.Tag.NOFRAMES</code> <td>BlockAction
2180 * <tr><td><code>HTML.Tag.OBJECT</code> <td>SpecialAction
2181 * <tr><td><code>HTML.Tag.OL</code> <td>BlockAction
2182 * <tr><td><code>HTML.Tag.OPTION</code> <td>FormAction
2183 * <tr><td><code>HTML.Tag.P</code> <td>ParagraphAction
2184 * <tr><td><code>HTML.Tag.PARAM</code> <td>HiddenAction
2185 * <tr><td><code>HTML.Tag.PRE</code> <td>PreAction
2186 * <tr><td><code>HTML.Tag.SAMP</code> <td>CharacterAction
2187 * <tr><td><code>HTML.Tag.SCRIPT</code> <td>HiddenAction
2188 * <tr><td><code>HTML.Tag.SELECT</code> <td>FormAction
2189 * <tr><td><code>HTML.Tag.SMALL</code> <td>CharacterAction
2190 * <tr><td><code>HTML.Tag.STRIKE</code> <td>CharacterAction
2191 * <tr><td><code>HTML.Tag.S</code> <td>CharacterAction
2192 * <tr><td><code>HTML.Tag.STRONG</code> <td>CharacterAction
2193 * <tr><td><code>HTML.Tag.STYLE</code> <td>StyleAction
2194 * <tr><td><code>HTML.Tag.SUB</code> <td>CharacterAction
2195 * <tr><td><code>HTML.Tag.SUP</code> <td>CharacterAction
2196 * <tr><td><code>HTML.Tag.TABLE</code> <td>BlockAction
2197 * <tr><td><code>HTML.Tag.TD</code> <td>BlockAction
2198 * <tr><td><code>HTML.Tag.TEXTAREA</code> <td>FormAction
2199 * <tr><td><code>HTML.Tag.TH</code> <td>BlockAction
2200 * <tr><td><code>HTML.Tag.TITLE</code> <td>TitleAction
2201 * <tr><td><code>HTML.Tag.TR</code> <td>BlockAction
2202 * <tr><td><code>HTML.Tag.TT</code> <td>CharacterAction
2203 * <tr><td><code>HTML.Tag.U</code> <td>CharacterAction
2204 * <tr><td><code>HTML.Tag.UL</code> <td>BlockAction
2205 * <tr><td><code>HTML.Tag.VAR</code> <td>CharacterAction
2206 * </table>
2207 * <p>
2208 * Once </html> is encountered, the Actions are no longer notified.
2209 */
2210 public class HTMLReader extends HTMLEditorKit.ParserCallback {
2211
2212 /**
2213 * Constructs an HTMLReader using default pop and push depth and no tag to insert.
2214 *
2215 * @param offset the starting offset
2216 */
2217 public HTMLReader(int offset) {
2218 this(offset, 0, 0, null);
2219 }
2220
2221 /**
2222 * Constructs an HTMLReader.
2223 *
2224 * @param offset the starting offset
2225 * @param popDepth how many parents to ascend before insert new element
2226 * @param pushDepth how many parents to descend (relative to popDepth) before
2227 * inserting
2228 * @param insertTag a tag to insert (may be null)
2229 */
2230 public HTMLReader(int offset, int popDepth, int pushDepth,
2231 HTML.Tag insertTag) {
2232 this(offset, popDepth, pushDepth, insertTag, true, false, true);
2233 }
2234
2235 /**
2236 * Generates a RuntimeException (will eventually generate
2237 * a BadLocationException when API changes are alloced) if inserting
2238 * into non empty document, <code>insertTag</code> is
2239 * non-<code>null</code>, and <code>offset</code> is not in the body.
2240 */
2241 // PENDING(sky): Add throws BadLocationException and remove
2242 // RuntimeException
2243 HTMLReader(int offset, int popDepth, int pushDepth,
2244 HTML.Tag insertTag, boolean insertInsertTag,
2245 boolean insertAfterImplied, boolean wantsTrailingNewline) {
2246 emptyDocument = (getLength() == 0);
2247 isStyleCSS = "text/css".equals(getDefaultStyleSheetType());
2248 this.offset = offset;
2249 threshold = HTMLDocument.this.getTokenThreshold();
2250 tagMap = new Hashtable<HTML.Tag, TagAction>(57);
2251 TagAction na = new TagAction();
2252 TagAction ba = new BlockAction();
2253 TagAction pa = new ParagraphAction();
2254 TagAction ca = new CharacterAction();
2255 TagAction sa = new SpecialAction();
2256 TagAction fa = new FormAction();
2257 TagAction ha = new HiddenAction();
2258 TagAction conv = new ConvertAction();
2259
2343 else {
2344 foundInsertTag = true;
2345 }
2346 if (insertAfterImplied) {
2347 this.popDepth = popDepth;
2348 this.pushDepth = pushDepth;
2349 this.insertAfterImplied = true;
2350 foundInsertTag = false;
2351 midInsert = false;
2352 this.insertInsertTag = true;
2353 this.wantsTrailingNewline = wantsTrailingNewline;
2354 }
2355 else {
2356 midInsert = (!emptyDocument && insertTag == null);
2357 if (midInsert) {
2358 generateEndsSpecsForMidInsert();
2359 }
2360 }
2361
2362 /**
2363 * This block initializes the <code>inParagraph</code> flag.
2364 * It is left in <code>false</code> value automatically
2365 * if the target document is empty or future inserts
2366 * were positioned into the 'body' tag.
2367 */
2368 if (!emptyDocument && !midInsert) {
2369 int targetOffset = Math.max(this.offset - 1, 0);
2370 Element elem =
2371 HTMLDocument.this.getCharacterElement(targetOffset);
2372 /* Going up by the left document structure path */
2373 for (int i = 0; i <= this.popDepth; i++) {
2374 elem = elem.getParentElement();
2375 }
2376 /* Going down by the right document structure path */
2377 for (int i = 0; i < this.pushDepth; i++) {
2378 int index = elem.getElementIndex(this.offset);
2379 elem = elem.getElement(index);
2380 }
2381 AttributeSet attrs = elem.getAttributes();
2382 if (attrs != null) {
2383 HTML.Tag tagToInsertInto =
2384 (HTML.Tag) attrs.getAttribute(StyleConstants.NameAttribute);
2385 if (tagToInsertInto != null) {
2386 this.inParagraph = tagToInsertInto.isParagraph();
2387 }
2388 }
2389 }
2390 }
2391
2392 /**
2393 * Generates an initial batch of end <code>ElementSpecs</code>
2394 * in parseBuffer to position future inserts into the body.
2395 */
2396 private void generateEndsSpecsForMidInsert() {
2397 int count = heightToElementWithName(HTML.Tag.BODY,
2398 Math.max(0, offset - 1));
2399 boolean joinNext = false;
2400
2401 if (count == -1 && offset > 0) {
2402 count = heightToElementWithName(HTML.Tag.BODY, offset);
2403 if (count != -1) {
2404 // Previous isn't in body, but current is. Have to
2405 // do some end specs, followed by join next.
2406 count = depthTo(offset - 1) - 1;
2407 joinNext = true;
2408 }
2409 }
2410 if (count == -1) {
2411 throw new RuntimeException("Must insert new content into body element-");
2412 }
2413 if (count != -1) {
2438 }
2439 // We should probably throw an exception if (count == -1)
2440 // Or look for the body and reset the offset.
2441 }
2442
2443 /**
2444 * @return number of parents to reach the child at offset.
2445 */
2446 private int depthTo(int offset) {
2447 Element e = getDefaultRootElement();
2448 int count = 0;
2449
2450 while (!e.isLeaf()) {
2451 count++;
2452 e = e.getElement(e.getElementIndex(offset));
2453 }
2454 return count;
2455 }
2456
2457 /**
2458 * @return number of parents of the leaf at <code>offset</code>
2459 * until a parent with name, <code>name</code> has been
2460 * found. -1 indicates no matching parent with
2461 * <code>name</code>.
2462 */
2463 private int heightToElementWithName(Object name, int offset) {
2464 Element e = getCharacterElement(offset).getParentElement();
2465 int count = 0;
2466
2467 while (e != null && e.getAttributes().getAttribute
2468 (StyleConstants.NameAttribute) != name) {
2469 count++;
2470 e = e.getParentElement();
2471 }
2472 return (e == null) ? -1 : count;
2473 }
2474
2475 /**
2476 * This will make sure there aren't two BODYs (the second is
2477 * typically created when you do a remove all, and then an insert).
2478 */
2479 private void adjustEndElement() {
2480 int length = getLength();
2481 if (length == 0) {
2687 if (inBlock == 0 && (foundInsertTag ||
2688 insertTag != HTML.Tag.COMMENT)) {
2689 // Comment outside of body, will not be able to show it,
2690 // but can add it as a property on the Document.
2691 addExternalComment(new String(data));
2692 return;
2693 }
2694 SimpleAttributeSet sas = new SimpleAttributeSet();
2695 sas.addAttribute(HTML.Attribute.COMMENT, new String(data));
2696 addSpecialElement(HTML.Tag.COMMENT, sas);
2697 }
2698
2699 TagAction action = tagMap.get(HTML.Tag.COMMENT);
2700 if (action != null) {
2701 action.start(HTML.Tag.COMMENT, new SimpleAttributeSet());
2702 action.end(HTML.Tag.COMMENT);
2703 }
2704 }
2705
2706 /**
2707 * Adds the comment <code>comment</code> to the set of comments
2708 * maintained outside of the scope of elements.
2709 */
2710 private void addExternalComment(String comment) {
2711 Object comments = getProperty(AdditionalComments);
2712 if (comments != null && !(comments instanceof Vector)) {
2713 // No place to put comment.
2714 return;
2715 }
2716 if (comments == null) {
2717 comments = new Vector<>();
2718 putProperty(AdditionalComments, comments);
2719 }
2720 @SuppressWarnings("unchecked")
2721 Vector<Object> v = (Vector<Object>)comments;
2722 v.addElement(comment);
2723 }
2724
2725 /**
2726 * Callback from the parser. Route to the appropriate
2727 * handler for the tag.
2761 styleAttributes = getStyleSheet().getDeclaration(decl);
2762 a.addAttributes(styleAttributes);
2763 }
2764 else {
2765 styleAttributes = null;
2766 }
2767
2768 TagAction action = tagMap.get(t);
2769 if (action != null) {
2770 action.start(t, a);
2771 action.end(t);
2772 }
2773 else if (getPreservesUnknownTags()) {
2774 // unknown tag, only add if should preserve it.
2775 addSpecialElement(t, a);
2776 }
2777 }
2778
2779 /**
2780 * This is invoked after the stream has been parsed, but before
2781 * <code>flush</code>. <code>eol</code> will be one of \n, \r
2782 * or \r\n, which ever is encountered the most in parsing the
2783 * stream.
2784 *
2785 * @since 1.3
2786 */
2787 public void handleEndOfLineString(String eol) {
2788 if (emptyDocument && eol != null) {
2789 putProperty(DefaultEditorKit.EndOfLineStringProperty,
2790 eol);
2791 }
2792 }
2793
2794 // ---- tag handling support ------------------------------
2795
2796 /**
2797 * Registers a handler for the given tag. By default
2798 * all of the well-known tags will have been registered.
2799 * This can be used to change the handling of a particular
2800 * tag or to add support for custom tags.
2801 *
3362 }
3363 }
3364
3365 void addParameter(AttributeSet a) {
3366 String name = (String) a.getAttribute(HTML.Attribute.NAME);
3367 String value = (String) a.getAttribute(HTML.Attribute.VALUE);
3368 if ((name != null) && (value != null)) {
3369 ElementSpec objSpec = parseBuffer.lastElement();
3370 MutableAttributeSet objAttr = (MutableAttributeSet) objSpec.getAttributes();
3371 objAttr.addAttribute(name, value);
3372 }
3373 }
3374 }
3375
3376 /**
3377 * Action to support forms by building all of the elements
3378 * used to represent form controls. This will process
3379 * the <INPUT>, <TEXTAREA>, <SELECT>,
3380 * and <OPTION> tags. The element created by
3381 * this action is expected to have the attribute
3382 * <code>StyleConstants.ModelAttribute</code> set to
3383 * the model that holds the state for the form control.
3384 * This enables multiple views, and allows document to
3385 * be iterated over picking up the data of the form.
3386 * The following are the model assignments for the
3387 * various type of form elements.
3388 * <table summary="model assignments for the various types of form elements">
3389 * <tr>
3390 * <th>Element Type
3391 * <th>Model Type
3392 * <tr>
3393 * <td>input, type button
3394 * <td>{@link DefaultButtonModel}
3395 * <tr>
3396 * <td>input, type checkbox
3397 * <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
3398 * <tr>
3399 * <td>input, type image
3400 * <td>{@link DefaultButtonModel}
3401 * <tr>
3402 * <td>input, type password
3550 if ( radioButtonGroupsMap == null ) { //fix for 4772743
3551 radioButtonGroupsMap = new HashMap<String, ButtonGroup>();
3552 }
3553 ButtonGroup radioButtonGroup = radioButtonGroupsMap.get(name);
3554 if (radioButtonGroup == null) {
3555 radioButtonGroup = new ButtonGroup();
3556 radioButtonGroupsMap.put(name,radioButtonGroup);
3557 }
3558 model.setGroup(radioButtonGroup);
3559 }
3560 boolean checked = (attr.getAttribute(HTML.Attribute.CHECKED) != null);
3561 model.setSelected(checked);
3562 attr.addAttribute(StyleConstants.ModelAttribute, model);
3563 }
3564 }
3565
3566 /**
3567 * If a <SELECT> tag is being processed, this
3568 * model will be a reference to the model being filled
3569 * with the <OPTION> elements (which produce
3570 * objects of type <code>Option</code>.
3571 */
3572 Object selectModel;
3573 int optionCount;
3574 }
3575
3576
3577 // --- utility methods used by the reader ------------------
3578
3579 /**
3580 * Pushes the current character style on a stack in preparation
3581 * for forming a new nested character style.
3582 */
3583 protected void pushCharacterStyle() {
3584 charAttrStack.push(charAttr.copyAttributes());
3585 }
3586
3587 /**
3588 * Pops a previously pushed character style off the stack
3589 * to return to a previous style.
3590 */
3809 int size = parseBuffer.size();
3810 if (endOfStream && (insertTag != null || insertAfterImplied) &&
3811 size > 0) {
3812 adjustEndSpecsForPartialInsert();
3813 size = parseBuffer.size();
3814 }
3815 ElementSpec[] spec = new ElementSpec[size];
3816 parseBuffer.copyInto(spec);
3817
3818 if (oldLength == 0 && (insertTag == null && !insertAfterImplied)) {
3819 create(spec);
3820 } else {
3821 insert(offset, spec);
3822 }
3823 parseBuffer.removeAllElements();
3824 offset += HTMLDocument.this.getLength() - oldLength;
3825 flushCount++;
3826 }
3827
3828 /**
3829 * This will be invoked for the last flush, if <code>insertTag</code>
3830 * is non null.
3831 */
3832 private void adjustEndSpecsForPartialInsert() {
3833 int size = parseBuffer.size();
3834 if (insertTagDepthDelta < 0) {
3835 // When inserting via an insertTag, the depths (of the tree
3836 // being read in, and existing hierarchy) may not match up.
3837 // This attemps to clean it up.
3838 int removeCounter = insertTagDepthDelta;
3839 while (removeCounter < 0 && size >= 0 &&
3840 parseBuffer.elementAt(size - 1).
3841 getType() == ElementSpec.EndTagType) {
3842 parseBuffer.removeElementAt(--size);
3843 removeCounter++;
3844 }
3845 }
3846 if (flushCount == 0 && (!insertAfterImplied ||
3847 !wantsTrailingNewline)) {
3848 // If this starts with content (or popDepth > 0 &&
3849 // pushDepth > 0) and ends with EndTagTypes, make sure
3887 counter--) {
3888 ElementSpec spec = parseBuffer.elementAt(counter);
3889 if (spec.getType() == ElementSpec.ContentType) {
3890 if (spec.getArray()[spec.getLength() - 1] != '\n') {
3891 SimpleAttributeSet attrs =new SimpleAttributeSet();
3892
3893 attrs.addAttribute(StyleConstants.NameAttribute,
3894 HTML.Tag.CONTENT);
3895 parseBuffer.insertElementAt(new ElementSpec(
3896 attrs,
3897 ElementSpec.ContentType, NEWLINE, 0, 1),
3898 counter + 1);
3899 }
3900 break;
3901 }
3902 }
3903 }
3904 }
3905
3906 /**
3907 * Adds the CSS rules in <code>rules</code>.
3908 */
3909 void addCSSRules(String rules) {
3910 StyleSheet ss = getStyleSheet();
3911 ss.addRule(rules);
3912 }
3913
3914 /**
3915 * Adds the CSS stylesheet at <code>href</code> to the known list
3916 * of stylesheets.
3917 */
3918 void linkCSSStyleSheet(String href) {
3919 URL url;
3920 try {
3921 url = new URL(base, href);
3922 } catch (MalformedURLException mfe) {
3923 try {
3924 url = new URL(href);
3925 } catch (MalformedURLException mfe2) {
3926 url = null;
3927 }
3928 }
3929 if (url != null) {
3930 getStyleSheet().importStyleSheet(url);
3931 }
3932 }
3933
3934 /**
3935 * Returns true if can insert starting at <code>t</code>. This
3936 * will return false if the insert tag is set, and hasn't been found
3937 * yet.
3938 */
3939 private boolean canInsertTag(HTML.Tag t, AttributeSet attr,
3940 boolean isBlockTag) {
3941 if (!foundInsertTag) {
3942 boolean needPImplied = ((t == HTML.Tag.IMPLIED)
3943 && (!inParagraph)
3944 && (!inPre));
3945 if (needPImplied && (nextTagAfterPImplied != null)) {
3946
3947 /*
3948 * If insertTag == null then just proceed to
3949 * foundInsertTag() call below and return true.
3950 */
3951 if (insertTag != null) {
3952 boolean nextTagIsInsertTag =
3953 isInsertTag(nextTagAfterPImplied);
3954 if ( (! nextTagIsInsertTag) || (! insertInsertTag) ) {
3955 return false;
4053 insertTagDepthDelta = depthTo(Math.max(0, offset - 1)) -
4054 popDepth + pushDepth - inBlock;
4055 if (isBlockTag) {
4056 // A start spec will be added (for this tag), so we account
4057 // for it here.
4058 insertTagDepthDelta++;
4059 }
4060 else {
4061 // An implied paragraph close (end spec) is going to be added,
4062 // so we account for it here.
4063 insertTagDepthDelta--;
4064 inParagraph = true;
4065 lastWasNewline = false;
4066 }
4067 }
4068
4069 /**
4070 * This is set to true when and end is invoked for {@literal <html>}.
4071 */
4072 private boolean receivedEndHTML;
4073 /** Number of times <code>flushBuffer</code> has been invoked. */
4074 private int flushCount;
4075 /** If true, behavior is similar to insertTag, but instead of
4076 * waiting for insertTag will wait for first Element without
4077 * an 'implied' attribute and begin inserting then. */
4078 private boolean insertAfterImplied;
4079 /** This is only used if insertAfterImplied is true. If false, only
4080 * inserting content, and there is a trailing newline it is removed. */
4081 private boolean wantsTrailingNewline;
4082 int threshold;
4083 int offset;
4084 boolean inParagraph = false;
4085 boolean impliedP = false;
4086 boolean inPre = false;
4087 boolean inTextArea = false;
4088 TextAreaDocument textAreaDocument = null;
4089 boolean inTitle = false;
4090 boolean lastWasNewline = true;
4091 boolean emptyAnchor;
4092 /** True if (!emptyDocument && insertTag == null), this is used so
4093 * much it is cached. */
|
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}, which implements the
45 * {@code HTMLEditorKit.ParserCallback} protocol that the parser
46 * expects. To change the structure one can subclass
47 * {@code HTMLReader}, and reimplement the method {@link
48 * #getReader(int)} to return the new reader implementation. The
49 * documentation for {@code HTMLReader} 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} attribute, which should
57 * always have a value of type {@code HTML.Tag} that identifies
58 * the kind of element. Some of the elements (such as comments) are
59 * synthesized. The {@code HTMLFactory} 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} 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} so
66 * that subclasses can disable it.</p>
67 *
68 * <p>The {@code Base} property determines the URL against which
69 * relative URLs are resolved. By default, this will be the
70 * {@code Document.StreamDescriptionProperty} if the value of the
71 * property is a URL. If a <BASE> 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}). Alternatives can be supplied by
77 * using the constructor that takes a {@code Content}
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>{@code
110 * <html>
111 * <head>
112 * <title>An example HTMLDocument</title>
113 * <style type="text/css">
114 * div { background-color: silver; }
115 * ul { color: red; }
116 * </style>
117 * </head>
118 * <body>
119 * <div id="BOX">
120 * <p>Paragraph 1</p>
121 * <p>Paragraph 2</p>
122 * </div>
123 * </body>
124 * </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)} 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}
138 * attribute matches the specified value. For example,
139 * {@code d.getElement("BOX")} returns the {@code DIV}
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} and {@code insertBeforeEnd}.
151 * For example, if {@code e} is the {@code DIV} element,
152 * {@code d.insertAfterStart(e, "<ul><li>List Item</li></ul>")}
153 * inserts the list before the first
154 * paragraph, and
155 * {@code d.insertBeforeEnd(e, "<ul><li>List Item</li></ul>")}
156 * inserts the list after the last
157 * paragraph. The {@code DIV} block becomes the parent of the
158 * newly inserted elements.</p>
159 *
160 * <p>Sibling elements can be inserted before or after any element by
161 * using the methods {@code insertBeforeStart} and
162 * {@code insertAfterEnd}. For example, if {@code e} is the
163 * {@code DIV} element,
164 * {@code d.insertBeforeStart(e, "<ul><li>List Item</li></ul>")}
165 * inserts the list before the {@code DIV} element, and
166 * {@code d.insertAfterEnd(e, "<ul><li>List Item</li></ul>")}
167 * inserts the list after the {@code DIV} element.
168 * The newly inserted elements
169 * become siblings of the {@code DIV} element.</p>
170 *
171 * <h3>Replacing elements</h3>
172 *
173 * <p>Elements and all their descendants can be replaced by using the
174 * methods {@code setInnerHTML} and {@code setOuterHTML}.
175 * For example, if {@code e} is the {@code DIV} element,
176 * {@code d.setInnerHTML(e, "<ul><li>List Item</li></ul>")}
177 * replaces all children paragraphs with
178 * the list, and
179 * {@code d.setOuterHTML(e, "<ul><li>List Item</li></ul>")}
180 * replaces the {@code DIV} element
181 * itself. In latter case the parent of the list is the
182 * {@code BODY} element.
183 *
184 * <h3>Summary</h3>
185 *
186 * <p>The following table shows the example document and the results
187 * of various methods described above.</p>
188 *
189 * <table border=1 cellspacing=0 summary="HTML Content of example above">
190 * <tr>
191 * <th>Example</th>
192 * <th>{@code insertAfterStart}</th>
193 * <th>{@code insertBeforeEnd}</th>
194 * <th>{@code insertBeforeStart}</th>
195 * <th>{@code insertAfterEnd}</th>
196 * <th>{@code setInnerHTML}</th>
197 * <th>{@code setOuterHTML}</th>
198 * </tr>
199 * <tr valign="top">
200 * <td style="white-space:nowrap">
201 * <div style="background-color: silver;">
202 * <p>Paragraph 1</p>
203 * <p>Paragraph 2</p>
204 * </div>
205 * </td>
206 * <!--insertAfterStart-->
207 * <td style="white-space:nowrap">
208 * <div style="background-color: silver;">
209 * <ul style="color: red;">
210 * <li>List Item</li>
211 * </ul>
212 * <p>Paragraph 1</p>
213 * <p>Paragraph 2</p>
214 * </div>
215 * </td>
216 * <!--insertBeforeEnd-->
217 * <td style="white-space:nowrap">
249 * <ul style="color: red;">
250 * <li>List Item</li>
251 * </ul>
252 * </div>
253 * </td>
254 * <!--setOuterHTML-->
255 * <td style="white-space:nowrap">
256 * <ul style="color: red;">
257 * <li>List Item</li>
258 * </ul>
259 * </td>
260 * </tr>
261 * </table>
262 *
263 * <p><strong>Warning:</strong> Serialized objects of this class will
264 * not be compatible with future Swing releases. The current
265 * serialization support is appropriate for short term storage or RMI
266 * between applications running the same version of Swing. As of 1.4,
267 * support for long term storage of all JavaBeans™
268 * has been added to the
269 * {@code java.beans} package. Please see {@link
270 * java.beans.XMLEncoder}.</p>
271 *
272 * @author Timothy Prinzing
273 * @author Scott Violet
274 * @author Sunita Mani
275 */
276 @SuppressWarnings("serial") // Same-version serialization only
277 public class HTMLDocument extends DefaultStyledDocument {
278 /**
279 * Constructs an HTML document using the default buffer size
280 * and a default {@code StyleSheet}. This is a convenience
281 * method for the constructor
282 * {@code HTMLDocument(Content, StyleSheet)}.
283 */
284 public HTMLDocument() {
285 this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet());
286 }
287
288 /**
289 * Constructs an HTML document with the default content
290 * storage implementation and the specified style/attribute
291 * storage mechanism. This is a convenience method for the
292 * constructor
293 * {@code HTMLDocument(Content, StyleSheet)}.
294 *
295 * @param styles the styles
296 */
297 public HTMLDocument(StyleSheet styles) {
298 this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
299 }
300
301 /**
302 * Constructs an HTML document with the given content
303 * storage implementation and the given style/attribute
304 * storage mechanism.
305 *
306 * @param c the container for the content
307 * @param styles the styles
308 */
309 public HTMLDocument(Content c, StyleSheet styles) {
310 super(c, styles);
311 }
312
313 /**
314 * Fetches the reader for the parser to use when loading the document
315 * with HTML. This is implemented to return an instance of
316 * {@code HTMLDocument.HTMLReader}.
317 * Subclasses can reimplement this
318 * method to change how the document gets structured if desired.
319 * (For example, to handle custom tags, or structurally represent character
320 * style elements.)
321 *
322 * @param pos the starting position
323 * @return the reader used by the parser to load the document
324 */
325 public HTMLEditorKit.ParserCallback getReader(int pos) {
326 Object desc = getProperty(Document.StreamDescriptionProperty);
327 if (desc instanceof URL) {
328 setBase((URL)desc);
329 }
330 HTMLReader reader = new HTMLReader(pos);
331 return reader;
332 }
333
334 /**
335 * Returns the reader for the parser to use to load the document
336 * with HTML. This is implemented to return an instance of
337 * {@code HTMLDocument.HTMLReader}.
338 * Subclasses can reimplement this
339 * method to change how the document gets structured if desired.
340 * (For example, to handle custom tags, or structurally represent character
341 * style elements.)
342 * <p>This is a convenience method for
343 * {@code getReader(int, int, int, HTML.Tag, TRUE)}.
344 *
345 * @param pos the starting position
346 * @param popDepth the number of {@code ElementSpec.EndTagTypes}
347 * to generate before inserting
348 * @param pushDepth the number of {@code ElementSpec.StartTagTypes}
349 * with a direction of {@code ElementSpec.JoinNextDirection}
350 * that should be generated before inserting,
351 * but after the end tags have been generated
352 * @param insertTag the first tag to start inserting into document
353 * @return the reader used by the parser to load the document
354 */
355 public HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
356 int pushDepth,
357 HTML.Tag insertTag) {
358 return getReader(pos, popDepth, pushDepth, insertTag, true);
359 }
360
361 /**
362 * Fetches the reader for the parser to use to load the document
363 * with HTML. This is implemented to return an instance of
364 * HTMLDocument.HTMLReader. Subclasses can reimplement this
365 * method to change how the document get structured if desired
366 * (e.g. to handle custom tags, structurally represent character
367 * style elements, etc.).
368 *
369 * @param popDepth the number of {@code ElementSpec.EndTagTypes}
370 * to generate before inserting
371 * @param pushDepth the number of {@code ElementSpec.StartTagTypes}
372 * with a direction of {@code ElementSpec.JoinNextDirection}
373 * that should be generated before inserting,
374 * but after the end tags have been generated
375 * @param insertTag the first tag to start inserting into document
376 * @param insertInsertTag false if all the Elements after insertTag should
377 * be inserted; otherwise insertTag will be inserted
378 * @return the reader used by the parser to load the document
379 */
380 HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
381 int pushDepth,
382 HTML.Tag insertTag,
383 boolean insertInsertTag) {
384 Object desc = getProperty(Document.StreamDescriptionProperty);
385 if (desc instanceof URL) {
386 setBase((URL)desc);
387 }
388 HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth,
389 insertTag, insertInsertTag, false,
390 true);
391 return reader;
392 }
393
394 /**
395 * Returns the location to resolve relative URLs against. By
396 * default this will be the document's URL if the document
397 * was loaded from a URL. If a base tag is found and
398 * can be parsed, it will be used as the base location.
399 *
400 * @return the base location
401 */
402 public URL getBase() {
403 return base;
404 }
405
406 /**
407 * Sets the location to resolve relative URLs against. By
408 * default this will be the document's URL if the document
409 * was loaded from a URL. If a base tag is found and
410 * can be parsed, it will be used as the base location.
411 * <p>This also sets the base of the {@code StyleSheet}
412 * to be {@code u} as well as the base of the document.
413 *
414 * @param u the desired base URL
415 */
416 public void setBase(URL u) {
417 base = u;
418 getStyleSheet().setBase(u);
419 }
420
421 /**
422 * Inserts new elements in bulk. This is how elements get created
423 * in the document. The parsing determines what structure is needed
424 * and creates the specification as a set of tokens that describe the
425 * edit while leaving the document free of a write-lock. This method
426 * can then be called in bursts by the reader to acquire a write-lock
427 * for a shorter duration (i.e. while the document is actually being
428 * altered).
429 *
430 * @param offset the starting offset
431 * @param data the element data
432 * @exception BadLocationException if the given position does not
510 else {
511 lastEnd = paragraph.getEndOffset();
512 }
513 MutableAttributeSet attr =
514 (MutableAttributeSet) paragraph.getAttributes();
515 changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
516 if (replace) {
517 attr.removeAttributes(attr);
518 }
519 attr.addAttributes(s);
520 }
521 changes.end();
522 fireChangedUpdate(changes);
523 fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
524 } finally {
525 writeUnlock();
526 }
527 }
528
529 /**
530 * Fetches the {@code StyleSheet} with the document-specific display
531 * rules (CSS) that were specified in the HTML document itself.
532 *
533 * @return the {@code StyleSheet}
534 */
535 public StyleSheet getStyleSheet() {
536 return (StyleSheet) getAttributeContext();
537 }
538
539 /**
540 * Fetches an iterator for the specified HTML tag.
541 * This can be used for things like iterating over the
542 * set of anchors contained, or iterating over the input
543 * elements.
544 *
545 * @param t the requested {@code HTML.Tag}
546 * @return the {@code Iterator} for the given HTML tag
547 * @see javax.swing.text.html.HTML.Tag
548 */
549 public Iterator getIterator(HTML.Tag t) {
550 if (t.isBlock()) {
551 // TBD
552 return null;
553 }
554 return new LeafIterator(t, this);
555 }
556
557 /**
558 * Creates a document leaf element that directly represents
559 * text (doesn't have any children). This is implemented
560 * to return an element of type
561 * {@code HTMLDocument.RunElement}.
562 *
563 * @param parent the parent element
564 * @param a the attributes for the element
565 * @param p0 the beginning of the range (must be at least 0)
566 * @param p1 the end of the range (must be at least p0)
567 * @return the new element
568 */
569 protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
570 return new RunElement(parent, a, p0, p1);
571 }
572
573 /**
574 * Creates a document branch element, that can contain other elements.
575 * This is implemented to return an element of type
576 * {@code HTMLDocument.BlockElement}.
577 *
578 * @param parent the parent element
579 * @param a the attributes
580 * @return the element
581 */
582 protected Element createBranchElement(Element parent, AttributeSet a) {
583 return new BlockElement(parent, a);
584 }
585
586 /**
587 * Creates the root element to be used to represent the
588 * default document structure.
589 *
590 * @return the element base
591 */
592 protected AbstractElement createDefaultRoot() {
593 // grabs a write-lock for this initialization and
594 // abandon it during initialization so in normal
595 // operation we can detect an illegitimate attempt
596 // to mutate attributes.
615 body.replace(0, 0, buff);
616 buff[0] = body;
617 html.replace(0, 0, buff);
618 writeUnlock();
619 return html;
620 }
621
622 /**
623 * Sets the number of tokens to buffer before trying to update
624 * the documents element structure.
625 *
626 * @param n the number of tokens to buffer
627 */
628 public void setTokenThreshold(int n) {
629 putProperty(TokenThreshold, n);
630 }
631
632 /**
633 * Gets the number of tokens to buffer before trying to update
634 * the documents element structure. The default value is
635 * {@code Integer.MAX_VALUE}.
636 *
637 * @return the number of tokens to buffer
638 */
639 public int getTokenThreshold() {
640 Integer i = (Integer) getProperty(TokenThreshold);
641 if (i != null) {
642 return i.intValue();
643 }
644 return Integer.MAX_VALUE;
645 }
646
647 /**
648 * Determines how unknown tags are handled by the parser.
649 * If set to true, unknown
650 * tags are put in the model, otherwise they are dropped.
651 *
652 * @param preservesTags true if unknown tags should be
653 * saved in the model, otherwise tags are dropped
654 * @see javax.swing.text.html.HTML.Tag
655 */
656 public void setPreservesUnknownTags(boolean preservesTags) {
657 preservesUnknownTags = preservesTags;
658 }
659
660 /**
661 * Returns the behavior the parser observes when encountering
662 * unknown tags.
663 *
664 * @see javax.swing.text.html.HTML.Tag
665 * @return true if unknown tags are to be preserved when parsing
666 */
667 public boolean getPreservesUnknownTags() {
668 return preservesUnknownTags;
669 }
670
671 /**
672 * Processes {@code HyperlinkEvents} that
673 * are generated by documents in an HTML frame.
674 * The {@code HyperlinkEvent} type, as the parameter suggests,
675 * is {@code HTMLFrameHyperlinkEvent}.
676 * In addition to the typical information contained in a
677 * {@code HyperlinkEvent},
678 * this event contains the element that corresponds to the frame in
679 * which the click happened (the source element) and the
680 * target name. The target name has 4 possible values:
681 * <ul>
682 * <li> _self
683 * <li> _parent
684 * <li> _top
685 * <li> a named frame
686 * </ul>
687 *
688 * If target is _self, the action is to change the value of the
689 * {@code HTML.Attribute.SRC} attribute and fires a
690 * {@code ChangedUpdate} event.
691 *<p>
692 * If the target is _parent, then it deletes the parent element,
693 * which is a <FRAMESET> element, and inserts a new <FRAME>
694 * element, and sets its {@code HTML.Attribute.SRC} attribute
695 * to have a value equal to the destination URL and fire a
696 * {@code RemovedUpdate} and {@code InsertUpdate}.
697 *<p>
698 * If the target is _top, this method does nothing. In the implementation
699 * of the view for a frame, namely the {@code FrameView},
700 * the processing of _top is handled. Given that _top implies
701 * replacing the entire document, it made sense to handle this outside
702 * of the document that it will replace.
703 *<p>
704 * If the target is a named frame, then the element hierarchy is searched
705 * for an element with a name equal to the target, its
706 * {@code HTML.Attribute.SRC} attribute is updated and a
707 * {@code ChangedUpdate} event is fired.
708 *
709 * @param e the event
710 */
711 public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent e) {
712 String frameName = e.getTarget();
713 Element element = e.getSourceElement();
714 String urlStr = e.getURL().toString();
715
716 if (frameName.equals("_self")) {
717 /*
718 The source and destination elements
719 are the same.
720 */
721 updateFrame(element, urlStr);
722 } else if (frameName.equals("_parent")) {
723 /*
724 The destination is the parent of the frame.
725 */
726 updateFrameSet(element.getParentElement(), urlStr);
727 } else {
728 /*
729 locate a named frame
730 */
731 Element targetElement = findFrame(frameName);
732 if (targetElement != null) {
733 updateFrame(targetElement, urlStr);
734 }
735 }
736 }
737
738
739 /**
740 * Searches the element hierarchy for an FRAME element
741 * that has its name attribute equal to the {@code frameName}.
742 *
743 * @param frameName
744 * @return the element whose NAME attribute has a value of
745 * {@code frameName}; returns {@code null}
746 * if not found
747 */
748 private Element findFrame(String frameName) {
749 ElementIterator it = new ElementIterator(this);
750 Element next;
751
752 while ((next = it.next()) != null) {
753 AttributeSet attr = next.getAttributes();
754 if (matchNameAttribute(attr, HTML.Tag.FRAME)) {
755 String frameTarget = (String)attr.getAttribute(HTML.Attribute.NAME);
756 if (frameTarget != null && frameTarget.equals(frameName)) {
757 break;
758 }
759 }
760 }
761 return next;
762 }
763
764 /**
765 * Returns true if {@code StyleConstants.NameAttribute} is
766 * equal to the tag that is passed in as a parameter.
767 *
768 * @param attr the attributes to be matched
769 * @param tag the value to be matched
770 * @return true if there is a match, false otherwise
771 * @see javax.swing.text.html.HTML.Attribute
772 */
773 static boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
774 Object o = attr.getAttribute(StyleConstants.NameAttribute);
775 if (o instanceof HTML.Tag) {
776 HTML.Tag name = (HTML.Tag) o;
777 if (name == tag) {
778 return true;
779 }
780 }
781 return false;
782 }
783
784 /**
785 * Replaces a frameset branch Element with a frame leaf element.
791 private void updateFrameSet(Element element, String url) {
792 try {
793 int startOffset = element.getStartOffset();
794 int endOffset = Math.min(getLength(), element.getEndOffset());
795 String html = "<frame";
796 if (url != null) {
797 html += " src=\"" + url + "\"";
798 }
799 html += ">";
800 installParserIfNecessary();
801 setOuterHTML(element, html);
802 } catch (BadLocationException e1) {
803 // Should handle this better
804 } catch (IOException ioe) {
805 // Should handle this better
806 }
807 }
808
809
810 /**
811 * Updates the Frame elements {@code HTML.Attribute.SRC attribute}
812 * and fires a {@code ChangedUpdate} event.
813 *
814 * @param element a FRAME element whose SRC attribute will be updated
815 * @param url a string specifying the new value for the SRC attribute
816 */
817 private void updateFrame(Element element, String url) {
818
819 try {
820 writeLock();
821 DefaultDocumentEvent changes = new DefaultDocumentEvent(element.getStartOffset(),
822 1,
823 DocumentEvent.EventType.CHANGE);
824 AttributeSet sCopy = element.getAttributes().copyAttributes();
825 MutableAttributeSet attr = (MutableAttributeSet) element.getAttributes();
826 changes.addEdit(new AttributeUndoableEdit(element, sCopy, false));
827 attr.removeAttribute(HTML.Attribute.SRC);
828 attr.addAttribute(HTML.Attribute.SRC, url);
829 changes.end();
830 fireChangedUpdate(changes);
831 fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
832 } finally {
840 * @return true if document will be viewed in a frame, otherwise false
841 */
842 boolean isFrameDocument() {
843 return frameDocument;
844 }
845
846 /**
847 * Sets a boolean state about whether the document will be
848 * viewed in a frame.
849 * @param frameDoc true if the document will be viewed in a frame,
850 * otherwise false
851 */
852 void setFrameDocumentState(boolean frameDoc) {
853 this.frameDocument = frameDoc;
854 }
855
856 /**
857 * Adds the specified map, this will remove a Map that has been
858 * previously registered with the same name.
859 *
860 * @param map the {@code Map} to be registered
861 */
862 void addMap(Map map) {
863 String name = map.getName();
864
865 if (name != null) {
866 Object maps = getProperty(MAP_PROPERTY);
867
868 if (maps == null) {
869 maps = new Hashtable<>(11);
870 putProperty(MAP_PROPERTY, maps);
871 }
872 if (maps instanceof Hashtable) {
873 @SuppressWarnings("unchecked")
874 Hashtable<Object, Object> tmp = (Hashtable)maps;
875 tmp.put("#" + name, map);
876 }
877 }
878 }
879
880 /**
881 * Removes a previously registered map.
882 * @param map the {@code Map} to be removed
883 */
884 void removeMap(Map map) {
885 String name = map.getName();
886
887 if (name != null) {
888 Object maps = getProperty(MAP_PROPERTY);
889
890 if (maps instanceof Hashtable) {
891 ((Hashtable)maps).remove("#" + name);
892 }
893 }
894 }
895
896 /**
897 * Returns the Map associated with the given name.
898 * @param name the name of the desired {@code Map}
899 * @return the {@code Map} or {@code null} if it can't
900 * be found, or if {@code name} is {@code null}
901 */
902 Map getMap(String name) {
903 if (name != null) {
904 Object maps = getProperty(MAP_PROPERTY);
905
906 if (maps != null && (maps instanceof Hashtable)) {
907 return (Map)((Hashtable)maps).get(name);
908 }
909 }
910 return null;
911 }
912
913 /**
914 * Returns an {@code Enumeration} of the possible Maps.
915 * @return the enumerated list of maps, or {@code null}
916 * if the maps are not an instance of {@code Hashtable}
917 */
918 Enumeration<Object> getMaps() {
919 Object maps = getProperty(MAP_PROPERTY);
920
921 if (maps instanceof Hashtable) {
922 @SuppressWarnings("unchecked")
923 Hashtable<Object, Object> tmp = (Hashtable) maps;
924 return tmp.elements();
925 }
926 return null;
927 }
928
929 /**
930 * Sets the content type language used for style sheets that do not
931 * explicitly specify the type. The default is text/css.
932 * @param contentType the content type language for the style sheets
933 */
934 /* public */
935 void setDefaultStyleSheetType(String contentType) {
936 putProperty(StyleType, contentType);
937 }
938
939 /**
940 * Returns the content type language used for style sheets. The default
941 * is text/css.
942 * @return the content type language used for the style sheets
943 */
944 /* public */
945 String getDefaultStyleSheetType() {
946 String retValue = (String)getProperty(StyleType);
947 if (retValue == null) {
948 return "text/css";
949 }
950 return retValue;
951 }
952
953 /**
954 * Sets the parser that is used by the methods that insert html
955 * into the existing document, such as {@code setInnerHTML},
956 * and {@code setOuterHTML}.
957 * <p>
958 * {@code HTMLEditorKit.createDefaultDocument} will set the parser
959 * for you. If you create an {@code HTMLDocument} by hand,
960 * be sure and set the parser accordingly.
961 * @param parser the parser to be used for text insertion
962 *
963 * @since 1.3
964 */
965 public void setParser(HTMLEditorKit.Parser parser) {
966 this.parser = parser;
967 putProperty("__PARSER__", null);
968 }
969
970 /**
971 * Returns the parser that is used when inserting HTML into the existing
972 * document.
973 * @return the parser used for text insertion
974 *
975 * @since 1.3
976 */
977 public HTMLEditorKit.Parser getParser() {
978 Object p = getProperty("__PARSER__");
979
980 if (p instanceof HTMLEditorKit.Parser) {
981 return (HTMLEditorKit.Parser)p;
982 }
983 return parser;
984 }
985
986 /**
987 * Replaces the children of the given element with the contents
988 * specified as an HTML string.
989 *
990 * <p>This will be seen as at least two events, n inserts followed by
991 * a remove.</p>
992 *
993 * <p>Consider the following structure (the {@code elem}
994 * parameter is <b>in bold</b>).</p>
995 *
996 * <pre>
997 * <body>
998 * |
999 * <b><div></b>
1000 * / \
1001 * <p> <p>
1002 * </pre>
1003 *
1004 * <p>Invoking {@code setInnerHTML(elem, "<ul><li>")}
1005 * results in the following structure (new elements are <font
1006 * style="color: red;">in red</font>).</p>
1007 *
1008 * <pre>
1009 * <body>
1010 * |
1011 * <b><div></b>
1012 * \
1013 * <font style="color: red;"><ul></font>
1014 * \
1015 * <font style="color: red;"><li></font>
1016 * </pre>
1017 *
1018 * <p>Parameter {@code elem} must not be a leaf element,
1019 * otherwise an {@code IllegalArgumentException} is thrown.
1020 * If either {@code elem} or {@code htmlText} parameter
1021 * is {@code null}, no changes are made to the document.</p>
1022 *
1023 * <p>For this to work correctly, the document must have an
1024 * {@code HTMLEditorKit.Parser} set. This will be the case
1025 * if the document was created from an HTMLEditorKit via the
1026 * {@code createDefaultDocument} method.</p>
1027 *
1028 * @param elem the branch element whose children will be replaced
1029 * @param htmlText the string to be parsed and assigned to {@code elem}
1030 * @throws IllegalArgumentException if {@code elem} is a leaf
1031 * @throws IllegalStateException if an {@code HTMLEditorKit.Parser}
1032 * has not been defined
1033 * @throws BadLocationException if replacement is impossible because of
1034 * a structural issue
1035 * @throws IOException if an I/O exception occurs
1036 * @since 1.3
1037 */
1038 public void setInnerHTML(Element elem, String htmlText) throws
1039 BadLocationException, IOException {
1040 verifyParser();
1041 if (elem != null && elem.isLeaf()) {
1042 throw new IllegalArgumentException
1043 ("Can not set inner HTML of a leaf");
1044 }
1045 if (elem != null && htmlText != null) {
1046 int oldCount = elem.getElementCount();
1047 int insertPosition = elem.getStartOffset();
1048 insertHTML(elem, elem.getStartOffset(), htmlText, true);
1049 if (elem.getElementCount() > oldCount) {
1050 // Elements were inserted, do the cleanup.
1051 removeElements(elem, elem.getElementCount() - oldCount,
1052 oldCount);
1053 }
1054 }
1055 }
1056
1057 /**
1058 * Replaces the given element in the parent with the contents
1059 * specified as an HTML string.
1060 *
1061 * <p>This will be seen as at least two events, n inserts followed by
1062 * a remove.</p>
1063 *
1064 * <p>When replacing a leaf this will attempt to make sure there is
1065 * a newline present if one is needed. This may result in an additional
1066 * element being inserted. Consider, if you were to replace a character
1067 * element that contained a newline with <img> this would create
1068 * two elements, one for the image, and one for the newline.</p>
1069 *
1070 * <p>If you try to replace the element at length you will most
1071 * likely end up with two elements, eg
1072 * {@code setOuterHTML(getCharacterElement (getLength()), "blah")}
1073 * will result in two leaf elements at the end, one
1074 * representing 'blah', and the other representing the end
1075 * element.</p>
1076 *
1077 * <p>Consider the following structure (the {@code elem}
1078 * parameter is <b>in bold</b>).</p>
1079 *
1080 * <pre>
1081 * <body>
1082 * |
1083 * <b><div></b>
1084 * / \
1085 * <p> <p>
1086 * </pre>
1087 *
1088 * <p>Invoking {@code setOuterHTML(elem, "<ul><li>")}
1089 * results in the following structure (new elements are <font
1090 * style="color: red;">in red</font>).</p>
1091 *
1092 * <pre>
1093 * <body>
1094 * |
1095 * <font style="color: red;"><ul></font>
1096 * \
1097 * <font style="color: red;"><li></font>
1098 * </pre>
1099 *
1100 * <p>If either {@code elem} or {@code htmlText}
1101 * parameter is {@code null}, no changes are made to the
1102 * document.</p>
1103 *
1104 * <p>For this to work correctly, the document must have an
1105 * HTMLEditorKit.Parser set. This will be the case if the document
1106 * was created from an HTMLEditorKit via the
1107 * {@code createDefaultDocument} method.</p>
1108 *
1109 * @param elem the element to replace
1110 * @param htmlText the string to be parsed and inserted in place of {@code elem}
1111 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1112 * been set
1113 * @throws BadLocationException if replacement is impossible because of
1114 * a structural issue
1115 * @throws IOException if an I/O exception occurs
1116 * @since 1.3
1117 */
1118 public void setOuterHTML(Element elem, String htmlText) throws
1119 BadLocationException, IOException {
1120 verifyParser();
1121 if (elem != null && elem.getParentElement() != null &&
1122 htmlText != null) {
1123 int start = elem.getStartOffset();
1124 int end = elem.getEndOffset();
1125 int startLength = getLength();
1126 // We don't want a newline if elem is a leaf, and doesn't contain
1127 // a newline.
1128 boolean wantsNewline = !elem.isLeaf();
1129 if (!wantsNewline && (end > startLength ||
1130 getText(end - 1, 1).charAt(0) == NEWLINE[0])){
1131 wantsNewline = true;
1132 }
1133 Element parent = elem.getParentElement();
1134 int oldCount = parent.getElementCount();
1135 insertHTML(parent, start, htmlText, wantsNewline);
1136 // Remove old.
1137 int newLength = getLength();
1138 if (oldCount != parent.getElementCount()) {
1139 int removeIndex = parent.getElementIndex(start + newLength -
1140 startLength);
1141 removeElements(parent, removeIndex, 1);
1142 }
1143 }
1144 }
1145
1146 /**
1147 * Inserts the HTML specified as a string at the start
1148 * of the element.
1149 *
1150 * <p>Consider the following structure (the {@code elem}
1151 * parameter is <b>in bold</b>).</p>
1152 *
1153 * <pre>
1154 * <body>
1155 * |
1156 * <b><div></b>
1157 * / \
1158 * <p> <p>
1159 * </pre>
1160 *
1161 * <p>Invoking
1162 * {@code insertAfterStart(elem, "<ul><li>")}
1163 * results in the following structure
1164 * (new elements are <font style="color: red;">in red</font>).</p>
1165 *
1166 * <pre>
1167 * <body>
1168 * |
1169 * <b><div></b>
1170 * / | \
1171 * <font style="color: red;"><ul></font> <p> <p>
1172 * /
1173 * <font style="color: red;"><li></font>
1174 * </pre>
1175 *
1176 * <p>Unlike the {@code insertBeforeStart} method, new
1177 * elements become <em>children</em> of the specified element,
1178 * not siblings.</p>
1179 *
1180 * <p>Parameter {@code elem} must not be a leaf element,
1181 * otherwise an {@code IllegalArgumentException} is thrown.
1182 * If either {@code elem} or {@code htmlText} parameter
1183 * is {@code null}, no changes are made to the document.</p>
1184 *
1185 * <p>For this to work correctly, the document must have an
1186 * {@code HTMLEditorKit.Parser} set. This will be the case
1187 * if the document was created from an HTMLEditorKit via the
1188 * {@code createDefaultDocument} method.</p>
1189 *
1190 * @param elem the branch element to be the root for the new text
1191 * @param htmlText the string to be parsed and assigned to {@code elem}
1192 * @throws IllegalArgumentException if {@code elem} is a leaf
1193 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1194 * been set on the document
1195 * @throws BadLocationException if insertion is impossible because of
1196 * a structural issue
1197 * @throws IOException if an I/O exception occurs
1198 * @since 1.3
1199 */
1200 public void insertAfterStart(Element elem, String htmlText) throws
1201 BadLocationException, IOException {
1202 verifyParser();
1203
1204 if (elem == null || htmlText == null) {
1205 return;
1206 }
1207
1208 if (elem.isLeaf()) {
1209 throw new IllegalArgumentException
1210 ("Can not insert HTML after start of a leaf");
1211 }
1212 insertHTML(elem, elem.getStartOffset(), htmlText, false);
1213 }
1214
1215 /**
1216 * Inserts the HTML specified as a string at the end of
1217 * the element.
1218 *
1219 * <p> If {@code elem}'s children are leaves, and the
1220 * character at a {@code elem.getEndOffset() - 1} is a newline,
1221 * this will insert before the newline so that there isn't text after
1222 * the newline.</p>
1223 *
1224 * <p>Consider the following structure (the {@code elem}
1225 * parameter is <b>in bold</b>).</p>
1226 *
1227 * <pre>
1228 * <body>
1229 * |
1230 * <b><div></b>
1231 * / \
1232 * <p> <p>
1233 * </pre>
1234 *
1235 * <p>Invoking {@code insertBeforeEnd(elem, "<ul><li>")}
1236 * results in the following structure (new elements are <font
1237 * style="color: red;">in red</font>).</p>
1238 *
1239 * <pre>
1240 * <body>
1241 * |
1242 * <b><div></b>
1243 * / | \
1244 * <p> <p> <font style="color: red;"><ul></font>
1245 * \
1246 * <font style="color: red;"><li></font>
1247 * </pre>
1248 *
1249 * <p>Unlike the {@code insertAfterEnd} method, new elements
1250 * become <em>children</em> of the specified element, not
1251 * siblings.</p>
1252 *
1253 * <p>Parameter {@code elem} must not be a leaf element,
1254 * otherwise an {@code IllegalArgumentException} is thrown.
1255 * If either {@code elem} or {@code htmlText} parameter
1256 * is {@code null}, no changes are made to the document.</p>
1257 *
1258 * <p>For this to work correctly, the document must have an
1259 * {@code HTMLEditorKit.Parser} set. This will be the case
1260 * if the document was created from an HTMLEditorKit via the
1261 * {@code createDefaultDocument} method.</p>
1262 *
1263 * @param elem the element to be the root for the new text
1264 * @param htmlText the string to be parsed and assigned to {@code elem}
1265 * @throws IllegalArgumentException if {@code elem} is a leaf
1266 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1267 * been set on the document
1268 * @throws BadLocationException if insertion is impossible because of
1269 * a structural issue
1270 * @throws IOException if an I/O exception occurs
1271 * @since 1.3
1272 */
1273 public void insertBeforeEnd(Element elem, String htmlText) throws
1274 BadLocationException, IOException {
1275 verifyParser();
1276 if (elem != null && elem.isLeaf()) {
1277 throw new IllegalArgumentException
1278 ("Can not set inner HTML before end of leaf");
1279 }
1280 if (elem != null) {
1281 int offset = elem.getEndOffset();
1282 if (elem.getElement(elem.getElementIndex(offset - 1)).isLeaf() &&
1283 getText(offset - 1, 1).charAt(0) == NEWLINE[0]) {
1284 offset--;
1285 }
1286 insertHTML(elem, offset, htmlText, false);
1287 }
1288 }
1289
1290 /**
1291 * Inserts the HTML specified as a string before the start of
1292 * the given element.
1293 *
1294 * <p>Consider the following structure (the {@code elem}
1295 * parameter is <b>in bold</b>).</p>
1296 *
1297 * <pre>
1298 * <body>
1299 * |
1300 * <b><div></b>
1301 * / \
1302 * <p> <p>
1303 * </pre>
1304 *
1305 * <p>Invoking
1306 * {@code insertBeforeStart(elem, "<ul><li>")}
1307 * results in the following structure
1308 * (new elements are <font style="color: red;">in red</font>).</p>
1309 *
1310 * <pre>
1311 * <body>
1312 * / \
1313 * <font style="color: red;"><ul></font> <b><div></b>
1314 * / / \
1315 * <font style="color: red;"><li></font> <p> <p>
1316 * </pre>
1317 *
1318 * <p>Unlike the {@code insertAfterStart} method, new
1319 * elements become <em>siblings</em> of the specified element, not
1320 * children.</p>
1321 *
1322 * <p>If either {@code elem} or {@code htmlText}
1323 * parameter is {@code null}, no changes are made to the
1324 * document.</p>
1325 *
1326 * <p>For this to work correctly, the document must have an
1327 * {@code HTMLEditorKit.Parser} set. This will be the case
1328 * if the document was created from an HTMLEditorKit via the
1329 * {@code createDefaultDocument} method.</p>
1330 *
1331 * @param elem the element the content is inserted before
1332 * @param htmlText the string to be parsed and inserted before {@code elem}
1333 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1334 * been set on the document
1335 * @throws BadLocationException if insertion is impossible because of
1336 * a structural issue
1337 * @throws IOException if an I/O exception occurs
1338 * @since 1.3
1339 */
1340 public void insertBeforeStart(Element elem, String htmlText) throws
1341 BadLocationException, IOException {
1342 verifyParser();
1343 if (elem != null) {
1344 Element parent = elem.getParentElement();
1345
1346 if (parent != null) {
1347 insertHTML(parent, elem.getStartOffset(), htmlText, false);
1348 }
1349 }
1350 }
1351
1352 /**
1353 * Inserts the HTML specified as a string after the end of the
1354 * given element.
1355 *
1356 * <p>Consider the following structure (the {@code elem}
1357 * parameter is <b>in bold</b>).</p>
1358 *
1359 * <pre>
1360 * <body>
1361 * |
1362 * <b><div></b>
1363 * / \
1364 * <p> <p>
1365 * </pre>
1366 *
1367 * <p>Invoking {@code insertAfterEnd(elem, "<ul><li>")}
1368 * results in the following structure (new elements are <font
1369 * style="color: red;">in red</font>).</p>
1370 *
1371 * <pre>
1372 * <body>
1373 * / \
1374 * <b><div></b> <font style="color: red;"><ul></font>
1375 * / \ \
1376 * <p> <p> <font style="color: red;"><li></font>
1377 * </pre>
1378 *
1379 * <p>Unlike the {@code insertBeforeEnd} method, new elements
1380 * become <em>siblings</em> of the specified element, not
1381 * children.</p>
1382 *
1383 * <p>If either {@code elem} or {@code htmlText}
1384 * parameter is {@code null}, no changes are made to the
1385 * document.</p>
1386 *
1387 * <p>For this to work correctly, the document must have an
1388 * {@code HTMLEditorKit.Parser} set. This will be the case
1389 * if the document was created from an HTMLEditorKit via the
1390 * {@code createDefaultDocument} method.</p>
1391 *
1392 * @param elem the element the content is inserted after
1393 * @param htmlText the string to be parsed and inserted after {@code elem}
1394 * @throws IllegalStateException if an HTMLEditorKit.Parser has not
1395 * been set on the document
1396 * @throws BadLocationException if insertion is impossible because of
1397 * a structural issue
1398 * @throws IOException if an I/O exception occurs
1399 * @since 1.3
1400 */
1401 public void insertAfterEnd(Element elem, String htmlText) throws
1402 BadLocationException, IOException {
1403 verifyParser();
1404 if (elem != null) {
1405 Element parent = elem.getParentElement();
1406
1407 if (parent != null) {
1408 // If we are going to insert the string into the body
1409 // section, it is necessary to set the corrsponding flag.
1410 if (HTML.Tag.BODY.name.equals(parent.getName())) {
1411 insertInBody = true;
1412 }
1413 int offset = elem.getEndOffset();
1414 if (offset > (getLength() + 1)) {
1415 offset--;
1416 }
1417 else if (elem.isLeaf() && getText(offset - 1, 1).
1418 charAt(0) == NEWLINE[0]) {
1419 offset--;
1420 }
1421 insertHTML(parent, offset, htmlText, false);
1422 // Cleanup the flag, if any.
1423 if (insertInBody) {
1424 insertInBody = false;
1425 }
1426 }
1427 }
1428 }
1429
1430 /**
1431 * Returns the element that has the given id {@code Attribute}.
1432 * If the element can't be found, {@code null} is returned.
1433 * Note that this method works on an {@code Attribute},
1434 * <i>not</i> a character tag. In the following HTML snippet:
1435 * {@code <a id="HelloThere">} the attribute is
1436 * 'id' and the character tag is 'a'.
1437 * This is a convenience method for
1438 * {@code getElement(RootElement, HTML.Attribute.id, id)}.
1439 * This is not thread-safe.
1440 *
1441 * @param id the string representing the desired {@code Attribute}
1442 * @return the element with the specified {@code Attribute}
1443 * or {@code null} if it can't be found,
1444 * or {@code null} if {@code id} is {@code null}
1445 * @see javax.swing.text.html.HTML.Attribute
1446 * @since 1.3
1447 */
1448 public Element getElement(String id) {
1449 if (id == null) {
1450 return null;
1451 }
1452 return getElement(getDefaultRootElement(), HTML.Attribute.ID, id,
1453 true);
1454 }
1455
1456 /**
1457 * Returns the child element of {@code e} that contains the
1458 * attribute, {@code attribute} with value {@code value}, or
1459 * {@code null} if one isn't found. This is not thread-safe.
1460 *
1461 * @param e the root element where the search begins
1462 * @param attribute the desired {@code Attribute}
1463 * @param value the values for the specified {@code Attribute}
1464 * @return the element with the specified {@code Attribute}
1465 * and the specified {@code value}, or {@code null}
1466 * if it can't be found
1467 * @see javax.swing.text.html.HTML.Attribute
1468 * @since 1.3
1469 */
1470 public Element getElement(Element e, Object attribute, Object value) {
1471 return getElement(e, attribute, value, true);
1472 }
1473
1474 /**
1475 * Returns the child element of {@code e} that contains the
1476 * attribute, {@code attribute} with value {@code value}, or
1477 * {@code null} if one isn't found. This is not thread-safe.
1478 * <p>
1479 * If {@code searchLeafAttributes} is true, and {@code e} is
1480 * a leaf, any attributes that are instances of {@code HTML.Tag}
1481 * with a value that is an {@code AttributeSet} will also be checked.
1482 *
1483 * @param e the root element where the search begins
1484 * @param attribute the desired {@code Attribute}
1485 * @param value the values for the specified {@code Attribute}
1486 * @return the element with the specified {@code Attribute}
1487 * and the specified {@code value}, or {@code null}
1488 * if it can't be found
1489 * @see javax.swing.text.html.HTML.Attribute
1490 */
1491 private Element getElement(Element e, Object attribute, Object value,
1492 boolean searchLeafAttributes) {
1493 AttributeSet attr = e.getAttributes();
1494
1495 if (attr != null && attr.isDefined(attribute)) {
1496 if (value.equals(attr.getAttribute(attribute))) {
1497 return e;
1498 }
1499 }
1500 if (!e.isLeaf()) {
1501 for (int counter = 0, maxCounter = e.getElementCount();
1502 counter < maxCounter; counter++) {
1503 Element retValue = getElement(e.getElement(counter), attribute,
1504 value, searchLeafAttributes);
1505
1506 if (retValue != null) {
1507 return retValue;
1515 if (names != null) {
1516 while (names.hasMoreElements()) {
1517 Object name = names.nextElement();
1518 if ((name instanceof HTML.Tag) &&
1519 (attr.getAttribute(name) instanceof AttributeSet)) {
1520
1521 AttributeSet check = (AttributeSet)attr.
1522 getAttribute(name);
1523 if (check.isDefined(attribute) &&
1524 value.equals(check.getAttribute(attribute))) {
1525 return e;
1526 }
1527 }
1528 }
1529 }
1530 }
1531 return null;
1532 }
1533
1534 /**
1535 * Verifies the document has an {@code HTMLEditorKit.Parser} set.
1536 * If {@code getParser} returns {@code null}, this will throw an
1537 * IllegalStateException.
1538 *
1539 * @throws IllegalStateException if the document does not have a Parser
1540 */
1541 private void verifyParser() {
1542 if (getParser() == null) {
1543 throw new IllegalStateException("No HTMLEditorKit.Parser");
1544 }
1545 }
1546
1547 /**
1548 * Installs a default Parser if one has not been installed yet.
1549 */
1550 private void installParserIfNecessary() {
1551 if (getParser() == null) {
1552 setParser(new HTMLEditorKit().getParser());
1553 }
1554 }
1555
1556 /**
1557 * Inserts a string of HTML into the document at the given position.
1558 * {@code parent} is used to identify the location to insert the
1559 * {@code html}. If {@code parent} is a leaf this can have
1560 * unexpected results.
1561 */
1562 private void insertHTML(Element parent, int offset, String html,
1563 boolean wantsTrailingNewline)
1564 throws BadLocationException, IOException {
1565 if (parent != null && html != null) {
1566 HTMLEditorKit.Parser parser = getParser();
1567 if (parser != null) {
1568 int lastOffset = Math.max(0, offset - 1);
1569 Element charElement = getCharacterElement(lastOffset);
1570 Element commonParent = parent;
1571 int pop = 0;
1572 int push = 0;
1573
1574 if (parent.getStartOffset() > lastOffset) {
1575 while (commonParent != null &&
1576 commonParent.getStartOffset() > lastOffset) {
1577 commonParent = commonParent.getParentElement();
1578 push++;
1579 }
1583 }
1584 }
1585 while (charElement != null && charElement != commonParent) {
1586 pop++;
1587 charElement = charElement.getParentElement();
1588 }
1589 if (charElement != null) {
1590 // Found it, do the insert.
1591 HTMLReader reader = new HTMLReader(offset, pop - 1, push,
1592 null, false, true,
1593 wantsTrailingNewline);
1594
1595 parser.parse(new StringReader(html), reader, true);
1596 reader.flush();
1597 }
1598 }
1599 }
1600 }
1601
1602 /**
1603 * Removes child Elements of the passed in Element {@code e}. This
1604 * will do the necessary cleanup to ensure the element representing the
1605 * end character is correctly created.
1606 * <p>This is not a general purpose method, it assumes that {@code e}
1607 * will still have at least one child after the remove, and it assumes
1608 * the character at {@code e.getStartOffset() - 1} is a newline and
1609 * is of length 1.
1610 */
1611 private void removeElements(Element e, int index, int count) throws BadLocationException {
1612 writeLock();
1613 try {
1614 int start = e.getElement(index).getStartOffset();
1615 int end = e.getElement(index + count - 1).getEndOffset();
1616 if (end > getLength()) {
1617 removeElementsAtEnd(e, index, count, start, end);
1618 }
1619 else {
1620 removeElements(e, index, count, start, end);
1621 }
1622 } finally {
1623 writeUnlock();
1624 }
1625 }
1626
1627 /**
1628 * Called to remove child elements of {@code e} when one of the
1629 * elements to remove is representing the end character.
1630 * <p>Since the Content will not allow a removal to the end character
1631 * this will do a remove from {@code start - 1} to {@code end}.
1632 * The end Element(s) will be removed, and the element representing
1633 * {@code start - 1} to {@code start} will be recreated. This
1634 * Element has to be recreated as after the content removal its offsets
1635 * become {@code start - 1} to {@code start - 1}.
1636 */
1637 private void removeElementsAtEnd(Element e, int index, int count,
1638 int start, int end) throws BadLocationException {
1639 // index must be > 0 otherwise no insert would have happened.
1640 boolean isLeaf = (e.getElement(index - 1).isLeaf());
1641 DefaultDocumentEvent dde = new DefaultDocumentEvent(
1642 start - 1, end - start + 1, DocumentEvent.
1643 EventType.REMOVE);
1644
1645 if (isLeaf) {
1646 Element endE = getCharacterElement(getLength());
1647 // e.getElement(index - 1) should represent the newline.
1648 index--;
1649 if (endE.getParentElement() != e) {
1650 // The hiearchies don't match, we'll have to manually
1651 // recreate the leaf at e.getElement(index - 1)
1652 replace(dde, e, index, ++count, start, end, true, true);
1653 }
1654 else {
1655 // The hierarchies for the end Element and
1661 }
1662 else {
1663 // Not a leaf, descend until we find the leaf representing
1664 // start - 1 and remove it.
1665 Element newLineE = e.getElement(index - 1);
1666 while (!newLineE.isLeaf()) {
1667 newLineE = newLineE.getElement(newLineE.getElementCount() - 1);
1668 }
1669 newLineE = newLineE.getParentElement();
1670 replace(dde, e, index, count, start, end, false, false);
1671 replace(dde, newLineE, newLineE.getElementCount() - 1, 1, start,
1672 end, true, true);
1673 }
1674 postRemoveUpdate(dde);
1675 dde.end();
1676 fireRemoveUpdate(dde);
1677 fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
1678 }
1679
1680 /**
1681 * This is used by {@code removeElementsAtEnd}, it removes
1682 * {@code count} elements starting at {@code start} from
1683 * {@code e}. If {@code remove} is true text of length
1684 * {@code start - 1} to {@code end - 1} is removed. If
1685 * {@code create} is true a new leaf is created of length 1.
1686 */
1687 private void replace(DefaultDocumentEvent dde, Element e, int index,
1688 int count, int start, int end, boolean remove,
1689 boolean create) throws BadLocationException {
1690 Element[] added;
1691 AttributeSet attrs = e.getElement(index).getAttributes();
1692 Element[] removed = new Element[count];
1693
1694 for (int counter = 0; counter < count; counter++) {
1695 removed[counter] = e.getElement(counter + index);
1696 }
1697 if (remove) {
1698 UndoableEdit u = getContent().remove(start - 1, end - start);
1699 if (u != null) {
1700 dde.addEdit(u);
1701 }
1702 }
1703 if (create) {
1704 added = new Element[1];
1705 added[0] = createLeafElement(e, attrs, start - 1, start);
1877 ((MutableAttributeSet)contentAttributeSet).
1878 addAttribute(StyleConstants.NameAttribute,
1879 HTML.Tag.CONTENT);
1880 NEWLINE = new char[1];
1881 NEWLINE[0] = '\n';
1882 }
1883
1884
1885 /**
1886 * An iterator to iterate over a particular type of
1887 * tag. The iterator is not thread safe. If reliable
1888 * access to the document is not already ensured by
1889 * the context under which the iterator is being used,
1890 * its use should be performed under the protection of
1891 * Document.render.
1892 */
1893 public abstract static class Iterator {
1894
1895 /**
1896 * Return the attributes for this tag.
1897 * @return the {@code AttributeSet} for this tag, or
1898 * {@code null} if none can be found
1899 */
1900 public abstract AttributeSet getAttributes();
1901
1902 /**
1903 * Returns the start of the range for which the current occurrence of
1904 * the tag is defined and has the same attributes.
1905 *
1906 * @return the start of the range, or -1 if it can't be found
1907 */
1908 public abstract int getStartOffset();
1909
1910 /**
1911 * Returns the end of the range for which the current occurrence of
1912 * the tag is defined and has the same attributes.
1913 *
1914 * @return the end of the range
1915 */
1916 public abstract int getEndOffset();
1917
1918 /**
1934 * Type of tag this iterator represents.
1935 * @return the tag
1936 */
1937 public abstract HTML.Tag getTag();
1938 }
1939
1940 /**
1941 * An iterator to iterate over a particular type of tag.
1942 */
1943 static class LeafIterator extends Iterator {
1944
1945 LeafIterator(HTML.Tag t, Document doc) {
1946 tag = t;
1947 pos = new ElementIterator(doc);
1948 endOffset = 0;
1949 next();
1950 }
1951
1952 /**
1953 * Returns the attributes for this tag.
1954 * @return the {@code AttributeSet} for this tag,
1955 * or {@code null} if none can be found
1956 */
1957 public AttributeSet getAttributes() {
1958 Element elem = pos.current();
1959 if (elem != null) {
1960 AttributeSet a = (AttributeSet)
1961 elem.getAttributes().getAttribute(tag);
1962 if (a == null) {
1963 a = elem.getAttributes();
1964 }
1965 return a;
1966 }
1967 return null;
1968 }
1969
1970 /**
1971 * Returns the start of the range for which the current occurrence of
1972 * the tag is defined and has the same attributes.
1973 *
1974 * @return the start of the range, or -1 if it can't be found
1975 */
1998 public void next() {
1999 for (nextLeaf(pos); isValid(); nextLeaf(pos)) {
2000 Element elem = pos.current();
2001 if (elem.getStartOffset() >= endOffset) {
2002 AttributeSet a = pos.current().getAttributes();
2003
2004 if (a.isDefined(tag) ||
2005 a.getAttribute(StyleConstants.NameAttribute) == tag) {
2006
2007 // we found the next one
2008 setEndOffset();
2009 break;
2010 }
2011 }
2012 }
2013 }
2014
2015 /**
2016 * Returns the type of tag this iterator represents.
2017 *
2018 * @return the {@code HTML.Tag} that this iterator represents.
2019 * @see javax.swing.text.html.HTML.Tag
2020 */
2021 public HTML.Tag getTag() {
2022 return tag;
2023 }
2024
2025 /**
2026 * Returns true if the current position is not {@code null}.
2027 * @return true if current position is not {@code null},
2028 * otherwise returns false
2029 */
2030 public boolean isValid() {
2031 return (pos.current() != null);
2032 }
2033
2034 /**
2035 * Moves the given iterator to the next leaf element.
2036 * @param iter the iterator to be scanned
2037 */
2038 void nextLeaf(ElementIterator iter) {
2039 for (iter.next(); iter.current() != null; iter.next()) {
2040 Element e = iter.current();
2041 if (e.isLeaf()) {
2042 break;
2043 }
2044 }
2045 }
2046
2047 /**
2048 * Marches a cloned iterator forward to locate the end
2049 * of the run. This sets the value of {@code endOffset}.
2050 */
2051 void setEndOffset() {
2052 AttributeSet a0 = getAttributes();
2053 endOffset = pos.current().getEndOffset();
2054 ElementIterator fwd = (ElementIterator) pos.clone();
2055 for (nextLeaf(fwd); fwd.current() != null; nextLeaf(fwd)) {
2056 Element e = fwd.current();
2057 AttributeSet a1 = (AttributeSet) e.getAttributes().getAttribute(tag);
2058 if ((a1 == null) || (! a1.equals(a0))) {
2059 break;
2060 }
2061 endOffset = e.getEndOffset();
2062 }
2063 }
2064
2065 private int endOffset;
2066 private HTML.Tag tag;
2067 private ElementIterator pos;
2068
2069 }
2070
2071 /**
2072 * An HTML reader to load an HTML document with an HTML
2073 * element structure. This is a set of callbacks from
2074 * the parser, implemented to create a set of elements
2075 * tagged with attributes. The parse builds up tokens
2076 * (ElementSpec) that describe the element subtree desired,
2077 * and burst it into the document under the protection of
2078 * a write lock using the insert method on the document
2079 * outer class.
2080 * <p>
2081 * The reader can be configured by registering actions
2082 * (of type {@code HTMLDocument.HTMLReader.TagAction})
2083 * that describe how to handle the action. The idea behind
2084 * the actions provided is that the most natural text editing
2085 * operations can be provided if the element structure boils
2086 * down to paragraphs with runs of some kind of style
2087 * in them. Some things are more naturally specified
2088 * structurally, so arbitrary structure should be allowed
2089 * above the paragraphs, but will need to be edited with structural
2090 * actions. The implication of this is that some of the
2091 * HTML elements specified in the stream being parsed will
2092 * be collapsed into attributes, and in some cases paragraphs
2093 * will be synthesized. When HTML elements have been
2094 * converted to attributes, the attribute key will be of
2095 * type HTML.Tag, and the value will be of type AttributeSet
2096 * so that no information is lost. This enables many of the
2097 * existing actions to work so that the user can type input,
2098 * hit the return key, backspace, delete, etc and have a
2099 * reasonable result. Selections can be created, and attributes
2100 * applied or removed, etc. With this in mind, the work done
2101 * by the reader can be categorized into the following kinds
2102 * of tasks:
2116 * <dt>Special
2117 * <dd>Produce an embedded graphical element.
2118 * <dt>Form
2119 * <dd>Produce an element that is like the embedded graphical
2120 * element, except that it also has a component model associated
2121 * with it.
2122 * <dt>Hidden
2123 * <dd>Create an element that is hidden from view when the
2124 * document is being viewed read-only, and visible when the
2125 * document is being edited. This is useful to keep the
2126 * model from losing information, and used to store things
2127 * like comments and unrecognized tags.
2128 *
2129 * </dl>
2130 * <p>
2131 * Currently, <APPLET>, <PARAM>, <MAP>, <AREA>, <LINK>,
2132 * <SCRIPT> and <STYLE> are unsupported.
2133 *
2134 * <p>
2135 * The assignment of the actions described is shown in the
2136 * following table for the tags defined in {@code HTML.Tag}.
2137 * <table border=1 summary="HTML tags and assigned actions">
2138 * <tr><th>Tag</th><th>Action</th></tr>
2139 * <tr><td>{@code HTML.Tag.A} <td>CharacterAction
2140 * <tr><td>{@code HTML.Tag.ADDRESS} <td>CharacterAction
2141 * <tr><td>{@code HTML.Tag.APPLET} <td>HiddenAction
2142 * <tr><td>{@code HTML.Tag.AREA} <td>AreaAction
2143 * <tr><td>{@code HTML.Tag.B} <td>CharacterAction
2144 * <tr><td>{@code HTML.Tag.BASE} <td>BaseAction
2145 * <tr><td>{@code HTML.Tag.BASEFONT} <td>CharacterAction
2146 * <tr><td>{@code HTML.Tag.BIG} <td>CharacterAction
2147 * <tr><td>{@code HTML.Tag.BLOCKQUOTE}<td>BlockAction
2148 * <tr><td>{@code HTML.Tag.BODY} <td>BlockAction
2149 * <tr><td>{@code HTML.Tag.BR} <td>SpecialAction
2150 * <tr><td>{@code HTML.Tag.CAPTION} <td>BlockAction
2151 * <tr><td>{@code HTML.Tag.CENTER} <td>BlockAction
2152 * <tr><td>{@code HTML.Tag.CITE} <td>CharacterAction
2153 * <tr><td>{@code HTML.Tag.CODE} <td>CharacterAction
2154 * <tr><td>{@code HTML.Tag.DD} <td>BlockAction
2155 * <tr><td>{@code HTML.Tag.DFN} <td>CharacterAction
2156 * <tr><td>{@code HTML.Tag.DIR} <td>BlockAction
2157 * <tr><td>{@code HTML.Tag.DIV} <td>BlockAction
2158 * <tr><td>{@code HTML.Tag.DL} <td>BlockAction
2159 * <tr><td>{@code HTML.Tag.DT} <td>ParagraphAction
2160 * <tr><td>{@code HTML.Tag.EM} <td>CharacterAction
2161 * <tr><td>{@code HTML.Tag.FONT} <td>CharacterAction
2162 * <tr><td>{@code HTML.Tag.FORM} <td>As of 1.4 a BlockAction
2163 * <tr><td>{@code HTML.Tag.FRAME} <td>SpecialAction
2164 * <tr><td>{@code HTML.Tag.FRAMESET} <td>BlockAction
2165 * <tr><td>{@code HTML.Tag.H1} <td>ParagraphAction
2166 * <tr><td>{@code HTML.Tag.H2} <td>ParagraphAction
2167 * <tr><td>{@code HTML.Tag.H3} <td>ParagraphAction
2168 * <tr><td>{@code HTML.Tag.H4} <td>ParagraphAction
2169 * <tr><td>{@code HTML.Tag.H5} <td>ParagraphAction
2170 * <tr><td>{@code HTML.Tag.H6} <td>ParagraphAction
2171 * <tr><td>{@code HTML.Tag.HEAD} <td>HeadAction
2172 * <tr><td>{@code HTML.Tag.HR} <td>SpecialAction
2173 * <tr><td>{@code HTML.Tag.HTML} <td>BlockAction
2174 * <tr><td>{@code HTML.Tag.I} <td>CharacterAction
2175 * <tr><td>{@code HTML.Tag.IMG} <td>SpecialAction
2176 * <tr><td>{@code HTML.Tag.INPUT} <td>FormAction
2177 * <tr><td>{@code HTML.Tag.ISINDEX} <td>IsndexAction
2178 * <tr><td>{@code HTML.Tag.KBD} <td>CharacterAction
2179 * <tr><td>{@code HTML.Tag.LI} <td>BlockAction
2180 * <tr><td>{@code HTML.Tag.LINK} <td>LinkAction
2181 * <tr><td>{@code HTML.Tag.MAP} <td>MapAction
2182 * <tr><td>{@code HTML.Tag.MENU} <td>BlockAction
2183 * <tr><td>{@code HTML.Tag.META} <td>MetaAction
2184 * <tr><td>{@code HTML.Tag.NOFRAMES} <td>BlockAction
2185 * <tr><td>{@code HTML.Tag.OBJECT} <td>SpecialAction
2186 * <tr><td>{@code HTML.Tag.OL} <td>BlockAction
2187 * <tr><td>{@code HTML.Tag.OPTION} <td>FormAction
2188 * <tr><td>{@code HTML.Tag.P} <td>ParagraphAction
2189 * <tr><td>{@code HTML.Tag.PARAM} <td>HiddenAction
2190 * <tr><td>{@code HTML.Tag.PRE} <td>PreAction
2191 * <tr><td>{@code HTML.Tag.SAMP} <td>CharacterAction
2192 * <tr><td>{@code HTML.Tag.SCRIPT} <td>HiddenAction
2193 * <tr><td>{@code HTML.Tag.SELECT} <td>FormAction
2194 * <tr><td>{@code HTML.Tag.SMALL} <td>CharacterAction
2195 * <tr><td>{@code HTML.Tag.STRIKE} <td>CharacterAction
2196 * <tr><td>{@code HTML.Tag.S} <td>CharacterAction
2197 * <tr><td>{@code HTML.Tag.STRONG} <td>CharacterAction
2198 * <tr><td>{@code HTML.Tag.STYLE} <td>StyleAction
2199 * <tr><td>{@code HTML.Tag.SUB} <td>CharacterAction
2200 * <tr><td>{@code HTML.Tag.SUP} <td>CharacterAction
2201 * <tr><td>{@code HTML.Tag.TABLE} <td>BlockAction
2202 * <tr><td>{@code HTML.Tag.TD} <td>BlockAction
2203 * <tr><td>{@code HTML.Tag.TEXTAREA} <td>FormAction
2204 * <tr><td>{@code HTML.Tag.TH} <td>BlockAction
2205 * <tr><td>{@code HTML.Tag.TITLE} <td>TitleAction
2206 * <tr><td>{@code HTML.Tag.TR} <td>BlockAction
2207 * <tr><td>{@code HTML.Tag.TT} <td>CharacterAction
2208 * <tr><td>{@code HTML.Tag.U} <td>CharacterAction
2209 * <tr><td>{@code HTML.Tag.UL} <td>BlockAction
2210 * <tr><td>{@code HTML.Tag.VAR} <td>CharacterAction
2211 * </table>
2212 * <p>
2213 * Once </html> is encountered, the Actions are no longer notified.
2214 */
2215 public class HTMLReader extends HTMLEditorKit.ParserCallback {
2216
2217 /**
2218 * Constructs an HTMLReader using default pop and push depth and no tag to insert.
2219 *
2220 * @param offset the starting offset
2221 */
2222 public HTMLReader(int offset) {
2223 this(offset, 0, 0, null);
2224 }
2225
2226 /**
2227 * Constructs an HTMLReader.
2228 *
2229 * @param offset the starting offset
2230 * @param popDepth how many parents to ascend before insert new element
2231 * @param pushDepth how many parents to descend (relative to popDepth) before
2232 * inserting
2233 * @param insertTag a tag to insert (may be null)
2234 */
2235 public HTMLReader(int offset, int popDepth, int pushDepth,
2236 HTML.Tag insertTag) {
2237 this(offset, popDepth, pushDepth, insertTag, true, false, true);
2238 }
2239
2240 /**
2241 * Generates a RuntimeException (will eventually generate
2242 * a BadLocationException when API changes are alloced) if inserting
2243 * into non empty document, {@code insertTag} is
2244 * non-{@code null}, and {@code offset} is not in the body.
2245 */
2246 // PENDING(sky): Add throws BadLocationException and remove
2247 // RuntimeException
2248 HTMLReader(int offset, int popDepth, int pushDepth,
2249 HTML.Tag insertTag, boolean insertInsertTag,
2250 boolean insertAfterImplied, boolean wantsTrailingNewline) {
2251 emptyDocument = (getLength() == 0);
2252 isStyleCSS = "text/css".equals(getDefaultStyleSheetType());
2253 this.offset = offset;
2254 threshold = HTMLDocument.this.getTokenThreshold();
2255 tagMap = new Hashtable<HTML.Tag, TagAction>(57);
2256 TagAction na = new TagAction();
2257 TagAction ba = new BlockAction();
2258 TagAction pa = new ParagraphAction();
2259 TagAction ca = new CharacterAction();
2260 TagAction sa = new SpecialAction();
2261 TagAction fa = new FormAction();
2262 TagAction ha = new HiddenAction();
2263 TagAction conv = new ConvertAction();
2264
2348 else {
2349 foundInsertTag = true;
2350 }
2351 if (insertAfterImplied) {
2352 this.popDepth = popDepth;
2353 this.pushDepth = pushDepth;
2354 this.insertAfterImplied = true;
2355 foundInsertTag = false;
2356 midInsert = false;
2357 this.insertInsertTag = true;
2358 this.wantsTrailingNewline = wantsTrailingNewline;
2359 }
2360 else {
2361 midInsert = (!emptyDocument && insertTag == null);
2362 if (midInsert) {
2363 generateEndsSpecsForMidInsert();
2364 }
2365 }
2366
2367 /**
2368 * This block initializes the {@code inParagraph} flag.
2369 * It is left in {@code false} value automatically
2370 * if the target document is empty or future inserts
2371 * were positioned into the 'body' tag.
2372 */
2373 if (!emptyDocument && !midInsert) {
2374 int targetOffset = Math.max(this.offset - 1, 0);
2375 Element elem =
2376 HTMLDocument.this.getCharacterElement(targetOffset);
2377 /* Going up by the left document structure path */
2378 for (int i = 0; i <= this.popDepth; i++) {
2379 elem = elem.getParentElement();
2380 }
2381 /* Going down by the right document structure path */
2382 for (int i = 0; i < this.pushDepth; i++) {
2383 int index = elem.getElementIndex(this.offset);
2384 elem = elem.getElement(index);
2385 }
2386 AttributeSet attrs = elem.getAttributes();
2387 if (attrs != null) {
2388 HTML.Tag tagToInsertInto =
2389 (HTML.Tag) attrs.getAttribute(StyleConstants.NameAttribute);
2390 if (tagToInsertInto != null) {
2391 this.inParagraph = tagToInsertInto.isParagraph();
2392 }
2393 }
2394 }
2395 }
2396
2397 /**
2398 * Generates an initial batch of end {@code ElementSpecs}
2399 * in parseBuffer to position future inserts into the body.
2400 */
2401 private void generateEndsSpecsForMidInsert() {
2402 int count = heightToElementWithName(HTML.Tag.BODY,
2403 Math.max(0, offset - 1));
2404 boolean joinNext = false;
2405
2406 if (count == -1 && offset > 0) {
2407 count = heightToElementWithName(HTML.Tag.BODY, offset);
2408 if (count != -1) {
2409 // Previous isn't in body, but current is. Have to
2410 // do some end specs, followed by join next.
2411 count = depthTo(offset - 1) - 1;
2412 joinNext = true;
2413 }
2414 }
2415 if (count == -1) {
2416 throw new RuntimeException("Must insert new content into body element-");
2417 }
2418 if (count != -1) {
2443 }
2444 // We should probably throw an exception if (count == -1)
2445 // Or look for the body and reset the offset.
2446 }
2447
2448 /**
2449 * @return number of parents to reach the child at offset.
2450 */
2451 private int depthTo(int offset) {
2452 Element e = getDefaultRootElement();
2453 int count = 0;
2454
2455 while (!e.isLeaf()) {
2456 count++;
2457 e = e.getElement(e.getElementIndex(offset));
2458 }
2459 return count;
2460 }
2461
2462 /**
2463 * @return number of parents of the leaf at {@code offset}
2464 * until a parent with name, {@code name} has been
2465 * found. -1 indicates no matching parent with
2466 * {@code name}.
2467 */
2468 private int heightToElementWithName(Object name, int offset) {
2469 Element e = getCharacterElement(offset).getParentElement();
2470 int count = 0;
2471
2472 while (e != null && e.getAttributes().getAttribute
2473 (StyleConstants.NameAttribute) != name) {
2474 count++;
2475 e = e.getParentElement();
2476 }
2477 return (e == null) ? -1 : count;
2478 }
2479
2480 /**
2481 * This will make sure there aren't two BODYs (the second is
2482 * typically created when you do a remove all, and then an insert).
2483 */
2484 private void adjustEndElement() {
2485 int length = getLength();
2486 if (length == 0) {
2692 if (inBlock == 0 && (foundInsertTag ||
2693 insertTag != HTML.Tag.COMMENT)) {
2694 // Comment outside of body, will not be able to show it,
2695 // but can add it as a property on the Document.
2696 addExternalComment(new String(data));
2697 return;
2698 }
2699 SimpleAttributeSet sas = new SimpleAttributeSet();
2700 sas.addAttribute(HTML.Attribute.COMMENT, new String(data));
2701 addSpecialElement(HTML.Tag.COMMENT, sas);
2702 }
2703
2704 TagAction action = tagMap.get(HTML.Tag.COMMENT);
2705 if (action != null) {
2706 action.start(HTML.Tag.COMMENT, new SimpleAttributeSet());
2707 action.end(HTML.Tag.COMMENT);
2708 }
2709 }
2710
2711 /**
2712 * Adds the comment {@code comment} to the set of comments
2713 * maintained outside of the scope of elements.
2714 */
2715 private void addExternalComment(String comment) {
2716 Object comments = getProperty(AdditionalComments);
2717 if (comments != null && !(comments instanceof Vector)) {
2718 // No place to put comment.
2719 return;
2720 }
2721 if (comments == null) {
2722 comments = new Vector<>();
2723 putProperty(AdditionalComments, comments);
2724 }
2725 @SuppressWarnings("unchecked")
2726 Vector<Object> v = (Vector<Object>)comments;
2727 v.addElement(comment);
2728 }
2729
2730 /**
2731 * Callback from the parser. Route to the appropriate
2732 * handler for the tag.
2766 styleAttributes = getStyleSheet().getDeclaration(decl);
2767 a.addAttributes(styleAttributes);
2768 }
2769 else {
2770 styleAttributes = null;
2771 }
2772
2773 TagAction action = tagMap.get(t);
2774 if (action != null) {
2775 action.start(t, a);
2776 action.end(t);
2777 }
2778 else if (getPreservesUnknownTags()) {
2779 // unknown tag, only add if should preserve it.
2780 addSpecialElement(t, a);
2781 }
2782 }
2783
2784 /**
2785 * This is invoked after the stream has been parsed, but before
2786 * {@code flush}. {@code eol} will be one of \n, \r
2787 * or \r\n, which ever is encountered the most in parsing the
2788 * stream.
2789 *
2790 * @since 1.3
2791 */
2792 public void handleEndOfLineString(String eol) {
2793 if (emptyDocument && eol != null) {
2794 putProperty(DefaultEditorKit.EndOfLineStringProperty,
2795 eol);
2796 }
2797 }
2798
2799 // ---- tag handling support ------------------------------
2800
2801 /**
2802 * Registers a handler for the given tag. By default
2803 * all of the well-known tags will have been registered.
2804 * This can be used to change the handling of a particular
2805 * tag or to add support for custom tags.
2806 *
3367 }
3368 }
3369
3370 void addParameter(AttributeSet a) {
3371 String name = (String) a.getAttribute(HTML.Attribute.NAME);
3372 String value = (String) a.getAttribute(HTML.Attribute.VALUE);
3373 if ((name != null) && (value != null)) {
3374 ElementSpec objSpec = parseBuffer.lastElement();
3375 MutableAttributeSet objAttr = (MutableAttributeSet) objSpec.getAttributes();
3376 objAttr.addAttribute(name, value);
3377 }
3378 }
3379 }
3380
3381 /**
3382 * Action to support forms by building all of the elements
3383 * used to represent form controls. This will process
3384 * the <INPUT>, <TEXTAREA>, <SELECT>,
3385 * and <OPTION> tags. The element created by
3386 * this action is expected to have the attribute
3387 * {@code StyleConstants.ModelAttribute} set to
3388 * the model that holds the state for the form control.
3389 * This enables multiple views, and allows document to
3390 * be iterated over picking up the data of the form.
3391 * The following are the model assignments for the
3392 * various type of form elements.
3393 * <table summary="model assignments for the various types of form elements">
3394 * <tr>
3395 * <th>Element Type
3396 * <th>Model Type
3397 * <tr>
3398 * <td>input, type button
3399 * <td>{@link DefaultButtonModel}
3400 * <tr>
3401 * <td>input, type checkbox
3402 * <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
3403 * <tr>
3404 * <td>input, type image
3405 * <td>{@link DefaultButtonModel}
3406 * <tr>
3407 * <td>input, type password
3555 if ( radioButtonGroupsMap == null ) { //fix for 4772743
3556 radioButtonGroupsMap = new HashMap<String, ButtonGroup>();
3557 }
3558 ButtonGroup radioButtonGroup = radioButtonGroupsMap.get(name);
3559 if (radioButtonGroup == null) {
3560 radioButtonGroup = new ButtonGroup();
3561 radioButtonGroupsMap.put(name,radioButtonGroup);
3562 }
3563 model.setGroup(radioButtonGroup);
3564 }
3565 boolean checked = (attr.getAttribute(HTML.Attribute.CHECKED) != null);
3566 model.setSelected(checked);
3567 attr.addAttribute(StyleConstants.ModelAttribute, model);
3568 }
3569 }
3570
3571 /**
3572 * If a <SELECT> tag is being processed, this
3573 * model will be a reference to the model being filled
3574 * with the <OPTION> elements (which produce
3575 * objects of type {@code Option}.
3576 */
3577 Object selectModel;
3578 int optionCount;
3579 }
3580
3581
3582 // --- utility methods used by the reader ------------------
3583
3584 /**
3585 * Pushes the current character style on a stack in preparation
3586 * for forming a new nested character style.
3587 */
3588 protected void pushCharacterStyle() {
3589 charAttrStack.push(charAttr.copyAttributes());
3590 }
3591
3592 /**
3593 * Pops a previously pushed character style off the stack
3594 * to return to a previous style.
3595 */
3814 int size = parseBuffer.size();
3815 if (endOfStream && (insertTag != null || insertAfterImplied) &&
3816 size > 0) {
3817 adjustEndSpecsForPartialInsert();
3818 size = parseBuffer.size();
3819 }
3820 ElementSpec[] spec = new ElementSpec[size];
3821 parseBuffer.copyInto(spec);
3822
3823 if (oldLength == 0 && (insertTag == null && !insertAfterImplied)) {
3824 create(spec);
3825 } else {
3826 insert(offset, spec);
3827 }
3828 parseBuffer.removeAllElements();
3829 offset += HTMLDocument.this.getLength() - oldLength;
3830 flushCount++;
3831 }
3832
3833 /**
3834 * This will be invoked for the last flush, if {@code insertTag}
3835 * is non null.
3836 */
3837 private void adjustEndSpecsForPartialInsert() {
3838 int size = parseBuffer.size();
3839 if (insertTagDepthDelta < 0) {
3840 // When inserting via an insertTag, the depths (of the tree
3841 // being read in, and existing hierarchy) may not match up.
3842 // This attemps to clean it up.
3843 int removeCounter = insertTagDepthDelta;
3844 while (removeCounter < 0 && size >= 0 &&
3845 parseBuffer.elementAt(size - 1).
3846 getType() == ElementSpec.EndTagType) {
3847 parseBuffer.removeElementAt(--size);
3848 removeCounter++;
3849 }
3850 }
3851 if (flushCount == 0 && (!insertAfterImplied ||
3852 !wantsTrailingNewline)) {
3853 // If this starts with content (or popDepth > 0 &&
3854 // pushDepth > 0) and ends with EndTagTypes, make sure
3892 counter--) {
3893 ElementSpec spec = parseBuffer.elementAt(counter);
3894 if (spec.getType() == ElementSpec.ContentType) {
3895 if (spec.getArray()[spec.getLength() - 1] != '\n') {
3896 SimpleAttributeSet attrs =new SimpleAttributeSet();
3897
3898 attrs.addAttribute(StyleConstants.NameAttribute,
3899 HTML.Tag.CONTENT);
3900 parseBuffer.insertElementAt(new ElementSpec(
3901 attrs,
3902 ElementSpec.ContentType, NEWLINE, 0, 1),
3903 counter + 1);
3904 }
3905 break;
3906 }
3907 }
3908 }
3909 }
3910
3911 /**
3912 * Adds the CSS rules in {@code rules}.
3913 */
3914 void addCSSRules(String rules) {
3915 StyleSheet ss = getStyleSheet();
3916 ss.addRule(rules);
3917 }
3918
3919 /**
3920 * Adds the CSS stylesheet at {@code href} to the known list
3921 * of stylesheets.
3922 */
3923 void linkCSSStyleSheet(String href) {
3924 URL url;
3925 try {
3926 url = new URL(base, href);
3927 } catch (MalformedURLException mfe) {
3928 try {
3929 url = new URL(href);
3930 } catch (MalformedURLException mfe2) {
3931 url = null;
3932 }
3933 }
3934 if (url != null) {
3935 getStyleSheet().importStyleSheet(url);
3936 }
3937 }
3938
3939 /**
3940 * Returns true if can insert starting at {@code t}. This
3941 * will return false if the insert tag is set, and hasn't been found
3942 * yet.
3943 */
3944 private boolean canInsertTag(HTML.Tag t, AttributeSet attr,
3945 boolean isBlockTag) {
3946 if (!foundInsertTag) {
3947 boolean needPImplied = ((t == HTML.Tag.IMPLIED)
3948 && (!inParagraph)
3949 && (!inPre));
3950 if (needPImplied && (nextTagAfterPImplied != null)) {
3951
3952 /*
3953 * If insertTag == null then just proceed to
3954 * foundInsertTag() call below and return true.
3955 */
3956 if (insertTag != null) {
3957 boolean nextTagIsInsertTag =
3958 isInsertTag(nextTagAfterPImplied);
3959 if ( (! nextTagIsInsertTag) || (! insertInsertTag) ) {
3960 return false;
4058 insertTagDepthDelta = depthTo(Math.max(0, offset - 1)) -
4059 popDepth + pushDepth - inBlock;
4060 if (isBlockTag) {
4061 // A start spec will be added (for this tag), so we account
4062 // for it here.
4063 insertTagDepthDelta++;
4064 }
4065 else {
4066 // An implied paragraph close (end spec) is going to be added,
4067 // so we account for it here.
4068 insertTagDepthDelta--;
4069 inParagraph = true;
4070 lastWasNewline = false;
4071 }
4072 }
4073
4074 /**
4075 * This is set to true when and end is invoked for {@literal <html>}.
4076 */
4077 private boolean receivedEndHTML;
4078 /** Number of times {@code flushBuffer} has been invoked. */
4079 private int flushCount;
4080 /** If true, behavior is similar to insertTag, but instead of
4081 * waiting for insertTag will wait for first Element without
4082 * an 'implied' attribute and begin inserting then. */
4083 private boolean insertAfterImplied;
4084 /** This is only used if insertAfterImplied is true. If false, only
4085 * inserting content, and there is a trailing newline it is removed. */
4086 private boolean wantsTrailingNewline;
4087 int threshold;
4088 int offset;
4089 boolean inParagraph = false;
4090 boolean impliedP = false;
4091 boolean inPre = false;
4092 boolean inTextArea = false;
4093 TextAreaDocument textAreaDocument = null;
4094 boolean inTitle = false;
4095 boolean lastWasNewline = true;
4096 boolean emptyAnchor;
4097 /** True if (!emptyDocument && insertTag == null), this is used so
4098 * much it is cached. */
|