/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javax.swing.text.html;
import java.awt.font.
TextAttribute;
import java.util.*;
import java.net.
URL;
import java.net.
MalformedURLException;
import java.io.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.undo.*;
import sun.swing.
SwingUtilities2;
import static sun.swing.
SwingUtilities2.
IMPLIED_CR;
/**
* 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>
*
* <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>
*
* <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>
*
* <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.</p>
*
* <h2>Modifying HTMLDocument</h2>
*
* <p>In addition to the methods provided by Document and
* StyledDocument for mutating an HTMLDocument, HTMLDocument provides
* a number of convenience methods. The following methods can be used
* to insert HTML content into an existing document.</p>
*
* <ul>
* <li>{@link #setInnerHTML(Element, String)}</li>
* <li>{@link #setOuterHTML(Element, String)}</li>
* <li>{@link #insertBeforeStart(Element, String)}</li>
* <li>{@link #insertAfterStart(Element, String)}</li>
* <li>{@link #insertBeforeEnd(Element, String)}</li>
* <li>{@link #insertAfterEnd(Element, String)}</li>
* </ul>
*
* <p>The following examples illustrate using these methods. Each
* example assumes the HTML document is initialized in the following
* way:</p>
*
* <pre>
* JEditorPane p = new JEditorPane();
* p.setContentType("text/html");
* p.setText("..."); // Document text is provided below.
* HTMLDocument d = (HTMLDocument) p.getDocument();
* </pre>
*
* <p>With the following HTML content:</p>
*
* <pre>
* <html>
* <head>
* <title>An example HTMLDocument</title>
* <style type="text/css">
* div { background-color: silver; }
* ul { color: red; }
* </style>
* </head>
* <body>
* <div id="BOX">
* <p>Paragraph 1</p>
* <p>Paragraph 2</p>
* </div>
* </body>
* </html>
* </pre>
*
* <p>All the methods for modifying an HTML document require an {@link
* Element}. Elements can be obtained from an HTML document by using
* the method {@link #getElement(Element e, Object attribute, Object
* value)}. It returns the first descendant element that contains the
* specified attribute with the given value, in depth-first order.
* For example, <code>d.getElement(d.getDefaultRootElement(),
* StyleConstants.NameAttribute, HTML.Tag.P)</code> returns the first
* paragraph element.</p>
*
* <p>A convenient shortcut for locating elements is the method {@link
* #getElement(String)}; returns an element whose <code>ID</code>
* attribute matches the specified value. For example,
* <code>d.getElement("BOX")</code> returns the <code>DIV</code>
* element.</p>
*
* <p>The {@link #getIterator(HTML.Tag t)} method can also be used for
* finding all occurrences of the specified HTML tag in the
* document.</p>
*
* <h3>Inserting elements</h3>
*
* <p>Elements can be inserted before or after the existing children
* of any non-leaf element by using the methods
* <code>insertAfterStart</code> and <code>insertBeforeEnd</code>.
* For example, if <code>e</code> is the <code>DIV</code> element,
* <code>d.insertAfterStart(e, "<ul><li>List
* Item</li></ul>")</code> inserts the list before the first
* paragraph, and <code>d.insertBeforeEnd(e, "<ul><li>List
* Item</li></ul>")</code> inserts the list after the last
* paragraph. The <code>DIV</code> block becomes the parent of the
* newly inserted elements.</p>
*
* <p>Sibling elements can be inserted before or after any element by
* using the methods <code>insertBeforeStart</code> and
* <code>insertAfterEnd</code>. For example, if <code>e</code> is the
* <code>DIV</code> element, <code>d.insertBeforeStart(e,
* "<ul><li>List Item</li></ul>")</code> inserts the list
* before the <code>DIV</code> element, and <code>d.insertAfterEnd(e,
* "<ul><li>List Item</li></ul>")</code> inserts the list
* after the <code>DIV</code> element. The newly inserted elements
* become siblings of the <code>DIV</code> element.</p>
*
* <h3>Replacing elements</h3>
*
* <p>Elements and all their descendants can be replaced by using the
* methods <code>setInnerHTML</code> and <code>setOuterHTML</code>.
* For example, if <code>e</code> is the <code>DIV</code> element,
* <code>d.setInnerHTML(e, "<ul><li>List
* Item</li></ul>")</code> replaces all children paragraphs with
* the list, and <code>d.setOuterHTML(e, "<ul><li>List
* Item</li></ul>")</code> replaces the <code>DIV</code> element
* itself. In latter case the parent of the list is the
* <code>BODY</code> element.
*
* <h3>Summary</h3>
*
* <p>The following table shows the example document and the results
* of various methods described above.</p>
*
* <table border=1 cellspacing=0>
* <tr>
* <th>Example</th>
* <th><code>insertAfterStart</code></th>
* <th><code>insertBeforeEnd</code></th>
* <th><code>insertBeforeStart</code></th>
* <th><code>insertAfterEnd</code></th>
* <th><code>setInnerHTML</code></th>
* <th><code>setOuterHTML</code></th>
* </tr>
* <tr valign="top">
* <td style="white-space:nowrap">
* <div style="background-color: silver;">
* <p>Paragraph 1</p>
* <p>Paragraph 2</p>
* </div>
* </td>
* <!--insertAfterStart-->
* <td style="white-space:nowrap">
* <div style="background-color: silver;">
* <ul style="color: red;">
* <li>List Item</li>
* </ul>
* <p>Paragraph 1</p>
* <p>Paragraph 2</p>
* </div>
* </td>
* <!--insertBeforeEnd-->
* <td style="white-space:nowrap">
* <div style="background-color: silver;">
* <p>Paragraph 1</p>
* <p>Paragraph 2</p>
* <ul style="color: red;">
* <li>List Item</li>
* </ul>
* </div>
* </td>
* <!--insertBeforeStart-->
* <td style="white-space:nowrap">
* <ul style="color: red;">
* <li>List Item</li>
* </ul>
* <div style="background-color: silver;">
* <p>Paragraph 1</p>
* <p>Paragraph 2</p>
* </div>
* </td>
* <!--insertAfterEnd-->
* <td style="white-space:nowrap">
* <div style="background-color: silver;">
* <p>Paragraph 1</p>
* <p>Paragraph 2</p>
* </div>
* <ul style="color: red;">
* <li>List Item</li>
* </ul>
* </td>
* <!--setInnerHTML-->
* <td style="white-space:nowrap">
* <div style="background-color: silver;">
* <ul style="color: red;">
* <li>List Item</li>
* </ul>
* </div>
* </td>
* <!--setOuterHTML-->
* <td style="white-space:nowrap">
* <ul style="color: red;">
* <li>List Item</li>
* </ul>
* </td>
* </tr>
* </table>
*
* <p><strong>Warning:</strong> Serialized objects of this class will
* not be compatible with future Swing releases. The current
* serialization support is appropriate for short term storage or RMI
* between applications running the same version of Swing. As of 1.4,
* support for long term storage of all JavaBeans™
* has been added to the
* <code>java.beans</code> package. Please see {@link
* java.beans.XMLEncoder}.</p>
*
* @author Timothy Prinzing
* @author Scott Violet
* @author Sunita Mani
*/
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="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
* in 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;
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 name 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>
*
* <p>Consider the following structure (the <code>elem</code>
* parameter is <b>in bold</b>).</p>
*
* <pre>
* <body>
* |
* <b><div></b>
* / \
* <p> <p>
* </pre>
*
* <p>Invoking <code>setInnerHTML(elem, "<ul><li>")</code>
* results in the following structure (new elements are <font
* color="red">in red</font>).</p>
*
* <pre>
* <body>
* |
* <b><div></b>
* \
* <font color="red"><ul></font>
* \
* <font color="red"><li></font>
* </pre>
*
* <p>Parameter <code>elem</code> must not be a leaf element,
* otherwise an <code>IllegalArgumentException</code> is thrown.
* If either <code>elem</code> or <code>htmlText</code> parameter
* is <code>null</code>, no changes are made to the document.</p>
*
* <p>For this to work correctly, 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.</p>
*
* @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>
*
* <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, and one for the newline.</p>
*
* <p>If you try to replace the element at length you will most
* likely end up with two elements, eg
* <code>setOuterHTML(getCharacterElement (getLength()),
* "blah")</code> will result in two leaf elements at the end, one
* representing 'blah', and the other representing the end
* element.</p>
*
* <p>Consider the following structure (the <code>elem</code>
* parameter is <b>in bold</b>).</p>
*
* <pre>
* <body>
* |
* <b><div></b>
* / \
* <p> <p>
* </pre>
*
* <p>Invoking <code>setOuterHTML(elem, "<ul><li>")</code>
* results in the following structure (new elements are <font
* color="red">in red</font>).</p>
*
* <pre>
* <body>
* |
* <font color="red"><ul></font>
* \
* <font color="red"><li></font>
* </pre>
*
* <p>If either <code>elem</code> or <code>htmlText</code>
* parameter is <code>null</code>, no changes are made to the
* document.</p>
*
* <p>For this to work correctly, 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.</p>
*
* @param elem the element to replace
* @param htmlText the string to be parsed and inserted in place of <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>Consider the following structure (the <code>elem</code>
* parameter is <b>in bold</b>).</p>
*
* <pre>
* <body>
* |
* <b><div></b>
* / \
* <p> <p>
* </pre>
*
* <p>Invoking <code>insertAfterStart(elem,
* "<ul><li>")</code> results in the following structure
* (new elements are <font color="red">in red</font>).</p>
*
* <pre>
* <body>
* |
* <b><div></b>
* / | \
* <font color="red"><ul></font> <p> <p>
* /
* <font color="red"><li></font>
* </pre>
*
* <p>Unlike the <code>insertBeforeStart</code> method, new
* elements become <em>children</em> of the specified element,
* not siblings.</p>
*
* <p>Parameter <code>elem</code> must not be a leaf element,
* otherwise an <code>IllegalArgumentException</code> is thrown.
* If either <code>elem</code> or <code>htmlText</code> parameter
* is <code>null</code>, no changes are made to the document.</p>
*
* <p>For this to work correctly, 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.</p>
*
* @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 IllegalArgumentException if <code>elem</code> is a leaf
* @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 ||
htmlText == null) {
return;
}
if (
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>
*
* <p>Consider the following structure (the <code>elem</code>
* parameter is <b>in bold</b>).</p>
*
* <pre>
* <body>
* |
* <b><div></b>
* / \
* <p> <p>
* </pre>
*
* <p>Invoking <code>insertBeforeEnd(elem, "<ul><li>")</code>
* results in the following structure (new elements are <font
* color="red">in red</font>).</p>
*
* <pre>
* <body>
* |
* <b><div></b>
* / | \
* <p> <p> <font color="red"><ul></font>
* \
* <font color="red"><li></font>
* </pre>
*
* <p>Unlike the <code>insertAfterEnd</code> method, new elements
* become <em>children</em> of the specified element, not
* siblings.</p>
*
* <p>Parameter <code>elem</code> must not be a leaf element,
* otherwise an <code>IllegalArgumentException</code> is thrown.
* If either <code>elem</code> or <code>htmlText</code> parameter
* is <code>null</code>, no changes are made to the document.</p>
*
* <p>For this to work correctly, 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.</p>
*
* @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 IllegalArgumentException if <code>elem</code> is a leaf
* @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");
}
if (
elem != null) {
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>Consider the following structure (the <code>elem</code>
* parameter is <b>in bold</b>).</p>
*
* <pre>
* <body>
* |
* <b><div></b>
* / \
* <p> <p>
* </pre>
*
* <p>Invoking <code>insertBeforeStart(elem,
* "<ul><li>")</code> results in the following structure
* (new elements are <font color="red">in red</font>).</p>
*
* <pre>
* <body>
* / \
* <font color="red"><ul></font> <b><div></b>
* / / \
* <font color="red"><li></font> <p> <p>
* </pre>
*
* <p>Unlike the <code>insertAfterStart</code> method, new
* elements become <em>siblings</em> of the specified element, not
* children.</p>
*
* <p>If either <code>elem</code> or <code>htmlText</code>
* parameter is <code>null</code>, no changes are made to the
* document.</p>
*
* <p>For this to work correctly, 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.</p>
*
* @param elem the element the content is inserted before
* @param htmlText the string to be parsed and inserted before <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>Consider the following structure (the <code>elem</code>
* parameter is <b>in bold</b>).</p>
*
* <pre>
* <body>
* |
* <b><div></b>
* / \
* <p> <p>
* </pre>
*
* <p>Invoking <code>insertAfterEnd(elem, "<ul><li>")</code>
* results in the following structure (new elements are <font
* color="red">in red</font>).</p>
*
* <pre>
* <body>
* / \
* <b><div></b> <font color="red"><ul></font>
* / \ \
* <p> <p> <font color="red"><li></font>
* </pre>
*
* <p>Unlike the <code>insertBeforeEnd</code> method, new elements
* become <em>siblings</em> of the specified element, not
* children.</p>
*
* <p>If either <code>elem</code> or <code>htmlText</code>
* parameter is <code>null</code>, no changes are made to the
* document.</p>
*
* <p>For this to work correctly, 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.</p>
*
* @param elem the element the content is inserted after
* @param htmlText the string to be parsed and inserted after <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) {
// If we are going to insert the string into the body
// section, it is necessary to set the corrsponding flag.
if (
HTML.
Tag.
BODY.
name.
equals(
parent.
getName())) {
insertInBody = true;
}
int
offset =
elem.
getEndOffset();
if (
offset > (
getLength() + 1)) {
offset--;
}
else if (
elem.
isLeaf() &&
getText(
offset - 1, 1).
charAt(0) ==
NEWLINE[0]) {
offset--;
}
insertHTML(
parent,
offset,
htmlText, false);
// Cleanup the flag, if any.
if (
insertInBody) {
insertInBody = 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<
String,
ButtonGroup>
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;
/**
* Indicates that direct insertion to body section takes place.
*/
private boolean
insertInBody = false;
/**
* I18N property key.
*
* @see AbstractDocument#I18NProperty
*/
private static final
String I18NProperty = "i18n";
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);
if (
a == null) {
a =
elem.
getAttributes();
}
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>.
* <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<
HTML.
Tag,
TagAction>(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();
}
}
/**
* This block initializes the <code>inParagraph</code> flag.
* It is left in <code>false</code> value automatically
* if the target document is empty or future inserts
* were positioned into the 'body' tag.
*/
if (!
emptyDocument && !
midInsert) {
int
targetOffset =
Math.
max(this.
offset - 1, 0);
Element elem =
HTMLDocument.this.
getCharacterElement(
targetOffset);
/* Going up by the left document structure path */
for (int
i = 0;
i <= this.
popDepth;
i++) {
elem =
elem.
getParentElement();
}
/* Going down by the right document structure path */
for (int
i = 0;
i < this.
pushDepth;
i++) {
int
index =
elem.
getElementIndex(this.
offset);
elem =
elem.
getElement(
index);
}
AttributeSet attrs =
elem.
getAttributes();
if (
attrs != null) {
HTML.
Tag tagToInsertInto =
(
HTML.
Tag)
attrs.
getAttribute(
StyleConstants.
NameAttribute);
if (
tagToInsertInto != null) {
this.
inParagraph =
tagToInsertInto.
isParagraph();
}
}
}
}
/**
* 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;
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<
Element>
elements = new
Stack<
Element>();
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 (
SwingUtilities2.
isComplexLayout(
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;
// If content is added directly to the body, it should
// be wrapped by p-implied.
inParagraph =
impliedP = !
insertInBody;
}
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 =
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 =
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 =
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 =
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<
String,
ButtonGroup>();
}
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<
Object>(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<
Object>(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] = '\n';
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 =
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 the 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 =
attr.
getAttribute(
HTML.
Attribute.
MULTIPLE) != null;
if ((
size > 1) ||
multiple) {
OptionListModel<
Option>
m = new
OptionListModel<
Option>();
if (
multiple) {
m.
setSelectionMode(
ListSelectionModel.
MULTIPLE_INTERVAL_SELECTION);
}
selectModel =
m;
} else {
selectModel = new
OptionComboBoxModel<
Option>();
}
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<
Option>
m = (
OptionListModel<
Option>)
selectModel;
m.
addElement(
option);
if (
option.
isSelected()) {
m.
addSelectionInterval(
optionCount,
optionCount);
m.
setInitialSelection(
optionCount);
}
} else if (
selectModel instanceof
OptionComboBoxModel) {
OptionComboBoxModel<
Option>
m = (
OptionComboBoxModel<
Option>)
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();
}
String value = (
String)
attr.
getAttribute(
HTML.
Attribute.
VALUE);
try {
doc.
insertString(0,
value, null);
} catch (
BadLocationException e) {
}
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<
String,
ButtonGroup>();
}
ButtonGroup radioButtonGroup =
radioButtonGroupsMap.
get(
name);
if (
radioButtonGroup == null) {
radioButtonGroup = new
ButtonGroup();
radioButtonGroupsMap.
put(
name,
radioButtonGroup);
}
model.
setGroup(
radioButtonGroup);
}
boolean
checked = (
attr.
getAttribute(
HTML.
Attribute.
CHECKED) != null);
model.
setSelected(
checked);
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) ?
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 data the content to add
* @param offs the initial offset
* @param length the length
*/
protected void
addContent(char[]
data, int
offs, int
length) {
addContent(
data,
offs,
length, true);
}
/**
* Adds some text with the current character attributes.
*
* @param data the content to add
* @param offs the initial offset
* @param length the length
* @param generateImpliedPIfNecessary whether to generate implied
* paragraphs
*/
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)) {
nextTagAfterPImplied =
t;
blockOpen(
HTML.
Tag.
IMPLIED, new
SimpleAttributeSet());
nextTagAfterPImplied = null;
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 hierarchy) may not match up.
// This attemps to clean it up.
int
removeCounter =
insertTagDepthDelta;
while (
removeCounter < 0 &&
size >= 0 &&
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 (
parseBuffer.
elementAt(0).
getType() ==
ElementSpec.
ContentType) {
index++;
}
}
index += (
popDepth +
pushDepth);
int
cCount = 0;
int
cStart =
index;
while (
index <
size &&
parseBuffer.
elementAt
(
index).
getType() ==
ElementSpec.
ContentType) {
index++;
cCount++;
}
if (
cCount > 1) {
while (
index <
size &&
parseBuffer.
elementAt
(
index).
getType() ==
ElementSpec.
EndTagType) {
index++;
}
if (
index ==
size) {
char[]
lastText =
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 =
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;
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) {
boolean
needPImplied = ((
t ==
HTML.
Tag.
IMPLIED)
&& (!
inParagraph)
&& (!
inPre));
if (
needPImplied && (
nextTagAfterPImplied != null)) {
/*
* If insertTag == null then just proceed to
* foundInsertTag() call below and return true.
*/
if (
insertTag != null) {
boolean
nextTagIsInsertTag =
isInsertTag(
nextTagAfterPImplied);
if ( (!
nextTagIsInsertTag) || (!
insertInsertTag) ) {
return false;
}
}
/*
* Proceed to foundInsertTag() call...
*/
} else if ((
insertTag != null && !
isInsertTag(
t))
|| (
insertAfterImplied
&& (
attr == null
||
attr.
isDefined(
IMPLIED)
||
t ==
HTML.
Tag.
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 {@literal <html>}.
*/
private boolean
receivedEndHTML;
/** Number of times <code>flushBuffer</code> has been invoked. */
private int
flushCount;
/** If true, behavior is similar 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<
Object>
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<
ElementSpec>();
protected
MutableAttributeSet charAttr = new
TaggedAttributeSet();
Stack<
AttributeSet>
charAttrStack = new
Stack<
AttributeSet>();
Hashtable<
HTML.
Tag,
TagAction>
tagMap;
int
inBlock = 0;
/**
* This attribute is sometimes used to refer to next tag
* to be handled after p-implied when the latter is
* the current tag which is being handled.
*/
private
HTML.
Tag nextTagAfterPImplied = null;
}
/**
* 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)
* @since 1.4
*/
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
* @since 1.4
*/
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);
}
}
}
}