001 /* HTMLDocument.java --
002 Copyright (C) 2005 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.text.html;
040
041 import gnu.classpath.NotImplementedException;
042
043 import java.io.IOException;
044 import java.io.StringReader;
045 import java.net.MalformedURLException;
046 import java.net.URL;
047 import java.util.ArrayList;
048 import java.util.HashMap;
049 import java.util.Stack;
050 import java.util.Vector;
051
052 import javax.swing.ButtonGroup;
053 import javax.swing.DefaultButtonModel;
054 import javax.swing.JEditorPane;
055 import javax.swing.ListSelectionModel;
056 import javax.swing.event.DocumentEvent;
057 import javax.swing.event.UndoableEditEvent;
058 import javax.swing.text.AbstractDocument;
059 import javax.swing.text.AttributeSet;
060 import javax.swing.text.BadLocationException;
061 import javax.swing.text.DefaultStyledDocument;
062 import javax.swing.text.Element;
063 import javax.swing.text.ElementIterator;
064 import javax.swing.text.GapContent;
065 import javax.swing.text.MutableAttributeSet;
066 import javax.swing.text.PlainDocument;
067 import javax.swing.text.SimpleAttributeSet;
068 import javax.swing.text.StyleConstants;
069 import javax.swing.text.html.HTML.Tag;
070
071 /**
072 * Represents the HTML document that is constructed by defining the text and
073 * other components (images, buttons, etc) in HTML language. This class can
074 * becomes the default document for {@link JEditorPane} after setting its
075 * content type to "text/html". HTML document also serves as an intermediate
076 * data structure when it is needed to parse HTML and then obtain the content of
077 * the certain types of tags. This class also has methods for modifying the HTML
078 * content.
079 *
080 * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
081 * @author Anthony Balkissoon (abalkiss@redhat.com)
082 * @author Lillian Angel (langel@redhat.com)
083 */
084 public class HTMLDocument extends DefaultStyledDocument
085 {
086 /** A key for document properies. The value for the key is
087 * a Vector of Strings of comments not found in the body.
088 */
089 public static final String AdditionalComments = "AdditionalComments";
090 URL baseURL = null;
091 boolean preservesUnknownTags = true;
092 int tokenThreshold = Integer.MAX_VALUE;
093 HTMLEditorKit.Parser parser;
094
095 /**
096 * Indicates whether this document is inside a frame or not.
097 */
098 private boolean frameDocument;
099
100 /**
101 * Package private to avoid accessor methods.
102 */
103 String baseTarget;
104
105 /**
106 * Constructs an HTML document using the default buffer size and a default
107 * StyleSheet.
108 */
109 public HTMLDocument()
110 {
111 this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet());
112 }
113
114 /**
115 * Constructs an HTML document with the default content storage
116 * implementation and the specified style/attribute storage mechanism.
117 *
118 * @param styles - the style sheet
119 */
120 public HTMLDocument(StyleSheet styles)
121 {
122 this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
123 }
124
125 /**
126 * Constructs an HTML document with the given content storage implementation
127 * and the given style/attribute storage mechanism.
128 *
129 * @param c - the document's content
130 * @param styles - the style sheet
131 */
132 public HTMLDocument(AbstractDocument.Content c, StyleSheet styles)
133 {
134 super(c, styles);
135 }
136
137 /**
138 * Gets the style sheet with the document display rules (CSS) that were specified
139 * in the HTML document.
140 *
141 * @return - the style sheet
142 */
143 public StyleSheet getStyleSheet()
144 {
145 return (StyleSheet) getAttributeContext();
146 }
147
148 /**
149 * This method creates a root element for the new document.
150 *
151 * @return the new default root
152 */
153 protected AbstractElement createDefaultRoot()
154 {
155 AbstractDocument.AttributeContext ctx = getAttributeContext();
156
157 // Create html element.
158 AttributeSet atts = ctx.getEmptySet();
159 atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.HTML);
160 BranchElement html = (BranchElement) createBranchElement(null, atts);
161
162 // Create body element.
163 atts = ctx.getEmptySet();
164 atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.BODY);
165 BranchElement body = (BranchElement) createBranchElement(html, atts);
166 html.replace(0, 0, new Element[] { body });
167
168 // Create p element.
169 atts = ctx.getEmptySet();
170 atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.P);
171 BranchElement p = (BranchElement) createBranchElement(body, atts);
172 body.replace(0, 0, new Element[] { p });
173
174 // Create an empty leaf element.
175 atts = ctx.getEmptySet();
176 atts = ctx.addAttribute(atts, StyleConstants.NameAttribute,
177 HTML.Tag.CONTENT);
178 Element leaf = createLeafElement(p, atts, 0, 1);
179 p.replace(0, 0, new Element[]{ leaf });
180
181 return html;
182 }
183
184 /**
185 * This method returns an HTMLDocument.RunElement object attached to
186 * parent representing a run of text from p0 to p1. The run has
187 * attributes described by a.
188 *
189 * @param parent - the parent element
190 * @param a - the attributes for the element
191 * @param p0 - the beginning of the range >= 0
192 * @param p1 - the end of the range >= p0
193 *
194 * @return the new element
195 */
196 protected Element createLeafElement(Element parent, AttributeSet a, int p0,
197 int p1)
198 {
199 return new RunElement(parent, a, p0, p1);
200 }
201
202 /**
203 * This method returns an HTMLDocument.BlockElement object representing the
204 * attribute set a and attached to parent.
205 *
206 * @param parent - the parent element
207 * @param a - the attributes for the element
208 *
209 * @return the new element
210 */
211 protected Element createBranchElement(Element parent, AttributeSet a)
212 {
213 return new BlockElement(parent, a);
214 }
215
216 /**
217 * Returns the parser used by this HTMLDocument to insert HTML.
218 *
219 * @return the parser used by this HTMLDocument to insert HTML.
220 */
221 public HTMLEditorKit.Parser getParser()
222 {
223 return parser;
224 }
225
226 /**
227 * Sets the parser used by this HTMLDocument to insert HTML.
228 *
229 * @param p the parser to use
230 */
231 public void setParser (HTMLEditorKit.Parser p)
232 {
233 parser = p;
234 }
235 /**
236 * Sets the number of tokens to buffer before trying to display the
237 * Document.
238 *
239 * @param n the number of tokens to buffer
240 */
241 public void setTokenThreshold (int n)
242 {
243 tokenThreshold = n;
244 }
245
246 /**
247 * Returns the number of tokens that are buffered before the document
248 * is rendered.
249 *
250 * @return the number of tokens buffered
251 */
252 public int getTokenThreshold ()
253 {
254 return tokenThreshold;
255 }
256
257 /**
258 * Returns the location against which to resolve relative URLs.
259 * This is the document's URL if the document was loaded from a URL.
260 * If a <code>base</code> tag is found, it will be used.
261 * @return the base URL
262 */
263 public URL getBase()
264 {
265 return baseURL;
266 }
267
268 /**
269 * Sets the location against which to resolve relative URLs.
270 * @param u the new base URL
271 */
272 public void setBase(URL u)
273 {
274 baseURL = u;
275 getStyleSheet().setBase(u);
276 }
277
278 /**
279 * Returns whether or not the parser preserves unknown HTML tags.
280 * @return true if the parser preserves unknown tags
281 */
282 public boolean getPreservesUnknownTags()
283 {
284 return preservesUnknownTags;
285 }
286
287 /**
288 * Sets the behaviour of the parser when it encounters unknown HTML tags.
289 * @param preservesTags true if the parser should preserve unknown tags.
290 */
291 public void setPreservesUnknownTags(boolean preservesTags)
292 {
293 preservesUnknownTags = preservesTags;
294 }
295
296 /**
297 * An iterator to iterate through LeafElements in the document.
298 */
299 class LeafIterator extends Iterator
300 {
301 HTML.Tag tag;
302 HTMLDocument doc;
303 ElementIterator it;
304
305 public LeafIterator (HTML.Tag t, HTMLDocument d)
306 {
307 doc = d;
308 tag = t;
309 it = new ElementIterator(doc);
310 }
311
312 /**
313 * Return the attributes for the tag associated with this iteartor
314 * @return the AttributeSet
315 */
316 public AttributeSet getAttributes()
317 {
318 if (it.current() != null)
319 return it.current().getAttributes();
320 return null;
321 }
322
323 /**
324 * Get the end of the range for the current occurrence of the tag
325 * being defined and having the same attributes.
326 * @return the end of the range
327 */
328 public int getEndOffset()
329 {
330 if (it.current() != null)
331 return it.current().getEndOffset();
332 return -1;
333 }
334
335 /**
336 * Get the start of the range for the current occurrence of the tag
337 * being defined and having the same attributes.
338 * @return the start of the range (-1 if it can't be found).
339 */
340
341 public int getStartOffset()
342 {
343 if (it.current() != null)
344 return it.current().getStartOffset();
345 return -1;
346 }
347
348 /**
349 * Advance the iterator to the next LeafElement .
350 */
351 public void next()
352 {
353 it.next();
354 while (it.current()!= null && !it.current().isLeaf())
355 it.next();
356 }
357
358 /**
359 * Indicates whether or not the iterator currently represents an occurrence
360 * of the tag.
361 * @return true if the iterator currently represents an occurrence of the
362 * tag.
363 */
364 public boolean isValid()
365 {
366 return it.current() != null;
367 }
368
369 /**
370 * Type of tag for this iterator.
371 */
372 public Tag getTag()
373 {
374 return tag;
375 }
376
377 }
378
379 public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent event)
380 {
381 String target = event.getTarget();
382 Element el = event.getSourceElement();
383 URL url = event.getURL();
384 if (target.equals("_self"))
385 {
386 updateFrame(el, url);
387 }
388 else if (target.equals("_parent"))
389 {
390 updateFrameSet(el.getParentElement(), url);
391 }
392 else
393 {
394 Element targetFrame = findFrame(target);
395 if (targetFrame != null)
396 updateFrame(targetFrame, url);
397 }
398 }
399
400 /**
401 * Finds the named frame inside this document.
402 *
403 * @param target the name to look for
404 *
405 * @return the frame if there is a matching frame, <code>null</code>
406 * otherwise
407 */
408 private Element findFrame(String target)
409 {
410 ElementIterator i = new ElementIterator(this);
411 Element next = null;
412 while ((next = i.next()) != null)
413 {
414 AttributeSet atts = next.getAttributes();
415 if (atts.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.FRAME)
416 {
417 String name = (String) atts.getAttribute(HTML.Attribute.NAME);
418 if (name != null && name.equals(target))
419 break;
420 }
421 }
422 return next;
423 }
424
425 /**
426 * Updates the frame that is represented by the specified element to
427 * refer to the specified URL.
428 *
429 * @param el the element
430 * @param url the new url
431 */
432 private void updateFrame(Element el, URL url)
433 {
434 try
435 {
436 writeLock();
437 DefaultDocumentEvent ev =
438 new DefaultDocumentEvent(el.getStartOffset(), 1,
439 DocumentEvent.EventType.CHANGE);
440 AttributeSet elAtts = el.getAttributes();
441 AttributeSet copy = elAtts.copyAttributes();
442 MutableAttributeSet matts = (MutableAttributeSet) elAtts;
443 ev.addEdit(new AttributeUndoableEdit(el, copy, false));
444 matts.removeAttribute(HTML.Attribute.SRC);
445 matts.addAttribute(HTML.Attribute.SRC, url.toString());
446 ev.end();
447 fireChangedUpdate(ev);
448 fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
449 }
450 finally
451 {
452 writeUnlock();
453 }
454 }
455
456 /**
457 * Updates the frameset that is represented by the specified element
458 * to create a frame that refers to the specified URL.
459 *
460 * @param el the element
461 * @param url the url
462 */
463 private void updateFrameSet(Element el, URL url)
464 {
465 int start = el.getStartOffset();
466 int end = el.getEndOffset();
467
468 StringBuilder html = new StringBuilder();
469 html.append("<frame");
470 if (url != null)
471 {
472 html.append(" src=\"");
473 html.append(url.toString());
474 html.append("\"");
475 }
476 html.append('>');
477 if (getParser() == null)
478 setParser(new HTMLEditorKit().getParser());
479 try
480 {
481 setOuterHTML(el, html.toString());
482 }
483 catch (BadLocationException ex)
484 {
485 ex.printStackTrace();
486 }
487 catch (IOException ex)
488 {
489 ex.printStackTrace();
490 }
491 }
492
493 /**
494 * Gets an iterator for the given HTML.Tag.
495 * @param t the requested HTML.Tag
496 * @return the Iterator
497 */
498 public HTMLDocument.Iterator getIterator (HTML.Tag t)
499 {
500 return new HTMLDocument.LeafIterator(t, this);
501 }
502
503 /**
504 * An iterator over a particular type of tag.
505 */
506 public abstract static class Iterator
507 {
508 /**
509 * Return the attribute set for this tag.
510 * @return the <code>AttributeSet</code> (null if none found).
511 */
512 public abstract AttributeSet getAttributes();
513
514 /**
515 * Get the end of the range for the current occurrence of the tag
516 * being defined and having the same attributes.
517 * @return the end of the range
518 */
519 public abstract int getEndOffset();
520
521 /**
522 * Get the start of the range for the current occurrence of the tag
523 * being defined and having the same attributes.
524 * @return the start of the range (-1 if it can't be found).
525 */
526 public abstract int getStartOffset();
527
528 /**
529 * Move the iterator forward.
530 */
531 public abstract void next();
532
533 /**
534 * Indicates whether or not the iterator currently represents an occurrence
535 * of the tag.
536 * @return true if the iterator currently represents an occurrence of the
537 * tag.
538 */
539 public abstract boolean isValid();
540
541 /**
542 * Type of tag this iterator represents.
543 * @return the tag.
544 */
545 public abstract HTML.Tag getTag();
546 }
547
548 public class BlockElement extends AbstractDocument.BranchElement
549 {
550 public BlockElement (Element parent, AttributeSet a)
551 {
552 super(parent, a);
553 }
554
555 /**
556 * Gets the resolving parent. Since HTML attributes are not
557 * inherited at the model level, this returns null.
558 */
559 public AttributeSet getResolveParent()
560 {
561 return null;
562 }
563
564 /**
565 * Gets the name of the element.
566 *
567 * @return the name of the element if it exists, null otherwise.
568 */
569 public String getName()
570 {
571 Object tag = getAttribute(StyleConstants.NameAttribute);
572 String name = null;
573 if (tag != null)
574 name = tag.toString();
575 if (name == null)
576 name = super.getName();
577 return name;
578 }
579 }
580
581 /**
582 * RunElement represents a section of text that has a set of
583 * HTML character level attributes assigned to it.
584 */
585 public class RunElement extends AbstractDocument.LeafElement
586 {
587
588 /**
589 * Constructs an element that has no children. It represents content
590 * within the document.
591 *
592 * @param parent - parent of this
593 * @param a - elements attributes
594 * @param start - the start offset >= 0
595 * @param end - the end offset
596 */
597 public RunElement(Element parent, AttributeSet a, int start, int end)
598 {
599 super(parent, a, start, end);
600 }
601
602 /**
603 * Gets the name of the element.
604 *
605 * @return the name of the element if it exists, null otherwise.
606 */
607 public String getName()
608 {
609 Object tag = getAttribute(StyleConstants.NameAttribute);
610 String name = null;
611 if (tag != null)
612 name = tag.toString();
613 if (name == null)
614 name = super.getName();
615 return name;
616 }
617
618 /**
619 * Gets the resolving parent. HTML attributes do not inherit at the
620 * model level, so this method returns null.
621 *
622 * @return null
623 */
624 public AttributeSet getResolveParent()
625 {
626 return null;
627 }
628 }
629
630 /**
631 * A reader to load an HTMLDocument with HTML structure.
632 *
633 * @author Anthony Balkissoon abalkiss at redhat dot com
634 */
635 public class HTMLReader extends HTMLEditorKit.ParserCallback
636 {
637 /**
638 * The maximum token threshold. We don't grow it larger than this.
639 */
640 private static final int MAX_THRESHOLD = 10000;
641
642 /**
643 * The threshold growth factor.
644 */
645 private static final int GROW_THRESHOLD = 5;
646
647 /**
648 * Holds the current character attribute set *
649 */
650 protected MutableAttributeSet charAttr = new SimpleAttributeSet();
651
652 protected Vector<ElementSpec> parseBuffer = new Vector<ElementSpec>();
653
654 /**
655 * The parse stack. It holds the current element tree path.
656 */
657 private Stack<HTML.Tag> parseStack = new Stack<HTML.Tag>();
658
659 /**
660 * A stack for character attribute sets *
661 */
662 Stack charAttrStack = new Stack();
663
664 /** A mapping between HTML.Tag objects and the actions that handle them **/
665 HashMap tagToAction;
666
667 /** Tells us whether we've received the '</html>' tag yet **/
668 boolean endHTMLEncountered = false;
669
670 /**
671 * Related to the constructor with explicit insertTag
672 */
673 int popDepth;
674
675 /**
676 * Related to the constructor with explicit insertTag
677 */
678 int pushDepth;
679
680 /**
681 * Related to the constructor with explicit insertTag
682 */
683 int offset;
684
685 /**
686 * The tag (inclusve), after that the insertion should start.
687 */
688 HTML.Tag insertTag;
689
690 /**
691 * This variable becomes true after the insert tag has been encountered.
692 */
693 boolean insertTagEncountered;
694
695
696 /** A temporary variable that helps with the printing out of debug information **/
697 boolean debug = false;
698
699 /**
700 * This is true when we are inside a pre tag.
701 */
702 boolean inPreTag = false;
703
704 /**
705 * This is true when we are inside a style tag. This will add text
706 * content inside this style tag beeing parsed as CSS.
707 *
708 * This is package private to avoid accessor methods.
709 */
710 boolean inStyleTag = false;
711
712 /**
713 * This is true when we are inside a <textarea> tag. Any text
714 * content will then be added to the text area.
715 *
716 * This is package private to avoid accessor methods.
717 */
718 boolean inTextArea = false;
719
720 /**
721 * This contains all stylesheets that are somehow read, either
722 * via embedded style tags, or via linked stylesheets. The
723 * elements will be String objects containing a stylesheet each.
724 */
725 ArrayList styles;
726
727 /**
728 * The document model for a textarea.
729 *
730 * This is package private to avoid accessor methods.
731 */
732 ResetablePlainDocument textAreaDocument;
733
734 /**
735 * The current model of a select tag. Can be a ComboBoxModel or a
736 * ListModel depending on the type of the select box.
737 */
738 Object selectModel;
739
740 /**
741 * The current option beeing read.
742 */
743 Option option;
744
745 /**
746 * The current number of options in the current select model.
747 */
748 int numOptions;
749
750 /**
751 * The current button groups mappings.
752 */
753 HashMap buttonGroups;
754
755 /**
756 * The token threshold. This gets increased while loading.
757 */
758 private int threshold;
759
760 public class TagAction
761 {
762 /**
763 * This method is called when a start tag is seen for one of the types
764 * of tags associated with this Action. By default this does nothing.
765 */
766 public void start(HTML.Tag t, MutableAttributeSet a)
767 {
768 // Nothing to do here.
769 }
770
771 /**
772 * Called when an end tag is seen for one of the types of tags associated
773 * with this Action. By default does nothing.
774 */
775 public void end(HTML.Tag t)
776 {
777 // Nothing to do here.
778 }
779 }
780
781 public class BlockAction extends TagAction
782 {
783 /**
784 * This method is called when a start tag is seen for one of the types
785 * of tags associated with this Action.
786 */
787 public void start(HTML.Tag t, MutableAttributeSet a)
788 {
789 // Tell the parse buffer to open a new block for this tag.
790 blockOpen(t, a);
791 }
792
793 /**
794 * Called when an end tag is seen for one of the types of tags associated
795 * with this Action.
796 */
797 public void end(HTML.Tag t)
798 {
799 // Tell the parse buffer to close this block.
800 blockClose(t);
801 }
802 }
803
804 public class CharacterAction extends TagAction
805 {
806 /**
807 * This method is called when a start tag is seen for one of the types
808 * of tags associated with this Action.
809 */
810 public void start(HTML.Tag t, MutableAttributeSet a)
811 {
812 // Put the old attribute set on the stack.
813 pushCharacterStyle();
814
815 // Initialize with link pseudo class.
816 if (t == HTML.Tag.A)
817 a.addAttribute(HTML.Attribute.PSEUDO_CLASS, "link");
818
819 // Just add the attributes in <code>a</code>.
820 charAttr.addAttribute(t, a.copyAttributes());
821 }
822
823 /**
824 * Called when an end tag is seen for one of the types of tags associated
825 * with this Action.
826 */
827 public void end(HTML.Tag t)
828 {
829 popCharacterStyle();
830 }
831 }
832
833 /**
834 * Processes elements that make up forms: <input>, <textarea>,
835 * <select> and <option>.
836 */
837 public class FormAction extends SpecialAction
838 {
839 /**
840 * This method is called when a start tag is seen for one of the types
841 * of tags associated with this Action.
842 */
843 public void start(HTML.Tag t, MutableAttributeSet a)
844 {
845 if (t == HTML.Tag.INPUT)
846 {
847 String type = (String) a.getAttribute(HTML.Attribute.TYPE);
848 if (type == null)
849 {
850 type = "text"; // Default to 'text' when nothing was specified.
851 a.addAttribute(HTML.Attribute.TYPE, type);
852 }
853 setModel(type, a);
854 }
855 else if (t == HTML.Tag.TEXTAREA)
856 {
857 inTextArea = true;
858 textAreaDocument = new ResetablePlainDocument();
859 a.addAttribute(StyleConstants.ModelAttribute, textAreaDocument);
860 }
861 else if (t == HTML.Tag.SELECT)
862 {
863 int size = HTML.getIntegerAttributeValue(a, HTML.Attribute.SIZE,
864 1);
865 boolean multi = a.getAttribute(HTML.Attribute.MULTIPLE) != null;
866 if (size > 1 || multi)
867 {
868 SelectListModel m = new SelectListModel();
869 if (multi)
870 m.getSelectionModel().setSelectionMode(ListSelectionModel
871 .MULTIPLE_INTERVAL_SELECTION);
872 selectModel = m;
873 }
874 else
875 {
876 selectModel = new SelectComboBoxModel();
877 }
878 a.addAttribute(StyleConstants.ModelAttribute, selectModel);
879 }
880 if (t == HTML.Tag.OPTION)
881 {
882 option = new Option(a);
883 if (selectModel instanceof SelectListModel)
884 {
885 SelectListModel m = (SelectListModel) selectModel;
886 m.addElement(option);
887 if (option.isSelected())
888 {
889 m.getSelectionModel().addSelectionInterval(numOptions,
890 numOptions);
891 m.addInitialSelection(numOptions);
892 }
893 }
894 else if (selectModel instanceof SelectComboBoxModel)
895 {
896 SelectComboBoxModel m = (SelectComboBoxModel) selectModel;
897 m.addElement(option);
898 if (option.isSelected())
899 {
900 m.setSelectedItem(option);
901 m.setInitialSelection(option);
902 }
903 }
904 numOptions++;
905 }
906 else
907 {
908 // Build the element.
909 super.start(t, a);
910 }
911 }
912
913 /**
914 * Called when an end tag is seen for one of the types of tags associated
915 * with this Action.
916 */
917 public void end(HTML.Tag t)
918 {
919 if (t == HTML.Tag.OPTION)
920 {
921 option = null;
922 }
923 else
924 {
925 if (t == HTML.Tag.TEXTAREA)
926 {
927 inTextArea = false;
928 }
929 else if (t == HTML.Tag.SELECT)
930 {
931 selectModel = null;
932 numOptions = 0;
933 }
934 // Finish the element.
935 super.end(t);
936 }
937 }
938
939 private void setModel(String type, MutableAttributeSet attrs)
940 {
941 if (type.equals("submit") || type.equals("reset")
942 || type.equals("image"))
943 {
944 // Create button.
945 attrs.addAttribute(StyleConstants.ModelAttribute,
946 new DefaultButtonModel());
947 }
948 else if (type.equals("text") || type.equals("password"))
949 {
950 String text = (String) attrs.getAttribute(HTML.Attribute.VALUE);
951 ResetablePlainDocument doc = new ResetablePlainDocument();
952 if (text != null)
953 {
954 doc.setInitialText(text);
955 try
956 {
957 doc.insertString(0, text, null);
958 }
959 catch (BadLocationException ex)
960 {
961 // Shouldn't happen.
962 assert false;
963 }
964 }
965 attrs.addAttribute(StyleConstants.ModelAttribute, doc);
966 }
967 else if (type.equals("file"))
968 {
969 attrs.addAttribute(StyleConstants.ModelAttribute,
970 new PlainDocument());
971 }
972 else if (type.equals("checkbox") || type.equals("radio"))
973 {
974 ResetableToggleButtonModel model =
975 new ResetableToggleButtonModel();
976 if (attrs.getAttribute(HTML.Attribute.SELECTED) != null)
977 {
978 model.setSelected(true);
979 model.setInitial(true);
980 }
981 if (type.equals("radio"))
982 {
983 String name = (String) attrs.getAttribute(HTML.Attribute.NAME);
984 if (name != null)
985 {
986 if (buttonGroups == null)
987 buttonGroups = new HashMap();
988 ButtonGroup group = (ButtonGroup) buttonGroups.get(name);
989 if (group == null)
990 {
991 group = new ButtonGroup();
992 buttonGroups.put(name, group);
993 }
994 model.setGroup(group);
995 }
996 }
997 attrs.addAttribute(StyleConstants.ModelAttribute, model);
998 }
999 }
1000 }
1001
1002 /**
1003 * Called for form tags.
1004 */
1005 class FormTagAction
1006 extends BlockAction
1007 {
1008 /**
1009 * Clears the button group mapping.
1010 */
1011 public void end(HTML.Tag t)
1012 {
1013 super.end(t);
1014 buttonGroups = null;
1015 }
1016 }
1017
1018 /**
1019 * This action indicates that the content between starting and closing HTML
1020 * elements (like script - /script) should not be visible. The content is
1021 * still inserted and can be accessed when iterating the HTML document. The
1022 * parser will only fire
1023 * {@link javax.swing.text.html.HTMLEditorKit.ParserCallback#handleText} for
1024 * the hidden tags, regardless from that html tags the hidden section may
1025 * contain.
1026 */
1027 public class HiddenAction
1028 extends TagAction
1029 {
1030 /**
1031 * This method is called when a start tag is seen for one of the types
1032 * of tags associated with this Action.
1033 */
1034 public void start(HTML.Tag t, MutableAttributeSet a)
1035 {
1036 blockOpen(t, a);
1037 }
1038
1039 /**
1040 * Called when an end tag is seen for one of the types of tags associated
1041 * with this Action.
1042 */
1043 public void end(HTML.Tag t)
1044 {
1045 blockClose(t);
1046 }
1047 }
1048
1049 /**
1050 * Handles <isindex> tags.
1051 */
1052 public class IsindexAction extends TagAction
1053 {
1054 /**
1055 * This method is called when a start tag is seen for one of the types
1056 * of tags associated with this Action.
1057 */
1058 public void start(HTML.Tag t, MutableAttributeSet a)
1059 {
1060 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
1061 addSpecialElement(t, a);
1062 blockClose(HTML.Tag.IMPLIED);
1063 }
1064 }
1065
1066 public class ParagraphAction extends BlockAction
1067 {
1068 /**
1069 * This method is called when a start tag is seen for one of the types
1070 * of tags associated with this Action.
1071 */
1072 public void start(HTML.Tag t, MutableAttributeSet a)
1073 {
1074 super.start(t, a);
1075 }
1076
1077 /**
1078 * Called when an end tag is seen for one of the types of tags associated
1079 * with this Action.
1080 */
1081 public void end(HTML.Tag t)
1082 {
1083 super.end(t);
1084 }
1085 }
1086
1087 /**
1088 * This action is performed when a <pre> tag is parsed.
1089 */
1090 public class PreAction extends BlockAction
1091 {
1092 /**
1093 * This method is called when a start tag is seen for one of the types
1094 * of tags associated with this Action.
1095 */
1096 public void start(HTML.Tag t, MutableAttributeSet a)
1097 {
1098 inPreTag = true;
1099 blockOpen(t, a);
1100 a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
1101 blockOpen(HTML.Tag.IMPLIED, a);
1102 }
1103
1104 /**
1105 * Called when an end tag is seen for one of the types of tags associated
1106 * with this Action.
1107 */
1108 public void end(HTML.Tag t)
1109 {
1110 blockClose(HTML.Tag.IMPLIED);
1111 inPreTag = false;
1112 blockClose(t);
1113 }
1114 }
1115
1116 /**
1117 * Inserts the elements that are represented by ths single tag with
1118 * attributes (only). The closing tag, even if present, mut follow
1119 * immediately after the starting tag without providing any additional
1120 * information. Hence the {@link TagAction#end} method need not be
1121 * overridden and still does nothing.
1122 */
1123 public class SpecialAction extends TagAction
1124 {
1125 /**
1126 * The functionality is delegated to {@link HTMLReader#addSpecialElement}
1127 */
1128 public void start(HTML.Tag t, MutableAttributeSet a)
1129 {
1130 addSpecialElement(t, a);
1131 }
1132 }
1133
1134 class AreaAction extends TagAction
1135 {
1136 /**
1137 * This method is called when a start tag is seen for one of the types
1138 * of tags associated with this Action.
1139 */
1140 public void start(HTML.Tag t, MutableAttributeSet a)
1141 throws NotImplementedException
1142 {
1143 // FIXME: Implement.
1144 }
1145
1146 /**
1147 * Called when an end tag is seen for one of the types of tags associated
1148 * with this Action.
1149 */
1150 public void end(HTML.Tag t)
1151 throws NotImplementedException
1152 {
1153 // FIXME: Implement.
1154 }
1155 }
1156
1157 /**
1158 * Converts HTML tags to CSS attributes.
1159 */
1160 class ConvertAction
1161 extends TagAction
1162 {
1163
1164 public void start(HTML.Tag tag, MutableAttributeSet atts)
1165 {
1166 pushCharacterStyle();
1167 charAttr.addAttribute(tag, atts.copyAttributes());
1168 StyleSheet styleSheet = getStyleSheet();
1169 // TODO: Add other tags here.
1170 if (tag == HTML.Tag.FONT)
1171 {
1172 String color = (String) atts.getAttribute(HTML.Attribute.COLOR);
1173 if (color != null)
1174 styleSheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color);
1175 String face = (String) atts.getAttribute(HTML.Attribute.FACE);
1176 if (face != null)
1177 styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY,
1178 face);
1179 String size = (String) atts.getAttribute(HTML.Attribute.SIZE);
1180 if (size != null)
1181 styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_SIZE,
1182 size);
1183 }
1184 }
1185
1186 public void end(HTML.Tag tag)
1187 {
1188 popCharacterStyle();
1189 }
1190 }
1191
1192 class BaseAction extends TagAction
1193 {
1194 /**
1195 * This method is called when a start tag is seen for one of the types
1196 * of tags associated with this Action.
1197 */
1198 public void start(HTML.Tag t, MutableAttributeSet a)
1199 {
1200 baseTarget = (String) a.getAttribute(HTML.Attribute.TARGET);
1201 }
1202 }
1203
1204 class HeadAction extends BlockAction
1205 {
1206 /**
1207 * This method is called when a start tag is seen for one of the types
1208 * of tags associated with this Action.
1209 */
1210 public void start(HTML.Tag t, MutableAttributeSet a)
1211 throws NotImplementedException
1212 {
1213 // FIXME: Implement.
1214 super.start(t, a);
1215 }
1216
1217 /**
1218 * Called when an end tag is seen for one of the types of tags associated
1219 * with this Action.
1220 */
1221 public void end(HTML.Tag t)
1222 {
1223 // We read in all the stylesheets that are embedded or referenced
1224 // inside the header.
1225 if (styles != null)
1226 {
1227 int numStyles = styles.size();
1228 for (int i = 0; i < numStyles; i++)
1229 {
1230 String style = (String) styles.get(i);
1231 getStyleSheet().addRule(style);
1232 }
1233 }
1234 super.end(t);
1235 }
1236 }
1237
1238 class LinkAction extends HiddenAction
1239 {
1240 /**
1241 * This method is called when a start tag is seen for one of the types
1242 * of tags associated with this Action.
1243 */
1244 public void start(HTML.Tag t, MutableAttributeSet a)
1245 {
1246 super.start(t, a);
1247 String type = (String) a.getAttribute(HTML.Attribute.TYPE);
1248 if (type == null)
1249 type = "text/css";
1250 if (type.equals("text/css"))
1251 {
1252 String rel = (String) a.getAttribute(HTML.Attribute.REL);
1253 String media = (String) a.getAttribute(HTML.Attribute.MEDIA);
1254 String title = (String) a.getAttribute(HTML.Attribute.TITLE);
1255 if (media == null)
1256 media = "all";
1257 else
1258 media = media.toLowerCase();
1259 if (rel != null)
1260 {
1261 rel = rel.toLowerCase();
1262 if ((media.indexOf("all") != -1
1263 || media.indexOf("screen") != -1)
1264 && (rel.equals("stylesheet")))
1265 {
1266 String href = (String) a.getAttribute(HTML.Attribute.HREF);
1267 URL url = null;
1268 try
1269 {
1270 url = new URL(baseURL, href);
1271 }
1272 catch (MalformedURLException ex)
1273 {
1274 try
1275 {
1276 url = new URL(href);
1277 }
1278 catch (MalformedURLException ex2)
1279 {
1280 url = null;
1281 }
1282 }
1283 if (url != null)
1284 {
1285 try
1286 {
1287 getStyleSheet().importStyleSheet(url);
1288 }
1289 catch (Exception ex)
1290 {
1291 // Don't let exceptions and runtime exceptions
1292 // in CSS parsing disprupt the HTML parsing
1293 // process. But inform the user/developer
1294 // on the console about it.
1295 ex.printStackTrace();
1296 }
1297 }
1298 }
1299 }
1300 }
1301 }
1302
1303 }
1304
1305 class MapAction extends TagAction
1306 {
1307 /**
1308 * This method is called when a start tag is seen for one of the types
1309 * of tags associated with this Action.
1310 */
1311 public void start(HTML.Tag t, MutableAttributeSet a)
1312 throws NotImplementedException
1313 {
1314 // FIXME: Implement.
1315 }
1316
1317 /**
1318 * Called when an end tag is seen for one of the types of tags associated
1319 * with this Action.
1320 */
1321 public void end(HTML.Tag t)
1322 throws NotImplementedException
1323 {
1324 // FIXME: Implement.
1325 }
1326 }
1327
1328 class MetaAction extends TagAction
1329 {
1330 /**
1331 * This method is called when a start tag is seen for one of the types
1332 * of tags associated with this Action.
1333 */
1334 public void start(HTML.Tag t, MutableAttributeSet a)
1335 throws NotImplementedException
1336 {
1337 // FIXME: Implement.
1338 }
1339
1340 /**
1341 * Called when an end tag is seen for one of the types of tags associated
1342 * with this Action.
1343 */
1344 public void end(HTML.Tag t)
1345 throws NotImplementedException
1346 {
1347 // FIXME: Implement.
1348 }
1349 }
1350
1351 class StyleAction extends TagAction
1352 {
1353 /**
1354 * This method is called when a start tag is seen for one of the types
1355 * of tags associated with this Action.
1356 */
1357 public void start(HTML.Tag t, MutableAttributeSet a)
1358 {
1359 inStyleTag = true;
1360 }
1361
1362 /**
1363 * Called when an end tag is seen for one of the types of tags associated
1364 * with this Action.
1365 */
1366 public void end(HTML.Tag t)
1367 {
1368 inStyleTag = false;
1369 }
1370 }
1371
1372 class TitleAction extends TagAction
1373 {
1374 /**
1375 * This method is called when a start tag is seen for one of the types
1376 * of tags associated with this Action.
1377 */
1378 public void start(HTML.Tag t, MutableAttributeSet a)
1379 throws NotImplementedException
1380 {
1381 // FIXME: Implement.
1382 }
1383
1384 /**
1385 * Called when an end tag is seen for one of the types of tags associated
1386 * with this Action.
1387 */
1388 public void end(HTML.Tag t)
1389 throws NotImplementedException
1390 {
1391 // FIXME: Implement.
1392 }
1393 }
1394
1395 public HTMLReader(int offset)
1396 {
1397 this (offset, 0, 0, null);
1398 }
1399
1400 public HTMLReader(int offset, int popDepth, int pushDepth,
1401 HTML.Tag insertTag)
1402 {
1403 this.insertTag = insertTag;
1404 this.offset = offset;
1405 this.popDepth = popDepth;
1406 this.pushDepth = pushDepth;
1407 threshold = getTokenThreshold();
1408 initTags();
1409 }
1410
1411 void initTags()
1412 {
1413 tagToAction = new HashMap(72);
1414 CharacterAction characterAction = new CharacterAction();
1415 HiddenAction hiddenAction = new HiddenAction();
1416 AreaAction areaAction = new AreaAction();
1417 BaseAction baseAction = new BaseAction();
1418 BlockAction blockAction = new BlockAction();
1419 SpecialAction specialAction = new SpecialAction();
1420 ParagraphAction paragraphAction = new ParagraphAction();
1421 HeadAction headAction = new HeadAction();
1422 FormAction formAction = new FormAction();
1423 IsindexAction isindexAction = new IsindexAction();
1424 LinkAction linkAction = new LinkAction();
1425 MapAction mapAction = new MapAction();
1426 PreAction preAction = new PreAction();
1427 MetaAction metaAction = new MetaAction();
1428 StyleAction styleAction = new StyleAction();
1429 TitleAction titleAction = new TitleAction();
1430
1431 ConvertAction convertAction = new ConvertAction();
1432 tagToAction.put(HTML.Tag.A, characterAction);
1433 tagToAction.put(HTML.Tag.ADDRESS, characterAction);
1434 tagToAction.put(HTML.Tag.APPLET, hiddenAction);
1435 tagToAction.put(HTML.Tag.AREA, areaAction);
1436 tagToAction.put(HTML.Tag.B, characterAction);
1437 tagToAction.put(HTML.Tag.BASE, baseAction);
1438 tagToAction.put(HTML.Tag.BASEFONT, characterAction);
1439 tagToAction.put(HTML.Tag.BIG, characterAction);
1440 tagToAction.put(HTML.Tag.BLOCKQUOTE, blockAction);
1441 tagToAction.put(HTML.Tag.BODY, blockAction);
1442 tagToAction.put(HTML.Tag.BR, specialAction);
1443 tagToAction.put(HTML.Tag.CAPTION, blockAction);
1444 tagToAction.put(HTML.Tag.CENTER, blockAction);
1445 tagToAction.put(HTML.Tag.CITE, characterAction);
1446 tagToAction.put(HTML.Tag.CODE, characterAction);
1447 tagToAction.put(HTML.Tag.DD, blockAction);
1448 tagToAction.put(HTML.Tag.DFN, characterAction);
1449 tagToAction.put(HTML.Tag.DIR, blockAction);
1450 tagToAction.put(HTML.Tag.DIV, blockAction);
1451 tagToAction.put(HTML.Tag.DL, blockAction);
1452 tagToAction.put(HTML.Tag.DT, paragraphAction);
1453 tagToAction.put(HTML.Tag.EM, characterAction);
1454 tagToAction.put(HTML.Tag.FONT, convertAction);
1455 tagToAction.put(HTML.Tag.FORM, new FormTagAction());
1456 tagToAction.put(HTML.Tag.FRAME, specialAction);
1457 tagToAction.put(HTML.Tag.FRAMESET, blockAction);
1458 tagToAction.put(HTML.Tag.H1, paragraphAction);
1459 tagToAction.put(HTML.Tag.H2, paragraphAction);
1460 tagToAction.put(HTML.Tag.H3, paragraphAction);
1461 tagToAction.put(HTML.Tag.H4, paragraphAction);
1462 tagToAction.put(HTML.Tag.H5, paragraphAction);
1463 tagToAction.put(HTML.Tag.H6, paragraphAction);
1464 tagToAction.put(HTML.Tag.HEAD, headAction);
1465 tagToAction.put(HTML.Tag.HR, specialAction);
1466 tagToAction.put(HTML.Tag.HTML, blockAction);
1467 tagToAction.put(HTML.Tag.I, characterAction);
1468 tagToAction.put(HTML.Tag.IMG, specialAction);
1469 tagToAction.put(HTML.Tag.INPUT, formAction);
1470 tagToAction.put(HTML.Tag.ISINDEX, isindexAction);
1471 tagToAction.put(HTML.Tag.KBD, characterAction);
1472 tagToAction.put(HTML.Tag.LI, blockAction);
1473 tagToAction.put(HTML.Tag.LINK, linkAction);
1474 tagToAction.put(HTML.Tag.MAP, mapAction);
1475 tagToAction.put(HTML.Tag.MENU, blockAction);
1476 tagToAction.put(HTML.Tag.META, metaAction);
1477 tagToAction.put(HTML.Tag.NOFRAMES, blockAction);
1478 tagToAction.put(HTML.Tag.OBJECT, specialAction);
1479 tagToAction.put(HTML.Tag.OL, blockAction);
1480 tagToAction.put(HTML.Tag.OPTION, formAction);
1481 tagToAction.put(HTML.Tag.P, paragraphAction);
1482 tagToAction.put(HTML.Tag.PARAM, hiddenAction);
1483 tagToAction.put(HTML.Tag.PRE, preAction);
1484 tagToAction.put(HTML.Tag.SAMP, characterAction);
1485 tagToAction.put(HTML.Tag.SCRIPT, hiddenAction);
1486 tagToAction.put(HTML.Tag.SELECT, formAction);
1487 tagToAction.put(HTML.Tag.SMALL, characterAction);
1488 tagToAction.put(HTML.Tag.STRIKE, characterAction);
1489 tagToAction.put(HTML.Tag.S, characterAction);
1490 tagToAction.put(HTML.Tag.STRONG, characterAction);
1491 tagToAction.put(HTML.Tag.STYLE, styleAction);
1492 tagToAction.put(HTML.Tag.SUB, characterAction);
1493 tagToAction.put(HTML.Tag.SUP, characterAction);
1494 tagToAction.put(HTML.Tag.TABLE, blockAction);
1495 tagToAction.put(HTML.Tag.TD, blockAction);
1496 tagToAction.put(HTML.Tag.TEXTAREA, formAction);
1497 tagToAction.put(HTML.Tag.TH, blockAction);
1498 tagToAction.put(HTML.Tag.TITLE, titleAction);
1499 tagToAction.put(HTML.Tag.TR, blockAction);
1500 tagToAction.put(HTML.Tag.TT, characterAction);
1501 tagToAction.put(HTML.Tag.U, characterAction);
1502 tagToAction.put(HTML.Tag.UL, blockAction);
1503 tagToAction.put(HTML.Tag.VAR, characterAction);
1504 }
1505
1506 /**
1507 * Pushes the current character style onto the stack.
1508 *
1509 */
1510 protected void pushCharacterStyle()
1511 {
1512 charAttrStack.push(charAttr.copyAttributes());
1513 }
1514
1515 /**
1516 * Pops a character style off of the stack and uses it as the
1517 * current character style.
1518 *
1519 */
1520 protected void popCharacterStyle()
1521 {
1522 if (!charAttrStack.isEmpty())
1523 charAttr = (MutableAttributeSet) charAttrStack.pop();
1524 }
1525
1526 /**
1527 * Registers a given tag with a given Action. All of the well-known tags
1528 * are registered by default, but this method can change their behaviour
1529 * or add support for custom or currently unsupported tags.
1530 *
1531 * @param t the Tag to register
1532 * @param a the Action for the Tag
1533 */
1534 protected void registerTag(HTML.Tag t, HTMLDocument.HTMLReader.TagAction a)
1535 {
1536 tagToAction.put (t, a);
1537 }
1538
1539 /**
1540 * This is the last method called on the HTMLReader, allowing any pending
1541 * changes to be flushed to the HTMLDocument.
1542 */
1543 public void flush() throws BadLocationException
1544 {
1545 flushImpl();
1546 }
1547
1548 /**
1549 * Flushes the buffer and handle partial inserts.
1550 *
1551 */
1552 private void flushImpl()
1553 throws BadLocationException
1554 {
1555 int oldLen = getLength();
1556 int size = parseBuffer.size();
1557 ElementSpec[] elems = new ElementSpec[size];
1558 parseBuffer.copyInto(elems);
1559 if (oldLen == 0)
1560 create(elems);
1561 else
1562 insert(offset, elems);
1563 parseBuffer.removeAllElements();
1564 offset += getLength() - oldLen;
1565 }
1566
1567 /**
1568 * This method is called by the parser to indicate a block of
1569 * text was encountered. Should insert the text appropriately.
1570 *
1571 * @param data the text that was inserted
1572 * @param pos the position at which the text was inserted
1573 */
1574 public void handleText(char[] data, int pos)
1575 {
1576 if (shouldInsert() && data != null && data.length > 0)
1577 {
1578 if (inTextArea)
1579 textAreaContent(data);
1580 else if (inPreTag)
1581 preContent(data);
1582 else if (option != null)
1583 option.setLabel(new String(data));
1584 else if (inStyleTag)
1585 {
1586 if (styles == null)
1587 styles = new ArrayList();
1588 styles.add(new String(data));
1589 }
1590 else
1591 addContent(data, 0, data.length);
1592
1593 }
1594 }
1595
1596 /**
1597 * Checks if the HTML tag should be inserted. The tags before insert tag (if
1598 * specified) are not inserted. Also, the tags after the end of the html are
1599 * not inserted.
1600 *
1601 * @return true if the tag should be inserted, false otherwise.
1602 */
1603 private boolean shouldInsert()
1604 {
1605 return ! endHTMLEncountered
1606 && (insertTagEncountered || insertTag == null);
1607 }
1608
1609 /**
1610 * This method is called by the parser and should route the call to the
1611 * proper handler for the tag.
1612 *
1613 * @param t the HTML.Tag
1614 * @param a the attribute set
1615 * @param pos the position at which the tag was encountered
1616 */
1617 public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos)
1618 {
1619 if (t == insertTag)
1620 insertTagEncountered = true;
1621
1622 if (shouldInsert())
1623 {
1624 TagAction action = (TagAction) tagToAction.get(t);
1625 if (action != null)
1626 action.start(t, a);
1627 }
1628 }
1629
1630 /**
1631 * This method called by parser to handle a comment block.
1632 *
1633 * @param data the comment
1634 * @param pos the position at which the comment was encountered
1635 */
1636 public void handleComment(char[] data, int pos)
1637 {
1638 if (shouldInsert())
1639 {
1640 TagAction action = (TagAction) tagToAction.get(HTML.Tag.COMMENT);
1641 if (action != null)
1642 {
1643 action.start(HTML.Tag.COMMENT, new SimpleAttributeSet());
1644 action.end(HTML.Tag.COMMENT);
1645 }
1646 }
1647 }
1648
1649 /**
1650 * This method is called by the parser and should route the call to the
1651 * proper handler for the tag.
1652 *
1653 * @param t the HTML.Tag
1654 * @param pos the position at which the tag was encountered
1655 */
1656 public void handleEndTag(HTML.Tag t, int pos)
1657 {
1658 if (shouldInsert())
1659 {
1660 // If this is the </html> tag we need to stop calling the Actions
1661 if (t == HTML.Tag.HTML)
1662 endHTMLEncountered = true;
1663
1664 TagAction action = (TagAction) tagToAction.get(t);
1665 if (action != null)
1666 action.end(t);
1667 }
1668 }
1669
1670 /**
1671 * This is a callback from the parser that should be routed to the
1672 * appropriate handler for the tag.
1673 *
1674 * @param t the HTML.Tag that was encountered
1675 * @param a the attribute set
1676 * @param pos the position at which the tag was encountered
1677 */
1678 public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos)
1679 {
1680 if (t == insertTag)
1681 insertTagEncountered = true;
1682
1683 if (shouldInsert())
1684 {
1685 TagAction action = (TagAction) tagToAction.get(t);
1686 if (action != null)
1687 {
1688 action.start(t, a);
1689 action.end(t);
1690 }
1691 }
1692 }
1693
1694 /**
1695 * This is invoked after the stream has been parsed but before it has been
1696 * flushed.
1697 *
1698 * @param eol one of \n, \r, or \r\n, whichever was encountered the most in
1699 * parsing the stream
1700 * @since 1.3
1701 */
1702 public void handleEndOfLineString(String eol)
1703 {
1704 // FIXME: Implement.
1705 }
1706
1707 /**
1708 * Adds the given text to the textarea document. Called only when we are
1709 * within a textarea.
1710 *
1711 * @param data the text to add to the textarea
1712 */
1713 protected void textAreaContent(char[] data)
1714 {
1715 try
1716 {
1717 int offset = textAreaDocument.getLength();
1718 String text = new String(data);
1719 textAreaDocument.setInitialText(text);
1720 textAreaDocument.insertString(offset, text, null);
1721 }
1722 catch (BadLocationException ex)
1723 {
1724 // Must not happen as we insert at a model location that we
1725 // got from the document itself.
1726 assert false;
1727 }
1728 }
1729
1730 /**
1731 * Adds the given text that was encountered in a <PRE> element.
1732 * This adds synthesized lines to hold the text runs.
1733 *
1734 * @param data the text
1735 */
1736 protected void preContent(char[] data)
1737 {
1738 int start = 0;
1739 for (int i = 0; i < data.length; i++)
1740 {
1741 if (data[i] == '\n')
1742 {
1743 addContent(data, start, i - start + 1);
1744 blockClose(HTML.Tag.IMPLIED);
1745 MutableAttributeSet atts = new SimpleAttributeSet();
1746 atts.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
1747 blockOpen(HTML.Tag.IMPLIED, atts);
1748 start = i + 1;
1749 }
1750 }
1751 if (start < data.length)
1752 {
1753 // Add remaining last line.
1754 addContent(data, start, data.length - start);
1755 }
1756 }
1757
1758 /**
1759 * Instructs the parse buffer to create a block element with the given
1760 * attributes.
1761 *
1762 * @param t the tag that requires opening a new block
1763 * @param attr the attribute set for the new block
1764 */
1765 protected void blockOpen(HTML.Tag t, MutableAttributeSet attr)
1766 {
1767 if (inImpliedParagraph())
1768 blockClose(HTML.Tag.IMPLIED);
1769
1770 // Push the new tag on top of the stack.
1771 parseStack.push(t);
1772
1773 DefaultStyledDocument.ElementSpec element;
1774
1775 AbstractDocument.AttributeContext ctx = getAttributeContext();
1776 AttributeSet copy = attr.copyAttributes();
1777 copy = ctx.addAttribute(copy, StyleConstants.NameAttribute, t);
1778 element = new DefaultStyledDocument.ElementSpec(copy,
1779 DefaultStyledDocument.ElementSpec.StartTagType);
1780 parseBuffer.addElement(element);
1781 }
1782
1783 /**
1784 * Returns true when we are currently inside a paragraph, either
1785 * a real one or an implied, false otherwise.
1786 *
1787 * @return
1788 */
1789 private boolean inParagraph()
1790 {
1791 boolean inParagraph = false;
1792 if (! parseStack.isEmpty())
1793 {
1794 HTML.Tag top = parseStack.peek();
1795 inParagraph = top == HTML.Tag.P || top == HTML.Tag.IMPLIED;
1796 }
1797 return inParagraph;
1798 }
1799
1800 private boolean inImpliedParagraph()
1801 {
1802 boolean inParagraph = false;
1803 if (! parseStack.isEmpty())
1804 {
1805 HTML.Tag top = parseStack.peek();
1806 inParagraph = top == HTML.Tag.IMPLIED;
1807 }
1808 return inParagraph;
1809 }
1810
1811 /**
1812 * Instructs the parse buffer to close the block element associated with
1813 * the given HTML.Tag
1814 *
1815 * @param t the HTML.Tag that is closing its block
1816 */
1817 protected void blockClose(HTML.Tag t)
1818 {
1819 DefaultStyledDocument.ElementSpec element;
1820
1821 if (inImpliedParagraph() && t != HTML.Tag.IMPLIED)
1822 blockClose(HTML.Tag.IMPLIED);
1823
1824 // Pull the token from the stack.
1825 if (! parseStack.isEmpty()) // Just to be sure.
1826 parseStack.pop();
1827
1828 // If the previous tag is a start tag then we insert a synthetic
1829 // content tag.
1830 DefaultStyledDocument.ElementSpec prev;
1831 prev = parseBuffer.size() > 0 ? (DefaultStyledDocument.ElementSpec)
1832 parseBuffer.get(parseBuffer.size() - 1) : null;
1833 if (prev != null &&
1834 prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType)
1835 {
1836 addContent(new char[]{' '}, 0, 1);
1837 }
1838
1839 element = new DefaultStyledDocument.ElementSpec(null,
1840 DefaultStyledDocument.ElementSpec.EndTagType);
1841 parseBuffer.addElement(element);
1842 }
1843
1844 /**
1845 * Adds text to the appropriate context using the current character
1846 * attribute set.
1847 *
1848 * @param data the text to add
1849 * @param offs the offset at which to add it
1850 * @param length the length of the text to add
1851 */
1852 protected void addContent(char[] data, int offs, int length)
1853 {
1854 addContent(data, offs, length, true);
1855 }
1856
1857 /**
1858 * Adds text to the appropriate context using the current character
1859 * attribute set, and possibly generating an IMPLIED Tag if necessary.
1860 *
1861 * @param data the text to add
1862 * @param offs the offset at which to add it
1863 * @param length the length of the text to add
1864 * @param generateImpliedPIfNecessary whether or not we should generate
1865 * an HTML.Tag.IMPLIED tag if necessary
1866 */
1867 protected void addContent(char[] data, int offs, int length,
1868 boolean generateImpliedPIfNecessary)
1869 {
1870 if (generateImpliedPIfNecessary && ! inParagraph())
1871 {
1872 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
1873 }
1874
1875 AbstractDocument.AttributeContext ctx = getAttributeContext();
1876 DefaultStyledDocument.ElementSpec element;
1877 AttributeSet attributes = null;
1878
1879 // Copy the attribute set, don't use the same object because
1880 // it may change
1881 if (charAttr != null)
1882 attributes = charAttr.copyAttributes();
1883 else
1884 attributes = ctx.getEmptySet();
1885 attributes = ctx.addAttribute(attributes, StyleConstants.NameAttribute,
1886 HTML.Tag.CONTENT);
1887 element = new DefaultStyledDocument.ElementSpec(attributes,
1888 DefaultStyledDocument.ElementSpec.ContentType,
1889 data, offs, length);
1890
1891 // Add the element to the buffer
1892 parseBuffer.addElement(element);
1893
1894 if (parseBuffer.size() > threshold)
1895 {
1896 if (threshold <= MAX_THRESHOLD)
1897 threshold *= GROW_THRESHOLD;
1898 try
1899 {
1900 flushImpl();
1901 }
1902 catch (BadLocationException ble)
1903 {
1904 // TODO: what to do here?
1905 }
1906 }
1907 }
1908
1909 /**
1910 * Adds content that is specified in the attribute set.
1911 *
1912 * @param t the HTML.Tag
1913 * @param a the attribute set specifying the special content
1914 */
1915 protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a)
1916 {
1917 if (t != HTML.Tag.FRAME && ! inParagraph())
1918 {
1919 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
1920 }
1921
1922 a.addAttribute(StyleConstants.NameAttribute, t);
1923
1924 // The two spaces are required because some special elements like HR
1925 // must be broken. At least two characters are needed to break into the
1926 // two parts.
1927 DefaultStyledDocument.ElementSpec spec =
1928 new DefaultStyledDocument.ElementSpec(a.copyAttributes(),
1929 DefaultStyledDocument.ElementSpec.ContentType,
1930 new char[] {' '}, 0, 1 );
1931 parseBuffer.add(spec);
1932 }
1933
1934 }
1935
1936 /**
1937 * Gets the reader for the parser to use when loading the document with HTML.
1938 *
1939 * @param pos - the starting position
1940 * @return - the reader
1941 */
1942 public HTMLEditorKit.ParserCallback getReader(int pos)
1943 {
1944 return new HTMLReader(pos);
1945 }
1946
1947 /**
1948 * Gets the reader for the parser to use when loading the document with HTML.
1949 *
1950 * @param pos - the starting position
1951 * @param popDepth - the number of EndTagTypes to generate before inserting
1952 * @param pushDepth - the number of StartTagTypes with a direction
1953 * of JoinNextDirection that should be generated before inserting,
1954 * but after the end tags have been generated.
1955 * @param insertTag - the first tag to start inserting into document
1956 * @return - the reader
1957 */
1958 public HTMLEditorKit.ParserCallback getReader(int pos,
1959 int popDepth,
1960 int pushDepth,
1961 HTML.Tag insertTag)
1962 {
1963 return new HTMLReader(pos, popDepth, pushDepth, insertTag);
1964 }
1965
1966 /**
1967 * Gets the reader for the parser to use when inserting the HTML fragment into
1968 * the document. Checks if the parser is present, sets the parent in the
1969 * element stack and removes any actions for BODY (it can be only one body in
1970 * a HTMLDocument).
1971 *
1972 * @param pos - the starting position
1973 * @param popDepth - the number of EndTagTypes to generate before inserting
1974 * @param pushDepth - the number of StartTagTypes with a direction of
1975 * JoinNextDirection that should be generated before inserting, but
1976 * after the end tags have been generated.
1977 * @param insertTag - the first tag to start inserting into document
1978 * @param parent the element that will be the parent in the document. HTML
1979 * parsing includes checks for the parent, so it must be available.
1980 * @return - the reader
1981 * @throws IllegalStateException if the parsert is not set.
1982 */
1983 public HTMLEditorKit.ParserCallback getInsertingReader(int pos, int popDepth,
1984 int pushDepth,
1985 HTML.Tag insertTag,
1986 final Element parent)
1987 throws IllegalStateException
1988 {
1989 if (parser == null)
1990 throw new IllegalStateException("Parser has not been set");
1991
1992 HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth, insertTag)
1993 {
1994 /**
1995 * Ignore BODY.
1996 */
1997 public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos)
1998 {
1999 if (t != HTML.Tag.BODY)
2000 super.handleStartTag(t, a, pos);
2001 }
2002
2003 /**
2004 * Ignore BODY.
2005 */
2006 public void handleEndTag(HTML.Tag t, int pos)
2007 {
2008 if (t != HTML.Tag.BODY)
2009 super.handleEndTag(t, pos);
2010 }
2011 };
2012
2013 return reader;
2014 }
2015
2016 /**
2017 * Gets the child element that contains the attribute with the value or null.
2018 * Not thread-safe.
2019 *
2020 * @param e - the element to begin search at
2021 * @param attribute - the desired attribute
2022 * @param value - the desired value
2023 * @return the element found with the attribute and value specified or null if
2024 * it is not found.
2025 */
2026 public Element getElement(Element e, Object attribute, Object value)
2027 {
2028 if (e != null)
2029 {
2030 if (e.getAttributes().containsAttribute(attribute, value))
2031 return e;
2032
2033 int count = e.getElementCount();
2034 for (int j = 0; j < count; j++)
2035 {
2036 Element child = e.getElement(j);
2037 if (child.getAttributes().containsAttribute(attribute, value))
2038 return child;
2039
2040 Element grandChild = getElement(child, attribute, value);
2041 if (grandChild != null)
2042 return grandChild;
2043 }
2044 }
2045 return null;
2046 }
2047
2048 /**
2049 * Returns the element that has the given id Attribute (for instance, <p id
2050 * ='my paragraph >'). If it is not found, null is returned. The HTML tag,
2051 * having this attribute, is not checked by this method and can be any. The
2052 * method is not thread-safe.
2053 *
2054 * @param attrId - the value of the attribute id to look for
2055 * @return the element that has the given id.
2056 */
2057 public Element getElement(String attrId)
2058 {
2059 return getElement(getDefaultRootElement(), HTML.Attribute.ID,
2060 attrId);
2061 }
2062
2063 /**
2064 * Replaces the children of the given element with the contents of
2065 * the string. The document must have an HTMLEditorKit.Parser set.
2066 * This will be seen as at least two events, n inserts followed by a remove.
2067 *
2068 * @param elem - the brance element whose children will be replaced
2069 * @param htmlText - the string to be parsed and assigned to element.
2070 * @throws BadLocationException
2071 * @throws IOException
2072 * @throws IllegalArgumentException - if elem is a leaf
2073 * @throws IllegalStateException - if an HTMLEditorKit.Parser has not been set
2074 */
2075 public void setInnerHTML(Element elem, String htmlText)
2076 throws BadLocationException, IOException
2077 {
2078 if (elem.isLeaf())
2079 throw new IllegalArgumentException("Element is a leaf");
2080
2081 int start = elem.getStartOffset();
2082 int end = elem.getEndOffset();
2083
2084 HTMLEditorKit.ParserCallback reader = getInsertingReader(
2085 end, 0, 0, HTML.Tag.BODY, elem);
2086
2087 // TODO charset
2088 getParser().parse(new StringReader(htmlText), reader, true);
2089
2090 // Remove the previous content
2091 remove(start, end - start);
2092 }
2093
2094 /**
2095 * Replaces the given element in the parent with the string. When replacing a
2096 * leaf, this will attempt to make sure there is a newline present if one is
2097 * needed. This may result in an additional element being inserted. This will
2098 * be seen as at least two events, n inserts followed by a remove. The
2099 * HTMLEditorKit.Parser must be set.
2100 *
2101 * @param elem - the branch element whose parent will be replaced
2102 * @param htmlText - the string to be parsed and assigned to elem
2103 * @throws BadLocationException
2104 * @throws IOException
2105 * @throws IllegalStateException - if parser is not set
2106 */
2107 public void setOuterHTML(Element elem, String htmlText)
2108 throws BadLocationException, IOException
2109 {
2110 // Remove the current element:
2111 int start = elem.getStartOffset();
2112 int end = elem.getEndOffset();
2113
2114 remove(start, end-start);
2115
2116 HTMLEditorKit.ParserCallback reader = getInsertingReader(
2117 start, 0, 0, HTML.Tag.BODY, elem);
2118
2119 // TODO charset
2120 getParser().parse(new StringReader(htmlText), reader, true);
2121 }
2122
2123 /**
2124 * Inserts the string before the start of the given element. The parser must
2125 * be set.
2126 *
2127 * @param elem - the element to be the root for the new text.
2128 * @param htmlText - the string to be parsed and assigned to elem
2129 * @throws BadLocationException
2130 * @throws IOException
2131 * @throws IllegalStateException - if parser has not been set
2132 */
2133 public void insertBeforeStart(Element elem, String htmlText)
2134 throws BadLocationException, IOException
2135 {
2136 HTMLEditorKit.ParserCallback reader = getInsertingReader(
2137 elem.getStartOffset(), 0, 0, HTML.Tag.BODY, elem);
2138
2139 // TODO charset
2140 getParser().parse(new StringReader(htmlText), reader, true);
2141 }
2142
2143 /**
2144 * Inserts the string at the end of the element. If elem's children are
2145 * leaves, and the character at elem.getEndOffset() - 1 is a newline, then it
2146 * will be inserted before the newline. The parser must be set.
2147 *
2148 * @param elem - the element to be the root for the new text
2149 * @param htmlText - the text to insert
2150 * @throws BadLocationException
2151 * @throws IOException
2152 * @throws IllegalStateException - if parser is not set
2153 */
2154 public void insertBeforeEnd(Element elem, String htmlText)
2155 throws BadLocationException, IOException
2156 {
2157 HTMLEditorKit.ParserCallback reader = getInsertingReader(
2158 elem.getEndOffset(), 0, 0, HTML.Tag.BODY, elem);
2159
2160 // TODO charset
2161 getParser().parse(new StringReader(htmlText), reader, true);
2162
2163 }
2164
2165 /**
2166 * Inserts the string after the end of the given element.
2167 * The parser must be set.
2168 *
2169 * @param elem - the element to be the root for the new text
2170 * @param htmlText - the text to insert
2171 * @throws BadLocationException
2172 * @throws IOException
2173 * @throws IllegalStateException - if parser is not set
2174 */
2175 public void insertAfterEnd(Element elem, String htmlText)
2176 throws BadLocationException, IOException
2177 {
2178 HTMLEditorKit.ParserCallback reader = getInsertingReader(
2179 elem.getEndOffset(), 0, 0, HTML.Tag.BODY, elem);
2180
2181 // TODO charset
2182 getParser().parse(new StringReader(htmlText), reader, true);
2183 }
2184
2185 /**
2186 * Inserts the string at the start of the element.
2187 * The parser must be set.
2188 *
2189 * @param elem - the element to be the root for the new text
2190 * @param htmlText - the text to insert
2191 * @throws BadLocationException
2192 * @throws IOException
2193 * @throws IllegalStateException - if parser is not set
2194 */
2195 public void insertAfterStart(Element elem, String htmlText)
2196 throws BadLocationException, IOException
2197 {
2198 HTMLEditorKit.ParserCallback reader = getInsertingReader(
2199 elem.getStartOffset(), 0, 0, HTML.Tag.BODY, elem);
2200
2201 // TODO charset
2202 getParser().parse(new StringReader(htmlText), reader, true);
2203 }
2204
2205 /**
2206 * Overridden to tag content with the synthetic HTML.Tag.CONTENT
2207 * tag.
2208 */
2209 protected void insertUpdate(DefaultDocumentEvent evt, AttributeSet att)
2210 {
2211 if (att == null)
2212 {
2213 SimpleAttributeSet sas = new SimpleAttributeSet();
2214 sas.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
2215 att = sas;
2216 }
2217 super.insertUpdate(evt, att);
2218 }
2219
2220 /**
2221 * Returns <code>true</code> when this document is inside a frame,
2222 * <code>false</code> otherwise.
2223 *
2224 * @return <code>true</code> when this document is inside a frame,
2225 * <code>false</code> otherwise
2226 */
2227 boolean isFrameDocument()
2228 {
2229 return frameDocument;
2230 }
2231
2232 /**
2233 * Set <code>true</code> when this document is inside a frame,
2234 * <code>false</code> otherwise.
2235 *
2236 * @param frameDoc <code>true</code> when this document is inside a frame,
2237 * <code>false</code> otherwise
2238 */
2239 void setFrameDocument(boolean frameDoc)
2240 {
2241 frameDocument = frameDoc;
2242 }
2243
2244 /**
2245 * Returns the target that is specified in the base tag, if this is the case.
2246 *
2247 * @return the target that is specified in the base tag, if this is the case
2248 */
2249 String getBaseTarget()
2250 {
2251 return baseTarget;
2252 }
2253
2254 /**
2255 * Updates the A tag's pseudo class value in response to a hyperlink
2256 * action.
2257 *
2258 * @param el the corresponding element
2259 * @param value the new value
2260 */
2261 void updateSpecialClass(Element el, HTML.Attribute cl, String value)
2262 {
2263 try
2264 {
2265 writeLock();
2266 DefaultDocumentEvent ev =
2267 new DefaultDocumentEvent(el.getStartOffset(), 1,
2268 DocumentEvent.EventType.CHANGE);
2269 AttributeSet elAtts = el.getAttributes();
2270 AttributeSet anchorAtts = (AttributeSet) elAtts.getAttribute(HTML.Tag.A);
2271 if (anchorAtts != null)
2272 {
2273 AttributeSet copy = elAtts.copyAttributes();
2274 StyleSheet ss = getStyleSheet();
2275 if (value != null)
2276 {
2277 anchorAtts = ss.addAttribute(anchorAtts, cl, value);
2278 }
2279 else
2280 {
2281 anchorAtts = ss.removeAttribute(anchorAtts, cl);
2282 }
2283 MutableAttributeSet matts = (MutableAttributeSet) elAtts;
2284 ev.addEdit(new AttributeUndoableEdit(el, copy, false));
2285 matts.removeAttribute(HTML.Tag.A);
2286 matts.addAttribute(HTML.Tag.A, anchorAtts);
2287 ev.end();
2288 fireChangedUpdate(ev);
2289 fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
2290 }
2291 }
2292 finally
2293 {
2294 writeUnlock();
2295 }
2296 }
2297
2298 }