From 554fd8c5195424bdbcabf5de30fdc183aba391bd Mon Sep 17 00:00:00 2001 From: upstream source tree Date: Sun, 15 Mar 2015 20:14:05 -0400 Subject: obtained gcc-4.6.4.tar.bz2 from upstream website; verified gcc-4.6.4.tar.bz2.sig; imported gcc-4.6.4 source tree from verified upstream tarball. downloading a git-generated archive based on the 'upstream' tag should provide you with a source tree that is binary identical to the one extracted from the above tarball. if you have obtained the source via the command 'git clone', however, do note that line-endings of files in your working directory might differ from line-endings of the respective files in the upstream repository. --- .../javax/swing/text/html/HTMLDocument.java | 2298 ++++++++++++++++++++ 1 file changed, 2298 insertions(+) create mode 100644 libjava/classpath/javax/swing/text/html/HTMLDocument.java (limited to 'libjava/classpath/javax/swing/text/html/HTMLDocument.java') diff --git a/libjava/classpath/javax/swing/text/html/HTMLDocument.java b/libjava/classpath/javax/swing/text/html/HTMLDocument.java new file mode 100644 index 000000000..9545be4e8 --- /dev/null +++ b/libjava/classpath/javax/swing/text/html/HTMLDocument.java @@ -0,0 +1,2298 @@ +/* HTMLDocument.java -- + Copyright (C) 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing.text.html; + +import gnu.classpath.NotImplementedException; + +import java.io.IOException; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Stack; +import java.util.Vector; + +import javax.swing.ButtonGroup; +import javax.swing.DefaultButtonModel; +import javax.swing.JEditorPane; +import javax.swing.ListSelectionModel; +import javax.swing.event.DocumentEvent; +import javax.swing.event.UndoableEditEvent; +import javax.swing.text.AbstractDocument; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.DefaultStyledDocument; +import javax.swing.text.Element; +import javax.swing.text.ElementIterator; +import javax.swing.text.GapContent; +import javax.swing.text.MutableAttributeSet; +import javax.swing.text.PlainDocument; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.html.HTML.Tag; + +/** + * Represents the HTML document that is constructed by defining the text and + * other components (images, buttons, etc) in HTML language. This class can + * becomes the default document for {@link JEditorPane} after setting its + * content type to "text/html". HTML document also serves as an intermediate + * data structure when it is needed to parse HTML and then obtain the content of + * the certain types of tags. This class also has methods for modifying the HTML + * content. + * + * @author Audrius Meskauskas (AudriusA@Bioinformatics.org) + * @author Anthony Balkissoon (abalkiss@redhat.com) + * @author Lillian Angel (langel@redhat.com) + */ +public class HTMLDocument extends DefaultStyledDocument +{ + /** A key for document properies. The value for the key is + * a Vector of Strings of comments not found in the body. + */ + public static final String AdditionalComments = "AdditionalComments"; + URL baseURL = null; + boolean preservesUnknownTags = true; + int tokenThreshold = Integer.MAX_VALUE; + HTMLEditorKit.Parser parser; + + /** + * Indicates whether this document is inside a frame or not. + */ + private boolean frameDocument; + + /** + * Package private to avoid accessor methods. + */ + String baseTarget; + + /** + * Constructs an HTML document using the default buffer size and a default + * StyleSheet. + */ + public HTMLDocument() + { + this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet()); + } + + /** + * Constructs an HTML document with the default content storage + * implementation and the specified style/attribute storage mechanism. + * + * @param styles - the style sheet + */ + public HTMLDocument(StyleSheet styles) + { + this(new GapContent(BUFFER_SIZE_DEFAULT), styles); + } + + /** + * Constructs an HTML document with the given content storage implementation + * and the given style/attribute storage mechanism. + * + * @param c - the document's content + * @param styles - the style sheet + */ + public HTMLDocument(AbstractDocument.Content c, StyleSheet styles) + { + super(c, styles); + } + + /** + * Gets the style sheet with the document display rules (CSS) that were specified + * in the HTML document. + * + * @return - the style sheet + */ + public StyleSheet getStyleSheet() + { + return (StyleSheet) getAttributeContext(); + } + + /** + * This method creates a root element for the new document. + * + * @return the new default root + */ + protected AbstractElement createDefaultRoot() + { + AbstractDocument.AttributeContext ctx = getAttributeContext(); + + // Create html element. + AttributeSet atts = ctx.getEmptySet(); + atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.HTML); + BranchElement html = (BranchElement) createBranchElement(null, atts); + + // Create body element. + atts = ctx.getEmptySet(); + atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.BODY); + BranchElement body = (BranchElement) createBranchElement(html, atts); + html.replace(0, 0, new Element[] { body }); + + // Create p element. + atts = ctx.getEmptySet(); + atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, HTML.Tag.P); + BranchElement p = (BranchElement) createBranchElement(body, atts); + body.replace(0, 0, new Element[] { p }); + + // Create an empty leaf element. + atts = ctx.getEmptySet(); + atts = ctx.addAttribute(atts, StyleConstants.NameAttribute, + HTML.Tag.CONTENT); + Element leaf = createLeafElement(p, atts, 0, 1); + p.replace(0, 0, new Element[]{ leaf }); + + return html; + } + + /** + * This method returns an HTMLDocument.RunElement object attached to + * parent representing a run of text from p0 to p1. The run has + * attributes described by a. + * + * @param parent - the parent element + * @param a - the attributes for the element + * @param p0 - the beginning of the range >= 0 + * @param p1 - the end of the range >= p0 + * + * @return the new element + */ + protected Element createLeafElement(Element parent, AttributeSet a, int p0, + int p1) + { + return new RunElement(parent, a, p0, p1); + } + + /** + * This method returns an HTMLDocument.BlockElement object representing the + * attribute set a and attached to parent. + * + * @param parent - the parent element + * @param a - the attributes for the element + * + * @return the new element + */ + protected Element createBranchElement(Element parent, AttributeSet a) + { + return new BlockElement(parent, a); + } + + /** + * Returns the parser used by this HTMLDocument to insert HTML. + * + * @return the parser used by this HTMLDocument to insert HTML. + */ + public HTMLEditorKit.Parser getParser() + { + return parser; + } + + /** + * Sets the parser used by this HTMLDocument to insert HTML. + * + * @param p the parser to use + */ + public void setParser (HTMLEditorKit.Parser p) + { + parser = p; + } + /** + * Sets the number of tokens to buffer before trying to display the + * Document. + * + * @param n the number of tokens to buffer + */ + public void setTokenThreshold (int n) + { + tokenThreshold = n; + } + + /** + * Returns the number of tokens that are buffered before the document + * is rendered. + * + * @return the number of tokens buffered + */ + public int getTokenThreshold () + { + return tokenThreshold; + } + + /** + * Returns the location against which to resolve relative URLs. + * This is the document's URL if the document was loaded from a URL. + * If a base tag is found, it will be used. + * @return the base URL + */ + public URL getBase() + { + return baseURL; + } + + /** + * Sets the location against which to resolve relative URLs. + * @param u the new base URL + */ + public void setBase(URL u) + { + baseURL = u; + getStyleSheet().setBase(u); + } + + /** + * Returns whether or not the parser preserves unknown HTML tags. + * @return true if the parser preserves unknown tags + */ + public boolean getPreservesUnknownTags() + { + return preservesUnknownTags; + } + + /** + * Sets the behaviour of the parser when it encounters unknown HTML tags. + * @param preservesTags true if the parser should preserve unknown tags. + */ + public void setPreservesUnknownTags(boolean preservesTags) + { + preservesUnknownTags = preservesTags; + } + + /** + * An iterator to iterate through LeafElements in the document. + */ + class LeafIterator extends Iterator + { + HTML.Tag tag; + HTMLDocument doc; + ElementIterator it; + + public LeafIterator (HTML.Tag t, HTMLDocument d) + { + doc = d; + tag = t; + it = new ElementIterator(doc); + } + + /** + * Return the attributes for the tag associated with this iteartor + * @return the AttributeSet + */ + public AttributeSet getAttributes() + { + if (it.current() != null) + return it.current().getAttributes(); + return null; + } + + /** + * Get the end of the range for the current occurrence of the tag + * being defined and having the same attributes. + * @return the end of the range + */ + public int getEndOffset() + { + if (it.current() != null) + return it.current().getEndOffset(); + return -1; + } + + /** + * Get the start of the range for the current occurrence of the tag + * being defined and having the same attributes. + * @return the start of the range (-1 if it can't be found). + */ + + public int getStartOffset() + { + if (it.current() != null) + return it.current().getStartOffset(); + return -1; + } + + /** + * Advance the iterator to the next LeafElement . + */ + public void next() + { + it.next(); + while (it.current()!= null && !it.current().isLeaf()) + it.next(); + } + + /** + * Indicates whether or not the iterator currently represents an occurrence + * of the tag. + * @return true if the iterator currently represents an occurrence of the + * tag. + */ + public boolean isValid() + { + return it.current() != null; + } + + /** + * Type of tag for this iterator. + */ + public Tag getTag() + { + return tag; + } + + } + + public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent event) + { + String target = event.getTarget(); + Element el = event.getSourceElement(); + URL url = event.getURL(); + if (target.equals("_self")) + { + updateFrame(el, url); + } + else if (target.equals("_parent")) + { + updateFrameSet(el.getParentElement(), url); + } + else + { + Element targetFrame = findFrame(target); + if (targetFrame != null) + updateFrame(targetFrame, url); + } + } + + /** + * Finds the named frame inside this document. + * + * @param target the name to look for + * + * @return the frame if there is a matching frame, null + * otherwise + */ + private Element findFrame(String target) + { + ElementIterator i = new ElementIterator(this); + Element next = null; + while ((next = i.next()) != null) + { + AttributeSet atts = next.getAttributes(); + if (atts.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.FRAME) + { + String name = (String) atts.getAttribute(HTML.Attribute.NAME); + if (name != null && name.equals(target)) + break; + } + } + return next; + } + + /** + * Updates the frame that is represented by the specified element to + * refer to the specified URL. + * + * @param el the element + * @param url the new url + */ + private void updateFrame(Element el, URL url) + { + try + { + writeLock(); + DefaultDocumentEvent ev = + new DefaultDocumentEvent(el.getStartOffset(), 1, + DocumentEvent.EventType.CHANGE); + AttributeSet elAtts = el.getAttributes(); + AttributeSet copy = elAtts.copyAttributes(); + MutableAttributeSet matts = (MutableAttributeSet) elAtts; + ev.addEdit(new AttributeUndoableEdit(el, copy, false)); + matts.removeAttribute(HTML.Attribute.SRC); + matts.addAttribute(HTML.Attribute.SRC, url.toString()); + ev.end(); + fireChangedUpdate(ev); + fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); + } + finally + { + writeUnlock(); + } + } + + /** + * Updates the frameset that is represented by the specified element + * to create a frame that refers to the specified URL. + * + * @param el the element + * @param url the url + */ + private void updateFrameSet(Element el, URL url) + { + int start = el.getStartOffset(); + int end = el.getEndOffset(); + + StringBuilder html = new StringBuilder(); + html.append("'); + if (getParser() == null) + setParser(new HTMLEditorKit().getParser()); + try + { + setOuterHTML(el, html.toString()); + } + catch (BadLocationException ex) + { + ex.printStackTrace(); + } + catch (IOException ex) + { + ex.printStackTrace(); + } + } + + /** + * Gets an iterator for the given HTML.Tag. + * @param t the requested HTML.Tag + * @return the Iterator + */ + public HTMLDocument.Iterator getIterator (HTML.Tag t) + { + return new HTMLDocument.LeafIterator(t, this); + } + + /** + * An iterator over a particular type of tag. + */ + public abstract static class Iterator + { + /** + * Return the attribute set for this tag. + * @return the AttributeSet (null if none found). + */ + public abstract AttributeSet getAttributes(); + + /** + * Get the end of the range for the current occurrence of the tag + * being defined and having the same attributes. + * @return the end of the range + */ + public abstract int getEndOffset(); + + /** + * Get the start of the range for the current occurrence of the tag + * being defined and having the same attributes. + * @return the start of the range (-1 if it can't be found). + */ + public abstract int getStartOffset(); + + /** + * Move the iterator forward. + */ + public abstract void next(); + + /** + * Indicates whether or not the iterator currently represents an occurrence + * of the tag. + * @return true if the iterator currently represents an occurrence of the + * tag. + */ + public abstract boolean isValid(); + + /** + * Type of tag this iterator represents. + * @return the tag. + */ + public abstract HTML.Tag getTag(); + } + + public class BlockElement extends AbstractDocument.BranchElement + { + public BlockElement (Element parent, AttributeSet a) + { + super(parent, a); + } + + /** + * Gets the resolving parent. Since HTML attributes are not + * inherited at the model level, this returns null. + */ + public AttributeSet getResolveParent() + { + return null; + } + + /** + * Gets the name of the element. + * + * @return the name of the element if it exists, null otherwise. + */ + public String getName() + { + Object tag = getAttribute(StyleConstants.NameAttribute); + String name = null; + if (tag != null) + name = tag.toString(); + if (name == null) + name = super.getName(); + return name; + } + } + + /** + * RunElement represents a section of text that has a set of + * HTML character level attributes assigned to it. + */ + public class RunElement extends AbstractDocument.LeafElement + { + + /** + * Constructs an element that has no children. It represents content + * within the document. + * + * @param parent - parent of this + * @param a - elements attributes + * @param start - the start offset >= 0 + * @param end - the end offset + */ + public RunElement(Element parent, AttributeSet a, int start, int end) + { + super(parent, a, start, end); + } + + /** + * Gets the name of the element. + * + * @return the name of the element if it exists, null otherwise. + */ + public String getName() + { + Object tag = getAttribute(StyleConstants.NameAttribute); + String name = null; + if (tag != null) + name = tag.toString(); + if (name == null) + name = super.getName(); + return name; + } + + /** + * Gets the resolving parent. HTML attributes do not inherit at the + * model level, so this method returns null. + * + * @return null + */ + public AttributeSet getResolveParent() + { + return null; + } + } + + /** + * A reader to load an HTMLDocument with HTML structure. + * + * @author Anthony Balkissoon abalkiss at redhat dot com + */ + public class HTMLReader extends HTMLEditorKit.ParserCallback + { + /** + * The maximum token threshold. We don't grow it larger than this. + */ + private static final int MAX_THRESHOLD = 10000; + + /** + * The threshold growth factor. + */ + private static final int GROW_THRESHOLD = 5; + + /** + * Holds the current character attribute set * + */ + protected MutableAttributeSet charAttr = new SimpleAttributeSet(); + + protected Vector parseBuffer = new Vector(); + + /** + * The parse stack. It holds the current element tree path. + */ + private Stack parseStack = new Stack(); + + /** + * A stack for character attribute sets * + */ + Stack charAttrStack = new Stack(); + + /** A mapping between HTML.Tag objects and the actions that handle them **/ + HashMap tagToAction; + + /** Tells us whether we've received the '' tag yet **/ + boolean endHTMLEncountered = false; + + /** + * Related to the constructor with explicit insertTag + */ + int popDepth; + + /** + * Related to the constructor with explicit insertTag + */ + int pushDepth; + + /** + * Related to the constructor with explicit insertTag + */ + int offset; + + /** + * The tag (inclusve), after that the insertion should start. + */ + HTML.Tag insertTag; + + /** + * This variable becomes true after the insert tag has been encountered. + */ + boolean insertTagEncountered; + + + /** A temporary variable that helps with the printing out of debug information **/ + boolean debug = false; + + /** + * This is true when we are inside a pre tag. + */ + boolean inPreTag = false; + + /** + * This is true when we are inside a style tag. This will add text + * content inside this style tag beeing parsed as CSS. + * + * This is package private to avoid accessor methods. + */ + boolean inStyleTag = false; + + /** + * This is true when we are inside a <textarea> tag. Any text + * content will then be added to the text area. + * + * This is package private to avoid accessor methods. + */ + boolean inTextArea = false; + + /** + * This contains all stylesheets that are somehow read, either + * via embedded style tags, or via linked stylesheets. The + * elements will be String objects containing a stylesheet each. + */ + ArrayList styles; + + /** + * The document model for a textarea. + * + * This is package private to avoid accessor methods. + */ + ResetablePlainDocument textAreaDocument; + + /** + * The current model of a select tag. Can be a ComboBoxModel or a + * ListModel depending on the type of the select box. + */ + Object selectModel; + + /** + * The current option beeing read. + */ + Option option; + + /** + * The current number of options in the current select model. + */ + int numOptions; + + /** + * The current button groups mappings. + */ + HashMap buttonGroups; + + /** + * The token threshold. This gets increased while loading. + */ + private int threshold; + + public class TagAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. By default this does nothing. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + { + // Nothing to do here. + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. By default does nothing. + */ + public void end(HTML.Tag t) + { + // Nothing to do here. + } + } + + public class BlockAction extends TagAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + { + // Tell the parse buffer to open a new block for this tag. + blockOpen(t, a); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // Tell the parse buffer to close this block. + blockClose(t); + } + } + + public class CharacterAction extends TagAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + { + // Put the old attribute set on the stack. + pushCharacterStyle(); + + // Initialize with link pseudo class. + if (t == HTML.Tag.A) + a.addAttribute(HTML.Attribute.PSEUDO_CLASS, "link"); + + // Just add the attributes in a. + charAttr.addAttribute(t, a.copyAttributes()); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + popCharacterStyle(); + } + } + + /** + * Processes elements that make up forms: <input>, <textarea>, + * <select> and <option>. + */ + public class FormAction extends SpecialAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + { + if (t == HTML.Tag.INPUT) + { + String type = (String) a.getAttribute(HTML.Attribute.TYPE); + if (type == null) + { + type = "text"; // Default to 'text' when nothing was specified. + a.addAttribute(HTML.Attribute.TYPE, type); + } + setModel(type, a); + } + else if (t == HTML.Tag.TEXTAREA) + { + inTextArea = true; + textAreaDocument = new ResetablePlainDocument(); + a.addAttribute(StyleConstants.ModelAttribute, textAreaDocument); + } + else if (t == HTML.Tag.SELECT) + { + int size = HTML.getIntegerAttributeValue(a, HTML.Attribute.SIZE, + 1); + boolean multi = a.getAttribute(HTML.Attribute.MULTIPLE) != null; + if (size > 1 || multi) + { + SelectListModel m = new SelectListModel(); + if (multi) + m.getSelectionModel().setSelectionMode(ListSelectionModel + .MULTIPLE_INTERVAL_SELECTION); + selectModel = m; + } + else + { + selectModel = new SelectComboBoxModel(); + } + a.addAttribute(StyleConstants.ModelAttribute, selectModel); + } + if (t == HTML.Tag.OPTION) + { + option = new Option(a); + if (selectModel instanceof SelectListModel) + { + SelectListModel m = (SelectListModel) selectModel; + m.addElement(option); + if (option.isSelected()) + { + m.getSelectionModel().addSelectionInterval(numOptions, + numOptions); + m.addInitialSelection(numOptions); + } + } + else if (selectModel instanceof SelectComboBoxModel) + { + SelectComboBoxModel m = (SelectComboBoxModel) selectModel; + m.addElement(option); + if (option.isSelected()) + { + m.setSelectedItem(option); + m.setInitialSelection(option); + } + } + numOptions++; + } + else + { + // Build the element. + super.start(t, a); + } + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + if (t == HTML.Tag.OPTION) + { + option = null; + } + else + { + if (t == HTML.Tag.TEXTAREA) + { + inTextArea = false; + } + else if (t == HTML.Tag.SELECT) + { + selectModel = null; + numOptions = 0; + } + // Finish the element. + super.end(t); + } + } + + private void setModel(String type, MutableAttributeSet attrs) + { + if (type.equals("submit") || type.equals("reset") + || type.equals("image")) + { + // Create button. + attrs.addAttribute(StyleConstants.ModelAttribute, + new DefaultButtonModel()); + } + else if (type.equals("text") || type.equals("password")) + { + String text = (String) attrs.getAttribute(HTML.Attribute.VALUE); + ResetablePlainDocument doc = new ResetablePlainDocument(); + if (text != null) + { + doc.setInitialText(text); + try + { + doc.insertString(0, text, null); + } + catch (BadLocationException ex) + { + // Shouldn't happen. + assert false; + } + } + attrs.addAttribute(StyleConstants.ModelAttribute, doc); + } + else if (type.equals("file")) + { + attrs.addAttribute(StyleConstants.ModelAttribute, + new PlainDocument()); + } + else if (type.equals("checkbox") || type.equals("radio")) + { + ResetableToggleButtonModel model = + new ResetableToggleButtonModel(); + if (attrs.getAttribute(HTML.Attribute.SELECTED) != null) + { + model.setSelected(true); + model.setInitial(true); + } + if (type.equals("radio")) + { + String name = (String) attrs.getAttribute(HTML.Attribute.NAME); + if (name != null) + { + if (buttonGroups == null) + buttonGroups = new HashMap(); + ButtonGroup group = (ButtonGroup) buttonGroups.get(name); + if (group == null) + { + group = new ButtonGroup(); + buttonGroups.put(name, group); + } + model.setGroup(group); + } + } + attrs.addAttribute(StyleConstants.ModelAttribute, model); + } + } + } + + /** + * Called for form tags. + */ + class FormTagAction + extends BlockAction + { + /** + * Clears the button group mapping. + */ + public void end(HTML.Tag t) + { + super.end(t); + buttonGroups = null; + } + } + + /** + * This action indicates that the content between starting and closing HTML + * elements (like script - /script) should not be visible. The content is + * still inserted and can be accessed when iterating the HTML document. The + * parser will only fire + * {@link javax.swing.text.html.HTMLEditorKit.ParserCallback#handleText} for + * the hidden tags, regardless from that html tags the hidden section may + * contain. + */ + public class HiddenAction + extends TagAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + { + blockOpen(t, a); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + blockClose(t); + } + } + + /** + * Handles <isindex> tags. + */ + public class IsindexAction extends TagAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + { + blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet()); + addSpecialElement(t, a); + blockClose(HTML.Tag.IMPLIED); + } + } + + public class ParagraphAction extends BlockAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + { + super.start(t, a); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + super.end(t); + } + } + + /** + * This action is performed when a <pre> tag is parsed. + */ + public class PreAction extends BlockAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + { + inPreTag = true; + blockOpen(t, a); + a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre"); + blockOpen(HTML.Tag.IMPLIED, a); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + blockClose(HTML.Tag.IMPLIED); + inPreTag = false; + blockClose(t); + } + } + + /** + * Inserts the elements that are represented by ths single tag with + * attributes (only). The closing tag, even if present, mut follow + * immediately after the starting tag without providing any additional + * information. Hence the {@link TagAction#end} method need not be + * overridden and still does nothing. + */ + public class SpecialAction extends TagAction + { + /** + * The functionality is delegated to {@link HTMLReader#addSpecialElement} + */ + public void start(HTML.Tag t, MutableAttributeSet a) + { + addSpecialElement(t, a); + } + } + + class AreaAction extends TagAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + throws NotImplementedException + { + // FIXME: Implement. + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + throws NotImplementedException + { + // FIXME: Implement. + } + } + + /** + * Converts HTML tags to CSS attributes. + */ + class ConvertAction + extends TagAction + { + + public void start(HTML.Tag tag, MutableAttributeSet atts) + { + pushCharacterStyle(); + charAttr.addAttribute(tag, atts.copyAttributes()); + StyleSheet styleSheet = getStyleSheet(); + // TODO: Add other tags here. + if (tag == HTML.Tag.FONT) + { + String color = (String) atts.getAttribute(HTML.Attribute.COLOR); + if (color != null) + styleSheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color); + String face = (String) atts.getAttribute(HTML.Attribute.FACE); + if (face != null) + styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY, + face); + String size = (String) atts.getAttribute(HTML.Attribute.SIZE); + if (size != null) + styleSheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_SIZE, + size); + } + } + + public void end(HTML.Tag tag) + { + popCharacterStyle(); + } + } + + class BaseAction extends TagAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + { + baseTarget = (String) a.getAttribute(HTML.Attribute.TARGET); + } + } + + class HeadAction extends BlockAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + throws NotImplementedException + { + // FIXME: Implement. + super.start(t, a); + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + // We read in all the stylesheets that are embedded or referenced + // inside the header. + if (styles != null) + { + int numStyles = styles.size(); + for (int i = 0; i < numStyles; i++) + { + String style = (String) styles.get(i); + getStyleSheet().addRule(style); + } + } + super.end(t); + } + } + + class LinkAction extends HiddenAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + { + super.start(t, a); + String type = (String) a.getAttribute(HTML.Attribute.TYPE); + if (type == null) + type = "text/css"; + if (type.equals("text/css")) + { + String rel = (String) a.getAttribute(HTML.Attribute.REL); + String media = (String) a.getAttribute(HTML.Attribute.MEDIA); + String title = (String) a.getAttribute(HTML.Attribute.TITLE); + if (media == null) + media = "all"; + else + media = media.toLowerCase(); + if (rel != null) + { + rel = rel.toLowerCase(); + if ((media.indexOf("all") != -1 + || media.indexOf("screen") != -1) + && (rel.equals("stylesheet"))) + { + String href = (String) a.getAttribute(HTML.Attribute.HREF); + URL url = null; + try + { + url = new URL(baseURL, href); + } + catch (MalformedURLException ex) + { + try + { + url = new URL(href); + } + catch (MalformedURLException ex2) + { + url = null; + } + } + if (url != null) + { + try + { + getStyleSheet().importStyleSheet(url); + } + catch (Exception ex) + { + // Don't let exceptions and runtime exceptions + // in CSS parsing disprupt the HTML parsing + // process. But inform the user/developer + // on the console about it. + ex.printStackTrace(); + } + } + } + } + } + } + + } + + class MapAction extends TagAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + throws NotImplementedException + { + // FIXME: Implement. + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + throws NotImplementedException + { + // FIXME: Implement. + } + } + + class MetaAction extends TagAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + throws NotImplementedException + { + // FIXME: Implement. + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + throws NotImplementedException + { + // FIXME: Implement. + } + } + + class StyleAction extends TagAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + { + inStyleTag = true; + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + { + inStyleTag = false; + } + } + + class TitleAction extends TagAction + { + /** + * This method is called when a start tag is seen for one of the types + * of tags associated with this Action. + */ + public void start(HTML.Tag t, MutableAttributeSet a) + throws NotImplementedException + { + // FIXME: Implement. + } + + /** + * Called when an end tag is seen for one of the types of tags associated + * with this Action. + */ + public void end(HTML.Tag t) + throws NotImplementedException + { + // FIXME: Implement. + } + } + + public HTMLReader(int offset) + { + this (offset, 0, 0, null); + } + + public HTMLReader(int offset, int popDepth, int pushDepth, + HTML.Tag insertTag) + { + this.insertTag = insertTag; + this.offset = offset; + this.popDepth = popDepth; + this.pushDepth = pushDepth; + threshold = getTokenThreshold(); + initTags(); + } + + void initTags() + { + tagToAction = new HashMap(72); + CharacterAction characterAction = new CharacterAction(); + HiddenAction hiddenAction = new HiddenAction(); + AreaAction areaAction = new AreaAction(); + BaseAction baseAction = new BaseAction(); + BlockAction blockAction = new BlockAction(); + SpecialAction specialAction = new SpecialAction(); + ParagraphAction paragraphAction = new ParagraphAction(); + HeadAction headAction = new HeadAction(); + FormAction formAction = new FormAction(); + IsindexAction isindexAction = new IsindexAction(); + LinkAction linkAction = new LinkAction(); + MapAction mapAction = new MapAction(); + PreAction preAction = new PreAction(); + MetaAction metaAction = new MetaAction(); + StyleAction styleAction = new StyleAction(); + TitleAction titleAction = new TitleAction(); + + ConvertAction convertAction = new ConvertAction(); + tagToAction.put(HTML.Tag.A, characterAction); + tagToAction.put(HTML.Tag.ADDRESS, characterAction); + tagToAction.put(HTML.Tag.APPLET, hiddenAction); + tagToAction.put(HTML.Tag.AREA, areaAction); + tagToAction.put(HTML.Tag.B, characterAction); + tagToAction.put(HTML.Tag.BASE, baseAction); + tagToAction.put(HTML.Tag.BASEFONT, characterAction); + tagToAction.put(HTML.Tag.BIG, characterAction); + tagToAction.put(HTML.Tag.BLOCKQUOTE, blockAction); + tagToAction.put(HTML.Tag.BODY, blockAction); + tagToAction.put(HTML.Tag.BR, specialAction); + tagToAction.put(HTML.Tag.CAPTION, blockAction); + tagToAction.put(HTML.Tag.CENTER, blockAction); + tagToAction.put(HTML.Tag.CITE, characterAction); + tagToAction.put(HTML.Tag.CODE, characterAction); + tagToAction.put(HTML.Tag.DD, blockAction); + tagToAction.put(HTML.Tag.DFN, characterAction); + tagToAction.put(HTML.Tag.DIR, blockAction); + tagToAction.put(HTML.Tag.DIV, blockAction); + tagToAction.put(HTML.Tag.DL, blockAction); + tagToAction.put(HTML.Tag.DT, paragraphAction); + tagToAction.put(HTML.Tag.EM, characterAction); + tagToAction.put(HTML.Tag.FONT, convertAction); + tagToAction.put(HTML.Tag.FORM, new FormTagAction()); + tagToAction.put(HTML.Tag.FRAME, specialAction); + tagToAction.put(HTML.Tag.FRAMESET, blockAction); + tagToAction.put(HTML.Tag.H1, paragraphAction); + tagToAction.put(HTML.Tag.H2, paragraphAction); + tagToAction.put(HTML.Tag.H3, paragraphAction); + tagToAction.put(HTML.Tag.H4, paragraphAction); + tagToAction.put(HTML.Tag.H5, paragraphAction); + tagToAction.put(HTML.Tag.H6, paragraphAction); + tagToAction.put(HTML.Tag.HEAD, headAction); + tagToAction.put(HTML.Tag.HR, specialAction); + tagToAction.put(HTML.Tag.HTML, blockAction); + tagToAction.put(HTML.Tag.I, characterAction); + tagToAction.put(HTML.Tag.IMG, specialAction); + tagToAction.put(HTML.Tag.INPUT, formAction); + tagToAction.put(HTML.Tag.ISINDEX, isindexAction); + tagToAction.put(HTML.Tag.KBD, characterAction); + tagToAction.put(HTML.Tag.LI, blockAction); + tagToAction.put(HTML.Tag.LINK, linkAction); + tagToAction.put(HTML.Tag.MAP, mapAction); + tagToAction.put(HTML.Tag.MENU, blockAction); + tagToAction.put(HTML.Tag.META, metaAction); + tagToAction.put(HTML.Tag.NOFRAMES, blockAction); + tagToAction.put(HTML.Tag.OBJECT, specialAction); + tagToAction.put(HTML.Tag.OL, blockAction); + tagToAction.put(HTML.Tag.OPTION, formAction); + tagToAction.put(HTML.Tag.P, paragraphAction); + tagToAction.put(HTML.Tag.PARAM, hiddenAction); + tagToAction.put(HTML.Tag.PRE, preAction); + tagToAction.put(HTML.Tag.SAMP, characterAction); + tagToAction.put(HTML.Tag.SCRIPT, hiddenAction); + tagToAction.put(HTML.Tag.SELECT, formAction); + tagToAction.put(HTML.Tag.SMALL, characterAction); + tagToAction.put(HTML.Tag.STRIKE, characterAction); + tagToAction.put(HTML.Tag.S, characterAction); + tagToAction.put(HTML.Tag.STRONG, characterAction); + tagToAction.put(HTML.Tag.STYLE, styleAction); + tagToAction.put(HTML.Tag.SUB, characterAction); + tagToAction.put(HTML.Tag.SUP, characterAction); + tagToAction.put(HTML.Tag.TABLE, blockAction); + tagToAction.put(HTML.Tag.TD, blockAction); + tagToAction.put(HTML.Tag.TEXTAREA, formAction); + tagToAction.put(HTML.Tag.TH, blockAction); + tagToAction.put(HTML.Tag.TITLE, titleAction); + tagToAction.put(HTML.Tag.TR, blockAction); + tagToAction.put(HTML.Tag.TT, characterAction); + tagToAction.put(HTML.Tag.U, characterAction); + tagToAction.put(HTML.Tag.UL, blockAction); + tagToAction.put(HTML.Tag.VAR, characterAction); + } + + /** + * Pushes the current character style onto the stack. + * + */ + protected void pushCharacterStyle() + { + charAttrStack.push(charAttr.copyAttributes()); + } + + /** + * Pops a character style off of the stack and uses it as the + * current character style. + * + */ + protected void popCharacterStyle() + { + if (!charAttrStack.isEmpty()) + charAttr = (MutableAttributeSet) charAttrStack.pop(); + } + + /** + * Registers a given tag with a given Action. All of the well-known tags + * are registered by default, but this method can change their behaviour + * or add support for custom or currently unsupported tags. + * + * @param t the Tag to register + * @param a the Action for the Tag + */ + protected void registerTag(HTML.Tag t, HTMLDocument.HTMLReader.TagAction a) + { + tagToAction.put (t, a); + } + + /** + * This is the last method called on the HTMLReader, allowing any pending + * changes to be flushed to the HTMLDocument. + */ + public void flush() throws BadLocationException + { + flushImpl(); + } + + /** + * Flushes the buffer and handle partial inserts. + * + */ + private void flushImpl() + throws BadLocationException + { + int oldLen = getLength(); + int size = parseBuffer.size(); + ElementSpec[] elems = new ElementSpec[size]; + parseBuffer.copyInto(elems); + if (oldLen == 0) + create(elems); + else + insert(offset, elems); + parseBuffer.removeAllElements(); + offset += getLength() - oldLen; + } + + /** + * This method is called by the parser to indicate a block of + * text was encountered. Should insert the text appropriately. + * + * @param data the text that was inserted + * @param pos the position at which the text was inserted + */ + public void handleText(char[] data, int pos) + { + if (shouldInsert() && data != null && data.length > 0) + { + if (inTextArea) + textAreaContent(data); + else if (inPreTag) + preContent(data); + else if (option != null) + option.setLabel(new String(data)); + else if (inStyleTag) + { + if (styles == null) + styles = new ArrayList(); + styles.add(new String(data)); + } + else + addContent(data, 0, data.length); + + } + } + + /** + * Checks if the HTML tag should be inserted. The tags before insert tag (if + * specified) are not inserted. Also, the tags after the end of the html are + * not inserted. + * + * @return true if the tag should be inserted, false otherwise. + */ + private boolean shouldInsert() + { + return ! endHTMLEncountered + && (insertTagEncountered || insertTag == null); + } + + /** + * This method is called by the parser and should route the call to the + * proper handler for the tag. + * + * @param t the HTML.Tag + * @param a the attribute set + * @param pos the position at which the tag was encountered + */ + public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) + { + if (t == insertTag) + insertTagEncountered = true; + + if (shouldInsert()) + { + TagAction action = (TagAction) tagToAction.get(t); + if (action != null) + action.start(t, a); + } + } + + /** + * This method called by parser to handle a comment block. + * + * @param data the comment + * @param pos the position at which the comment was encountered + */ + public void handleComment(char[] data, int pos) + { + if (shouldInsert()) + { + TagAction action = (TagAction) tagToAction.get(HTML.Tag.COMMENT); + if (action != null) + { + action.start(HTML.Tag.COMMENT, new SimpleAttributeSet()); + action.end(HTML.Tag.COMMENT); + } + } + } + + /** + * This method is called by the parser and should route the call to the + * proper handler for the tag. + * + * @param t the HTML.Tag + * @param pos the position at which the tag was encountered + */ + public void handleEndTag(HTML.Tag t, int pos) + { + if (shouldInsert()) + { + // If this is the tag we need to stop calling the Actions + if (t == HTML.Tag.HTML) + endHTMLEncountered = true; + + TagAction action = (TagAction) tagToAction.get(t); + if (action != null) + action.end(t); + } + } + + /** + * This is a callback from the parser that should be routed to the + * appropriate handler for the tag. + * + * @param t the HTML.Tag that was encountered + * @param a the attribute set + * @param pos the position at which the tag was encountered + */ + public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) + { + if (t == insertTag) + insertTagEncountered = true; + + if (shouldInsert()) + { + TagAction action = (TagAction) tagToAction.get(t); + if (action != null) + { + action.start(t, a); + action.end(t); + } + } + } + + /** + * This is invoked after the stream has been parsed but before it has been + * flushed. + * + * @param eol one of \n, \r, or \r\n, whichever was encountered the most in + * parsing the stream + * @since 1.3 + */ + public void handleEndOfLineString(String eol) + { + // FIXME: Implement. + } + + /** + * Adds the given text to the textarea document. Called only when we are + * within a textarea. + * + * @param data the text to add to the textarea + */ + protected void textAreaContent(char[] data) + { + try + { + int offset = textAreaDocument.getLength(); + String text = new String(data); + textAreaDocument.setInitialText(text); + textAreaDocument.insertString(offset, text, null); + } + catch (BadLocationException ex) + { + // Must not happen as we insert at a model location that we + // got from the document itself. + assert false; + } + } + + /** + * Adds the given text that was encountered in a
 element.
+     * This adds synthesized lines to hold the text runs.
+     *
+     * @param data the text
+     */
+    protected void preContent(char[] data)
+    {
+      int start = 0;
+      for (int i = 0; i < data.length; i++)
+        {
+          if (data[i] == '\n')
+            {
+              addContent(data, start, i - start + 1);
+              blockClose(HTML.Tag.IMPLIED);
+              MutableAttributeSet atts = new SimpleAttributeSet();
+              atts.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
+              blockOpen(HTML.Tag.IMPLIED, atts);
+              start = i + 1;
+            }
+        }
+      if (start < data.length)
+        {
+          // Add remaining last line.
+          addContent(data, start, data.length - start);
+        }
+    }
+
+    /**
+     * Instructs the parse buffer to create a block element with the given
+     * attributes.
+     *
+     * @param t the tag that requires opening a new block
+     * @param attr the attribute set for the new block
+     */
+    protected void blockOpen(HTML.Tag t, MutableAttributeSet attr)
+    {
+      if (inImpliedParagraph())
+        blockClose(HTML.Tag.IMPLIED);
+
+      // Push the new tag on top of the stack.
+      parseStack.push(t);
+
+      DefaultStyledDocument.ElementSpec element;
+
+      AbstractDocument.AttributeContext ctx = getAttributeContext();
+      AttributeSet copy = attr.copyAttributes();
+      copy = ctx.addAttribute(copy, StyleConstants.NameAttribute, t);
+      element = new DefaultStyledDocument.ElementSpec(copy,
+                               DefaultStyledDocument.ElementSpec.StartTagType);
+      parseBuffer.addElement(element);
+    }
+
+    /**
+     * Returns true when we are currently inside a paragraph, either
+     * a real one or an implied, false otherwise.
+     *
+     * @return
+     */
+    private boolean inParagraph()
+    {
+      boolean inParagraph = false;
+      if (! parseStack.isEmpty())
+        {
+          HTML.Tag top = parseStack.peek();
+          inParagraph = top == HTML.Tag.P || top == HTML.Tag.IMPLIED;
+        }
+      return inParagraph;
+    }
+
+    private boolean inImpliedParagraph()
+    {
+      boolean inParagraph = false;
+      if (! parseStack.isEmpty())
+        {
+          HTML.Tag top = parseStack.peek();
+          inParagraph = top == HTML.Tag.IMPLIED;
+        }
+      return inParagraph;
+    }
+
+    /**
+     * Instructs the parse buffer to close the block element associated with
+     * the given HTML.Tag
+     *
+     * @param t the HTML.Tag that is closing its block
+     */
+    protected void blockClose(HTML.Tag t)
+    {
+      DefaultStyledDocument.ElementSpec element;
+
+      if (inImpliedParagraph() && t != HTML.Tag.IMPLIED)
+        blockClose(HTML.Tag.IMPLIED);
+
+      // Pull the token from the stack.
+      if (! parseStack.isEmpty()) // Just to be sure.
+        parseStack.pop();
+
+      // If the previous tag is a start tag then we insert a synthetic
+      // content tag.
+      DefaultStyledDocument.ElementSpec prev;
+      prev = parseBuffer.size() > 0 ? (DefaultStyledDocument.ElementSpec)
+                                parseBuffer.get(parseBuffer.size() - 1) : null;
+      if (prev != null &&
+          prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType)
+        {
+          addContent(new char[]{' '}, 0, 1);
+        }
+
+      element = new DefaultStyledDocument.ElementSpec(null,
+                                DefaultStyledDocument.ElementSpec.EndTagType);
+      parseBuffer.addElement(element);
+    }
+
+    /**
+     * Adds text to the appropriate context using the current character
+     * attribute set.
+     *
+     * @param data the text to add
+     * @param offs the offset at which to add it
+     * @param length the length of the text to add
+     */
+    protected void addContent(char[] data, int offs, int length)
+    {
+      addContent(data, offs, length, true);
+    }
+
+    /**
+     * Adds text to the appropriate context using the current character
+     * attribute set, and possibly generating an IMPLIED Tag if necessary.
+     *
+     * @param data the text to add
+     * @param offs the offset at which to add it
+     * @param length the length of the text to add
+     * @param generateImpliedPIfNecessary whether or not we should generate
+     * an HTML.Tag.IMPLIED tag if necessary
+     */
+    protected void addContent(char[] data, int offs, int length,
+                              boolean generateImpliedPIfNecessary)
+    {
+      if (generateImpliedPIfNecessary && ! inParagraph())
+        {
+          blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
+        }
+
+      AbstractDocument.AttributeContext ctx = getAttributeContext();
+      DefaultStyledDocument.ElementSpec element;
+      AttributeSet attributes = null;
+
+      // Copy the attribute set, don't use the same object because
+      // it may change
+      if (charAttr != null)
+        attributes = charAttr.copyAttributes();
+      else
+        attributes = ctx.getEmptySet();
+      attributes = ctx.addAttribute(attributes, StyleConstants.NameAttribute,
+                                    HTML.Tag.CONTENT);
+      element = new DefaultStyledDocument.ElementSpec(attributes,
+                                DefaultStyledDocument.ElementSpec.ContentType,
+                                data, offs, length);
+
+      // Add the element to the buffer
+      parseBuffer.addElement(element);
+
+      if (parseBuffer.size() > threshold)
+        {
+          if (threshold <= MAX_THRESHOLD)
+            threshold *= GROW_THRESHOLD;
+          try
+            {
+              flushImpl();
+            }
+          catch (BadLocationException ble)
+            {
+              // TODO: what to do here?
+            }
+        }
+    }
+
+    /**
+     * Adds content that is specified in the attribute set.
+     *
+     * @param t the HTML.Tag
+     * @param a the attribute set specifying the special content
+     */
+    protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a)
+    {
+      if (t != HTML.Tag.FRAME && ! inParagraph())
+        {
+          blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
+        }
+
+      a.addAttribute(StyleConstants.NameAttribute, t);
+
+      // The two spaces are required because some special elements like HR
+      // must be broken. At least two characters are needed to break into the
+      // two parts.
+      DefaultStyledDocument.ElementSpec spec =
+        new DefaultStyledDocument.ElementSpec(a.copyAttributes(),
+          DefaultStyledDocument.ElementSpec.ContentType,
+          new char[] {' '}, 0, 1 );
+      parseBuffer.add(spec);
+    }
+
+  }
+
+  /**
+   * Gets the reader for the parser to use when loading the document with HTML.
+   *
+   * @param pos - the starting position
+   * @return - the reader
+   */
+  public HTMLEditorKit.ParserCallback getReader(int pos)
+  {
+    return new HTMLReader(pos);
+  }
+
+  /**
+   * Gets the reader for the parser to use when loading the document with HTML.
+   *
+   * @param pos - the starting position
+   * @param popDepth - the number of EndTagTypes to generate before inserting
+   * @param pushDepth - the number of StartTagTypes with a direction
+   * of JoinNextDirection that should be generated before inserting,
+   * but after the end tags have been generated.
+   * @param insertTag - the first tag to start inserting into document
+   * @return - the reader
+   */
+  public HTMLEditorKit.ParserCallback getReader(int pos,
+                                                int popDepth,
+                                                int pushDepth,
+                                                HTML.Tag insertTag)
+  {
+    return new HTMLReader(pos, popDepth, pushDepth, insertTag);
+  }
+
+  /**
+   * Gets the reader for the parser to use when inserting the HTML fragment into
+   * the document. Checks if the parser is present, sets the parent in the
+   * element stack and removes any actions for BODY (it can be only one body in
+   * a HTMLDocument).
+   *
+   * @param pos - the starting position
+   * @param popDepth - the number of EndTagTypes to generate before inserting
+   * @param pushDepth - the number of StartTagTypes with a direction of
+   *          JoinNextDirection that should be generated before inserting, but
+   *          after the end tags have been generated.
+   * @param insertTag - the first tag to start inserting into document
+   * @param parent the element that will be the parent in the document. HTML
+   *          parsing includes checks for the parent, so it must be available.
+   * @return - the reader
+   * @throws IllegalStateException if the parsert is not set.
+   */
+  public HTMLEditorKit.ParserCallback getInsertingReader(int pos, int popDepth,
+                                                         int pushDepth,
+                                                         HTML.Tag insertTag,
+                                                         final Element parent)
+      throws IllegalStateException
+  {
+    if (parser == null)
+      throw new IllegalStateException("Parser has not been set");
+
+    HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth, insertTag)
+    {
+      /**
+       * Ignore BODY.
+       */
+      public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos)
+      {
+        if (t != HTML.Tag.BODY)
+          super.handleStartTag(t, a, pos);
+      }
+
+      /**
+       * Ignore BODY.
+       */
+      public void handleEndTag(HTML.Tag t, int pos)
+      {
+        if (t != HTML.Tag.BODY)
+          super.handleEndTag(t, pos);
+      }
+    };
+
+    return reader;
+  }
+
+  /**
+   * Gets the child element that contains the attribute with the value or null.
+   * Not thread-safe.
+   *
+   * @param e - the element to begin search at
+   * @param attribute - the desired attribute
+   * @param value - the desired value
+   * @return the element found with the attribute and value specified or null if
+   *         it is not found.
+   */
+  public Element getElement(Element e, Object attribute, Object value)
+  {
+    if (e != null)
+      {
+        if (e.getAttributes().containsAttribute(attribute, value))
+          return e;
+
+        int count = e.getElementCount();
+        for (int j = 0; j < count; j++)
+          {
+            Element child = e.getElement(j);
+            if (child.getAttributes().containsAttribute(attribute, value))
+              return child;
+
+            Element grandChild = getElement(child, attribute, value);
+            if (grandChild != null)
+              return grandChild;
+          }
+      }
+    return null;
+  }
+
+  /**
+   * Returns the element that has the given id Attribute (for instance, <p id
+   * ='my paragraph >'). If it is not found, null is returned. The HTML tag,
+   * having this attribute, is not checked by this method and can be any. The
+   * method is not thread-safe.
+   *
+   * @param attrId - the value of the attribute id to look for
+   * @return the element that has the given id.
+   */
+  public Element getElement(String attrId)
+  {
+    return getElement(getDefaultRootElement(), HTML.Attribute.ID,
+                      attrId);
+  }
+
+  /**
+   * Replaces the children of the given element with the contents of
+   * the string. The document must have an HTMLEditorKit.Parser set.
+   * This will be seen as at least two events, n inserts followed by a remove.
+   *
+   * @param elem - the brance element whose children will be replaced
+   * @param htmlText - the string to be parsed and assigned to element.
+   * @throws BadLocationException
+   * @throws IOException
+   * @throws IllegalArgumentException - if elem is a leaf
+   * @throws IllegalStateException - if an HTMLEditorKit.Parser has not been set
+   */
+  public void setInnerHTML(Element elem, String htmlText)
+    throws BadLocationException, IOException
+  {
+    if (elem.isLeaf())
+      throw new IllegalArgumentException("Element is a leaf");
+
+    int start = elem.getStartOffset();
+    int end = elem.getEndOffset();
+
+    HTMLEditorKit.ParserCallback reader = getInsertingReader(
+      end, 0, 0, HTML.Tag.BODY, elem);
+
+    // TODO charset
+    getParser().parse(new StringReader(htmlText), reader, true);
+
+    // Remove the previous content
+    remove(start, end - start);
+  }
+
+  /**
+   * Replaces the given element in the parent with the string. When replacing a
+   * leaf, this will attempt to make sure there is a newline present if one is
+   * needed. This may result in an additional element being inserted. This will
+   * be seen as at least two events, n inserts followed by a remove. The
+   * HTMLEditorKit.Parser must be set.
+   *
+   * @param elem - the branch element whose parent will be replaced
+   * @param htmlText - the string to be parsed and assigned to elem
+   * @throws BadLocationException
+   * @throws IOException
+   * @throws IllegalStateException - if parser is not set
+   */
+public void setOuterHTML(Element elem, String htmlText)
+      throws BadLocationException, IOException
+  {
+    // Remove the current element:
+    int start = elem.getStartOffset();
+    int end = elem.getEndOffset();
+
+    remove(start, end-start);
+
+    HTMLEditorKit.ParserCallback reader = getInsertingReader(
+      start, 0, 0, HTML.Tag.BODY, elem);
+
+    // TODO charset
+    getParser().parse(new StringReader(htmlText), reader, true);
+  }
+
+  /**
+   * Inserts the string before the start of the given element. The parser must
+   * be set.
+   *
+   * @param elem - the element to be the root for the new text.
+   * @param htmlText - the string to be parsed and assigned to elem
+   * @throws BadLocationException
+   * @throws IOException
+   * @throws IllegalStateException - if parser has not been set
+   */
+  public void insertBeforeStart(Element elem, String htmlText)
+      throws BadLocationException, IOException
+  {
+    HTMLEditorKit.ParserCallback reader = getInsertingReader(
+      elem.getStartOffset(), 0, 0, HTML.Tag.BODY, elem);
+
+    // TODO charset
+    getParser().parse(new StringReader(htmlText), reader, true);
+  }
+
+  /**
+   * Inserts the string at the end of the element. If elem's children are
+   * leaves, and the character at elem.getEndOffset() - 1 is a newline, then it
+   * will be inserted before the newline. The parser must be set.
+   *
+   * @param elem - the element to be the root for the new text
+   * @param htmlText - the text to insert
+   * @throws BadLocationException
+   * @throws IOException
+   * @throws IllegalStateException - if parser is not set
+   */
+  public void insertBeforeEnd(Element elem, String htmlText)
+      throws BadLocationException, IOException
+  {
+    HTMLEditorKit.ParserCallback reader = getInsertingReader(
+      elem.getEndOffset(), 0, 0, HTML.Tag.BODY, elem);
+
+    // TODO charset
+    getParser().parse(new StringReader(htmlText), reader, true);
+
+  }
+
+  /**
+   * Inserts the string after the end of the given element.
+   * The parser must be set.
+   *
+   * @param elem - the element to be the root for the new text
+   * @param htmlText - the text to insert
+   * @throws BadLocationException
+   * @throws IOException
+   * @throws IllegalStateException - if parser is not set
+   */
+  public void insertAfterEnd(Element elem, String htmlText)
+      throws BadLocationException, IOException
+  {
+    HTMLEditorKit.ParserCallback reader = getInsertingReader(
+      elem.getEndOffset(), 0, 0, HTML.Tag.BODY, elem);
+
+    // TODO charset
+    getParser().parse(new StringReader(htmlText), reader, true);
+  }
+
+  /**
+   * Inserts the string at the start of the element.
+   * The parser must be set.
+   *
+   * @param elem - the element to be the root for the new text
+   * @param htmlText - the text to insert
+   * @throws BadLocationException
+   * @throws IOException
+   * @throws IllegalStateException - if parser is not set
+   */
+  public void insertAfterStart(Element elem, String htmlText)
+      throws BadLocationException, IOException
+  {
+    HTMLEditorKit.ParserCallback reader = getInsertingReader(
+      elem.getStartOffset(), 0, 0, HTML.Tag.BODY, elem);
+
+    // TODO charset
+    getParser().parse(new StringReader(htmlText), reader, true);
+  }
+
+  /**
+   * Overridden to tag content with the synthetic HTML.Tag.CONTENT
+   * tag.
+   */
+  protected void insertUpdate(DefaultDocumentEvent evt, AttributeSet att)
+  {
+    if (att == null)
+      {
+        SimpleAttributeSet sas = new SimpleAttributeSet();
+        sas.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
+        att = sas;
+      }
+    super.insertUpdate(evt, att);
+  }
+
+  /**
+   * Returns true when this document is inside a frame,
+   * false otherwise.
+   *
+   * @return true when this document is inside a frame,
+   *         false otherwise
+   */
+  boolean isFrameDocument()
+  {
+    return frameDocument;
+  }
+
+  /**
+   * Set true when this document is inside a frame,
+   * false otherwise.
+   *
+   * @param frameDoc true when this document is inside a frame,
+   *                 false otherwise
+   */
+  void setFrameDocument(boolean frameDoc)
+  {
+    frameDocument = frameDoc;
+  }
+
+  /**
+   * Returns the target that is specified in the base tag, if this is the case.
+   *
+   * @return the target that is specified in the base tag, if this is the case
+   */
+  String getBaseTarget()
+  {
+    return baseTarget;
+  }
+
+  /**
+   * Updates the A tag's pseudo class value in response to a hyperlink
+   * action.
+   *
+   * @param el the corresponding element
+   * @param value the new value
+   */
+  void updateSpecialClass(Element el, HTML.Attribute cl, String value)
+  {
+    try
+    {
+      writeLock();
+      DefaultDocumentEvent ev =
+        new DefaultDocumentEvent(el.getStartOffset(), 1,
+                                 DocumentEvent.EventType.CHANGE);
+      AttributeSet elAtts = el.getAttributes();
+      AttributeSet anchorAtts = (AttributeSet) elAtts.getAttribute(HTML.Tag.A);
+      if (anchorAtts != null)
+        {
+          AttributeSet copy = elAtts.copyAttributes();
+          StyleSheet ss = getStyleSheet();
+          if (value != null)
+            {
+              anchorAtts = ss.addAttribute(anchorAtts, cl, value);
+            }
+          else
+            {
+              anchorAtts = ss.removeAttribute(anchorAtts, cl);
+            }
+          MutableAttributeSet matts = (MutableAttributeSet) elAtts;
+          ev.addEdit(new AttributeUndoableEdit(el, copy, false));
+          matts.removeAttribute(HTML.Tag.A);
+          matts.addAttribute(HTML.Tag.A, anchorAtts);
+          ev.end();
+          fireChangedUpdate(ev);
+          fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
+        }
+    }
+  finally
+    {
+      writeUnlock();
+    }
+  }
+
+}
-- 
cgit v1.2.3