| /* 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 <code>base</code> 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, <code>null</code> | |
| * 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("<frame"); | |
| if (url != null) | |
| { | |
| html.append(" src=\""); | |
| html.append(url.toString()); | |
| html.append("\""); | |
| } | |
| 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 <code>AttributeSet</code> (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<ElementSpec> parseBuffer = new Vector<ElementSpec>(); | |
| /** | |
| * The parse stack. It holds the current element tree path. | |
| */ | |
| private Stack<HTML.Tag> parseStack = new Stack<HTML.Tag>(); | |
| /** | |
| * 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 '</html>' 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 <code>a</code>. | |
| 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 </html> 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 <PRE> 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 <code>true</code> when this document is inside a frame, | |
| * <code>false</code> otherwise. | |
| * | |
| * @return <code>true</code> when this document is inside a frame, | |
| * <code>false</code> otherwise | |
| */ | |
| boolean isFrameDocument() | |
| { | |
| return frameDocument; | |
| } | |
| /** | |
| * Set <code>true</code> when this document is inside a frame, | |
| * <code>false</code> otherwise. | |
| * | |
| * @param frameDoc <code>true</code> when this document is inside a frame, | |
| * <code>false</code> 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(); | |
| } | |
| } | |
| } |