- /*
- * @(#)HTMLDocument.java 1.167 04/05/05
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
- package javax.swing.text.html;
-
- import java.awt.Color;
- import java.awt.Component;
- import java.awt.font.TextAttribute;
- import java.util.*;
- import java.net.URL;
- import java.net.URLEncoder;
- import java.net.MalformedURLException;
- import java.io.*;
- import javax.swing.*;
- import javax.swing.event.*;
- import javax.swing.text.*;
- import javax.swing.undo.*;
- import java.text.Bidi;
-
- /**
- * A document that models HTML. The purpose of this model
- * is to support both browsing and editing. As a result,
- * the structure described by an HTML document is not
- * exactly replicated by default. The element structure that
- * is modeled by default, is built by the class
- * <code>HTMLDocument.HTMLReader</code>, which implements
- * the <code>HTMLEditorKit.ParserCallback</code> protocol
- * that the parser expects. To change the structure one
- * can subclass <code>HTMLReader</code>, and reimplement the method
- * {@link #getReader(int)} to return the new reader
- * implementation. The documentation for <code>HTMLReader</code>
- * should be consulted for the details of
- * the default structure created. The intent is that
- * the document be non-lossy (although reproducing the
- * HTML format may result in a different format).
- * <p>
- * The document models only HTML, and makes no attempt to
- * store view attributes in it. The elements are identified
- * by the <code>StyleContext.NameAttribute</code> attribute,
- * which should always have a value of type <code>HTML.Tag</code>
- * that identifies the kind of element. Some of the elements
- * (such as comments) are synthesized. The <code>HTMLFactory</code>
- * uses this attribute to determine what kind of view to build.
- * <p>
- * This document supports incremental loading. The
- * <code>TokenThreshold</code> property controls how
- * much of the parse is buffered before trying to update
- * the element structure of the document. This property
- * is set by the <code>EditorKit</code> so that subclasses can disable
- * it.
- * <p>
- * The <code>Base</code> property determines the URL
- * against which relative URLs are resolved.
- * By default, this will be the
- * <code>Document.StreamDescriptionProperty</code> if
- * the value of the property is a URL. If a <BASE>
- * tag is encountered, the base will become the URL specified
- * by that tag. Because the base URL is a property, it
- * can of course be set directly.
- * <p>
- * The default content storage mechanism for this document
- * is a gap buffer (<code>GapContent</code>).
- * Alternatives can be supplied by using the constructor
- * that takes a <code>Content</code> implementation.
- *
- * @author Timothy Prinzing
- * @author Scott Violet
- * @author Sunita Mani
- * @version 1.167 05/05/04
- */
- public class HTMLDocument extends DefaultStyledDocument {
- /**
- * Constructs an HTML document using the default buffer size
- * and a default <code>StyleSheet</code>. This is a convenience
- * method for the constructor
- * <code>HTMLDocument(Content, StyleSheet)</code>.
- */
- 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. This is a convenience method for the
- * constructor
- * <code>HTMLDocument(Content, StyleSheet)</code>.
- *
- * @param styles the styles
- */
- 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 container for the content
- * @param styles the styles
- */
- public HTMLDocument(Content c, StyleSheet styles) {
- super(c, styles);
- }
-
- /**
- * Fetches the reader for the parser to use when loading the document
- * with HTML. This is implemented to return an instance of
- * <code>HTMLDocument.HTMLReader</code>.
- * Subclasses can reimplement this
- * method to change how the document gets structured if desired.
- * (For example, to handle custom tags, or structurally represent character
- * style elements.)
- *
- * @param pos the starting position
- * @return the reader used by the parser to load the document
- */
- public HTMLEditorKit.ParserCallback getReader(int pos) {
- Object desc = getProperty(Document.StreamDescriptionProperty);
- if (desc instanceof URL) {
- setBase((URL)desc);
- }
- HTMLReader reader = new HTMLReader(pos);
- return reader;
- }
-
- /**
- * Returns the reader for the parser to use to load the document
- * with HTML. This is implemented to return an instance of
- * <code>HTMLDocument.HTMLReader</code>.
- * Subclasses can reimplement this
- * method to change how the document gets structured if desired.
- * (For example, to handle custom tags, or structurally represent character
- * style elements.)
- * <p>This is a convenience method for
- * <code>getReader(int, int, int, HTML.Tag, TRUE)</code>.
- *
- * @param popDepth the number of <code>ElementSpec.EndTagTypes</code>
- * to generate before inserting
- * @param pushDepth the number of <code>ElementSpec.StartTagTypes</code>
- * with a direction of <code>ElementSpec.JoinNextDirection</code>
- * 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 used by the parser to load the document
- */
- public HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
- int pushDepth,
- HTML.Tag insertTag) {
- return getReader(pos, popDepth, pushDepth, insertTag, true);
- }
-
- /**
- * Fetches the reader for the parser to use to load the document
- * with HTML. This is implemented to return an instance of
- * HTMLDocument.HTMLReader. Subclasses can reimplement this
- * method to change how the document get structured if desired
- * (e.g. to handle custom tags, structurally represent character
- * style elements, etc.).
- *
- * @param popDepth the number of <code>ElementSpec.EndTagTypes</code>
- * to generate before inserting
- * @param pushDepth the number of <code>ElementSpec.StartTagTypes</code>
- * with a direction of <code>ElementSpec.JoinNextDirection</code>
- * 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 insertInsertTag false if all the Elements after insertTag should
- * be inserted; otherwise insertTag will be inserted
- * @return the reader used by the parser to load the document
- */
- HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
- int pushDepth,
- HTML.Tag insertTag,
- boolean insertInsertTag) {
- Object desc = getProperty(Document.StreamDescriptionProperty);
- if (desc instanceof URL) {
- setBase((URL)desc);
- }
- HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth,
- insertTag, insertInsertTag, false,
- true);
- return reader;
- }
-
- /**
- * Returns the location to resolve relative URLs against. By
- * default this will be the document's URL if the document
- * was loaded from a URL. If a base tag is found and
- * can be parsed, it will be used as the base location.
- *
- * @return the base location
- */
- public URL getBase() {
- return base;
- }
-
- /**
- * Sets the location to resolve relative URLs against. By
- * default this will be the document's URL if the document
- * was loaded from a URL. If a base tag is found and
- * can be parsed, it will be used as the base location.
- * <p>This also sets the base of the <code>StyleSheet</code>
- * to be <code>u</code> as well as the base of the document.
- *
- * @param u the desired base URL
- */
- public void setBase(URL u) {
- base = u;
- getStyleSheet().setBase(u);
- }
-
- /**
- * Inserts new elements in bulk. This is how elements get created
- * in the document. The parsing determines what structure is needed
- * and creates the specification as a set of tokens that describe the
- * edit while leaving the document free of a write-lock. This method
- * can then be called in bursts by the reader to acquire a write-lock
- * for a shorter duration (i.e. while the document is actually being
- * altered).
- *
- * @param offset the starting offset
- * @param data the element data
- * @exception BadLocationException if the given position does not
- * represent a valid location in the associated document.
- */
- protected void insert(int offset, ElementSpec[] data) throws BadLocationException {
- super.insert(offset, data);
- }
-
- /**
- * Updates document structure as a result of text insertion. This
- * will happen within a write lock. This implementation simply
- * parses the inserted content for line breaks and builds up a set
- * of instructions for the element buffer.
- *
- * @param chng a description of the document change
- * @param attr the attributes
- */
- protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
- if(attr == null) {
- attr = contentAttributeSet;
- }
-
- // If this is the composed text element, merge the content attribute to it
- else if (attr.isDefined(StyleConstants.ComposedTextAttribute)) {
- ((MutableAttributeSet)attr).addAttributes(contentAttributeSet);
- }
-
- if (attr.isDefined(IMPLIED_CR)) {
- ((MutableAttributeSet)attr).removeAttribute(IMPLIED_CR);
- }
-
- super.insertUpdate(chng, attr);
- }
-
- /**
- * Replaces the contents of the document with the given
- * element specifications. This is called before insert if
- * the loading is done in bursts. This is the only method called
- * if loading the document entirely in one burst.
- *
- * @param data the new contents of the document
- */
- protected void create(ElementSpec[] data) {
- super.create(data);
- }
-
- /**
- * Sets attributes for a paragraph.
- * <p>
- * This method is thread safe, although most Swing methods
- * are not. Please see
- * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
- * and Swing</A> for more information.
- *
- * @param offset the offset into the paragraph (must be at least 0)
- * @param length the number of characters affected (must be at least 0)
- * @param s the attributes
- * @param replace whether to replace existing attributes, or merge them
- */
- public void setParagraphAttributes(int offset, int length, AttributeSet s,
- boolean replace) {
- try {
- writeLock();
- // Make sure we send out a change for the length of the paragraph.
- int end = Math.min(offset + length, getLength());
- Element e = getParagraphElement(offset);
- offset = e.getStartOffset();
- e = getParagraphElement(end);
- length = Math.max(0, e.getEndOffset() - offset);
- DefaultDocumentEvent changes =
- new DefaultDocumentEvent(offset, length,
- DocumentEvent.EventType.CHANGE);
- AttributeSet sCopy = s.copyAttributes();
- int lastEnd = Integer.MAX_VALUE;
- for (int pos = offset; pos <= end; pos = lastEnd) {
- Element paragraph = getParagraphElement(pos);
- if (lastEnd == paragraph.getEndOffset()) {
- lastEnd++;
- }
- else {
- lastEnd = paragraph.getEndOffset();
- }
- MutableAttributeSet attr =
- (MutableAttributeSet) paragraph.getAttributes();
- changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
- if (replace) {
- attr.removeAttributes(attr);
- }
- attr.addAttributes(s);
- }
- changes.end();
- fireChangedUpdate(changes);
- fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
- } finally {
- writeUnlock();
- }
- }
-
- /**
- * Fetches the <code>StyleSheet</code> with the document-specific display
- * rules (CSS) that were specified in the HTML document itself.
- *
- * @return the <code>StyleSheet</code>
- */
- public StyleSheet getStyleSheet() {
- return (StyleSheet) getAttributeContext();
- }
-
- /**
- * Fetches an iterator for the specified HTML tag.
- * This can be used for things like iterating over the
- * set of anchors contained, or iterating over the input
- * elements.
- *
- * @param t the requested <code>HTML.Tag</code>
- * @return the <code>Iterator</code> for the given HTML tag
- * @see javax.swing.text.html.HTML.Tag
- */
- public Iterator getIterator(HTML.Tag t) {
- if (t.isBlock()) {
- // TBD
- return null;
- }
- return new LeafIterator(t, this);
- }
-
- /**
- * Creates a document leaf element that directly represents
- * text (doesn't have any children). This is implemented
- * to return an element of type
- * <code>HTMLDocument.RunElement</code>.
- *
- * @param parent the parent element
- * @param a the attributes for the element
- * @param p0 the beginning of the range (must be at least 0)
- * @param p1 the end of the range (must be at least p0)
- * @return the new element
- */
- protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
- return new RunElement(parent, a, p0, p1);
- }
-
- /**
- * Creates a document branch element, that can contain other elements.
- * This is implemented to return an element of type
- * <code>HTMLDocument.BlockElement</code>.
- *
- * @param parent the parent element
- * @param a the attributes
- * @return the element
- */
- protected Element createBranchElement(Element parent, AttributeSet a) {
- return new BlockElement(parent, a);
- }
-
- /**
- * Creates the root element to be used to represent the
- * default document structure.
- *
- * @return the element base
- */
- protected AbstractElement createDefaultRoot() {
- // grabs a write-lock for this initialization and
- // abandon it during initialization so in normal
- // operation we can detect an illegitimate attempt
- // to mutate attributes.
- writeLock();
- MutableAttributeSet a = new SimpleAttributeSet();
- a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.HTML);
- BlockElement html = new BlockElement(null, a.copyAttributes());
- a.removeAttributes(a);
- a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.BODY);
- BlockElement body = new BlockElement(html, a.copyAttributes());
- a.removeAttributes(a);
- a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.P);
- getStyleSheet().addCSSAttributeFromHTML(a, CSS.Attribute.MARGIN_TOP, "0");
- BlockElement paragraph = new BlockElement(body, a.copyAttributes());
- a.removeAttributes(a);
- a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
- RunElement brk = new RunElement(paragraph, a, 0, 1);
- Element[] buff = new Element[1];
- buff[0] = brk;
- paragraph.replace(0, 0, buff);
- buff[0] = paragraph;
- body.replace(0, 0, buff);
- buff[0] = body;
- html.replace(0, 0, buff);
- writeUnlock();
- return html;
- }
-
- /**
- * Sets the number of tokens to buffer before trying to update
- * the documents element structure.
- *
- * @param n the number of tokens to buffer
- */
- public void setTokenThreshold(int n) {
- putProperty(TokenThreshold, new Integer(n));
- }
-
- /**
- * Gets the number of tokens to buffer before trying to update
- * the documents element structure. The default value is
- * <code>Integer.MAX_VALUE</code>.
- *
- * @return the number of tokens to buffer
- */
- public int getTokenThreshold() {
- Integer i = (Integer) getProperty(TokenThreshold);
- if (i != null) {
- return i.intValue();
- }
- return Integer.MAX_VALUE;
- }
-
- /**
- * Determines how unknown tags are handled by the parser.
- * If set to true, unknown
- * tags are put in the model, otherwise they are dropped.
- *
- * @param preservesTags true if unknown tags should be
- * saved in the model, otherwise tags are dropped
- * @see javax.swing.text.html.HTML.Tag
- */
- public void setPreservesUnknownTags(boolean preservesTags) {
- preservesUnknownTags = preservesTags;
- }
-
- /**
- * Returns the behavior the parser observes when encountering
- * unknown tags.
- *
- * @see javax.swing.text.html.HTML.Tag
- * @return true if unknown tags are to be preserved when parsing
- */
- public boolean getPreservesUnknownTags() {
- return preservesUnknownTags;
- }
-
- /**
- * Processes <code>HyperlinkEvents</code> that
- * are generated by documents in an HTML frame.
- * The <code>HyperlinkEvent</code> type, as the parameter suggests,
- * is <code>HTMLFrameHyperlinkEvent</code>.
- * In addition to the typical information contained in a
- * <code>HyperlinkEvent</code>,
- * this event contains the element that corresponds to the frame in
- * which the click happened (the source element) and the
- * target name. The target name has 4 possible values:
- * <ul>
- * <li> _self
- * <li> _parent
- * <li> _top
- * <li> a named frame
- * </ul>
- *
- * If target is _self, the action is to change the value of the
- * <code>HTML.Attribute.SRC</code> attribute and fires a
- * <code>ChangedUpdate</code> event.
- *<p>
- * If the target is _parent, then it deletes the parent element,
- * which is a <FRAMESET> element, and inserts a new <FRAME>
- * element, and sets its <code>HTML.Attribute.SRC</code> attribute
- * to have a value equal to the destination URL and fire a
- * <code>RemovedUpdate</code> and <code>InsertUpdate</code>.
- *<p>
- * If the target is _top, this method does nothing. In the implementation
- * of the view for a frame, namely the <code>FrameView</code>,
- * the processing of _top is handled. Given that _top implies
- * replacing the entire document, it made sense to handle this outside
- * of the document that it will replace.
- *<p>
- * If the target is a named frame, then the element hierarchy is searched
- * for an element with a name equal to the target, its
- * <code>HTML.Attribute.SRC</code> attribute is updated and a
- * <code>ChangedUpdate</code> event is fired.
- *
- * @param e the event
- */
- public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent e) {
- String frameName = e.getTarget();
- Element element = e.getSourceElement();
- String urlStr = e.getURL().toString();
-
- if (frameName.equals("_self")) {
- /*
- The source and destination elements
- are the same.
- */
- updateFrame(element, urlStr);
- } else if (frameName.equals("_parent")) {
- /*
- The destination is the parent of the frame.
- */
- updateFrameSet(element.getParentElement(), urlStr);
- } else {
- /*
- locate a named frame
- */
- Element targetElement = findFrame(frameName);
- if (targetElement != null) {
- updateFrame(targetElement, urlStr);
- }
- }
- }
-
-
- /**
- * Searches the element hierarchy for an FRAME element
- * that has its name attribute equal to the <code>frameName</code>.
- *
- * @param frameName
- * @return the element whose NAME attribute has a value of
- * <code>frameName</code> returns <code>null</code>
- * if not found
- */
- private Element findFrame(String frameName) {
- ElementIterator it = new ElementIterator(this);
- Element next = null;
-
- while ((next = it.next()) != null) {
- AttributeSet attr = next.getAttributes();
- if (matchNameAttribute(attr, HTML.Tag.FRAME)) {
- String frameTarget = (String)attr.getAttribute(HTML.Attribute.NAME);
- if (frameTarget != null && frameTarget.equals(frameName)) {
- break;
- }
- }
- }
- return next;
- }
-
- /**
- * Returns true if <code>StyleConstants.NameAttribute</code> is
- * equal to the tag that is passed in as a parameter.
- *
- * @param attr the attributes to be matched
- * @param tag the value to be matched
- * @return true if there is a match, false otherwise
- * @see javax.swing.text.html.HTML.Attribute
- */
- static boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
- Object o = attr.getAttribute(StyleConstants.NameAttribute);
- if (o instanceof HTML.Tag) {
- HTML.Tag name = (HTML.Tag) o;
- if (name == tag) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Replaces a frameset branch Element with a frame leaf element.
- *
- * @param element the frameset element to remove
- * @param url the value for the SRC attribute for the
- * new frame that will replace the frameset
- */
- private void updateFrameSet(Element element, String url) {
- try {
- int startOffset = element.getStartOffset();
- int endOffset = Math.min(getLength(), element.getEndOffset());
- String html = "<frame";
- if (url != null) {
- html += " src=\"" + url + "\"";
- }
- html += ">";
- installParserIfNecessary();
- setOuterHTML(element, html);
- } catch (BadLocationException e1) {
- // Should handle this better
- } catch (IOException ioe) {
- // Should handle this better
- }
- }
-
-
- /**
- * Updates the Frame elements <code>HTML.Attribute.SRC attribute</code>
- * and fires a <code>ChangedUpdate</code> event.
- *
- * @param element a FRAME element whose SRC attribute will be updated
- * @param url a string specifying the new value for the SRC attribute
- */
- private void updateFrame(Element element, String url) {
-
- try {
- writeLock();
- DefaultDocumentEvent changes = new DefaultDocumentEvent(element.getStartOffset(),
- 1,
- DocumentEvent.EventType.CHANGE);
- AttributeSet sCopy = element.getAttributes().copyAttributes();
- MutableAttributeSet attr = (MutableAttributeSet) element.getAttributes();
- changes.addEdit(new AttributeUndoableEdit(element, sCopy, false));
- attr.removeAttribute(HTML.Attribute.SRC);
- attr.addAttribute(HTML.Attribute.SRC, url);
- changes.end();
- fireChangedUpdate(changes);
- fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
- } finally {
- writeUnlock();
- }
- }
-
-
- /**
- * Returns true if the document will be viewed in a frame.
- * @return true if document will be viewed in a frame, otherwise false
- */
- boolean isFrameDocument() {
- return frameDocument;
- }
-
- /**
- * Sets a boolean state about whether the document will be
- * viewed in a frame.
- * @param frameDoc true if the document will be viewed in a frame,
- * otherwise false
- */
- void setFrameDocumentState(boolean frameDoc) {
- this.frameDocument = frameDoc;
- }
-
- /**
- * Adds the specified map, this will remove a Map that has been
- * previously registered with the same name.
- *
- * @param map the <code>Map</code> to be registered
- */
- void addMap(Map map) {
- String name = map.getName();
-
- if (name != null) {
- Object maps = getProperty(MAP_PROPERTY);
-
- if (maps == null) {
- maps = new Hashtable(11);
- putProperty(MAP_PROPERTY, maps);
- }
- if (maps instanceof Hashtable) {
- ((Hashtable)maps).put("#" + name, map);
- }
- }
- }
-
- /**
- * Removes a previously registered map.
- * @param map the <code>Map</code> to be removed
- */
- void removeMap(Map map) {
- String name = map.getName();
-
- if (name != null) {
- Object maps = getProperty(MAP_PROPERTY);
-
- if (maps instanceof Hashtable) {
- ((Hashtable)maps).remove("#" + name);
- }
- }
- }
-
- /**
- * Returns the Map associated with the given name.
- * @param the name of the desired <code>Map</code>
- * @return the <code>Map</code> or <code>null</code> if it can't
- * be found, or if <code>name</code> is <code>null</code>
- */
- Map getMap(String name) {
- if (name != null) {
- Object maps = getProperty(MAP_PROPERTY);
-
- if (maps != null && (maps instanceof Hashtable)) {
- return (Map)((Hashtable)maps).get(name);
- }
- }
- return null;
- }
-
- /**
- * Returns an <code>Enumeration</code> of the possible Maps.
- * @return the enumerated list of maps, or <code>null</code>
- * if the maps are not an instance of <code>Hashtable</code>
- */
- Enumeration getMaps() {
- Object maps = getProperty(MAP_PROPERTY);
-
- if (maps instanceof Hashtable) {
- return ((Hashtable)maps).elements();
- }
- return null;
- }
-
- /**
- * Sets the content type language used for style sheets that do not
- * explicitly specify the type. The default is text/css.
- * @param contentType the content type language for the style sheets
- */
- /* public */
- void setDefaultStyleSheetType(String contentType) {
- putProperty(StyleType, contentType);
- }
-
- /**
- * Returns the content type language used for style sheets. The default
- * is text/css.
- * @return the content type language used for the style sheets
- */
- /* public */
- String getDefaultStyleSheetType() {
- String retValue = (String)getProperty(StyleType);
- if (retValue == null) {
- return "text/css";
- }
- return retValue;
- }
-
- /**
- * Sets the parser that is used by the methods that insert html
- * into the existing document, such as <code>setInnerHTML</code>,
- * and <code>setOuterHTML</code>.
- * <p>
- * <code>HTMLEditorKit.createDefaultDocument</code> will set the parser
- * for you. If you create an <code>HTMLDocument</code> by hand,
- * be sure and set the parser accordingly.
- * @param parser the parser to be used for text insertion
- *
- * @since 1.3
- */
- public void setParser(HTMLEditorKit.Parser parser) {
- this.parser = parser;
- putProperty("__PARSER__", null);
- }
-
- /**
- * Returns the parser that is used when inserting HTML into the existing
- * document.
- * @return the parser used for text insertion
- *
- * @since 1.3
- */
- public HTMLEditorKit.Parser getParser() {
- Object p = getProperty("__PARSER__");
-
- if (p instanceof HTMLEditorKit.Parser) {
- return (HTMLEditorKit.Parser)p;
- }
- return parser;
- }
-
- /**
- * Replaces the children of the given element with the contents
- * specified as an HTML string.
- * <p>This will be seen as at least two events, n inserts followed by
- * a remove.
- * <p>For this to work correcty, the document must have an
- * <code>HTMLEditorKit.Parser</code> set. This will be the case
- * if the document was created from an HTMLEditorKit via the
- * <code>createDefaultDocument</code> method.
- *
- * @param elem the branch element whose children will be replaced
- * @param htmlText the string to be parsed and assigned to <code>elem</code>
- * @throws IllegalArgumentException if <code>elem</code> is a leaf
- * @throws IllegalStateException if an <code>HTMLEditorKit.Parser</code>
- * has not been defined
- * @since 1.3
- */
- public void setInnerHTML(Element elem, String htmlText) throws
- BadLocationException, IOException {
- verifyParser();
- if (elem != null && elem.isLeaf()) {
- throw new IllegalArgumentException
- ("Can not set inner HTML of a leaf");
- }
- if (elem != null && htmlText != null) {
- int oldCount = elem.getElementCount();
- int insertPosition = elem.getStartOffset();
- insertHTML(elem, elem.getStartOffset(), htmlText, true);
- if (elem.getElementCount() > oldCount) {
- // Elements were inserted, do the cleanup.
- removeElements(elem, elem.getElementCount() - oldCount,
- oldCount);
- }
- }
- }
-
- /**
- * Replaces the given element in the parent with the contents
- * specified as an HTML string.
- * <p>This will be seen as at least two events, n inserts followed by
- * a remove.
- * <p>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. Consider, if you were to replace a character
- * element that contained a newline with <img> this would create
- * two elements, one for the image, ane one for the newline.
- * <p>If you try to replace the element at length you will most likely
- * end up with two elements, eg setOuterHTML(getCharacterElement
- * (getLength()), "blah") will result in two leaf elements at the end,
- * one representing 'blah', and the other representing the end element.
- * <p>For this to work correcty, the document must have an
- * HTMLEditorKit.Parser set. This will be the case if the document
- * was created from an HTMLEditorKit via the
- * <code>createDefaultDocument</code> method.
- *
- * @param elem the branch element whose children will be replaced
- * @param htmlText the string to be parsed and assigned to <code>elem</code>
- * @throws IllegalStateException if an HTMLEditorKit.Parser has not
- * been set
- * @since 1.3
- */
- public void setOuterHTML(Element elem, String htmlText) throws
- BadLocationException, IOException {
- verifyParser();
- if (elem != null && elem.getParentElement() != null &&
- htmlText != null) {
- int start = elem.getStartOffset();
- int end = elem.getEndOffset();
- int startLength = getLength();
- // We don't want a newline if elem is a leaf, and doesn't contain
- // a newline.
- boolean wantsNewline = !elem.isLeaf();
- if (!wantsNewline && (end > startLength ||
- getText(end - 1, 1).charAt(0) == NEWLINE[0])){
- wantsNewline = true;
- }
- Element parent = elem.getParentElement();
- int oldCount = parent.getElementCount();
- insertHTML(parent, start, htmlText, wantsNewline);
- // Remove old.
- int newLength = getLength();
- if (oldCount != parent.getElementCount()) {
- int removeIndex = parent.getElementIndex(start + newLength -
- startLength);
- removeElements(parent, removeIndex, 1);
- }
- }
- }
-
- /**
- * Inserts the HTML specified as a string at the start
- * of the element.
- * <p>For this to work correcty, the document must have an
- * <code>HTMLEditorKit.Parser</code> set. This will be the case
- * if the document was created from an HTMLEditorKit via the
- * <code>createDefaultDocument</code> method.
- *
- * @param elem the branch element to be the root for the new text
- * @param htmlText the string to be parsed and assigned to <code>elem</code>
- * @throws IllegalStateException if an HTMLEditorKit.Parser has not
- * been set on the document
- * @since 1.3
- */
- public void insertAfterStart(Element elem, String htmlText) throws
- BadLocationException, IOException {
- verifyParser();
- if (elem != null && elem.isLeaf()) {
- throw new IllegalArgumentException
- ("Can not insert HTML after start of a leaf");
- }
- insertHTML(elem, elem.getStartOffset(), htmlText, false);
- }
-
- /**
- * Inserts the HTML specified as a string at the end of
- * the element.
- * <p> If <code>elem</code>'s children are leaves, and the
- * character at a <code>elem.getEndOffset() - 1</code> is a newline,
- * this will insert before the newline so that there isn't text after
- * the newline.
- * <p>For this to work correcty, the document must have an
- * <code>HTMLEditorKit.Parser</code> set. This will be the case
- * if the document was created from an HTMLEditorKit via the
- * <code>createDefaultDocument</code> method.
- *
- * @param elem the element to be the root for the new text
- * @param htmlText the string to be parsed and assigned to <code>elem</code>
- * @throws IllegalStateException if an HTMLEditorKit.Parser has not
- * been set on the document
- * @since 1.3
- */
- public void insertBeforeEnd(Element elem, String htmlText) throws
- BadLocationException, IOException {
- verifyParser();
- if (elem != null && elem.isLeaf()) {
- throw new IllegalArgumentException
- ("Can not set inner HTML before end of leaf");
- }
- int offset = elem.getEndOffset();
- if (elem.getElement(elem.getElementIndex(offset - 1)).isLeaf() &&
- getText(offset - 1, 1).charAt(0) == NEWLINE[0]) {
- offset--;
- }
- insertHTML(elem, offset, htmlText, false);
- }
-
- /**
- * Inserts the HTML specified as a string before the start of
- * the given element.
- * <p>For this to work correcty, the document must have an
- * <code>HTMLEditorKit.Parser</code> set. This will be the case
- * if the document was created from an HTMLEditorKit via the
- * <code>createDefaultDocument</code> method.
- *
- * @param elem the element to be the root for the new text
- * @param htmlText the string to be parsed and assigned to <code>elem</code>
- * @throws IllegalStateException if an HTMLEditorKit.Parser has not
- * been set on the document
- * @since 1.3
- */
- public void insertBeforeStart(Element elem, String htmlText) throws
- BadLocationException, IOException {
- verifyParser();
- if (elem != null) {
- Element parent = elem.getParentElement();
-
- if (parent != null) {
- insertHTML(parent, elem.getStartOffset(), htmlText, false);
- }
- }
- }
-
- /**
- * Inserts the HTML specified as a string after the
- * the end of the given element.
- * <p>For this to work correcty, the document must have an
- * <code>HTMLEditorKit.Parser</code> set. This will be the case
- * if the document was created from an HTMLEditorKit via the
- * <code>createDefaultDocument</code> method.
- *
- * @param elem the element to be the root for the new text
- * @param htmlText the string to be parsed and assigned to <code>elem</code>
- * @throws IllegalStateException if an HTMLEditorKit.Parser has not
- * been set on the document
- * @since 1.3
- */
- public void insertAfterEnd(Element elem, String htmlText) throws
- BadLocationException, IOException {
- verifyParser();
- if (elem != null) {
- Element parent = elem.getParentElement();
-
- if (parent != null) {
- int offset = elem.getEndOffset();
- if (offset > getLength()) {
- offset--;
- }
- else if (elem.isLeaf() && getText(offset - 1, 1).
- charAt(0) == NEWLINE[0]) {
- offset--;
- }
- insertHTML(parent, offset, htmlText, false);
- }
- }
- }
-
- /**
- * Returns the element that has the given id <code>Attribute</code>.
- * If the element can't be found, <code>null</code> is returned.
- * Note that this method works on an <code>Attribute</code>,
- * <i>not</i> a character tag. In the following HTML snippet:
- * <code><a id="HelloThere"></code> the attribute is
- * 'id' and the character tag is 'a'.
- * This is a convenience method for
- * <code>getElement(RootElement, HTML.Attribute.id, id)</code>.
- * This is not thread-safe.
- *
- * @param id the string representing the desired <code>Attribute</code>
- * @return the element with the specified <code>Attribute</code>
- * or <code>null</code> if it can't be found,
- * or <code>null</code> if <code>id</code> is <code>null</code>
- * @see javax.swing.text.html.HTML.Attribute
- * @since 1.3
- */
- public Element getElement(String id) {
- if (id == null) {
- return null;
- }
- return getElement(getDefaultRootElement(), HTML.Attribute.ID, id,
- true);
- }
-
- /**
- * Returns the child element of <code>e</code> that contains the
- * attribute, <code>attribute</code> with value <code>value</code>, or
- * <code>null</code> if one isn't found. This is not thread-safe.
- *
- * @param e the root element where the search begins
- * @param attribute the desired <code>Attribute</code>
- * @param value the values for the specified <code>Attribute</code>
- * @return the element with the specified <code>Attribute</code>
- * and the specified <code>value</code>, or <code>null</code>
- * if it can't be found
- * @see javax.swing.text.html.HTML.Attribute
- * @since 1.3
- */
- public Element getElement(Element e, Object attribute, Object value) {
- return getElement(e, attribute, value, true);
- }
-
- /**
- * Returns the child element of <code>e</code> that contains the
- * attribute, <code>attribute</code> with value <code>value</code>, or
- * <code>null</code> if one isn't found. This is not thread-safe.
- * <p>
- * If <code>searchLeafAttributes</code> is true, and <code>e</code> is
- * a leaf, any attributes that are instances of <code>HTML.Tag</code>
- * with a value that is an <code>AttributeSet</code> will also be checked.
- *
- * @param e the root element where the search begins
- * @param attribute the desired <code>Attribute</code>
- * @param value the values for the specified <code>Attribute</code>
- * @return the element with the specified <code>Attribute</code>
- * and the specified <code>value</code>, or <code>null</code>
- * if it can't be found
- * @see javax.swing.text.html.HTML.Attribute
- */
- private Element getElement(Element e, Object attribute, Object value,
- boolean searchLeafAttributes) {
- AttributeSet attr = e.getAttributes();
-
- if (attr != null && attr.isDefined(attribute)) {
- if (value.equals(attr.getAttribute(attribute))) {
- return e;
- }
- }
- if (!e.isLeaf()) {
- for (int counter = 0, maxCounter = e.getElementCount();
- counter < maxCounter; counter++) {
- Element retValue = getElement(e.getElement(counter), attribute,
- value, searchLeafAttributes);
-
- if (retValue != null) {
- return retValue;
- }
- }
- }
- else if (searchLeafAttributes && attr != null) {
- // For some leaf elements we store the actual attributes inside
- // the AttributeSet of the Element (such as anchors).
- Enumeration names = attr.getAttributeNames();
- if (names != null) {
- while (names.hasMoreElements()) {
- Object name = names.nextElement();
- if ((name instanceof HTML.Tag) &&
- (attr.getAttribute(name) instanceof AttributeSet)) {
-
- AttributeSet check = (AttributeSet)attr.
- getAttribute(name);
- if (check.isDefined(attribute) &&
- value.equals(check.getAttribute(attribute))) {
- return e;
- }
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Verifies the document has an <code>HTMLEditorKit.Parser</code> set.
- * If <code>getParser</code> returns <code>null</code>, this will throw an
- * IllegalStateException.
- *
- * @throws IllegalStateException if the document does not have a Parser
- */
- private void verifyParser() {
- if (getParser() == null) {
- throw new IllegalStateException("No HTMLEditorKit.Parser");
- }
- }
-
- /**
- * Installs a default Parser if one has not been installed yet.
- */
- private void installParserIfNecessary() {
- if (getParser() == null) {
- setParser(new HTMLEditorKit().getParser());
- }
- }
-
- /**
- * Inserts a string of HTML into the document at the given position.
- * <code>parent</code> is used to identify the location to insert the
- * <code>html</code>. If <code>parent</code> is a leaf this can have
- * unexpected results.
- */
- private void insertHTML(Element parent, int offset, String html,
- boolean wantsTrailingNewline)
- throws BadLocationException, IOException {
- if (parent != null && html != null) {
- HTMLEditorKit.Parser parser = getParser();
- if (parser != null) {
- int lastOffset = Math.max(0, offset - 1);
- Element charElement = getCharacterElement(lastOffset);
- Element commonParent = parent;
- int pop = 0;
- int push = 0;
-
- if (parent.getStartOffset() > lastOffset) {
- while (commonParent != null &&
- commonParent.getStartOffset() > lastOffset) {
- commonParent = commonParent.getParentElement();
- push++;
- }
- if (commonParent == null) {
- throw new BadLocationException("No common parent",
- offset);
- }
- }
- while (charElement != null && charElement != commonParent) {
- pop++;
- charElement = charElement.getParentElement();
- }
- if (charElement != null) {
- // Found it, do the insert.
- HTMLReader reader = new HTMLReader(offset, pop - 1, push,
- null, false, true,
- wantsTrailingNewline);
-
- parser.parse(new StringReader(html), reader, true);
- reader.flush();
- }
- }
- }
- }
-
- /**
- * Removes child Elements of the passed in Element <code>e</code>. This
- * will do the necessary cleanup to ensure the element representing the
- * end character is correctly created.
- * <p>This is not a general purpose method, it assumes that <code>e</code>
- * will still have at least one child after the remove, and it assumes
- * the character at <code>e.getStartOffset() - 1</code> is a newline and
- * is of length 1.
- */
- private void removeElements(Element e, int index, int count) throws BadLocationException {
- writeLock();
- try {
- int start = e.getElement(index).getStartOffset();
- int end = e.getElement(index + count - 1).getEndOffset();
- if (end > getLength()) {
- removeElementsAtEnd(e, index, count, start, end);
- }
- else {
- removeElements(e, index, count, start, end);
- }
- } finally {
- writeUnlock();
- }
- }
-
- /**
- * Called to remove child elements of <code>e</code> when one of the
- * elements to remove is representing the end character.
- * <p>Since the Content will not allow a removal to the end character
- * this will do a remove from <code>start - 1</code> to <code>end</code>.
- * The end Element(s) will be removed, and the element representing
- * <code>start - 1</code> to <code>start</code> will be recreated. This
- * Element has to be recreated as after the content removal its offsets
- * become <code>start - 1</code> to <code>start - 1</code>.
- */
- private void removeElementsAtEnd(Element e, int index, int count,
- int start, int end) throws BadLocationException {
- // index must be > 0 otherwise no insert would have happened.
- boolean isLeaf = (e.getElement(index - 1).isLeaf());
- DefaultDocumentEvent dde = new DefaultDocumentEvent(
- start - 1, end - start + 1, DocumentEvent.
- EventType.REMOVE);
-
- if (isLeaf) {
- Element endE = getCharacterElement(getLength());
- // e.getElement(index - 1) should represent the newline.
- index--;
- if (endE.getParentElement() != e) {
- // The hiearchies don't match, we'll have to manually
- // recreate the leaf at e.getElement(index - 1)
- replace(dde, e, index, ++count, start, end, true, true);
- }
- else {
- // The hierarchies for the end Element and
- // e.getElement(index - 1), match, we can safely remove
- // the Elements and the end content will be aligned
- // appropriately.
- replace(dde, e, index, count, start, end, true, false);
- }
- }
- else {
- // Not a leaf, descend until we find the leaf representing
- // start - 1 and remove it.
- Element newLineE = e.getElement(index - 1);
- while (!newLineE.isLeaf()) {
- newLineE = newLineE.getElement(newLineE.getElementCount() - 1);
- }
- newLineE = newLineE.getParentElement();
- replace(dde, e, index, count, start, end, false, false);
- replace(dde, newLineE, newLineE.getElementCount() - 1, 1, start,
- end, true, true);
- }
- postRemoveUpdate(dde);
- dde.end();
- fireRemoveUpdate(dde);
- fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
- }
-
- /**
- * This is used by <code>removeElementsAtEnd</code>, it removes
- * <code>count</code> elements starting at <code>start</code> from
- * <code>e</code>. If <code>remove</code> is true text of length
- * <code>start - 1</code> to <code>end - 1</code> is removed. If
- * <code>create</code> is true a new leaf is created of length 1.
- */
- private void replace(DefaultDocumentEvent dde, Element e, int index,
- int count, int start, int end, boolean remove,
- boolean create) throws BadLocationException {
- Element[] added;
- AttributeSet attrs = e.getElement(index).getAttributes();
- Element[] removed = new Element[count];
-
- for (int counter = 0; counter < count; counter++) {
- removed[counter] = e.getElement(counter + index);
- }
- if (remove) {
- UndoableEdit u = getContent().remove(start - 1, end - start);
- if (u != null) {
- dde.addEdit(u);
- }
- }
- if (create) {
- added = new Element[1];
- added[0] = createLeafElement(e, attrs, start - 1, start);
- }
- else {
- added = new Element[0];
- }
- dde.addEdit(new ElementEdit(e, index, removed, added));
- ((AbstractDocument.BranchElement)e).replace(
- index, removed.length, added);
- }
-
- /**
- * Called to remove child Elements when the end is not touched.
- */
- private void removeElements(Element e, int index, int count,
- int start, int end) throws BadLocationException {
- Element[] removed = new Element[count];
- Element[] added = new Element[0];
- for (int counter = 0; counter < count; counter++) {
- removed[counter] = e.getElement(counter + index);
- }
- DefaultDocumentEvent dde = new DefaultDocumentEvent
- (start, end - start, DocumentEvent.EventType.REMOVE);
- ((AbstractDocument.BranchElement)e).replace(index, removed.length,
- added);
- dde.addEdit(new ElementEdit(e, index, removed, added));
- UndoableEdit u = getContent().remove(start, end - start);
- if (u != null) {
- dde.addEdit(u);
- }
- postRemoveUpdate(dde);
- dde.end();
- fireRemoveUpdate(dde);
- if (u != null) {
- fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
- }
- }
-
-
- // These two are provided for inner class access. The are named different
- // than the super class as the super class implementations are final.
- void obtainLock() {
- writeLock();
- }
-
- void releaseLock() {
- writeUnlock();
- }
-
- //
- // Provided for inner class access.
- //
-
- /**
- * Notifies all listeners that have registered interest for
- * notification on this event type. The event instance
- * is lazily created using the parameters passed into
- * the fire method.
- *
- * @param e the event
- * @see EventListenerList
- */
- protected void fireChangedUpdate(DocumentEvent e) {
- super.fireChangedUpdate(e);
- }
-
- /**
- * Notifies all listeners that have registered interest for
- * notification on this event type. The event instance
- * is lazily created using the parameters passed into
- * the fire method.
- *
- * @param e the event
- * @see EventListenerList
- */
- protected void fireUndoableEditUpdate(UndoableEditEvent e) {
- super.fireUndoableEditUpdate(e);
- }
-
- boolean hasBaseTag() {
- return hasBaseTag;
- }
-
- String getBaseTarget() {
- return baseTarget;
- }
-
- /*
- * state defines whether the document is a frame document
- * or not.
- */
- private boolean frameDocument = false;
- private boolean preservesUnknownTags = true;
-
- /*
- * Used to store button groups for radio buttons in
- * a form.
- */
- private HashMap radioButtonGroupsMap;
-
- /**
- * Document property for the number of tokens to buffer
- * before building an element subtree to represent them.
- */
- static final String TokenThreshold = "token threshold";
-
- private static final int MaxThreshold = 10000;
-
- private static final int StepThreshold = 5;
-
-
- /**
- * Document property key value. The value for the key will be a Vector
- * of Strings that are comments not found in the body.
- */
- public static final String AdditionalComments = "AdditionalComments";
-
- /**
- * Document property key value. The value for the key will be a
- * String indicating the default type of stylesheet links.
- */
- /* public */ static final String StyleType = "StyleType";
-
- /**
- * The location to resolve relative URLs against. By
- * default this will be the document's URL if the document
- * was loaded from a URL. If a base tag is found and
- * can be parsed, it will be used as the base location.
- */
- URL base;
-
- /**
- * does the document have base tag
- */
- boolean hasBaseTag = false;
-
- /**
- * BASE tag's TARGET attribute value
- */
- private String baseTarget = null;
-
- /**
- * The parser that is used when inserting html into the existing
- * document.
- */
- private HTMLEditorKit.Parser parser;
-
- /**
- * Used for inserts when a null AttributeSet is supplied.
- */
- private static AttributeSet contentAttributeSet;
-
- /**
- * Property Maps are registered under, will be a Hashtable.
- */
- static String MAP_PROPERTY = "__MAP__";
-
- private static char[] NEWLINE;
- private static final String IMPLIED_CR = "CR";
-
- /**
- * I18N property key.
- *
- * @see AbstractDocument.I18NProperty
- */
- private static final String I18NProperty = "i18n";
-
- private static final boolean isComplex(char ch) {
- return (ch >= '\u0900' && ch <= '\u0D7F') || // Indic
- (ch >= '\u0E00' && ch <= '\u0E7F'); // Thai
- }
-
- private static final boolean isComplex(char[] text, int start, int limit) {
- for (int i = start; i < limit; ++i) {
- if (isComplex(text[i])) {
- return true;
- }
- }
- return false;
- }
-
- static {
- contentAttributeSet = new SimpleAttributeSet();
- ((MutableAttributeSet)contentAttributeSet).
- addAttribute(StyleConstants.NameAttribute,
- HTML.Tag.CONTENT);
- NEWLINE = new char[1];
- NEWLINE[0] = '\n';
- }
-
-
- /**
- * An iterator to iterate over a particular type of
- * tag. The iterator is not thread safe. If reliable
- * access to the document is not already ensured by
- * the context under which the iterator is being used,
- * its use should be performed under the protection of
- * Document.render.
- */
- public static abstract class Iterator {
-
- /**
- * Return the attributes for this tag.
- * @return the <code>AttributeSet</code> for this tag, or
- * <code>null</code> if none can be found
- */
- public abstract AttributeSet getAttributes();
-
- /**
- * Returns the start of the range for which the current occurrence of
- * the tag is defined and has the same attributes.
- *
- * @return the start of the range, or -1 if it can't be found
- */
- public abstract int getStartOffset();
-
- /**
- * Returns the end of the range for which the current occurrence of
- * the tag is defined and has the same attributes.
- *
- * @return the end of the range
- */
- public abstract int getEndOffset();
-
- /**
- * Move the iterator forward to the next occurrence
- * of the tag it represents.
- */
- public abstract void next();
-
- /**
- * Indicates if the iterator is currently
- * representing an occurrence of a tag. If
- * false there are no more tags for this iterator.
- * @return true if the iterator is currently representing an
- * occurrence of a tag, otherwise returns false
- */
- public abstract boolean isValid();
-
- /**
- * Type of tag this iterator represents.
- */
- public abstract HTML.Tag getTag();
- }
-
- /**
- * An iterator to iterate over a particular type of tag.
- */
- static class LeafIterator extends Iterator {
-
- LeafIterator(HTML.Tag t, Document doc) {
- tag = t;
- pos = new ElementIterator(doc);
- endOffset = 0;
- next();
- }
-
- /**
- * Returns the attributes for this tag.
- * @return the <code>AttributeSet</code> for this tag,
- * or <code>null</code> if none can be found
- */
- public AttributeSet getAttributes() {
- Element elem = pos.current();
- if (elem != null) {
- AttributeSet a = (AttributeSet)
- elem.getAttributes().getAttribute(tag);
- return a;
- }
- return null;
- }
-
- /**
- * Returns the start of the range for which the current occurrence of
- * the tag is defined and has the same attributes.
- *
- * @return the start of the range, or -1 if it can't be found
- */
- public int getStartOffset() {
- Element elem = pos.current();
- if (elem != null) {
- return elem.getStartOffset();
- }
- return -1;
- }
-
- /**
- * Returns the end of the range for which the current occurrence of
- * the tag is defined and has the same attributes.
- *
- * @return the end of the range
- */
- public int getEndOffset() {
- return endOffset;
- }
-
- /**
- * Moves the iterator forward to the next occurrence
- * of the tag it represents.
- */
- public void next() {
- for (nextLeaf(pos); isValid(); nextLeaf(pos)) {
- Element elem = pos.current();
- if (elem.getStartOffset() >= endOffset) {
- AttributeSet a = pos.current().getAttributes();
-
- if (a.isDefined(tag) ||
- a.getAttribute(StyleConstants.NameAttribute) == tag) {
-
- // we found the next one
- setEndOffset();
- break;
- }
- }
- }
- }
-
- /**
- * Returns the type of tag this iterator represents.
- *
- * @return the <code>HTML.Tag</code> that this iterator represents.
- * @see javax.swing.text.html.HTML.Tag
- */
- public HTML.Tag getTag() {
- return tag;
- }
-
- /**
- * Returns true if the current position is not <code>null</code>.
- * @return true if current position is not <code>null</code>,
- * otherwise returns false
- */
- public boolean isValid() {
- return (pos.current() != null);
- }
-
- /**
- * Moves the given iterator to the next leaf element.
- * @param iter the iterator to be scanned
- */
- void nextLeaf(ElementIterator iter) {
- for (iter.next(); iter.current() != null; iter.next()) {
- Element e = iter.current();
- if (e.isLeaf()) {
- break;
- }
- }
- }
-
- /**
- * Marches a cloned iterator forward to locate the end
- * of the run. This sets the value of <code>endOffset</code>.
- */
- void setEndOffset() {
- AttributeSet a0 = getAttributes();
- endOffset = pos.current().getEndOffset();
- ElementIterator fwd = (ElementIterator) pos.clone();
- for (nextLeaf(fwd); fwd.current() != null; nextLeaf(fwd)) {
- Element e = fwd.current();
- AttributeSet a1 = (AttributeSet) e.getAttributes().getAttribute(tag);
- if ((a1 == null) || (! a1.equals(a0))) {
- break;
- }
- endOffset = e.getEndOffset();
- }
- }
-
- private int endOffset;
- private HTML.Tag tag;
- private ElementIterator pos;
-
- }
-
- /**
- * An HTML reader to load an HTML document with an HTML
- * element structure. This is a set of callbacks from
- * the parser, implemented to create a set of elements
- * tagged with attributes. The parse builds up tokens
- * (ElementSpec) that describe the element subtree desired,
- * and burst it into the document under the protection of
- * a write lock using the insert method on the document
- * outer class.
- * <p>
- * The reader can be configured by registering actions
- * (of type <code>HTMLDocument.HTMLReader.TagAction</code>)
- * that describe how to handle the action. The idea behind
- * the actions provided is that the most natural text editing
- * operations can be provided if the element structure boils
- * down to paragraphs with runs of some kind of style
- * in them. Some things are more naturally specified
- * structurally, so arbitrary structure should be allowed
- * above the paragraphs, but will need to be edited with structural
- * actions. The implication of this is that some of the
- * HTML elements specified in the stream being parsed will
- * be collapsed into attributes, and in some cases paragraphs
- * will be synthesized. When HTML elements have been
- * converted to attributes, the attribute key will be of
- * type HTML.Tag, and the value will be of type AttributeSet
- * so that no information is lost. This enables many of the
- * existing actions to work so that the user can type input,
- * hit the return key, backspace, delete, etc and have a
- * reasonable result. Selections can be created, and attributes
- * applied or removed, etc. With this in mind, the work done
- * by the reader can be categorized into the following kinds
- * of tasks:
- * <dl>
- * <dt>Block
- * <dd>Build the structure like it's specified in the stream.
- * This produces elements that contain other elements.
- * <dt>Paragraph
- * <dd>Like block except that it's expected that the element
- * will be used with a paragraph view so a paragraph element
- * won't need to be synthesized.
- * <dt>Character
- * <dd>Contribute the element as an attribute that will start
- * and stop at arbitrary text locations. This will ultimately
- * be mixed into a run of text, with all of the currently
- * flattened HTML character elements.
- * <dt>Special
- * <dd>Produce an embedded graphical element.
- * <dt>Form
- * <dd>Produce an element that is like the embedded graphical
- * element, except that it also has a component model associated
- * with it.
- * <dt>Hidden
- * <dd>Create an element that is hidden from view when the
- * document is being viewed read-only, and visible when the
- * document is being edited. This is useful to keep the
- * model from losing information, and used to store things
- * like comments and unrecognized tags.
- *
- * </dl>
- * <p>
- * Currently, <APPLET>, <PARAM>, <MAP>, <AREA>, <LINK>,
- * <SCRIPT> and <STYLE> are unsupported.
- *
- * <p>
- * The assignment of the actions described is shown in the
- * following table for the tags defined in <code>HTML.Tag</code>.<P>
- * <table border=1 summary="HTML tags and assigned actions">
- * <tr><th>Tag</th><th>Action</th></tr>
- * <tr><td><code>HTML.Tag.A</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.ADDRESS</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.APPLET</code> <td>HiddenAction
- * <tr><td><code>HTML.Tag.AREA</code> <td>AreaAction
- * <tr><td><code>HTML.Tag.B</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.BASE</code> <td>BaseAction
- * <tr><td><code>HTML.Tag.BASEFONT</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.BIG</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.BLOCKQUOTE</code><td>BlockAction
- * <tr><td><code>HTML.Tag.BODY</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.BR</code> <td>SpecialAction
- * <tr><td><code>HTML.Tag.CAPTION</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.CENTER</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.CITE</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.CODE</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.DD</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.DFN</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.DIR</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.DIV</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.DL</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.DT</code> <td>ParagraphAction
- * <tr><td><code>HTML.Tag.EM</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.FONT</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.FORM</code> <td>As of 1.4 a BlockAction
- * <tr><td><code>HTML.Tag.FRAME</code> <td>SpecialAction
- * <tr><td><code>HTML.Tag.FRAMESET</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.H1</code> <td>ParagraphAction
- * <tr><td><code>HTML.Tag.H2</code> <td>ParagraphAction
- * <tr><td><code>HTML.Tag.H3</code> <td>ParagraphAction
- * <tr><td><code>HTML.Tag.H4</code> <td>ParagraphAction
- * <tr><td><code>HTML.Tag.H5</code> <td>ParagraphAction
- * <tr><td><code>HTML.Tag.H6</code> <td>ParagraphAction
- * <tr><td><code>HTML.Tag.HEAD</code> <td>HeadAction
- * <tr><td><code>HTML.Tag.HR</code> <td>SpecialAction
- * <tr><td><code>HTML.Tag.HTML</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.I</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.IMG</code> <td>SpecialAction
- * <tr><td><code>HTML.Tag.INPUT</code> <td>FormAction
- * <tr><td><code>HTML.Tag.ISINDEX</code> <td>IsndexAction
- * <tr><td><code>HTML.Tag.KBD</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.LI</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.LINK</code> <td>LinkAction
- * <tr><td><code>HTML.Tag.MAP</code> <td>MapAction
- * <tr><td><code>HTML.Tag.MENU</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.META</code> <td>MetaAction
- * <tr><td><code>HTML.Tag.NOFRAMES</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.OBJECT</code> <td>SpecialAction
- * <tr><td><code>HTML.Tag.OL</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.OPTION</code> <td>FormAction
- * <tr><td><code>HTML.Tag.P</code> <td>ParagraphAction
- * <tr><td><code>HTML.Tag.PARAM</code> <td>HiddenAction
- * <tr><td><code>HTML.Tag.PRE</code> <td>PreAction
- * <tr><td><code>HTML.Tag.SAMP</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.SCRIPT</code> <td>HiddenAction
- * <tr><td><code>HTML.Tag.SELECT</code> <td>FormAction
- * <tr><td><code>HTML.Tag.SMALL</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.STRIKE</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.S</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.STRONG</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.STYLE</code> <td>StyleAction
- * <tr><td><code>HTML.Tag.SUB</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.SUP</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.TABLE</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.TD</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.TEXTAREA</code> <td>FormAction
- * <tr><td><code>HTML.Tag.TH</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.TITLE</code> <td>TitleAction
- * <tr><td><code>HTML.Tag.TR</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.TT</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.U</code> <td>CharacterAction
- * <tr><td><code>HTML.Tag.UL</code> <td>BlockAction
- * <tr><td><code>HTML.Tag.VAR</code> <td>CharacterAction
- * </table>
- * <p>
- * Once </html> is encountered, the Actions are no longer notified.
- */
- public class HTMLReader extends HTMLEditorKit.ParserCallback {
-
- public HTMLReader(int offset) {
- this(offset, 0, 0, null);
- }
-
- public HTMLReader(int offset, int popDepth, int pushDepth,
- HTML.Tag insertTag) {
- this(offset, popDepth, pushDepth, insertTag, true, false, true);
- }
-
- /**
- * Generates a RuntimeException (will eventually generate
- * a BadLocationException when API changes are alloced) if inserting
- * into non empty document, <code>insertTag</code> is
- * non-<code>null</code>, and <code>offset</code> is not in the body.
- */
- // PENDING(sky): Add throws BadLocationException and remove
- // RuntimeException
- HTMLReader(int offset, int popDepth, int pushDepth,
- HTML.Tag insertTag, boolean insertInsertTag,
- boolean insertAfterImplied, boolean wantsTrailingNewline) {
- emptyDocument = (getLength() == 0);
- isStyleCSS = "text/css".equals(getDefaultStyleSheetType());
- this.offset = offset;
- threshold = HTMLDocument.this.getTokenThreshold();
- tagMap = new Hashtable(57);
- TagAction na = new TagAction();
- TagAction ba = new BlockAction();
- TagAction pa = new ParagraphAction();
- TagAction ca = new CharacterAction();
- TagAction sa = new SpecialAction();
- TagAction fa = new FormAction();
- TagAction ha = new HiddenAction();
- TagAction conv = new ConvertAction();
-
- // register handlers for the well known tags
- tagMap.put(HTML.Tag.A, new AnchorAction());
- tagMap.put(HTML.Tag.ADDRESS, ca);
- tagMap.put(HTML.Tag.APPLET, ha);
- tagMap.put(HTML.Tag.AREA, new AreaAction());
- tagMap.put(HTML.Tag.B, conv);
- tagMap.put(HTML.Tag.BASE, new BaseAction());
- tagMap.put(HTML.Tag.BASEFONT, ca);
- tagMap.put(HTML.Tag.BIG, ca);
- tagMap.put(HTML.Tag.BLOCKQUOTE, ba);
- tagMap.put(HTML.Tag.BODY, ba);
- tagMap.put(HTML.Tag.BR, sa);
- tagMap.put(HTML.Tag.CAPTION, ba);
- tagMap.put(HTML.Tag.CENTER, ba);
- tagMap.put(HTML.Tag.CITE, ca);
- tagMap.put(HTML.Tag.CODE, ca);
- tagMap.put(HTML.Tag.DD, ba);
- tagMap.put(HTML.Tag.DFN, ca);
- tagMap.put(HTML.Tag.DIR, ba);
- tagMap.put(HTML.Tag.DIV, ba);
- tagMap.put(HTML.Tag.DL, ba);
- tagMap.put(HTML.Tag.DT, pa);
- tagMap.put(HTML.Tag.EM, ca);
- tagMap.put(HTML.Tag.FONT, conv);
- tagMap.put(HTML.Tag.FORM, new FormTagAction());
- tagMap.put(HTML.Tag.FRAME, sa);
- tagMap.put(HTML.Tag.FRAMESET, ba);
- tagMap.put(HTML.Tag.H1, pa);
- tagMap.put(HTML.Tag.H2, pa);
- tagMap.put(HTML.Tag.H3, pa);
- tagMap.put(HTML.Tag.H4, pa);
- tagMap.put(HTML.Tag.H5, pa);
- tagMap.put(HTML.Tag.H6, pa);
- tagMap.put(HTML.Tag.HEAD, new HeadAction());
- tagMap.put(HTML.Tag.HR, sa);
- tagMap.put(HTML.Tag.HTML, ba);
- tagMap.put(HTML.Tag.I, conv);
- tagMap.put(HTML.Tag.IMG, sa);
- tagMap.put(HTML.Tag.INPUT, fa);
- tagMap.put(HTML.Tag.ISINDEX, new IsindexAction());
- tagMap.put(HTML.Tag.KBD, ca);
- tagMap.put(HTML.Tag.LI, ba);
- tagMap.put(HTML.Tag.LINK, new LinkAction());
- tagMap.put(HTML.Tag.MAP, new MapAction());
- tagMap.put(HTML.Tag.MENU, ba);
- tagMap.put(HTML.Tag.META, new MetaAction());
- tagMap.put(HTML.Tag.NOBR, ca);
- tagMap.put(HTML.Tag.NOFRAMES, ba);
- tagMap.put(HTML.Tag.OBJECT, sa);
- tagMap.put(HTML.Tag.OL, ba);
- tagMap.put(HTML.Tag.OPTION, fa);
- tagMap.put(HTML.Tag.P, pa);
- tagMap.put(HTML.Tag.PARAM, new ObjectAction());
- tagMap.put(HTML.Tag.PRE, new PreAction());
- tagMap.put(HTML.Tag.SAMP, ca);
- tagMap.put(HTML.Tag.SCRIPT, ha);
- tagMap.put(HTML.Tag.SELECT, fa);
- tagMap.put(HTML.Tag.SMALL, ca);
- tagMap.put(HTML.Tag.SPAN, ca);
- tagMap.put(HTML.Tag.STRIKE, conv);
- tagMap.put(HTML.Tag.S, ca);
- tagMap.put(HTML.Tag.STRONG, ca);
- tagMap.put(HTML.Tag.STYLE, new StyleAction());
- tagMap.put(HTML.Tag.SUB, conv);
- tagMap.put(HTML.Tag.SUP, conv);
- tagMap.put(HTML.Tag.TABLE, ba);
- tagMap.put(HTML.Tag.TD, ba);
- tagMap.put(HTML.Tag.TEXTAREA, fa);
- tagMap.put(HTML.Tag.TH, ba);
- tagMap.put(HTML.Tag.TITLE, new TitleAction());
- tagMap.put(HTML.Tag.TR, ba);
- tagMap.put(HTML.Tag.TT, ca);
- tagMap.put(HTML.Tag.U, conv);
- tagMap.put(HTML.Tag.UL, ba);
- tagMap.put(HTML.Tag.VAR, ca);
-
- if (insertTag != null) {
- this.insertTag = insertTag;
- this.popDepth = popDepth;
- this.pushDepth = pushDepth;
- this.insertInsertTag = insertInsertTag;
- foundInsertTag = false;
- }
- else {
- foundInsertTag = true;
- }
- if (insertAfterImplied) {
- this.popDepth = popDepth;
- this.pushDepth = pushDepth;
- this.insertAfterImplied = true;
- foundInsertTag = false;
- midInsert = false;
- this.insertInsertTag = true;
- this.wantsTrailingNewline = wantsTrailingNewline;
- }
- else {
- midInsert = (!emptyDocument && insertTag == null);
- if (midInsert) {
- generateEndsSpecsForMidInsert();
- }
- }
- }
-
- /**
- * Generates an initial batch of end <code>ElementSpecs</code>
- * in parseBuffer to position future inserts into the body.
- */
- private void generateEndsSpecsForMidInsert() {
- int count = heightToElementWithName(HTML.Tag.BODY,
- Math.max(0, offset - 1));
- boolean joinNext = false;
-
- if (count == -1 && offset > 0) {
- count = heightToElementWithName(HTML.Tag.BODY, offset);
- if (count != -1) {
- // Previous isn't in body, but current is. Have to
- // do some end specs, followed by join next.
- count = depthTo(offset - 1) - 1;
- joinNext = true;
- }
- }
- if (count == -1) {
- throw new RuntimeException("Must insert new content into body element-");
- }
- if (count != -1) {
- // Insert a newline, if necessary.
- try {
- if (!joinNext && offset > 0 &&
- !getText(offset - 1, 1).equals("\n")) {
- SimpleAttributeSet newAttrs = new SimpleAttributeSet();
- newAttrs.addAttribute(StyleConstants.NameAttribute,
- HTML.Tag.CONTENT);
- ElementSpec spec = new ElementSpec(newAttrs,
- ElementSpec.ContentType, NEWLINE, 0, 1);
- parseBuffer.addElement(spec);
- }
- // Should never throw, but will catch anyway.
- } catch (BadLocationException ble) {}
- while (count-- > 0) {
- parseBuffer.addElement(new ElementSpec
- (null, ElementSpec.EndTagType));
- }
- if (joinNext) {
- ElementSpec spec = new ElementSpec(null, ElementSpec.
- StartTagType);
-
- spec.setDirection(ElementSpec.JoinNextDirection);
- parseBuffer.addElement(spec);
- }
- }
- // We should probably throw an exception if (count == -1)
- // Or look for the body and reset the offset.
- }
-
- /**
- * @return number of parents to reach the child at offset.
- */
- private int depthTo(int offset) {
- Element e = getDefaultRootElement();
- int count = 0;
-
- while (!e.isLeaf()) {
- count++;
- e = e.getElement(e.getElementIndex(offset));
- }
- return count;
- }
-
- /**
- * @return number of parents of the leaf at <code>offset</code>
- * until a parent with name, <code>name</code> has been
- * found. -1 indicates no matching parent with
- * <code>name</code>.
- */
- private int heightToElementWithName(Object name, int offset) {
- Element e = getCharacterElement(offset).getParentElement();
- int count = 0;
-
- while (e != null && e.getAttributes().getAttribute
- (StyleConstants.NameAttribute) != name) {
- count++;
- e = e.getParentElement();
- }
- return (e == null) ? -1 : count;
- }
-
- /**
- * This will make sure there aren't two BODYs (the second is
- * typically created when you do a remove all, and then an insert).
- */
- private void adjustEndElement() {
- int length = getLength();
- if (length == 0) {
- return;
- }
- obtainLock();
- try {
- Element[] pPath = getPathTo(length - 1);
- int pLength = pPath.length;
- if (pLength > 1 && pPath[1].getAttributes().getAttribute
- (StyleConstants.NameAttribute) == HTML.Tag.BODY &&
- pPath[1].getEndOffset() == length) {
- String lastText = getText(length - 1, 1);
- DefaultDocumentEvent event = null;
- Element[] added;
- Element[] removed;
- int index;
- // Remove the fake second body.
- added = new Element[0];
- removed = new Element[1];
- index = pPath[0].getElementIndex(length);
- removed[0] = pPath[0].getElement(index);
- ((BranchElement)pPath[0]).replace(index, 1, added);
- ElementEdit firstEdit = new ElementEdit(pPath[0], index,
- removed, added);
-
- // Insert a new element to represent the end that the
- // second body was representing.
- SimpleAttributeSet sas = new SimpleAttributeSet();
- sas.addAttribute(StyleConstants.NameAttribute,
- HTML.Tag.CONTENT);
- sas.addAttribute(IMPLIED_CR, Boolean.TRUE);
- added = new Element[1];
- added[0] = createLeafElement(pPath[pLength - 1],
- sas, length, length + 1);
- index = pPath[pLength - 1].getElementCount();
- ((BranchElement)pPath[pLength - 1]).replace(index, 0,
- added);
- event = new DefaultDocumentEvent(length, 1,
- DocumentEvent.EventType.CHANGE);
- event.addEdit(new ElementEdit(pPath[pLength - 1],
- index, new Element[0], added));
- event.addEdit(firstEdit);
- event.end();
- fireChangedUpdate(event);
- fireUndoableEditUpdate(new UndoableEditEvent(this, event));
-
- if (lastText.equals("\n")) {
- // We now have two \n's, one part of the Document.
- // We need to remove one
- event = new DefaultDocumentEvent(length - 1, 1,
- DocumentEvent.EventType.REMOVE);
- removeUpdate(event);
- UndoableEdit u = getContent().remove(length - 1, 1);
- if (u != null) {
- event.addEdit(u);
- }
- postRemoveUpdate(event);
- // Mark the edit as done.
- event.end();
- fireRemoveUpdate(event);
- fireUndoableEditUpdate(new UndoableEditEvent(
- this, event));
- }
- }
- }
- catch (BadLocationException ble) {
- }
- finally {
- releaseLock();
- }
- }
-
- private Element[] getPathTo(int offset) {
- Stack elements = new Stack();
- Element e = getDefaultRootElement();
- int index;
- while (!e.isLeaf()) {
- elements.push(e);
- e = e.getElement(e.getElementIndex(offset));
- }
- Element[] retValue = new Element[elements.size()];
- elements.copyInto(retValue);
- return retValue;
- }
-
- // -- HTMLEditorKit.ParserCallback methods --------------------
-
- /**
- * The last method called on the reader. It allows
- * any pending changes to be flushed into the document.
- * Since this is currently loading synchronously, the entire
- * set of changes are pushed in at this point.
- */
- public void flush() throws BadLocationException {
- if (emptyDocument && !insertAfterImplied) {
- if (HTMLDocument.this.getLength() > 0 ||
- parseBuffer.size() > 0) {
- flushBuffer(true);
- adjustEndElement();
- }
- // We won't insert when
- }
- else {
- flushBuffer(true);
- }
- }
-
- /**
- * Called by the parser to indicate a block of text was
- * encountered.
- */
- public void handleText(char[] data, int pos) {
- if (receivedEndHTML || (midInsert && !inBody)) {
- return;
- }
-
- // see if complex glyph layout support is needed
- if(HTMLDocument.this.getProperty(I18NProperty).equals( Boolean.FALSE ) ) {
- // if a default direction of right-to-left has been specified,
- // we want complex layout even if the text is all left to right.
- Object d = getProperty(TextAttribute.RUN_DIRECTION);
- if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
- HTMLDocument.this.putProperty( I18NProperty, Boolean.TRUE);
- } else {
- if (Bidi.requiresBidi(data, 0, data.length) ||
- isComplex(data, 0, data.length)) {
- //
- HTMLDocument.this.putProperty( I18NProperty, Boolean.TRUE);
- }
- }
- }
-
- if (inTextArea) {
- textAreaContent(data);
- } else if (inPre) {
- preContent(data);
- } else if (inTitle) {
- putProperty(Document.TitleProperty, new String(data));
- } else if (option != null) {
- option.setLabel(new String(data));
- } else if (inStyle) {
- if (styles != null) {
- styles.addElement(new String(data));
- }
- } else if (inBlock > 0) {
- if (!foundInsertTag && insertAfterImplied) {
- // Assume content should be added.
- foundInsertTag(false);
- foundInsertTag = true;
- inParagraph = impliedP = true;
- }
- if (data.length >= 1) {
- addContent(data, 0, data.length);
- }
- }
- }
-
- /**
- * Callback from the parser. Route to the appropriate
- * handler for the tag.
- */
- public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
- if (receivedEndHTML) {
- return;
- }
- if (midInsert && !inBody) {
- if (t == HTML.Tag.BODY) {
- inBody = true;
- // Increment inBlock since we know we are in the body,
- // this is needed incase an implied-p is needed. If
- // inBlock isn't incremented, and an implied-p is
- // encountered, addContent won't be called!
- inBlock++;
- }
- return;
- }
- if (!inBody && t == HTML.Tag.BODY) {
- inBody = true;
- }
- if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
- // Map the style attributes.
- String decl = (String)a.getAttribute(HTML.Attribute.STYLE);
- a.removeAttribute(HTML.Attribute.STYLE);
- styleAttributes = getStyleSheet().getDeclaration(decl);
- a.addAttributes(styleAttributes);
- }
- else {
- styleAttributes = null;
- }
- TagAction action = (TagAction) tagMap.get(t);
-
- if (action != null) {
- action.start(t, a);
- }
- }
-
- public void handleComment(char[] data, int pos) {
- if (receivedEndHTML) {
- addExternalComment(new String(data));
- return;
- }
- if (inStyle) {
- if (styles != null) {
- styles.addElement(new String(data));
- }
- }
- else if (getPreservesUnknownTags()) {
- if (inBlock == 0 && (foundInsertTag ||
- insertTag != HTML.Tag.COMMENT)) {
- // Comment outside of body, will not be able to show it,
- // but can add it as a property on the Document.
- addExternalComment(new String(data));
- return;
- }
- SimpleAttributeSet sas = new SimpleAttributeSet();
- sas.addAttribute(HTML.Attribute.COMMENT, new String(data));
- addSpecialElement(HTML.Tag.COMMENT, sas);
- }
-
- TagAction action = (TagAction)tagMap.get(HTML.Tag.COMMENT);
- if (action != null) {
- action.start(HTML.Tag.COMMENT, new SimpleAttributeSet());
- action.end(HTML.Tag.COMMENT);
- }
- }
-
- /**
- * Adds the comment <code>comment</code> to the set of comments
- * maintained outside of the scope of elements.
- */
- private void addExternalComment(String comment) {
- Object comments = getProperty(AdditionalComments);
- if (comments != null && !(comments instanceof Vector)) {
- // No place to put comment.
- return;
- }
- if (comments == null) {
- comments = new Vector();
- putProperty(AdditionalComments, comments);
- }
- ((Vector)comments).addElement(comment);
- }
-
- /**
- * Callback from the parser. Route to the appropriate
- * handler for the tag.
- */
- public void handleEndTag(HTML.Tag t, int pos) {
- if (receivedEndHTML || (midInsert && !inBody)) {
- return;
- }
- if (t == HTML.Tag.HTML) {
- receivedEndHTML = true;
- }
- if (t == HTML.Tag.BODY) {
- inBody = false;
- if (midInsert) {
- inBlock--;
- }
- }
- TagAction action = (TagAction) tagMap.get(t);
- if (action != null) {
- action.end(t);
- }
- }
-
- /**
- * Callback from the parser. Route to the appropriate
- * handler for the tag.
- */
- public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
- if (receivedEndHTML || (midInsert && !inBody)) {
- return;
- }
-
- if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
- // Map the style attributes.
- String decl = (String)a.getAttribute(HTML.Attribute.STYLE);
- a.removeAttribute(HTML.Attribute.STYLE);
- styleAttributes = getStyleSheet().getDeclaration(decl);
- a.addAttributes(styleAttributes);
- }
- else {
- styleAttributes = null;
- }
-
- TagAction action = (TagAction) tagMap.get(t);
- if (action != null) {
- action.start(t, a);
- action.end(t);
- }
- else if (getPreservesUnknownTags()) {
- // unknown tag, only add if should preserve it.
- addSpecialElement(t, a);
- }
- }
-
- /**
- * This is invoked after the stream has been parsed, but before
- * <code>flush</code>. <code>eol</code> will be one of \n, \r
- * or \r\n, which ever is encountered the most in parsing the
- * stream.
- *
- * @since 1.3
- */
- public void handleEndOfLineString(String eol) {
- if (emptyDocument && eol != null) {
- putProperty(DefaultEditorKit.EndOfLineStringProperty,
- eol);
- }
- }
-
- // ---- tag handling support ------------------------------
-
- /**
- * Registers a handler for the given tag. By default
- * all of the well-known tags will have been registered.
- * This can be used to change the handling of a particular
- * tag or to add support for custom tags.
- */
- protected void registerTag(HTML.Tag t, TagAction a) {
- tagMap.put(t, a);
- }
-
- /**
- * An action to be performed in response
- * to parsing a tag. This allows customization
- * of how each tag is handled and avoids a large
- * switch statement.
- */
- public class TagAction {
-
- /**
- * Called when a start tag is seen for the
- * type of tag this action was registered
- * to. The tag argument indicates the actual
- * tag for those actions that are shared across
- * many tags. By default this does nothing and
- * completely ignores the tag.
- */
- public void start(HTML.Tag t, MutableAttributeSet a) {
- }
-
- /**
- * Called when an end tag is seen for the
- * type of tag this action was registered
- * to. The tag argument indicates the actual
- * tag for those actions that are shared across
- * many tags. By default this does nothing and
- * completely ignores the tag.
- */
- public void end(HTML.Tag t) {
- }
-
- }
-
- public class BlockAction extends TagAction {
-
- public void start(HTML.Tag t, MutableAttributeSet attr) {
- blockOpen(t, attr);
- }
-
- public void end(HTML.Tag t) {
- blockClose(t);
- }
- }
-
-
- /**
- * Action used for the actual element form tag. This is named such
- * as there was already a public class named FormAction.
- */
- private class FormTagAction extends BlockAction {
- public void start(HTML.Tag t, MutableAttributeSet attr) {
- super.start(t, attr);
- // initialize a ButtonGroupsMap when
- // FORM tag is encountered. This will
- // be used for any radio buttons that
- // might be defined in the FORM.
- // for new group new ButtonGroup will be created (fix for 4529702)
- // group name is a key in radioButtonGroupsMap
- radioButtonGroupsMap = new HashMap();
- }
-
- public void end(HTML.Tag t) {
- super.end(t);
- // reset the button group to null since
- // the form has ended.
- radioButtonGroupsMap = null;
- }
- }
-
-
- public class ParagraphAction extends BlockAction {
-
- public void start(HTML.Tag t, MutableAttributeSet a) {
- super.start(t, a);
- inParagraph = true;
- }
-
- public void end(HTML.Tag t) {
- super.end(t);
- inParagraph = false;
- }
- }
-
- public class SpecialAction extends TagAction {
-
- public void start(HTML.Tag t, MutableAttributeSet a) {
- addSpecialElement(t, a);
- }
-
- }
-
- public class IsindexAction extends TagAction {
-
- public void start(HTML.Tag t, MutableAttributeSet a) {
- blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
- addSpecialElement(t, a);
- blockClose(HTML.Tag.IMPLIED);
- }
-
- }
-
-
- public class HiddenAction extends TagAction {
-
- public void start(HTML.Tag t, MutableAttributeSet a) {
- addSpecialElement(t, a);
- }
-
- public void end(HTML.Tag t) {
- if (!isEmpty(t)) {
- MutableAttributeSet a = new SimpleAttributeSet();
- a.addAttribute(HTML.Attribute.ENDTAG, "true");
- addSpecialElement(t, a);
- }
- }
-
- boolean isEmpty(HTML.Tag t) {
- if (t == HTML.Tag.APPLET ||
- t == HTML.Tag.SCRIPT) {
- return false;
- }
- return true;
- }
- }
-
-
- /**
- * Subclass of HiddenAction to set the content type for style sheets,
- * and to set the name of the default style sheet.
- */
- class MetaAction extends HiddenAction {
-
- public void start(HTML.Tag t, MutableAttributeSet a) {
- Object equiv = a.getAttribute(HTML.Attribute.HTTPEQUIV);
- if (equiv != null) {
- equiv = ((String)equiv).toLowerCase();
- if (equiv.equals("content-style-type")) {
- String value = (String)a.getAttribute
- (HTML.Attribute.CONTENT);
- setDefaultStyleSheetType(value);
- isStyleCSS = "text/css".equals
- (getDefaultStyleSheetType());
- }
- else if (equiv.equals("default-style")) {
- defaultStyle = (String)a.getAttribute
- (HTML.Attribute.CONTENT);
- }
- }
- super.start(t, a);
- }
-
- boolean isEmpty(HTML.Tag t) {
- return true;
- }
- }
-
-
- /**
- * End if overridden to create the necessary stylesheets that
- * are referenced via the link tag. It is done in this manner
- * as the meta tag can be used to specify an alternate style sheet,
- * and is not guaranteed to come before the link tags.
- */
- class HeadAction extends BlockAction {
-
- public void start(HTML.Tag t, MutableAttributeSet a) {
- inHead = true;
- // This check of the insertTag is put in to avoid considering
- // the implied-p that is generated for the head. This allows
- // inserts for HR to work correctly.
- if ((insertTag == null && !insertAfterImplied) ||
- (insertTag == HTML.Tag.HEAD) ||
- (insertAfterImplied &&
- (foundInsertTag || !a.isDefined(IMPLIED)))) {
- super.start(t, a);
- }
- }
-
- public void end(HTML.Tag t) {
- inHead = inStyle = false;
- // See if there is a StyleSheet to link to.
- if (styles != null) {
- boolean isDefaultCSS = isStyleCSS;
- for (int counter = 0, maxCounter = styles.size();
- counter < maxCounter;) {
- Object value = styles.elementAt(counter);
- if (value == HTML.Tag.LINK) {
- handleLink((AttributeSet)styles.
- elementAt(++counter));
- counter++;
- }
- else {
- // Rule.
- // First element gives type.
- String type = (String)styles.elementAt(++counter);
- boolean isCSS = (type == null) ? isDefaultCSS :
- type.equals("text/css");
- while (++counter < maxCounter &&
- (styles.elementAt(counter)
- instanceof String)) {
- if (isCSS) {
- addCSSRules((String)styles.elementAt
- (counter));
- }
- }
- }
- }
- }
- if ((insertTag == null && !insertAfterImplied) ||
- insertTag == HTML.Tag.HEAD ||
- (insertAfterImplied && foundInsertTag)) {
- super.end(t);
- }
- }
-
- boolean isEmpty(HTML.Tag t) {
- return false;
- }
-
- private void handleLink(AttributeSet attr) {
- // Link.
- String type = (String)attr.getAttribute(HTML.Attribute.TYPE);
- if (type == null) {
- type = getDefaultStyleSheetType();
- }
- // Only choose if type==text/css
- // Select link if rel==stylesheet.
- // Otherwise if rel==alternate stylesheet and
- // title matches default style.
- if (type.equals("text/css")) {
- String rel = (String)attr.getAttribute(HTML.Attribute.REL);
- String title = (String)attr.getAttribute
- (HTML.Attribute.TITLE);
- String media = (String)attr.getAttribute
- (HTML.Attribute.MEDIA);
- 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") ||
- (rel.equals("alternate stylesheet") &&
- title.equals(defaultStyle)))) {
- linkCSSStyleSheet((String)attr.getAttribute
- (HTML.Attribute.HREF));
- }
- }
- }
- }
- }
-
-
- /**
- * A subclass to add the AttributeSet to styles if the
- * attributes contains an attribute for 'rel' with value
- * 'stylesheet' or 'alternate stylesheet'.
- */
- class LinkAction extends HiddenAction {
-
- public void start(HTML.Tag t, MutableAttributeSet a) {
- String rel = (String)a.getAttribute(HTML.Attribute.REL);
- if (rel != null) {
- rel = rel.toLowerCase();
- if (rel.equals("stylesheet") ||
- rel.equals("alternate stylesheet")) {
- if (styles == null) {
- styles = new Vector(3);
- }
- styles.addElement(t);
- styles.addElement(a.copyAttributes());
- }
- }
- super.start(t, a);
- }
- }
-
- class MapAction extends TagAction {
-
- public void start(HTML.Tag t, MutableAttributeSet a) {
- lastMap = new Map((String)a.getAttribute(HTML.Attribute.NAME));
- addMap(lastMap);
- }
-
- public void end(HTML.Tag t) {
- }
- }
-
-
- class AreaAction extends TagAction {
-
- public void start(HTML.Tag t, MutableAttributeSet a) {
- if (lastMap != null) {
- lastMap.addArea(a.copyAttributes());
- }
- }
-
- public void end(HTML.Tag t) {
- }
- }
-
-
- class StyleAction extends TagAction {
-
- public void start(HTML.Tag t, MutableAttributeSet a) {
- if (inHead) {
- if (styles == null) {
- styles = new Vector(3);
- }
- styles.addElement(t);
- styles.addElement(a.getAttribute(HTML.Attribute.TYPE));
- inStyle = true;
- }
- }
-
- public void end(HTML.Tag t) {
- inStyle = false;
- }
-
- boolean isEmpty(HTML.Tag t) {
- return false;
- }
- }
-
-
- public class PreAction extends BlockAction {
-
- public void start(HTML.Tag t, MutableAttributeSet attr) {
- inPre = true;
- blockOpen(t, attr);
- attr.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
- blockOpen(HTML.Tag.IMPLIED, attr);
- }
-
- public void end(HTML.Tag t) {
- blockClose(HTML.Tag.IMPLIED);
- // set inPre to false after closing, so that if a newline
- // is added it won't generate a blockOpen.
- inPre = false;
- blockClose(t);
- }
- }
-
- public class CharacterAction extends TagAction {
-
- public void start(HTML.Tag t, MutableAttributeSet attr) {
- pushCharacterStyle();
- if (!foundInsertTag) {
- // Note that the third argument should really be based off
- // inParagraph and impliedP. If we're wrong (that is
- // insertTagDepthDelta shouldn't be changed), we'll end up
- // removing an extra EndSpec, which won't matter anyway.
- boolean insert = canInsertTag(t, attr, false);
- if (foundInsertTag) {
- if (!inParagraph) {
- inParagraph = impliedP = true;
- }
- }
- if (!insert) {
- return;
- }
- }
- if (attr.isDefined(IMPLIED)) {
- attr.removeAttribute(IMPLIED);
- }
- charAttr.addAttribute(t, attr.copyAttributes());
- if (styleAttributes != null) {
- charAttr.addAttributes(styleAttributes);
- }
- }
-
- public void end(HTML.Tag t) {
- popCharacterStyle();
- }
- }
-
- /**
- * Provides conversion of HTML tag/attribute
- * mappings that have a corresponding StyleConstants
- * and CSS mapping. The conversion is to CSS attributes.
- */
- class ConvertAction extends TagAction {
-
- public void start(HTML.Tag t, MutableAttributeSet attr) {
- pushCharacterStyle();
- if (!foundInsertTag) {
- // Note that the third argument should really be based off
- // inParagraph and impliedP. If we're wrong (that is
- // insertTagDepthDelta shouldn't be changed), we'll end up
- // removing an extra EndSpec, which won't matter anyway.
- boolean insert = canInsertTag(t, attr, false);
- if (foundInsertTag) {
- if (!inParagraph) {
- inParagraph = impliedP = true;
- }
- }
- if (!insert) {
- return;
- }
- }
- if (attr.isDefined(IMPLIED)) {
- attr.removeAttribute(IMPLIED);
- }
- if (styleAttributes != null) {
- charAttr.addAttributes(styleAttributes);
- }
- // We also need to add attr, otherwise we lose custom
- // attributes, including class/id for style lookups, and
- // further confuse style lookup (doesn't have tag).
- charAttr.addAttribute(t, attr.copyAttributes());
- StyleSheet sheet = getStyleSheet();
- if (t == HTML.Tag.B) {
- sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_WEIGHT, "bold");
- } else if (t == HTML.Tag.I) {
- sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_STYLE, "italic");
- } else if (t == HTML.Tag.U) {
- Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
- String value = "underline";
- value = (v != null) ? value + "," + v.toString() : value;
- sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
- } else if (t == HTML.Tag.STRIKE) {
- Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
- String value = "line-through";
- value = (v != null) ? value + "," + v.toString() : value;
- sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
- } else if (t == HTML.Tag.SUP) {
- Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
- String value = "sup";
- value = (v != null) ? value + "," + v.toString() : value;
- sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
- } else if (t == HTML.Tag.SUB) {
- Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
- String value = "sub";
- value = (v != null) ? value + "," + v.toString() : value;
- sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
- } else if (t == HTML.Tag.FONT) {
- String color = (String) attr.getAttribute(HTML.Attribute.COLOR);
- if (color != null) {
- sheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color);
- }
- String face = (String) attr.getAttribute(HTML.Attribute.FACE);
- if (face != null) {
- sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY, face);
- }
- String size = (String) attr.getAttribute(HTML.Attribute.SIZE);
- if (size != null) {
- sheet.addCSSAttributeFromHTML(charAttr, CSS.Attribute.FONT_SIZE, size);
- }
- }
- }
-
- public void end(HTML.Tag t) {
- popCharacterStyle();
- }
-
- }
-
- class AnchorAction extends CharacterAction {
-
- public void start(HTML.Tag t, MutableAttributeSet attr) {
- // set flag to catch empty anchors
- emptyAnchor = true;
- super.start(t, attr);
- }
-
- public void end(HTML.Tag t) {
- if (emptyAnchor) {
- // if the anchor was empty it was probably a
- // named anchor point and we don't want to throw
- // it away.
- char[] one = new char[1];
- one[0] = ' ';
- addContent(one, 0, 1);
- }
- super.end(t);
- }
- }
-
- class TitleAction extends HiddenAction {
-
- public void start(HTML.Tag t, MutableAttributeSet attr) {
- inTitle = true;
- super.start(t, attr);
- }
-
- public void end(HTML.Tag t) {
- inTitle = false;
- super.end(t);
- }
-
- boolean isEmpty(HTML.Tag t) {
- return false;
- }
- }
-
-
- class BaseAction extends TagAction {
-
- public void start(HTML.Tag t, MutableAttributeSet attr) {
- String href = (String) attr.getAttribute(HTML.Attribute.HREF);
- if (href != null) {
- try {
- URL newBase = new URL(base, href);
- setBase(newBase);
- hasBaseTag = true;
- } catch (MalformedURLException ex) {
- }
- }
- baseTarget = (String) attr.getAttribute(HTML.Attribute.TARGET);
- }
- }
-
- class ObjectAction extends SpecialAction {
-
- public void start(HTML.Tag t, MutableAttributeSet a) {
- if (t == HTML.Tag.PARAM) {
- addParameter(a);
- } else {
- super.start(t, a);
- }
- }
-
- public void end(HTML.Tag t) {
- if (t != HTML.Tag.PARAM) {
- super.end(t);
- }
- }
-
- void addParameter(AttributeSet a) {
- String name = (String) a.getAttribute(HTML.Attribute.NAME);
- String value = (String) a.getAttribute(HTML.Attribute.VALUE);
- if ((name != null) && (value != null)) {
- ElementSpec objSpec = (ElementSpec) parseBuffer.lastElement();
- MutableAttributeSet objAttr = (MutableAttributeSet) objSpec.getAttributes();
- objAttr.addAttribute(name, value);
- }
- }
- }
-
- /**
- * Action to support forms by building all of the elements
- * used to represent form controls. This will process
- * the <INPUT>, <TEXTAREA>, <SELECT>,
- * and <OPTION> tags. The element created by
- * this action is expected to have the attribute
- * <code>StyleConstants.ModelAttribute</code> set to
- * the model that holds the state for the form control.
- * This enables multiple views, and allows document to
- * be iterated over picking up the data of the form.
- * The following are the model assignments for the
- * various type of form elements.
- * <table summary="model assignments for the various types of form elements">
- * <tr>
- * <th>Element Type
- * <th>Model Type
- * <tr>
- * <td>input, type button
- * <td>{@link DefaultButtonModel}
- * <tr>
- * <td>input, type checkbox
- * <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
- * <tr>
- * <td>input, type image
- * <td>{@link DefaultButtonModel}
- * <tr>
- * <td>input, type password
- * <td>{@link PlainDocument}
- * <tr>
- * <td>input, type radio
- * <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
- * <tr>
- * <td>input, type reset
- * <td>{@link DefaultButtonModel}
- * <tr>
- * <td>input, type submit
- * <td>{@link DefaultButtonModel}
- * <tr>
- * <td>input, type text or type is null.
- * <td>{@link PlainDocument}
- * <tr>
- * <td>select
- * <td>{@link DefaultComboBoxModel} or an {@link DefaultListModel}, with an item type of Option
- * <tr>
- * <td>textarea
- * <td>{@link PlainDocument}
- * </table>
- *
- */
- public class FormAction extends SpecialAction {
-
- public void start(HTML.Tag t, MutableAttributeSet attr) {
- if (t == HTML.Tag.INPUT) {
- String type = (String)
- attr.getAttribute(HTML.Attribute.TYPE);
- /*
- * if type is not defined teh default is
- * assumed to be text.
- */
- if (type == null) {
- type = "text";
- attr.addAttribute(HTML.Attribute.TYPE, "text");
- }
- setModel(type, attr);
- } else if (t == HTML.Tag.TEXTAREA) {
- inTextArea = true;
- textAreaDocument = new TextAreaDocument();
- attr.addAttribute(StyleConstants.ModelAttribute,
- textAreaDocument);
- } else if (t == HTML.Tag.SELECT) {
- int size = HTML.getIntegerAttributeValue(attr,
- HTML.Attribute.SIZE,
- 1);
- boolean multiple = ((String)attr.getAttribute(HTML.Attribute.MULTIPLE) != null);
- if ((size > 1) || multiple) {
- OptionListModel m = new OptionListModel();
- if (multiple) {
- m.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
- }
- selectModel = m;
- } else {
- selectModel = new OptionComboBoxModel();
- }
- attr.addAttribute(StyleConstants.ModelAttribute,
- selectModel);
-
- }
-
- // build the element, unless this is an option.
- if (t == HTML.Tag.OPTION) {
- option = new Option(attr);
-
- if (selectModel instanceof OptionListModel) {
- OptionListModel m = (OptionListModel)selectModel;
- m.addElement(option);
- if (option.isSelected()) {
- m.addSelectionInterval(optionCount, optionCount);
- m.setInitialSelection(optionCount);
- }
- } else if (selectModel instanceof OptionComboBoxModel) {
- OptionComboBoxModel m = (OptionComboBoxModel)selectModel;
- m.addElement(option);
- if (option.isSelected()) {
- m.setSelectedItem(option);
- m.setInitialSelection(option);
- }
- }
- optionCount++;
- } else {
- super.start(t, attr);
- }
- }
-
- public void end(HTML.Tag t) {
- if (t == HTML.Tag.OPTION) {
- option = null;
- } else {
- if (t == HTML.Tag.SELECT) {
- selectModel = null;
- optionCount = 0;
- } else if (t == HTML.Tag.TEXTAREA) {
- inTextArea = false;
-
- /* Now that the textarea has ended,
- * store the entire initial text
- * of the text area. This will
- * enable us to restore the initial
- * state if a reset is requested.
- */
- textAreaDocument.storeInitialText();
- }
- super.end(t);
- }
- }
-
- void setModel(String type, MutableAttributeSet attr) {
- if (type.equals("submit") ||
- type.equals("reset") ||
- type.equals("image")) {
-
- // button model
- attr.addAttribute(StyleConstants.ModelAttribute,
- new DefaultButtonModel());
- } else if (type.equals("text") ||
- type.equals("password")) {
- // plain text model
- int maxLength = HTML.getIntegerAttributeValue(
- attr, HTML.Attribute.MAXLENGTH, -1);
- Document doc;
-
- if (maxLength > 0) {
- doc = new FixedLengthDocument(maxLength);
- }
- else {
- doc = new PlainDocument();
- }
- attr.addAttribute(StyleConstants.ModelAttribute, doc);
- } else if (type.equals("file")) {
- // plain text model
- attr.addAttribute(StyleConstants.ModelAttribute,
- new PlainDocument());
- } else if (type.equals("checkbox") ||
- type.equals("radio")) {
- JToggleButton.ToggleButtonModel model = new JToggleButton.ToggleButtonModel();
- if (type.equals("radio")) {
- String name = (String) attr.getAttribute(HTML.Attribute.NAME);
- if ( radioButtonGroupsMap == null ) { //fix for 4772743
- radioButtonGroupsMap = new HashMap();
- }
- ButtonGroup radioButtonGroup = (ButtonGroup)radioButtonGroupsMap.get(name);
- if (radioButtonGroup == null) {
- radioButtonGroup = new ButtonGroup();
- radioButtonGroupsMap.put(name,radioButtonGroup);
- }
- model.setGroup(radioButtonGroup);
- }
- attr.addAttribute(StyleConstants.ModelAttribute, model);
- }
- }
-
- /**
- * If a <SELECT> tag is being processed, this
- * model will be a reference to the model being filled
- * with the <OPTION> elements (which produce
- * objects of type <code>Option</code>.
- */
- Object selectModel;
- int optionCount;
- }
-
-
- // --- utility methods used by the reader ------------------
-
- /**
- * Pushes the current character style on a stack in preparation
- * for forming a new nested character style.
- */
- protected void pushCharacterStyle() {
- charAttrStack.push(charAttr.copyAttributes());
- }
-
- /**
- * Pops a previously pushed character style off the stack
- * to return to a previous style.
- */
- protected void popCharacterStyle() {
- if (!charAttrStack.empty()) {
- charAttr = (MutableAttributeSet) charAttrStack.peek();
- charAttrStack.pop();
- }
- }
-
- /**
- * Adds the given content to the textarea document.
- * This method gets called when we are in a textarea
- * context. Therefore all text that is seen belongs
- * to the text area and is hence added to the
- * TextAreaDocument associated with the text area.
- */
- protected void textAreaContent(char[] data) {
- try {
- textAreaDocument.insertString(textAreaDocument.getLength(), new String(data), null);
- } catch (BadLocationException e) {
- // Should do something reasonable
- }
- }
-
- /**
- * Adds the given content that was encountered in a
- * PRE element. This synthesizes lines to hold the
- * runs of text, and makes calls to addContent to
- * actually add the text.
- */
- protected void preContent(char[] data) {
- int last = 0;
- for (int i = 0; i < data.length; i++) {
- if (data[i] == '\n') {
- addContent(data, last, i - last + 1);
- blockClose(HTML.Tag.IMPLIED);
- MutableAttributeSet a = new SimpleAttributeSet();
- a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
- blockOpen(HTML.Tag.IMPLIED, a);
- last = i + 1;
- }
- }
- if (last < data.length) {
- addContent(data, last, data.length - last);
- }
- }
-
- /**
- * Adds an instruction to the parse buffer to create a
- * block element with the given attributes.
- */
- protected void blockOpen(HTML.Tag t, MutableAttributeSet attr) {
- if (impliedP) {
- blockClose(HTML.Tag.IMPLIED);
- }
-
- inBlock++;
-
- if (!canInsertTag(t, attr, true)) {
- return;
- }
- if (attr.isDefined(IMPLIED)) {
- attr.removeAttribute(IMPLIED);
- }
- lastWasNewline = false;
- attr.addAttribute(StyleConstants.NameAttribute, t);
- ElementSpec es = new ElementSpec(
- attr.copyAttributes(), ElementSpec.StartTagType);
- parseBuffer.addElement(es);
- }
-
- /**
- * Adds an instruction to the parse buffer to close out
- * a block element of the given type.
- */
- protected void blockClose(HTML.Tag t) {
- inBlock--;
-
- if (!foundInsertTag) {
- return;
- }
-
- // Add a new line, if the last character wasn't one. This is
- // needed for proper positioning of the cursor. addContent
- // with true will force an implied paragraph to be generated if
- // there isn't one. This may result in a rather bogus structure
- // (perhaps a table with a child pargraph), but the paragraph
- // is needed for proper positioning and display.
- if(!lastWasNewline) {
- pushCharacterStyle();
- charAttr.addAttribute(IMPLIED_CR, Boolean.TRUE);
- addContent(NEWLINE, 0, 1, true);
- popCharacterStyle();
- lastWasNewline = true;
- }
-
- if (impliedP) {
- impliedP = false;
- inParagraph = false;
- if (t != HTML.Tag.IMPLIED) {
- blockClose(HTML.Tag.IMPLIED);
- }
- }
- // an open/close with no content will be removed, so we
- // add a space of content to keep the element being formed.
- ElementSpec prev = (parseBuffer.size() > 0) ?
- (ElementSpec) parseBuffer.lastElement() : null;
- if (prev != null && prev.getType() == ElementSpec.StartTagType) {
- char[] one = new char[1];
- one[0] = ' ';
- addContent(one, 0, 1);
- }
- ElementSpec es = new ElementSpec(
- null, ElementSpec.EndTagType);
- parseBuffer.addElement(es);
- }
-
- /**
- * Adds some text with the current character attributes.
- *
- * @param embedded the attributes of an embedded object.
- */
- protected void addContent(char[] data, int offs, int length) {
- addContent(data, offs, length, true);
- }
-
- /**
- * Adds some text with the current character attributes.
- *
- * @param embedded the attributes of an embedded object.
- */
- protected void addContent(char[] data, int offs, int length,
- boolean generateImpliedPIfNecessary) {
- if (!foundInsertTag) {
- return;
- }
-
- if (generateImpliedPIfNecessary && (! inParagraph) && (! inPre)) {
- blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
- inParagraph = true;
- impliedP = true;
- }
- emptyAnchor = false;
- charAttr.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
- AttributeSet a = charAttr.copyAttributes();
- ElementSpec es = new ElementSpec(
- a, ElementSpec.ContentType, data, offs, length);
- parseBuffer.addElement(es);
-
- if (parseBuffer.size() > threshold) {
- if ( threshold <= MaxThreshold ) {
- threshold *= StepThreshold;
- }
- try {
- flushBuffer(false);
- } catch (BadLocationException ble) {
- }
- }
- if(length > 0) {
- lastWasNewline = (data[offs + length - 1] == '\n');
- }
- }
-
- /**
- * Adds content that is basically specified entirely
- * in the attribute set.
- */
- protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a) {
- if ((t != HTML.Tag.FRAME) && (! inParagraph) && (! inPre)) {
- blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
- inParagraph = true;
- impliedP = true;
- }
- if (!canInsertTag(t, a, t.isBlock())) {
- return;
- }
- if (a.isDefined(IMPLIED)) {
- a.removeAttribute(IMPLIED);
- }
- emptyAnchor = false;
- a.addAttributes(charAttr);
- a.addAttribute(StyleConstants.NameAttribute, t);
- char[] one = new char[1];
- one[0] = ' ';
- ElementSpec es = new ElementSpec(
- a.copyAttributes(), ElementSpec.ContentType, one, 0, 1);
- parseBuffer.addElement(es);
- // Set this to avoid generating a newline for frames, frames
- // shouldn't have any content, and shouldn't need a newline.
- if (t == HTML.Tag.FRAME) {
- lastWasNewline = true;
- }
- }
-
- /**
- * Flushes the current parse buffer into the document.
- * @param endOfStream true if there is no more content to parser
- */
- void flushBuffer(boolean endOfStream) throws BadLocationException {
- int oldLength = HTMLDocument.this.getLength();
- int size = parseBuffer.size();
- if (endOfStream && (insertTag != null || insertAfterImplied) &&
- size > 0) {
- adjustEndSpecsForPartialInsert();
- size = parseBuffer.size();
- }
- ElementSpec[] spec = new ElementSpec[size];
- parseBuffer.copyInto(spec);
-
- if (oldLength == 0 && (insertTag == null && !insertAfterImplied)) {
- create(spec);
- } else {
- insert(offset, spec);
- }
- parseBuffer.removeAllElements();
- offset += HTMLDocument.this.getLength() - oldLength;
- flushCount++;
- }
-
- /**
- * This will be invoked for the last flush, if <code>insertTag</code>
- * is non null.
- */
- private void adjustEndSpecsForPartialInsert() {
- int size = parseBuffer.size();
- if (insertTagDepthDelta < 0) {
- // When inserting via an insertTag, the depths (of the tree
- // being read in, and existing hiearchy) may not match up.
- // This attemps to clean it up.
- int removeCounter = insertTagDepthDelta;
- while (removeCounter < 0 && size >= 0 &&
- ((ElementSpec)parseBuffer.elementAt(size - 1)).
- getType() == ElementSpec.EndTagType) {
- parseBuffer.removeElementAt(--size);
- removeCounter++;
- }
- }
- if (flushCount == 0 && (!insertAfterImplied ||
- !wantsTrailingNewline)) {
- // If this starts with content (or popDepth > 0 &&
- // pushDepth > 0) and ends with EndTagTypes, make sure
- // the last content isn't a \n, otherwise will end up with
- // an extra \n in the middle of content.
- int index = 0;
- if (pushDepth > 0) {
- if (((ElementSpec)parseBuffer.elementAt(0)).getType() ==
- ElementSpec.ContentType) {
- index++;
- }
- }
- index += (popDepth + pushDepth);
- int cCount = 0;
- int cStart = index;
- while (index < size && ((ElementSpec)parseBuffer.elementAt
- (index)).getType() == ElementSpec.ContentType) {
- index++;
- cCount++;
- }
- if (cCount > 1) {
- while (index < size && ((ElementSpec)parseBuffer.elementAt
- (index)).getType() == ElementSpec.EndTagType) {
- index++;
- }
- if (index == size) {
- char[] lastText = ((ElementSpec)parseBuffer.elementAt
- (cStart + cCount - 1)).getArray();
- if (lastText.length == 1 && lastText[0] == NEWLINE[0]){
- index = cStart + cCount - 1;
- while (size > index) {
- parseBuffer.removeElementAt(--size);
- }
- }
- }
- }
- }
- if (wantsTrailingNewline) {
- // Make sure there is in fact a newline
- for (int counter = parseBuffer.size() - 1; counter >= 0;
- counter--) {
- ElementSpec spec = (ElementSpec)parseBuffer.
- elementAt(counter);
- if (spec.getType() == ElementSpec.ContentType) {
- if (spec.getArray()[spec.getLength() - 1] != '\n') {
- SimpleAttributeSet attrs =new SimpleAttributeSet();
-
- attrs.addAttribute(StyleConstants.NameAttribute,
- HTML.Tag.CONTENT);
- parseBuffer.insertElementAt(new ElementSpec(
- attrs,
- ElementSpec.ContentType, NEWLINE, 0, 1),
- counter + 1);
- }
- break;
- }
- }
- }
- }
-
- /**
- * Adds the CSS rules in <code>rules</code>.
- */
- void addCSSRules(String rules) {
- StyleSheet ss = getStyleSheet();
- ss.addRule(rules);
- }
-
- /**
- * Adds the CSS stylesheet at <code>href</code> to the known list
- * of stylesheets.
- */
- void linkCSSStyleSheet(String href) {
- URL url = null;
- try {
- url = new URL(base, href);
- } catch (MalformedURLException mfe) {
- try {
- url = new URL(href);
- } catch (MalformedURLException mfe2) {
- url = null;
- }
- }
- if (url != null) {
- getStyleSheet().importStyleSheet(url);
- }
- }
-
- /**
- * Returns true if can insert starting at <code>t</code>. This
- * will return false if the insert tag is set, and hasn't been found
- * yet.
- */
- private boolean canInsertTag(HTML.Tag t, AttributeSet attr,
- boolean isBlockTag) {
- if (!foundInsertTag) {
- if ((insertTag != null && !isInsertTag(t)) ||
- (insertAfterImplied && (t == HTML.Tag.IMPLIED ||
- (attr == null || attr.isDefined(IMPLIED))))) {
- return false;
- }
- // Allow the insert if t matches the insert tag, or
- // insertAfterImplied is true and the element is implied.
- foundInsertTag(isBlockTag);
- if (!insertInsertTag) {
- return false;
- }
- }
- return true;
- }
-
- private boolean isInsertTag(HTML.Tag tag) {
- return (insertTag == tag);
- }
-
- private void foundInsertTag(boolean isBlockTag) {
- foundInsertTag = true;
- if (!insertAfterImplied && (popDepth > 0 || pushDepth > 0)) {
- try {
- if (offset == 0 || !getText(offset - 1, 1).equals("\n")) {
- // Need to insert a newline.
- AttributeSet newAttrs = null;
- boolean joinP = true;
-
- if (offset != 0) {
- // Determine if we can use JoinPrevious, we can't
- // if the Element has some attributes that are
- // not meant to be duplicated.
- Element charElement = getCharacterElement
- (offset - 1);
- AttributeSet attrs = charElement.getAttributes();
-
- if (attrs.isDefined(StyleConstants.
- ComposedTextAttribute)) {
- joinP = false;
- }
- else {
- Object name = attrs.getAttribute
- (StyleConstants.NameAttribute);
- if (name instanceof HTML.Tag) {
- HTML.Tag tag = (HTML.Tag)name;
- if (tag == HTML.Tag.IMG ||
- tag == HTML.Tag.HR ||
- tag == HTML.Tag.COMMENT ||
- (tag instanceof HTML.UnknownTag)) {
- joinP = false;
- }
- }
- }
- }
- if (!joinP) {
- // If not joining with the previous element, be
- // sure and set the name (otherwise it will be
- // inherited).
- newAttrs = new SimpleAttributeSet();
- ((SimpleAttributeSet)newAttrs).addAttribute
- (StyleConstants.NameAttribute,
- HTML.Tag.CONTENT);
- }
- ElementSpec es = new ElementSpec(newAttrs,
- ElementSpec.ContentType, NEWLINE, 0,
- NEWLINE.length);
- if (joinP) {
- es.setDirection(ElementSpec.
- JoinPreviousDirection);
- }
- parseBuffer.addElement(es);
- }
- } catch (BadLocationException ble) {}
- }
- // pops
- for (int counter = 0; counter < popDepth; counter++) {
- parseBuffer.addElement(new ElementSpec(null, ElementSpec.
- EndTagType));
- }
- // pushes
- for (int counter = 0; counter < pushDepth; counter++) {
- ElementSpec es = new ElementSpec(null, ElementSpec.
- StartTagType);
- es.setDirection(ElementSpec.JoinNextDirection);
- parseBuffer.addElement(es);
- }
- insertTagDepthDelta = depthTo(Math.max(0, offset - 1)) -
- popDepth + pushDepth - inBlock;
- if (isBlockTag) {
- // A start spec will be added (for this tag), so we account
- // for it here.
- insertTagDepthDelta++;
- }
- else {
- // An implied paragraph close (end spec) is going to be added,
- // so we account for it here.
- insertTagDepthDelta--;
- inParagraph = true;
- lastWasNewline = false;
- }
- }
-
- /**
- * This is set to true when and end is invoked for <html>.
- */
- private boolean receivedEndHTML;
- /** Number of times <code>flushBuffer</code> has been invoked. */
- private int flushCount;
- /** If true, behavior is similiar to insertTag, but instead of
- * waiting for insertTag will wait for first Element without
- * an 'implied' attribute and begin inserting then. */
- private boolean insertAfterImplied;
- /** This is only used if insertAfterImplied is true. If false, only
- * inserting content, and there is a trailing newline it is removed. */
- private boolean wantsTrailingNewline;
- int threshold;
- int offset;
- boolean inParagraph = false;
- boolean impliedP = false;
- boolean inPre = false;
- boolean inTextArea = false;
- TextAreaDocument textAreaDocument = null;
- boolean inTitle = false;
- boolean lastWasNewline = true;
- boolean emptyAnchor;
- /** True if (!emptyDocument && insertTag == null), this is used so
- * much it is cached. */
- boolean midInsert;
- /** True when the body has been encountered. */
- boolean inBody;
- /** If non null, gives parent Tag that insert is to happen at. */
- HTML.Tag insertTag;
- /** If true, the insertTag is inserted, otherwise elements after
- * the insertTag is found are inserted. */
- boolean insertInsertTag;
- /** Set to true when insertTag has been found. */
- boolean foundInsertTag;
- /** When foundInsertTag is set to true, this will be updated to
- * reflect the delta between the two structures. That is, it
- * will be the depth the inserts are happening at minus the
- * depth of the tags being passed in. A value of 0 (the common
- * case) indicates the structures match, a value greater than 0 indicates
- * the insert is happening at a deeper depth than the stream is
- * parsing, and a value less than 0 indicates the insert is happening earlier
- * in the tree that the parser thinks and that we will need to remove
- * EndTagType specs in the flushBuffer method.
- */
- int insertTagDepthDelta;
- /** How many parents to ascend before insert new elements. */
- int popDepth;
- /** How many parents to descend (relative to popDepth) before
- * inserting. */
- int pushDepth;
- /** Last Map that was encountered. */
- Map lastMap;
- /** Set to true when a style element is encountered. */
- boolean inStyle = false;
- /** Name of style to use. Obtained from Meta tag. */
- String defaultStyle;
- /** Vector describing styles that should be include. Will consist
- * of a bunch of HTML.Tags, which will either be:
- * <p>LINK: in which case it is followed by an AttributeSet
- * <p>STYLE: in which case the following element is a String
- * indicating the type (may be null), and the elements following
- * it until the next HTML.Tag are the rules as Strings.
- */
- Vector styles;
- /** True if inside the head tag. */
- boolean inHead = false;
- /** Set to true if the style language is text/css. Since this is
- * used alot, it is cached. */
- boolean isStyleCSS;
- /** True if inserting into an empty document. */
- boolean emptyDocument;
- /** Attributes from a style Attribute. */
- AttributeSet styleAttributes;
-
- /**
- * Current option, if in an option element (needed to
- * load the label.
- */
- Option option;
-
- protected Vector<ElementSpec> parseBuffer = new Vector(); // Vector<ElementSpec>
- protected MutableAttributeSet charAttr = new TaggedAttributeSet();
- Stack charAttrStack = new Stack();
- Hashtable tagMap;
- int inBlock = 0;
- }
-
-
- /**
- * Used by StyleSheet to determine when to avoid removing HTML.Tags
- * matching StyleConstants.
- */
- static class TaggedAttributeSet extends SimpleAttributeSet {
- TaggedAttributeSet() {
- super();
- }
- }
-
-
- /**
- * An element that represents a chunk of text that has
- * a set of HTML character level attributes assigned to
- * it.
- */
- public class RunElement extends LeafElement {
-
- /**
- * Constructs an element that represents content within the
- * document (has no children).
- *
- * @param parent the parent element
- * @param a the element attributes
- * @param offs0 the start offset (must be at least 0)
- * @param offs1 the end offset (must be at least offs0)
- */
- public RunElement(Element parent, AttributeSet a, int offs0, int offs1) {
- super(parent, a, offs0, offs1);
- }
-
- /**
- * Gets the name of the element.
- *
- * @return the name, null if none
- */
- public String getName() {
- Object o = getAttribute(StyleConstants.NameAttribute);
- if (o != null) {
- return o.toString();
- }
- return super.getName();
- }
-
- /**
- * Gets the resolving parent. HTML attributes are not inherited
- * at the model level so we override this to return null.
- *
- * @return null, there are none
- * @see AttributeSet#getResolveParent
- */
- public AttributeSet getResolveParent() {
- return null;
- }
- }
-
- /**
- * An element that represents a structural <em>block</em> of
- * HTML.
- */
- public class BlockElement extends BranchElement {
-
- /**
- * Constructs a composite element that initially contains
- * no children.
- *
- * @param parent the parent element
- * @param a the attributes for the element
- */
- public BlockElement(Element parent, AttributeSet a) {
- super(parent, a);
- }
-
- /**
- * Gets the name of the element.
- *
- * @return the name, null if none
- */
- public String getName() {
- Object o = getAttribute(StyleConstants.NameAttribute);
- if (o != null) {
- return o.toString();
- }
- return super.getName();
- }
-
- /**
- * Gets the resolving parent. HTML attributes are not inherited
- * at the model level so we override this to return null.
- *
- * @return null, there are none
- * @see AttributeSet#getResolveParent
- */
- public AttributeSet getResolveParent() {
- return null;
- }
-
- }
-
-
- /**
- * Document that allows you to set the maximum length of the text.
- */
- private static class FixedLengthDocument extends PlainDocument {
- private int maxLength;
-
- public FixedLengthDocument(int maxLength) {
- this.maxLength = maxLength;
- }
-
- public void insertString(int offset, String str, AttributeSet a)
- throws BadLocationException {
- if (str != null && str.length() + getLength() <= maxLength) {
- super.insertString(offset, str, a);
- }
- }
- }
- }