aboutsummaryrefslogtreecommitdiff
path: root/src/share/classes/javax/swing/text/html
diff options
context:
space:
mode:
authorduke <none@none>2007-12-01 00:00:00 +0000
committerduke <none@none>2007-12-01 00:00:00 +0000
commit59308f67f9b7038cfa2ceb9ee9ba27645b927cb5 (patch)
tree182810ab2fece13f57a928d026f93e9ede0827f9 /src/share/classes/javax/swing/text/html
Initial loadjdk7-b24
Diffstat (limited to 'src/share/classes/javax/swing/text/html')
-rw-r--r--src/share/classes/javax/swing/text/html/AccessibleHTML.java3058
-rw-r--r--src/share/classes/javax/swing/text/html/BRView.java57
-rw-r--r--src/share/classes/javax/swing/text/html/BlockView.java443
-rw-r--r--src/share/classes/javax/swing/text/html/CSS.java3383
-rw-r--r--src/share/classes/javax/swing/text/html/CSSBorder.java435
-rw-r--r--src/share/classes/javax/swing/text/html/CSSParser.java854
-rw-r--r--src/share/classes/javax/swing/text/html/CommentView.java141
-rw-r--r--src/share/classes/javax/swing/text/html/EditableView.java128
-rw-r--r--src/share/classes/javax/swing/text/html/FormSubmitEvent.java92
-rw-r--r--src/share/classes/javax/swing/text/html/FormView.java932
-rw-r--r--src/share/classes/javax/swing/text/html/FrameSetView.java322
-rw-r--r--src/share/classes/javax/swing/text/html/FrameView.java479
-rw-r--r--src/share/classes/javax/swing/text/html/HRuleView.java316
-rw-r--r--src/share/classes/javax/swing/text/html/HTML.java697
-rw-r--r--src/share/classes/javax/swing/text/html/HTMLDocument.java4185
-rw-r--r--src/share/classes/javax/swing/text/html/HTMLEditorKit.java2279
-rw-r--r--src/share/classes/javax/swing/text/html/HTMLFrameHyperlinkEvent.java134
-rw-r--r--src/share/classes/javax/swing/text/html/HTMLWriter.java1276
-rw-r--r--src/share/classes/javax/swing/text/html/HiddenTagView.java361
-rw-r--r--src/share/classes/javax/swing/text/html/ImageView.java997
-rw-r--r--src/share/classes/javax/swing/text/html/InlineView.java225
-rw-r--r--src/share/classes/javax/swing/text/html/IsindexView.java114
-rw-r--r--src/share/classes/javax/swing/text/html/LineView.java186
-rw-r--r--src/share/classes/javax/swing/text/html/ListView.java122
-rw-r--r--src/share/classes/javax/swing/text/html/Map.java505
-rw-r--r--src/share/classes/javax/swing/text/html/MinimalHTMLWriter.java726
-rw-r--r--src/share/classes/javax/swing/text/html/MuxingAttributeSet.java311
-rw-r--r--src/share/classes/javax/swing/text/html/NoFramesView.java173
-rw-r--r--src/share/classes/javax/swing/text/html/ObjectView.java182
-rw-r--r--src/share/classes/javax/swing/text/html/Option.java120
-rw-r--r--src/share/classes/javax/swing/text/html/OptionComboBoxModel.java63
-rw-r--r--src/share/classes/javax/swing/text/html/OptionListModel.java571
-rw-r--r--src/share/classes/javax/swing/text/html/ParagraphView.java294
-rw-r--r--src/share/classes/javax/swing/text/html/ResourceLoader.java60
-rw-r--r--src/share/classes/javax/swing/text/html/StyleSheet.java3340
-rw-r--r--src/share/classes/javax/swing/text/html/TableView.java1801
-rw-r--r--src/share/classes/javax/swing/text/html/TextAreaDocument.java68
-rw-r--r--src/share/classes/javax/swing/text/html/default.css267
-rw-r--r--src/share/classes/javax/swing/text/html/package.html59
-rw-r--r--src/share/classes/javax/swing/text/html/parser/AttributeList.java172
-rw-r--r--src/share/classes/javax/swing/text/html/parser/ContentModel.java255
-rw-r--r--src/share/classes/javax/swing/text/html/parser/ContentModelState.java295
-rw-r--r--src/share/classes/javax/swing/text/html/parser/DTD.java451
-rw-r--r--src/share/classes/javax/swing/text/html/parser/DTDConstants.java82
-rw-r--r--src/share/classes/javax/swing/text/html/parser/DocumentParser.java281
-rw-r--r--src/share/classes/javax/swing/text/html/parser/Element.java175
-rw-r--r--src/share/classes/javax/swing/text/html/parser/Entity.java139
-rw-r--r--src/share/classes/javax/swing/text/html/parser/Parser.java2312
-rw-r--r--src/share/classes/javax/swing/text/html/parser/ParserDelegator.java120
-rw-r--r--src/share/classes/javax/swing/text/html/parser/ResourceLoader.java60
-rw-r--r--src/share/classes/javax/swing/text/html/parser/TagElement.java74
-rw-r--r--src/share/classes/javax/swing/text/html/parser/TagStack.java220
-rw-r--r--src/share/classes/javax/swing/text/html/parser/html32.bdtdbin0 -> 16173 bytes
-rw-r--r--src/share/classes/javax/swing/text/html/parser/package.html54
54 files changed, 34446 insertions, 0 deletions
diff --git a/src/share/classes/javax/swing/text/html/AccessibleHTML.java b/src/share/classes/javax/swing/text/html/AccessibleHTML.java
new file mode 100644
index 000000000..a4680179c
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/AccessibleHTML.java
@@ -0,0 +1,3058 @@
+/*
+ * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.*;
+import javax.accessibility.*;
+import java.text.BreakIterator;
+
+/*
+ * The AccessibleHTML class provide information about the contents
+ * of a HTML document to assistive technologies.
+ *
+ * @author Lynn Monsanto
+ */
+class AccessibleHTML implements Accessible {
+
+ /**
+ * The editor.
+ */
+ private JEditorPane editor;
+ /**
+ * Current model.
+ */
+ private Document model;
+ /**
+ * DocumentListener installed on the current model.
+ */
+ private DocumentListener docListener;
+ /**
+ * PropertyChangeListener installed on the editor
+ */
+ private PropertyChangeListener propChangeListener;
+ /**
+ * The root ElementInfo for the document
+ */
+ private ElementInfo rootElementInfo;
+ /*
+ * The root accessible context for the document
+ */
+ private RootHTMLAccessibleContext rootHTMLAccessibleContext;
+
+ public AccessibleHTML(JEditorPane pane) {
+ editor = pane;
+ propChangeListener = new PropertyChangeHandler();
+ setDocument(editor.getDocument());
+
+ docListener = new DocumentHandler();
+ }
+
+ /**
+ * Sets the document.
+ */
+ private void setDocument(Document document) {
+ if (model != null) {
+ model.removeDocumentListener(docListener);
+ }
+ if (editor != null) {
+ editor.removePropertyChangeListener(propChangeListener);
+ }
+ this.model = document;
+ if (model != null) {
+ if (rootElementInfo != null) {
+ rootElementInfo.invalidate(false);
+ }
+ buildInfo();
+ model.addDocumentListener(docListener);
+ }
+ else {
+ rootElementInfo = null;
+ }
+ if (editor != null) {
+ editor.addPropertyChangeListener(propChangeListener);
+ }
+ }
+
+ /**
+ * Returns the Document currently presenting information for.
+ */
+ private Document getDocument() {
+ return model;
+ }
+
+ /**
+ * Returns the JEditorPane providing information for.
+ */
+ private JEditorPane getTextComponent() {
+ return editor;
+ }
+
+ /**
+ * Returns the ElementInfo representing the root Element.
+ */
+ private ElementInfo getRootInfo() {
+ return rootElementInfo;
+ }
+
+ /**
+ * Returns the root <code>View</code> associated with the current text
+ * component.
+ */
+ private View getRootView() {
+ return getTextComponent().getUI().getRootView(getTextComponent());
+ }
+
+ /**
+ * Returns the bounds the root View will be rendered in.
+ */
+ private Rectangle getRootEditorRect() {
+ Rectangle alloc = getTextComponent().getBounds();
+ if ((alloc.width > 0) && (alloc.height > 0)) {
+ alloc.x = alloc.y = 0;
+ Insets insets = editor.getInsets();
+ alloc.x += insets.left;
+ alloc.y += insets.top;
+ alloc.width -= insets.left + insets.right;
+ alloc.height -= insets.top + insets.bottom;
+ return alloc;
+ }
+ return null;
+ }
+
+ /**
+ * If possible acquires a lock on the Document. If a lock has been
+ * obtained a key will be retured that should be passed to
+ * <code>unlock</code>.
+ */
+ private Object lock() {
+ Document document = getDocument();
+
+ if (document instanceof AbstractDocument) {
+ ((AbstractDocument)document).readLock();
+ return document;
+ }
+ return null;
+ }
+
+ /**
+ * Releases a lock previously obtained via <code>lock</code>.
+ */
+ private void unlock(Object key) {
+ if (key != null) {
+ ((AbstractDocument)key).readUnlock();
+ }
+ }
+
+ /**
+ * Rebuilds the information from the current info.
+ */
+ private void buildInfo() {
+ Object lock = lock();
+
+ try {
+ Document doc = getDocument();
+ Element root = doc.getDefaultRootElement();
+
+ rootElementInfo = new ElementInfo(root);
+ rootElementInfo.validate();
+ } finally {
+ unlock(lock);
+ }
+ }
+
+ /*
+ * Create an ElementInfo subclass based on the passed in Element.
+ */
+ ElementInfo createElementInfo(Element e, ElementInfo parent) {
+ AttributeSet attrs = e.getAttributes();
+
+ if (attrs != null) {
+ Object name = attrs.getAttribute(StyleConstants.NameAttribute);
+
+ if (name == HTML.Tag.IMG) {
+ return new IconElementInfo(e, parent);
+ }
+ else if (name == HTML.Tag.CONTENT || name == HTML.Tag.CAPTION) {
+ return new TextElementInfo(e, parent);
+ }
+ else if (name == HTML.Tag.TABLE) {
+ return new TableElementInfo(e, parent);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the root AccessibleContext for the document
+ */
+ public AccessibleContext getAccessibleContext() {
+ if (rootHTMLAccessibleContext == null) {
+ rootHTMLAccessibleContext =
+ new RootHTMLAccessibleContext(rootElementInfo);
+ }
+ return rootHTMLAccessibleContext;
+ }
+
+ /*
+ * The roow AccessibleContext for the document
+ */
+ private class RootHTMLAccessibleContext extends HTMLAccessibleContext {
+
+ public RootHTMLAccessibleContext(ElementInfo elementInfo) {
+ super(elementInfo);
+ }
+
+ /**
+ * Gets the accessibleName property of this object. The accessibleName
+ * property of an object is a localized String that designates the purpose
+ * of the object. For example, the accessibleName property of a label
+ * or button might be the text of the label or button itself. In the
+ * case of an object that doesn't display its name, the accessibleName
+ * should still be set. For example, in the case of a text field used
+ * to enter the name of a city, the accessibleName for the en_US locale
+ * could be 'city.'
+ *
+ * @return the localized name of the object; null if this
+ * object does not have a name
+ *
+ * @see #setAccessibleName
+ */
+ public String getAccessibleName() {
+ if (model != null) {
+ return (String)model.getProperty(Document.TitleProperty);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the accessibleDescription property of this object. If this
+ * property isn't set, returns the content type of this
+ * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
+ *
+ * @return the localized description of the object; <code>null</code>
+ * if this object does not have a description
+ *
+ * @see #setAccessibleName
+ */
+ public String getAccessibleDescription() {
+ return editor.getContentType();
+ }
+
+ /**
+ * Gets the role of this object. The role of the object is the generic
+ * purpose or use of the class of this object. For example, the role
+ * of a push button is AccessibleRole.PUSH_BUTTON. The roles in
+ * AccessibleRole are provided so component developers can pick from
+ * a set of predefined roles. This enables assistive technologies to
+ * provide a consistent interface to various tweaked subclasses of
+ * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
+ * that act like a push button) as well as distinguish between sublasses
+ * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
+ * and AccessibleRole.RADIO_BUTTON for radio buttons).
+ * <p>Note that the AccessibleRole class is also extensible, so
+ * custom component developers can define their own AccessibleRole's
+ * if the set of predefined roles is inadequate.
+ *
+ * @return an instance of AccessibleRole describing the role of the object
+ * @see AccessibleRole
+ */
+ public AccessibleRole getAccessibleRole() {
+ return AccessibleRole.TEXT;
+ }
+ }
+
+ /*
+ * Base AccessibleContext class for HTML elements
+ */
+ protected abstract class HTMLAccessibleContext extends AccessibleContext
+ implements Accessible, AccessibleComponent {
+
+ protected ElementInfo elementInfo;
+
+ public HTMLAccessibleContext(ElementInfo elementInfo) {
+ this.elementInfo = elementInfo;
+ }
+
+ // begin AccessibleContext implementation ...
+ public AccessibleContext getAccessibleContext() {
+ return this;
+ }
+
+ /**
+ * Gets the state set of this object.
+ *
+ * @return an instance of AccessibleStateSet describing the states
+ * of the object
+ * @see AccessibleStateSet
+ */
+ public AccessibleStateSet getAccessibleStateSet() {
+ AccessibleStateSet states = new AccessibleStateSet();
+ Component comp = getTextComponent();
+
+ if (comp.isEnabled()) {
+ states.add(AccessibleState.ENABLED);
+ }
+ if (comp instanceof JTextComponent &&
+ ((JTextComponent)comp).isEditable()) {
+
+ states.add(AccessibleState.EDITABLE);
+ states.add(AccessibleState.FOCUSABLE);
+ }
+ if (comp.isVisible()) {
+ states.add(AccessibleState.VISIBLE);
+ }
+ if (comp.isShowing()) {
+ states.add(AccessibleState.SHOWING);
+ }
+ return states;
+ }
+
+ /**
+ * Gets the 0-based index of this object in its accessible parent.
+ *
+ * @return the 0-based index of this object in its parent; -1 if this
+ * object does not have an accessible parent.
+ *
+ * @see #getAccessibleParent
+ * @see #getAccessibleChildrenCount
+ * @see #getAccessibleChild
+ */
+ public int getAccessibleIndexInParent() {
+ return elementInfo.getIndexInParent();
+ }
+
+ /**
+ * Returns the number of accessible children of the object.
+ *
+ * @return the number of accessible children of the object.
+ */
+ public int getAccessibleChildrenCount() {
+ return elementInfo.getChildCount();
+ }
+
+ /**
+ * Returns the specified Accessible child of the object. The Accessible
+ * children of an Accessible object are zero-based, so the first child
+ * of an Accessible child is at index 0, the second child is at index 1,
+ * and so on.
+ *
+ * @param i zero-based index of child
+ * @return the Accessible child of the object
+ * @see #getAccessibleChildrenCount
+ */
+ public Accessible getAccessibleChild(int i) {
+ ElementInfo childInfo = elementInfo.getChild(i);
+ if (childInfo != null && childInfo instanceof Accessible) {
+ return (Accessible)childInfo;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the locale of the component. If the component does not have a
+ * locale, then the locale of its parent is returned.
+ *
+ * @return this component's locale. If this component does not have
+ * a locale, the locale of its parent is returned.
+ *
+ * @exception IllegalComponentStateException
+ * If the Component does not have its own locale and has not yet been
+ * added to a containment hierarchy such that the locale can be
+ * determined from the containing parent.
+ */
+ public Locale getLocale() throws IllegalComponentStateException {
+ return editor.getLocale();
+ }
+ // ... end AccessibleContext implementation
+
+ // begin AccessibleComponent implementation ...
+ public AccessibleComponent getAccessibleComponent() {
+ return this;
+ }
+
+ /**
+ * Gets the background color of this object.
+ *
+ * @return the background color, if supported, of the object;
+ * otherwise, null
+ * @see #setBackground
+ */
+ public Color getBackground() {
+ return getTextComponent().getBackground();
+ }
+
+ /**
+ * Sets the background color of this object.
+ *
+ * @param c the new Color for the background
+ * @see #setBackground
+ */
+ public void setBackground(Color c) {
+ getTextComponent().setBackground(c);
+ }
+
+ /**
+ * Gets the foreground color of this object.
+ *
+ * @return the foreground color, if supported, of the object;
+ * otherwise, null
+ * @see #setForeground
+ */
+ public Color getForeground() {
+ return getTextComponent().getForeground();
+ }
+
+ /**
+ * Sets the foreground color of this object.
+ *
+ * @param c the new Color for the foreground
+ * @see #getForeground
+ */
+ public void setForeground(Color c) {
+ getTextComponent().setForeground(c);
+ }
+
+ /**
+ * Gets the Cursor of this object.
+ *
+ * @return the Cursor, if supported, of the object; otherwise, null
+ * @see #setCursor
+ */
+ public Cursor getCursor() {
+ return getTextComponent().getCursor();
+ }
+
+ /**
+ * Sets the Cursor of this object.
+ *
+ * @param c the new Cursor for the object
+ * @see #getCursor
+ */
+ public void setCursor(Cursor cursor) {
+ getTextComponent().setCursor(cursor);
+ }
+
+ /**
+ * Gets the Font of this object.
+ *
+ * @return the Font,if supported, for the object; otherwise, null
+ * @see #setFont
+ */
+ public Font getFont() {
+ return getTextComponent().getFont();
+ }
+
+ /**
+ * Sets the Font of this object.
+ *
+ * @param f the new Font for the object
+ * @see #getFont
+ */
+ public void setFont(Font f) {
+ getTextComponent().setFont(f);
+ }
+
+ /**
+ * Gets the FontMetrics of this object.
+ *
+ * @param f the Font
+ * @return the FontMetrics, if supported, the object; otherwise, null
+ * @see #getFont
+ */
+ public FontMetrics getFontMetrics(Font f) {
+ return getTextComponent().getFontMetrics(f);
+ }
+
+ /**
+ * Determines if the object is enabled. Objects that are enabled
+ * will also have the AccessibleState.ENABLED state set in their
+ * AccessibleStateSets.
+ *
+ * @return true if object is enabled; otherwise, false
+ * @see #setEnabled
+ * @see AccessibleContext#getAccessibleStateSet
+ * @see AccessibleState#ENABLED
+ * @see AccessibleStateSet
+ */
+ public boolean isEnabled() {
+ return getTextComponent().isEnabled();
+ }
+
+ /**
+ * Sets the enabled state of the object.
+ *
+ * @param b if true, enables this object; otherwise, disables it
+ * @see #isEnabled
+ */
+ public void setEnabled(boolean b) {
+ getTextComponent().setEnabled(b);
+ }
+
+ /**
+ * Determines if the object is visible. Note: this means that the
+ * object intends to be visible; however, it may not be
+ * showing on the screen because one of the objects that this object
+ * is contained by is currently not visible. To determine if an object
+ * is showing on the screen, use isShowing().
+ * <p>Objects that are visible will also have the
+ * AccessibleState.VISIBLE state set in their AccessibleStateSets.
+ *
+ * @return true if object is visible; otherwise, false
+ * @see #setVisible
+ * @see AccessibleContext#getAccessibleStateSet
+ * @see AccessibleState#VISIBLE
+ * @see AccessibleStateSet
+ */
+ public boolean isVisible() {
+ return getTextComponent().isVisible();
+ }
+
+ /**
+ * Sets the visible state of the object.
+ *
+ * @param b if true, shows this object; otherwise, hides it
+ * @see #isVisible
+ */
+ public void setVisible(boolean b) {
+ getTextComponent().setVisible(b);
+ }
+
+ /**
+ * Determines if the object is showing. This is determined by checking
+ * the visibility of the object and its ancestors.
+ * Note: this
+ * will return true even if the object is obscured by another (for
+ * example, it is underneath a menu that was pulled down).
+ *
+ * @return true if object is showing; otherwise, false
+ */
+ public boolean isShowing() {
+ return getTextComponent().isShowing();
+ }
+
+ /**
+ * Checks whether the specified point is within this object's bounds,
+ * where the point's x and y coordinates are defined to be relative
+ * to the coordinate system of the object.
+ *
+ * @param p the Point relative to the coordinate system of the object
+ * @return true if object contains Point; otherwise false
+ * @see #getBounds
+ */
+ public boolean contains(Point p) {
+ Rectangle r = getBounds();
+ if (r != null) {
+ return r.contains(p.x, p.y);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the location of the object on the screen.
+ *
+ * @return the location of the object on screen; null if this object
+ * is not on the screen
+ * @see #getBounds
+ * @see #getLocation
+ */
+ public Point getLocationOnScreen() {
+ Point editorLocation = getTextComponent().getLocationOnScreen();
+ Rectangle r = getBounds();
+ if (r != null) {
+ return new Point(editorLocation.x + r.x,
+ editorLocation.y + r.y);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the location of the object relative to the parent in the form
+ * of a point specifying the object's top-left corner in the screen's
+ * coordinate space.
+ *
+ * @return An instance of Point representing the top-left corner of the
+ * object's bounds in the coordinate space of the screen; null if
+ * this object or its parent are not on the screen
+ * @see #getBounds
+ * @see #getLocationOnScreen
+ */
+ public Point getLocation() {
+ Rectangle r = getBounds();
+ if (r != null) {
+ return new Point(r.x, r.y);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the location of the object relative to the parent.
+ * @param p the new position for the top-left corner
+ * @see #getLocation
+ */
+ public void setLocation(Point p) {
+ }
+
+ /**
+ * Gets the bounds of this object in the form of a Rectangle object.
+ * The bounds specify this object's width, height, and location
+ * relative to its parent.
+ *
+ * @return A rectangle indicating this component's bounds; null if
+ * this object is not on the screen.
+ * @see #contains
+ */
+ public Rectangle getBounds() {
+ return elementInfo.getBounds();
+ }
+
+ /**
+ * Sets the bounds of this object in the form of a Rectangle object.
+ * The bounds specify this object's width, height, and location
+ * relative to its parent.
+ *
+ * @param r rectangle indicating this component's bounds
+ * @see #getBounds
+ */
+ public void setBounds(Rectangle r) {
+ }
+
+ /**
+ * Returns the size of this object in the form of a Dimension object.
+ * The height field of the Dimension object contains this object's
+ * height, and the width field of the Dimension object contains this
+ * object's width.
+ *
+ * @return A Dimension object that indicates the size of this component;
+ * null if this object is not on the screen
+ * @see #setSize
+ */
+ public Dimension getSize() {
+ Rectangle r = getBounds();
+ if (r != null) {
+ return new Dimension(r.width, r.height);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Resizes this object so that it has width and height.
+ *
+ * @param d The dimension specifying the new size of the object.
+ * @see #getSize
+ */
+ public void setSize(Dimension d) {
+ Component comp = getTextComponent();
+ comp.setSize(d);
+ }
+
+ /**
+ * Returns the Accessible child, if one exists, contained at the local
+ * coordinate Point.
+ *
+ * @param p The point relative to the coordinate system of this object.
+ * @return the Accessible, if it exists, at the specified location;
+ * otherwise null
+ */
+ public Accessible getAccessibleAt(Point p) {
+ ElementInfo innerMostElement = getElementInfoAt(rootElementInfo, p);
+ if (innerMostElement instanceof Accessible) {
+ return (Accessible)innerMostElement;
+ } else {
+ return null;
+ }
+ }
+
+ private ElementInfo getElementInfoAt(ElementInfo elementInfo, Point p) {
+ if (elementInfo.getBounds() == null) {
+ return null;
+ }
+ if (elementInfo.getChildCount() == 0 &&
+ elementInfo.getBounds().contains(p)) {
+ return elementInfo;
+
+ } else {
+ if (elementInfo instanceof TableElementInfo) {
+ // Handle table caption as a special case since it's the
+ // only table child that is not a table row.
+ ElementInfo captionInfo =
+ ((TableElementInfo)elementInfo).getCaptionInfo();
+ if (captionInfo != null) {
+ Rectangle bounds = captionInfo.getBounds();
+ if (bounds != null && bounds.contains(p)) {
+ return captionInfo;
+ }
+ }
+ }
+ for (int i = 0; i < elementInfo.getChildCount(); i++)
+{
+ ElementInfo childInfo = elementInfo.getChild(i);
+ ElementInfo retValue = getElementInfoAt(childInfo, p);
+ if (retValue != null) {
+ return retValue;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether this object can accept focus or not. Objects that
+ * can accept focus will also have the AccessibleState.FOCUSABLE state
+ * set in their AccessibleStateSets.
+ *
+ * @return true if object can accept focus; otherwise false
+ * @see AccessibleContext#getAccessibleStateSet
+ * @see AccessibleState#FOCUSABLE
+ * @see AccessibleState#FOCUSED
+ * @see AccessibleStateSet
+ */
+ public boolean isFocusTraversable() {
+ Component comp = getTextComponent();
+ if (comp instanceof JTextComponent) {
+ if (((JTextComponent)comp).isEditable()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Requests focus for this object. If this object cannot accept focus,
+ * nothing will happen. Otherwise, the object will attempt to take
+ * focus.
+ * @see #isFocusTraversable
+ */
+ public void requestFocus() {
+ // TIGER - 4856191
+ if (! isFocusTraversable()) {
+ return;
+ }
+
+ Component comp = getTextComponent();
+ if (comp instanceof JTextComponent) {
+
+ comp.requestFocusInWindow();
+
+ try {
+ if (elementInfo.validateIfNecessary()) {
+ // set the caret position to the start of this component
+ Element elem = elementInfo.getElement();
+ ((JTextComponent)comp).setCaretPosition(elem.getStartOffset());
+
+ // fire a AccessibleState.FOCUSED property change event
+ AccessibleContext ac = editor.getAccessibleContext();
+ PropertyChangeEvent pce = new PropertyChangeEvent(this,
+ AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
+ null, AccessibleState.FOCUSED);
+ ac.firePropertyChange(
+ AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
+ null, pce);
+ }
+ } catch (IllegalArgumentException e) {
+ // don't fire property change event
+ }
+ }
+ }
+
+ /**
+ * Adds the specified focus listener to receive focus events from this
+ * component.
+ *
+ * @param l the focus listener
+ * @see #removeFocusListener
+ */
+ public void addFocusListener(FocusListener l) {
+ getTextComponent().addFocusListener(l);
+ }
+
+ /**
+ * Removes the specified focus listener so it no longer receives focus
+ * events from this component.
+ *
+ * @param l the focus listener
+ * @see #addFocusListener
+ */
+ public void removeFocusListener(FocusListener l) {
+ getTextComponent().removeFocusListener(l);
+ }
+ // ... end AccessibleComponent implementation
+ } // ... end HTMLAccessibleContext
+
+
+
+ /*
+ * ElementInfo for text
+ */
+ class TextElementInfo extends ElementInfo implements Accessible {
+
+ TextElementInfo(Element element, ElementInfo parent) {
+ super(element, parent);
+ }
+
+ // begin AccessibleText implementation ...
+ private AccessibleContext accessibleContext;
+
+ public AccessibleContext getAccessibleContext() {
+ if (accessibleContext == null) {
+ accessibleContext = new TextAccessibleContext(this);
+ }
+ return accessibleContext;
+ }
+
+ /*
+ * AccessibleContext for text elements
+ */
+ public class TextAccessibleContext extends HTMLAccessibleContext
+ implements AccessibleText {
+
+ public TextAccessibleContext(ElementInfo elementInfo) {
+ super(elementInfo);
+ }
+
+ public AccessibleText getAccessibleText() {
+ return this;
+ }
+
+ /**
+ * Gets the accessibleName property of this object. The accessibleName
+ * property of an object is a localized String that designates the purpose
+ * of the object. For example, the accessibleName property of a label
+ * or button might be the text of the label or button itself. In the
+ * case of an object that doesn't display its name, the accessibleName
+ * should still be set. For example, in the case of a text field used
+ * to enter the name of a city, the accessibleName for the en_US locale
+ * could be 'city.'
+ *
+ * @return the localized name of the object; null if this
+ * object does not have a name
+ *
+ * @see #setAccessibleName
+ */
+ public String getAccessibleName() {
+ if (model != null) {
+ return (String)model.getProperty(Document.TitleProperty);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the accessibleDescription property of this object. If this
+ * property isn't set, returns the content type of this
+ * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
+ *
+ * @return the localized description of the object; <code>null</code>
+ * if this object does not have a description
+ *
+ * @see #setAccessibleName
+ */
+ public String getAccessibleDescription() {
+ return editor.getContentType();
+ }
+
+ /**
+ * Gets the role of this object. The role of the object is the generic
+ * purpose or use of the class of this object. For example, the role
+ * of a push button is AccessibleRole.PUSH_BUTTON. The roles in
+ * AccessibleRole are provided so component developers can pick from
+ * a set of predefined roles. This enables assistive technologies to
+ * provide a consistent interface to various tweaked subclasses of
+ * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
+ * that act like a push button) as well as distinguish between sublasses
+ * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
+ * and AccessibleRole.RADIO_BUTTON for radio buttons).
+ * <p>Note that the AccessibleRole class is also extensible, so
+ * custom component developers can define their own AccessibleRole's
+ * if the set of predefined roles is inadequate.
+ *
+ * @return an instance of AccessibleRole describing the role of the object
+ * @see AccessibleRole
+ */
+ public AccessibleRole getAccessibleRole() {
+ return AccessibleRole.TEXT;
+ }
+
+ /**
+ * Given a point in local coordinates, return the zero-based index
+ * of the character under that Point. If the point is invalid,
+ * this method returns -1.
+ *
+ * @param p the Point in local coordinates
+ * @return the zero-based index of the character under Point p; if
+ * Point is invalid returns -1.
+ */
+ public int getIndexAtPoint(Point p) {
+ View v = getView();
+ if (v != null) {
+ return v.viewToModel(p.x, p.y, getBounds());
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Determine the bounding box of the character at the given
+ * index into the string. The bounds are returned in local
+ * coordinates. If the index is invalid an empty rectangle is
+ * returned.
+ *
+ * @param i the index into the String
+ * @return the screen coordinates of the character's the bounding box,
+ * if index is invalid returns an empty rectangle.
+ */
+ public Rectangle getCharacterBounds(int i) {
+ try {
+ return editor.getUI().modelToView(editor, i);
+ } catch (BadLocationException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Return the number of characters (valid indicies)
+ *
+ * @return the number of characters
+ */
+ public int getCharCount() {
+ if (validateIfNecessary()) {
+ Element elem = elementInfo.getElement();
+ return elem.getEndOffset() - elem.getStartOffset();
+ }
+ return 0;
+ }
+
+ /**
+ * Return the zero-based offset of the caret.
+ *
+ * Note: That to the right of the caret will have the same index
+ * value as the offset (the caret is between two characters).
+ * @return the zero-based offset of the caret.
+ */
+ public int getCaretPosition() {
+ View v = getView();
+ if (v == null) {
+ return -1;
+ }
+ Container c = v.getContainer();
+ if (c == null) {
+ return -1;
+ }
+ if (c instanceof JTextComponent) {
+ return ((JTextComponent)c).getCaretPosition();
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * IndexedSegment extends Segment adding the offset into the
+ * the model the <code>Segment</code> was asked for.
+ */
+ private class IndexedSegment extends Segment {
+ /**
+ * Offset into the model that the position represents.
+ */
+ public int modelOffset;
+ }
+
+ public String getAtIndex(int part, int index) {
+ return getAtIndex(part, index, 0);
+ }
+
+
+ public String getAfterIndex(int part, int index) {
+ return getAtIndex(part, index, 1);
+ }
+
+ public String getBeforeIndex(int part, int index) {
+ return getAtIndex(part, index, -1);
+ }
+
+ /**
+ * Gets the word, sentence, or character at <code>index</code>.
+ * If <code>direction</code> is non-null this will find the
+ * next/previous word/sentence/character.
+ */
+ private String getAtIndex(int part, int index, int direction) {
+ if (model instanceof AbstractDocument) {
+ ((AbstractDocument)model).readLock();
+ }
+ try {
+ if (index < 0 || index >= model.getLength()) {
+ return null;
+ }
+ switch (part) {
+ case AccessibleText.CHARACTER:
+ if (index + direction < model.getLength() &&
+ index + direction >= 0) {
+ return model.getText(index + direction, 1);
+ }
+ break;
+
+
+ case AccessibleText.WORD:
+ case AccessibleText.SENTENCE:
+ IndexedSegment seg = getSegmentAt(part, index);
+ if (seg != null) {
+ if (direction != 0) {
+ int next;
+
+
+ if (direction < 0) {
+ next = seg.modelOffset - 1;
+ }
+ else {
+ next = seg.modelOffset + direction * seg.count;
+ }
+ if (next >= 0 && next <= model.getLength()) {
+ seg = getSegmentAt(part, next);
+ }
+ else {
+ seg = null;
+ }
+ }
+ if (seg != null) {
+ return new String(seg.array, seg.offset,
+ seg.count);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ } catch (BadLocationException e) {
+ } finally {
+ if (model instanceof AbstractDocument) {
+ ((AbstractDocument)model).readUnlock();
+ }
+ }
+ return null;
+ }
+
+ /*
+ * Returns the paragraph element for the specified index.
+ */
+ private Element getParagraphElement(int index) {
+ if (model instanceof PlainDocument ) {
+ PlainDocument sdoc = (PlainDocument)model;
+ return sdoc.getParagraphElement(index);
+ } else if (model instanceof StyledDocument) {
+ StyledDocument sdoc = (StyledDocument)model;
+ return sdoc.getParagraphElement(index);
+ } else {
+ Element para = null;
+ for (para = model.getDefaultRootElement(); ! para.isLeaf(); ) {
+ int pos = para.getElementIndex(index);
+ para = para.getElement(pos);
+ }
+ if (para == null) {
+ return null;
+ }
+ return para.getParentElement();
+ }
+ }
+
+ /*
+ * Returns a <code>Segment</code> containing the paragraph text
+ * at <code>index</code>, or null if <code>index</code> isn't
+ * valid.
+ */
+ private IndexedSegment getParagraphElementText(int index)
+ throws BadLocationException {
+ Element para = getParagraphElement(index);
+
+
+ if (para != null) {
+ IndexedSegment segment = new IndexedSegment();
+ try {
+ int length = para.getEndOffset() - para.getStartOffset();
+ model.getText(para.getStartOffset(), length, segment);
+ } catch (BadLocationException e) {
+ return null;
+ }
+ segment.modelOffset = para.getStartOffset();
+ return segment;
+ }
+ return null;
+ }
+
+
+ /**
+ * Returns the Segment at <code>index</code> representing either
+ * the paragraph or sentence as identified by <code>part</code>, or
+ * null if a valid paragraph/sentence can't be found. The offset
+ * will point to the start of the word/sentence in the array, and
+ * the modelOffset will point to the location of the word/sentence
+ * in the model.
+ */
+ private IndexedSegment getSegmentAt(int part, int index)
+ throws BadLocationException {
+
+ IndexedSegment seg = getParagraphElementText(index);
+ if (seg == null) {
+ return null;
+ }
+ BreakIterator iterator;
+ switch (part) {
+ case AccessibleText.WORD:
+ iterator = BreakIterator.getWordInstance(getLocale());
+ break;
+ case AccessibleText.SENTENCE:
+ iterator = BreakIterator.getSentenceInstance(getLocale());
+ break;
+ default:
+ return null;
+ }
+ seg.first();
+ iterator.setText(seg);
+ int end = iterator.following(index - seg.modelOffset + seg.offset);
+ if (end == BreakIterator.DONE) {
+ return null;
+ }
+ if (end > seg.offset + seg.count) {
+ return null;
+ }
+ int begin = iterator.previous();
+ if (begin == BreakIterator.DONE ||
+ begin >= seg.offset + seg.count) {
+ return null;
+ }
+ seg.modelOffset = seg.modelOffset + begin - seg.offset;
+ seg.offset = begin;
+ seg.count = end - begin;
+ return seg;
+ }
+
+ /**
+ * Return the AttributeSet for a given character at a given index
+ *
+ * @param i the zero-based index into the text
+ * @return the AttributeSet of the character
+ */
+ public AttributeSet getCharacterAttribute(int i) {
+ if (model instanceof StyledDocument) {
+ StyledDocument doc = (StyledDocument)model;
+ Element elem = doc.getCharacterElement(i);
+ if (elem != null) {
+ return elem.getAttributes();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the start offset within the selected text.
+ * If there is no selection, but there is
+ * a caret, the start and end offsets will be the same.
+ *
+ * @return the index into the text of the start of the selection
+ */
+ public int getSelectionStart() {
+ return editor.getSelectionStart();
+ }
+
+ /**
+ * Returns the end offset within the selected text.
+ * If there is no selection, but there is
+ * a caret, the start and end offsets will be the same.
+ *
+ * @return the index into teh text of the end of the selection
+ */
+ public int getSelectionEnd() {
+ return editor.getSelectionEnd();
+ }
+
+ /**
+ * Returns the portion of the text that is selected.
+ *
+ * @return the String portion of the text that is selected
+ */
+ public String getSelectedText() {
+ return editor.getSelectedText();
+ }
+
+ /*
+ * Returns the text substring starting at the specified
+ * offset with the specified length.
+ */
+ private String getText(int offset, int length)
+ throws BadLocationException {
+
+ if (model != null && model instanceof StyledDocument) {
+ StyledDocument doc = (StyledDocument)model;
+ return model.getText(offset, length);
+ } else {
+ return null;
+ }
+ }
+ }
+ }
+
+ /*
+ * ElementInfo for images
+ */
+ private class IconElementInfo extends ElementInfo implements Accessible {
+
+ private int width = -1;
+ private int height = -1;
+
+ IconElementInfo(Element element, ElementInfo parent) {
+ super(element, parent);
+ }
+
+ protected void invalidate(boolean first) {
+ super.invalidate(first);
+ width = height = -1;
+ }
+
+ private int getImageSize(Object key) {
+ if (validateIfNecessary()) {
+ int size = getIntAttr(getAttributes(), key, -1);
+
+ if (size == -1) {
+ View v = getView();
+
+ size = 0;
+ if (v instanceof ImageView) {
+ Image img = ((ImageView)v).getImage();
+ if (img != null) {
+ if (key == HTML.Attribute.WIDTH) {
+ size = img.getWidth(null);
+ }
+ else {
+ size = img.getHeight(null);
+ }
+ }
+ }
+ }
+ return size;
+ }
+ return 0;
+ }
+
+ // begin AccessibleIcon implementation ...
+ private AccessibleContext accessibleContext;
+
+ public AccessibleContext getAccessibleContext() {
+ if (accessibleContext == null) {
+ accessibleContext = new IconAccessibleContext(this);
+ }
+ return accessibleContext;
+ }
+
+ /*
+ * AccessibleContext for images
+ */
+ protected class IconAccessibleContext extends HTMLAccessibleContext
+ implements AccessibleIcon {
+
+ public IconAccessibleContext(ElementInfo elementInfo) {
+ super(elementInfo);
+ }
+
+ /**
+ * Gets the accessibleName property of this object. The accessibleName
+ * property of an object is a localized String that designates the purpose
+ * of the object. For example, the accessibleName property of a label
+ * or button might be the text of the label or button itself. In the
+ * case of an object that doesn't display its name, the accessibleName
+ * should still be set. For example, in the case of a text field used
+ * to enter the name of a city, the accessibleName for the en_US locale
+ * could be 'city.'
+ *
+ * @return the localized name of the object; null if this
+ * object does not have a name
+ *
+ * @see #setAccessibleName
+ */
+ public String getAccessibleName() {
+ return getAccessibleIconDescription();
+ }
+
+ /**
+ * Gets the accessibleDescription property of this object. If this
+ * property isn't set, returns the content type of this
+ * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
+ *
+ * @return the localized description of the object; <code>null</code>
+ * if this object does not have a description
+ *
+ * @see #setAccessibleName
+ */
+ public String getAccessibleDescription() {
+ return editor.getContentType();
+ }
+
+ /**
+ * Gets the role of this object. The role of the object is the generic
+ * purpose or use of the class of this object. For example, the role
+ * of a push button is AccessibleRole.PUSH_BUTTON. The roles in
+ * AccessibleRole are provided so component developers can pick from
+ * a set of predefined roles. This enables assistive technologies to
+ * provide a consistent interface to various tweaked subclasses of
+ * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
+ * that act like a push button) as well as distinguish between sublasses
+ * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
+ * and AccessibleRole.RADIO_BUTTON for radio buttons).
+ * <p>Note that the AccessibleRole class is also extensible, so
+ * custom component developers can define their own AccessibleRole's
+ * if the set of predefined roles is inadequate.
+ *
+ * @return an instance of AccessibleRole describing the role of the object
+ * @see AccessibleRole
+ */
+ public AccessibleRole getAccessibleRole() {
+ return AccessibleRole.ICON;
+ }
+
+ public AccessibleIcon [] getAccessibleIcon() {
+ AccessibleIcon [] icons = new AccessibleIcon[1];
+ icons[0] = this;
+ return icons;
+ }
+
+ /**
+ * Gets the description of the icon. This is meant to be a brief
+ * textual description of the object. For example, it might be
+ * presented to a blind user to give an indication of the purpose
+ * of the icon.
+ *
+ * @return the description of the icon
+ */
+ public String getAccessibleIconDescription() {
+ return ((ImageView)getView()).getAltText();
+ }
+
+ /**
+ * Sets the description of the icon. This is meant to be a brief
+ * textual description of the object. For example, it might be
+ * presented to a blind user to give an indication of the purpose
+ * of the icon.
+ *
+ * @param description the description of the icon
+ */
+ public void setAccessibleIconDescription(String description) {
+ }
+
+ /**
+ * Gets the width of the icon
+ *
+ * @return the width of the icon.
+ */
+ public int getAccessibleIconWidth() {
+ if (width == -1) {
+ width = getImageSize(HTML.Attribute.WIDTH);
+ }
+ return width;
+ }
+
+ /**
+ * Gets the height of the icon
+ *
+ * @return the height of the icon.
+ */
+ public int getAccessibleIconHeight() {
+ if (height == -1) {
+ height = getImageSize(HTML.Attribute.HEIGHT);
+ }
+ return height;
+ }
+ }
+ // ... end AccessibleIconImplementation
+ }
+
+
+ /**
+ * TableElementInfo encapsulates information about a HTML.Tag.TABLE.
+ * To make access fast it crates a grid containing the children to
+ * allow for access by row, column. TableElementInfo will contain
+ * TableRowElementInfos, which will contain TableCellElementInfos.
+ * Any time one of the rows or columns becomes invalid the table is
+ * invalidated. This is because any time one of the child attributes
+ * changes the size of the grid may have changed.
+ */
+ private class TableElementInfo extends ElementInfo
+ implements Accessible {
+
+ protected ElementInfo caption;
+
+ /**
+ * Allocation of the table by row x column. There may be holes (eg
+ * nulls) depending upon the html, any cell that has a rowspan/colspan
+ * > 1 will be contained multiple times in the grid.
+ */
+ private TableCellElementInfo[][] grid;
+
+
+ TableElementInfo(Element e, ElementInfo parent) {
+ super(e, parent);
+ }
+
+ public ElementInfo getCaptionInfo() {
+ return caption;
+ }
+
+ /**
+ * Overriden to update the grid when validating.
+ */
+ protected void validate() {
+ super.validate();
+ updateGrid();
+ }
+
+ /**
+ * Overriden to only alloc instances of TableRowElementInfos.
+ */
+ protected void loadChildren(Element e) {
+
+ for (int counter = 0; counter < e.getElementCount(); counter++) {
+ Element child = e.getElement(counter);
+ AttributeSet attrs = child.getAttributes();
+
+ if (attrs.getAttribute(StyleConstants.NameAttribute) ==
+ HTML.Tag.TR) {
+ addChild(new TableRowElementInfo(child, this, counter));
+
+ } else if (attrs.getAttribute(StyleConstants.NameAttribute) ==
+ HTML.Tag.CAPTION) {
+ // Handle captions as a special case since all other
+ // children are table rows.
+ caption = createElementInfo(child, this);
+ }
+ }
+ }
+
+ /**
+ * Updates the grid.
+ */
+ private void updateGrid() {
+ // Determine the max row/col count.
+ int delta = 0;
+ int maxCols = 0;
+ int rows = 0;
+ for (int counter = 0; counter < getChildCount(); counter++) {
+ TableRowElementInfo row = getRow(counter);
+ int prev = 0;
+ for (int y = 0; y < delta; y++) {
+ prev = Math.max(prev, getRow(counter - y - 1).
+ getColumnCount(y + 2));
+ }
+ delta = Math.max(row.getRowCount(), delta);
+ delta--;
+ maxCols = Math.max(maxCols, row.getColumnCount() + prev);
+ }
+ rows = getChildCount() + delta;
+
+ // Alloc
+ grid = new TableCellElementInfo[rows][];
+ for (int counter = 0; counter < rows; counter++) {
+ grid[counter] = new TableCellElementInfo[maxCols];
+ }
+ // Update
+ for (int counter = 0; counter < rows; counter++) {
+ getRow(counter).updateGrid(counter);
+ }
+ }
+
+ /**
+ * Returns the TableCellElementInfo at the specified index.
+ */
+ public TableRowElementInfo getRow(int index) {
+ return (TableRowElementInfo)getChild(index);
+ }
+
+ /**
+ * Returns the TableCellElementInfo by row and column.
+ */
+ public TableCellElementInfo getCell(int r, int c) {
+ if (validateIfNecessary() && r < grid.length &&
+ c < grid[0].length) {
+ return grid[r][c];
+ }
+ return null;
+ }
+
+ /**
+ * Returns the rowspan of the specified entry.
+ */
+ public int getRowExtentAt(int r, int c) {
+ TableCellElementInfo cell = getCell(r, c);
+
+ if (cell != null) {
+ int rows = cell.getRowCount();
+ int delta = 1;
+
+ while ((r - delta) >= 0 && grid[r - delta][c] == cell) {
+ delta++;
+ }
+ return rows - delta + 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the colspan of the specified entry.
+ */
+ public int getColumnExtentAt(int r, int c) {
+ TableCellElementInfo cell = getCell(r, c);
+
+ if (cell != null) {
+ int cols = cell.getColumnCount();
+ int delta = 1;
+
+ while ((c - delta) >= 0 && grid[r][c - delta] == cell) {
+ delta++;
+ }
+ return cols - delta + 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the number of rows in the table.
+ */
+ public int getRowCount() {
+ if (validateIfNecessary()) {
+ return grid.length;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the number of columns in the table.
+ */
+ public int getColumnCount() {
+ if (validateIfNecessary() && grid.length > 0) {
+ return grid[0].length;
+ }
+ return 0;
+ }
+
+ // begin AccessibleTable implementation ...
+ private AccessibleContext accessibleContext;
+
+ public AccessibleContext getAccessibleContext() {
+ if (accessibleContext == null) {
+ accessibleContext = new TableAccessibleContext(this);
+ }
+ return accessibleContext;
+ }
+
+ /*
+ * AccessibleContext for tables
+ */
+ public class TableAccessibleContext extends HTMLAccessibleContext
+ implements AccessibleTable {
+
+ private AccessibleHeadersTable rowHeadersTable;
+
+ public TableAccessibleContext(ElementInfo elementInfo) {
+ super(elementInfo);
+ }
+
+ /**
+ * Gets the accessibleName property of this object. The accessibleName
+ * property of an object is a localized String that designates the purpose
+ * of the object. For example, the accessibleName property of a label
+ * or button might be the text of the label or button itself. In the
+ * case of an object that doesn't display its name, the accessibleName
+ * should still be set. For example, in the case of a text field used
+ * to enter the name of a city, the accessibleName for the en_US locale
+ * could be 'city.'
+ *
+ * @return the localized name of the object; null if this
+ * object does not have a name
+ *
+ * @see #setAccessibleName
+ */
+ public String getAccessibleName() {
+ // return the role of the object
+ return getAccessibleRole().toString();
+ }
+
+ /**
+ * Gets the accessibleDescription property of this object. If this
+ * property isn't set, returns the content type of this
+ * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
+ *
+ * @return the localized description of the object; <code>null</code>
+ * if this object does not have a description
+ *
+ * @see #setAccessibleName
+ */
+ public String getAccessibleDescription() {
+ return editor.getContentType();
+ }
+
+ /**
+ * Gets the role of this object. The role of the object is the generic
+ * purpose or use of the class of this object. For example, the role
+ * of a push button is AccessibleRole.PUSH_BUTTON. The roles in
+ * AccessibleRole are provided so component developers can pick from
+ * a set of predefined roles. This enables assistive technologies to
+ * provide a consistent interface to various tweaked subclasses of
+ * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
+ * that act like a push button) as well as distinguish between sublasses
+ * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
+ * and AccessibleRole.RADIO_BUTTON for radio buttons).
+ * <p>Note that the AccessibleRole class is also extensible, so
+ * custom component developers can define their own AccessibleRole's
+ * if the set of predefined roles is inadequate.
+ *
+ * @return an instance of AccessibleRole describing the role of the object
+ * @see AccessibleRole
+ */
+ public AccessibleRole getAccessibleRole() {
+ return AccessibleRole.TABLE;
+ }
+
+ /**
+ * Gets the 0-based index of this object in its accessible parent.
+ *
+ * @return the 0-based index of this object in its parent; -1 if this
+ * object does not have an accessible parent.
+ *
+ * @see #getAccessibleParent
+ * @see #getAccessibleChildrenCount
+ * @gsee #getAccessibleChild
+ */
+ public int getAccessibleIndexInParent() {
+ return elementInfo.getIndexInParent();
+ }
+
+ /**
+ * Returns the number of accessible children of the object.
+ *
+ * @return the number of accessible children of the object.
+ */
+ public int getAccessibleChildrenCount() {
+ return ((TableElementInfo)elementInfo).getRowCount() *
+ ((TableElementInfo)elementInfo).getColumnCount();
+ }
+
+ /**
+ * Returns the specified Accessible child of the object. The Accessible
+ * children of an Accessible object are zero-based, so the first child
+ * of an Accessible child is at index 0, the second child is at index 1,
+ * and so on.
+ *
+ * @param i zero-based index of child
+ * @return the Accessible child of the object
+ * @see #getAccessibleChildrenCount
+ */
+ public Accessible getAccessibleChild(int i) {
+ int rowCount = ((TableElementInfo)elementInfo).getRowCount();
+ int columnCount = ((TableElementInfo)elementInfo).getColumnCount();
+ int r = i / rowCount;
+ int c = i % columnCount;
+ if (r < 0 || r >= rowCount || c < 0 || c >= columnCount) {
+ return null;
+ } else {
+ return getAccessibleAt(r, c);
+ }
+ }
+
+ public AccessibleTable getAccessibleTable() {
+ return this;
+ }
+
+ /**
+ * Returns the caption for the table.
+ *
+ * @return the caption for the table
+ */
+ public Accessible getAccessibleCaption() {
+ ElementInfo captionInfo = getCaptionInfo();
+ if (captionInfo instanceof Accessible) {
+ return (Accessible)caption;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the caption for the table.
+ *
+ * @param a the caption for the table
+ */
+ public void setAccessibleCaption(Accessible a) {
+ }
+
+ /**
+ * Returns the summary description of the table.
+ *
+ * @return the summary description of the table
+ */
+ public Accessible getAccessibleSummary() {
+ return null;
+ }
+
+ /**
+ * Sets the summary description of the table
+ *
+ * @param a the summary description of the table
+ */
+ public void setAccessibleSummary(Accessible a) {
+ }
+
+ /**
+ * Returns the number of rows in the table.
+ *
+ * @return the number of rows in the table
+ */
+ public int getAccessibleRowCount() {
+ return ((TableElementInfo)elementInfo).getRowCount();
+ }
+
+ /**
+ * Returns the number of columns in the table.
+ *
+ * @return the number of columns in the table
+ */
+ public int getAccessibleColumnCount() {
+ return ((TableElementInfo)elementInfo).getColumnCount();
+ }
+
+ /**
+ * Returns the Accessible at a specified row and column
+ * in the table.
+ *
+ * @param r zero-based row of the table
+ * @param c zero-based column of the table
+ * @return the Accessible at the specified row and column
+ */
+ public Accessible getAccessibleAt(int r, int c) {
+ TableCellElementInfo cellInfo = getCell(r, c);
+ if (cellInfo != null) {
+ return cellInfo.getAccessible();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the number of rows occupied by the Accessible at
+ * a specified row and column in the table.
+ *
+ * @return the number of rows occupied by the Accessible at a
+ * given specified (row, column)
+ */
+ public int getAccessibleRowExtentAt(int r, int c) {
+ return ((TableElementInfo)elementInfo).getRowExtentAt(r, c);
+ }
+
+ /**
+ * Returns the number of columns occupied by the Accessible at
+ * a specified row and column in the table.
+ *
+ * @return the number of columns occupied by the Accessible at a
+ * given specified row and column
+ */
+ public int getAccessibleColumnExtentAt(int r, int c) {
+ return ((TableElementInfo)elementInfo).getColumnExtentAt(r, c);
+ }
+
+ /**
+ * Returns the row headers as an AccessibleTable.
+ *
+ * @return an AccessibleTable representing the row
+ * headers
+ */
+ public AccessibleTable getAccessibleRowHeader() {
+ return rowHeadersTable;
+ }
+
+ /**
+ * Sets the row headers.
+ *
+ * @param table an AccessibleTable representing the
+ * row headers
+ */
+ public void setAccessibleRowHeader(AccessibleTable table) {
+ }
+
+ /**
+ * Returns the column headers as an AccessibleTable.
+ *
+ * @return an AccessibleTable representing the column
+ * headers
+ */
+ public AccessibleTable getAccessibleColumnHeader() {
+ return null;
+ }
+
+ /**
+ * Sets the column headers.
+ *
+ * @param table an AccessibleTable representing the
+ * column headers
+ */
+ public void setAccessibleColumnHeader(AccessibleTable table) {
+ }
+
+ /**
+ * Returns the description of the specified row in the table.
+ *
+ * @param r zero-based row of the table
+ * @return the description of the row
+ */
+ public Accessible getAccessibleRowDescription(int r) {
+ return null;
+ }
+
+ /**
+ * Sets the description text of the specified row of the table.
+ *
+ * @param r zero-based row of the table
+ * @param a the description of the row
+ */
+ public void setAccessibleRowDescription(int r, Accessible a) {
+ }
+
+ /**
+ * Returns the description text of the specified column in the table.
+ *
+ * @param c zero-based column of the table
+ * @return the text description of the column
+ */
+ public Accessible getAccessibleColumnDescription(int c) {
+ return null;
+ }
+
+ /**
+ * Sets the description text of the specified column in the table.
+ *
+ * @param c zero-based column of the table
+ * @param a the text description of the column
+ */
+ public void setAccessibleColumnDescription(int c, Accessible a) {
+ }
+
+ /**
+ * Returns a boolean value indicating whether the accessible at
+ * a specified row and column is selected.
+ *
+ * @param r zero-based row of the table
+ * @param c zero-based column of the table
+ * @return the boolean value true if the accessible at the
+ * row and column is selected. Otherwise, the boolean value
+ * false
+ */
+ public boolean isAccessibleSelected(int r, int c) {
+ if (validateIfNecessary()) {
+ if (r < 0 || r >= getAccessibleRowCount() ||
+ c < 0 || c >= getAccessibleColumnCount()) {
+ return false;
+ }
+ TableCellElementInfo cell = getCell(r, c);
+ if (cell != null) {
+ Element elem = cell.getElement();
+ int start = elem.getStartOffset();
+ int end = elem.getEndOffset();
+ return start >= editor.getSelectionStart() &&
+ end <= editor.getSelectionEnd();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns a boolean value indicating whether the specified row
+ * is selected.
+ *
+ * @param r zero-based row of the table
+ * @return the boolean value true if the specified row is selected.
+ * Otherwise, false.
+ */
+ public boolean isAccessibleRowSelected(int r) {
+ if (validateIfNecessary()) {
+ if (r < 0 || r >= getAccessibleRowCount()) {
+ return false;
+ }
+ int nColumns = getAccessibleColumnCount();
+
+ TableCellElementInfo startCell = getCell(r, 0);
+ if (startCell == null) {
+ return false;
+ }
+ int start = startCell.getElement().getStartOffset();
+
+ TableCellElementInfo endCell = getCell(r, nColumns-1);
+ if (endCell == null) {
+ return false;
+ }
+ int end = endCell.getElement().getEndOffset();
+
+ return start >= editor.getSelectionStart() &&
+ end <= editor.getSelectionEnd();
+ }
+ return false;
+ }
+
+ /**
+ * Returns a boolean value indicating whether the specified column
+ * is selected.
+ *
+ * @param r zero-based column of the table
+ * @return the boolean value true if the specified column is selected.
+ * Otherwise, false.
+ */
+ public boolean isAccessibleColumnSelected(int c) {
+ if (validateIfNecessary()) {
+ if (c < 0 || c >= getAccessibleColumnCount()) {
+ return false;
+ }
+ int nRows = getAccessibleRowCount();
+
+ TableCellElementInfo startCell = getCell(0, c);
+ if (startCell == null) {
+ return false;
+ }
+ int start = startCell.getElement().getStartOffset();
+
+ TableCellElementInfo endCell = getCell(nRows-1, c);
+ if (endCell == null) {
+ return false;
+ }
+ int end = endCell.getElement().getEndOffset();
+ return start >= editor.getSelectionStart() &&
+ end <= editor.getSelectionEnd();
+ }
+ return false;
+ }
+
+ /**
+ * Returns the selected rows in a table.
+ *
+ * @return an array of selected rows where each element is a
+ * zero-based row of the table
+ */
+ public int [] getSelectedAccessibleRows() {
+ if (validateIfNecessary()) {
+ int nRows = getAccessibleRowCount();
+ Vector vec = new Vector();
+
+ for (int i = 0; i < nRows; i++) {
+ if (isAccessibleRowSelected(i)) {
+ vec.addElement(new Integer(i));
+ }
+ }
+ int retval[] = new int[vec.size()];
+ for (int i = 0; i < retval.length; i++) {
+ retval[i] = ((Integer)vec.elementAt(i)).intValue();
+ }
+ return retval;
+ }
+ return new int[0];
+ }
+
+ /**
+ * Returns the selected columns in a table.
+ *
+ * @return an array of selected columns where each element is a
+ * zero-based column of the table
+ */
+ public int [] getSelectedAccessibleColumns() {
+ if (validateIfNecessary()) {
+ int nColumns = getAccessibleRowCount();
+ Vector vec = new Vector();
+
+ for (int i = 0; i < nColumns; i++) {
+ if (isAccessibleColumnSelected(i)) {
+ vec.addElement(new Integer(i));
+ }
+ }
+ int retval[] = new int[vec.size()];
+ for (int i = 0; i < retval.length; i++) {
+ retval[i] = ((Integer)vec.elementAt(i)).intValue();
+ }
+ return retval;
+ }
+ return new int[0];
+ }
+
+ // begin AccessibleExtendedTable implementation -------------
+
+ /**
+ * Returns the row number of an index in the table.
+ *
+ * @param index the zero-based index in the table
+ * @return the zero-based row of the table if one exists;
+ * otherwise -1.
+ */
+ public int getAccessibleRow(int index) {
+ if (validateIfNecessary()) {
+ int numCells = getAccessibleColumnCount() *
+ getAccessibleRowCount();
+ if (index >= numCells) {
+ return -1;
+ } else {
+ return index / getAccessibleColumnCount();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the column number of an index in the table.
+ *
+ * @param index the zero-based index in the table
+ * @return the zero-based column of the table if one exists;
+ * otherwise -1.
+ */
+ public int getAccessibleColumn(int index) {
+ if (validateIfNecessary()) {
+ int numCells = getAccessibleColumnCount() *
+ getAccessibleRowCount();
+ if (index >= numCells) {
+ return -1;
+ } else {
+ return index % getAccessibleColumnCount();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the index at a row and column in the table.
+ *
+ * @param r zero-based row of the table
+ * @param c zero-based column of the table
+ * @return the zero-based index in the table if one exists;
+ * otherwise -1.
+ */
+ public int getAccessibleIndex(int r, int c) {
+ if (validateIfNecessary()) {
+ if (r >= getAccessibleRowCount() ||
+ c >= getAccessibleColumnCount()) {
+ return -1;
+ } else {
+ return r * getAccessibleColumnCount() + c;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the row header at a row in a table.
+ * @param r zero-based row of the table
+ *
+ * @return a String representing the row header
+ * if one exists; otherwise null.
+ */
+ public String getAccessibleRowHeader(int r) {
+ if (validateIfNecessary()) {
+ TableCellElementInfo cellInfo = getCell(r, 0);
+ if (cellInfo.isHeaderCell()) {
+ View v = cellInfo.getView();
+ if (v != null && model != null) {
+ try {
+ return model.getText(v.getStartOffset(),
+ v.getEndOffset() -
+ v.getStartOffset());
+ } catch (BadLocationException e) {
+ return null;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the column header at a column in a table.
+ * @param c zero-based column of the table
+ *
+ * @return a String representing the column header
+ * if one exists; otherwise null.
+ */
+ public String getAccessibleColumnHeader(int c) {
+ if (validateIfNecessary()) {
+ TableCellElementInfo cellInfo = getCell(0, c);
+ if (cellInfo.isHeaderCell()) {
+ View v = cellInfo.getView();
+ if (v != null && model != null) {
+ try {
+ return model.getText(v.getStartOffset(),
+ v.getEndOffset() -
+ v.getStartOffset());
+ } catch (BadLocationException e) {
+ return null;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public void addRowHeader(TableCellElementInfo cellInfo, int rowNumber) {
+ if (rowHeadersTable == null) {
+ rowHeadersTable = new AccessibleHeadersTable();
+ }
+ rowHeadersTable.addHeader(cellInfo, rowNumber);
+ }
+ // end of AccessibleExtendedTable implementation ------------
+
+ protected class AccessibleHeadersTable implements AccessibleTable {
+
+ // Header information is modeled as a Hashtable of
+ // ArrayLists where each Hashtable entry represents
+ // a row containing one or more headers.
+ private Hashtable headers = new Hashtable();
+ private int rowCount = 0;
+ private int columnCount = 0;
+
+ public void addHeader(TableCellElementInfo cellInfo, int rowNumber) {
+ Integer rowInteger = new Integer(rowNumber);
+ ArrayList list = (ArrayList)headers.get(rowInteger);
+ if (list == null) {
+ list = new ArrayList();
+ headers.put(rowInteger, list);
+ }
+ list.add(cellInfo);
+ }
+
+ /**
+ * Returns the caption for the table.
+ *
+ * @return the caption for the table
+ */
+ public Accessible getAccessibleCaption() {
+ return null;
+ }
+
+ /**
+ * Sets the caption for the table.
+ *
+ * @param a the caption for the table
+ */
+ public void setAccessibleCaption(Accessible a) {
+ }
+
+ /**
+ * Returns the summary description of the table.
+ *
+ * @return the summary description of the table
+ */
+ public Accessible getAccessibleSummary() {
+ return null;
+ }
+
+ /**
+ * Sets the summary description of the table
+ *
+ * @param a the summary description of the table
+ */
+ public void setAccessibleSummary(Accessible a) {
+ }
+
+ /**
+ * Returns the number of rows in the table.
+ *
+ * @return the number of rows in the table
+ */
+ public int getAccessibleRowCount() {
+ return rowCount;
+ }
+
+ /**
+ * Returns the number of columns in the table.
+ *
+ * @return the number of columns in the table
+ */
+ public int getAccessibleColumnCount() {
+ return columnCount;
+ }
+
+ private TableCellElementInfo getElementInfoAt(int r, int c) {
+ ArrayList list = (ArrayList)headers.get(new Integer(r));
+ if (list != null) {
+ return (TableCellElementInfo)list.get(c);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the Accessible at a specified row and column
+ * in the table.
+ *
+ * @param r zero-based row of the table
+ * @param c zero-based column of the table
+ * @return the Accessible at the specified row and column
+ */
+ public Accessible getAccessibleAt(int r, int c) {
+ ElementInfo elementInfo = getElementInfoAt(r, c);
+ if (elementInfo instanceof Accessible) {
+ return (Accessible)elementInfo;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the number of rows occupied by the Accessible at
+ * a specified row and column in the table.
+ *
+ * @return the number of rows occupied by the Accessible at a
+ * given specified (row, column)
+ */
+ public int getAccessibleRowExtentAt(int r, int c) {
+ TableCellElementInfo elementInfo = getElementInfoAt(r, c);
+ if (elementInfo != null) {
+ return elementInfo.getRowCount();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Returns the number of columns occupied by the Accessible at
+ * a specified row and column in the table.
+ *
+ * @return the number of columns occupied by the Accessible at a
+ * given specified row and column
+ */
+ public int getAccessibleColumnExtentAt(int r, int c) {
+ TableCellElementInfo elementInfo = getElementInfoAt(r, c);
+ if (elementInfo != null) {
+ return elementInfo.getRowCount();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Returns the row headers as an AccessibleTable.
+ *
+ * @return an AccessibleTable representing the row
+ * headers
+ */
+ public AccessibleTable getAccessibleRowHeader() {
+ return null;
+ }
+
+ /**
+ * Sets the row headers.
+ *
+ * @param table an AccessibleTable representing the
+ * row headers
+ */
+ public void setAccessibleRowHeader(AccessibleTable table) {
+ }
+
+ /**
+ * Returns the column headers as an AccessibleTable.
+ *
+ * @return an AccessibleTable representing the column
+ * headers
+ */
+ public AccessibleTable getAccessibleColumnHeader() {
+ return null;
+ }
+
+ /**
+ * Sets the column headers.
+ *
+ * @param table an AccessibleTable representing the
+ * column headers
+ */
+ public void setAccessibleColumnHeader(AccessibleTable table) {
+ }
+
+ /**
+ * Returns the description of the specified row in the table.
+ *
+ * @param r zero-based row of the table
+ * @return the description of the row
+ */
+ public Accessible getAccessibleRowDescription(int r) {
+ return null;
+ }
+
+ /**
+ * Sets the description text of the specified row of the table.
+ *
+ * @param r zero-based row of the table
+ * @param a the description of the row
+ */
+ public void setAccessibleRowDescription(int r, Accessible a) {
+ }
+
+ /**
+ * Returns the description text of the specified column in the table.
+ *
+ * @param c zero-based column of the table
+ * @return the text description of the column
+ */
+ public Accessible getAccessibleColumnDescription(int c) {
+ return null;
+ }
+
+ /**
+ * Sets the description text of the specified column in the table.
+ *
+ * @param c zero-based column of the table
+ * @param a the text description of the column
+ */
+ public void setAccessibleColumnDescription(int c, Accessible a) {
+ }
+
+ /**
+ * Returns a boolean value indicating whether the accessible at
+ * a specified row and column is selected.
+ *
+ * @param r zero-based row of the table
+ * @param c zero-based column of the table
+ * @return the boolean value true if the accessible at the
+ * row and column is selected. Otherwise, the boolean value
+ * false
+ */
+ public boolean isAccessibleSelected(int r, int c) {
+ return false;
+ }
+
+ /**
+ * Returns a boolean value indicating whether the specified row
+ * is selected.
+ *
+ * @param r zero-based row of the table
+ * @return the boolean value true if the specified row is selected.
+ * Otherwise, false.
+ */
+ public boolean isAccessibleRowSelected(int r) {
+ return false;
+ }
+
+ /**
+ * Returns a boolean value indicating whether the specified column
+ * is selected.
+ *
+ * @param r zero-based column of the table
+ * @return the boolean value true if the specified column is selected.
+ * Otherwise, false.
+ */
+ public boolean isAccessibleColumnSelected(int c) {
+ return false;
+ }
+
+ /**
+ * Returns the selected rows in a table.
+ *
+ * @return an array of selected rows where each element is a
+ * zero-based row of the table
+ */
+ public int [] getSelectedAccessibleRows() {
+ return new int [0];
+ }
+
+ /**
+ * Returns the selected columns in a table.
+ *
+ * @return an array of selected columns where each element is a
+ * zero-based column of the table
+ */
+ public int [] getSelectedAccessibleColumns() {
+ return new int [0];
+ }
+ }
+ } // ... end AccessibleHeadersTable
+
+ /*
+ * ElementInfo for table rows
+ */
+ private class TableRowElementInfo extends ElementInfo {
+
+ private TableElementInfo parent;
+ private int rowNumber;
+
+ TableRowElementInfo(Element e, TableElementInfo parent, int rowNumber) {
+ super(e, parent);
+ this.parent = parent;
+ this.rowNumber = rowNumber;
+ }
+
+ protected void loadChildren(Element e) {
+ for (int x = 0; x < e.getElementCount(); x++) {
+ AttributeSet attrs = e.getElement(x).getAttributes();
+
+ if (attrs.getAttribute(StyleConstants.NameAttribute) ==
+ HTML.Tag.TH) {
+ TableCellElementInfo headerElementInfo =
+ new TableCellElementInfo(e.getElement(x), this, true);
+ addChild(headerElementInfo);
+
+ AccessibleTable at =
+ parent.getAccessibleContext().getAccessibleTable();
+ TableAccessibleContext tableElement =
+ (TableAccessibleContext)at;
+ tableElement.addRowHeader(headerElementInfo, rowNumber);
+
+ } else if (attrs.getAttribute(StyleConstants.NameAttribute) ==
+ HTML.Tag.TD) {
+ addChild(new TableCellElementInfo(e.getElement(x), this,
+ false));
+ }
+ }
+ }
+
+ /**
+ * Returns the max of the rowspans of the cells in this row.
+ */
+ public int getRowCount() {
+ int rowCount = 1;
+ if (validateIfNecessary()) {
+ for (int counter = 0; counter < getChildCount();
+ counter++) {
+
+ TableCellElementInfo cell = (TableCellElementInfo)
+ getChild(counter);
+
+ if (cell.validateIfNecessary()) {
+ rowCount = Math.max(rowCount, cell.getRowCount());
+ }
+ }
+ }
+ return rowCount;
+ }
+
+ /**
+ * Returns the sum of the column spans of the individual
+ * cells in this row.
+ */
+ public int getColumnCount() {
+ int colCount = 0;
+ if (validateIfNecessary()) {
+ for (int counter = 0; counter < getChildCount();
+ counter++) {
+ TableCellElementInfo cell = (TableCellElementInfo)
+ getChild(counter);
+
+ if (cell.validateIfNecessary()) {
+ colCount += cell.getColumnCount();
+ }
+ }
+ }
+ return colCount;
+ }
+
+ /**
+ * Overriden to invalidate the table as well as
+ * TableRowElementInfo.
+ */
+ protected void invalidate(boolean first) {
+ super.invalidate(first);
+ getParent().invalidate(true);
+ }
+
+ /**
+ * Places the TableCellElementInfos for this element in
+ * the grid.
+ */
+ private void updateGrid(int row) {
+ if (validateIfNecessary()) {
+ boolean emptyRow = false;
+
+ while (!emptyRow) {
+ for (int counter = 0; counter < grid[row].length;
+ counter++) {
+ if (grid[row][counter] == null) {
+ emptyRow = true;
+ break;
+ }
+ }
+ if (!emptyRow) {
+ row++;
+ }
+ }
+ for (int col = 0, counter = 0; counter < getChildCount();
+ counter++) {
+ TableCellElementInfo cell = (TableCellElementInfo)
+ getChild(counter);
+
+ while (grid[row][col] != null) {
+ col++;
+ }
+ for (int rowCount = cell.getRowCount() - 1;
+ rowCount >= 0; rowCount--) {
+ for (int colCount = cell.getColumnCount() - 1;
+ colCount >= 0; colCount--) {
+ grid[row + rowCount][col + colCount] = cell;
+ }
+ }
+ col += cell.getColumnCount();
+ }
+ }
+ }
+
+ /**
+ * Returns the column count of the number of columns that have
+ * a rowcount >= rowspan.
+ */
+ private int getColumnCount(int rowspan) {
+ if (validateIfNecessary()) {
+ int cols = 0;
+ for (int counter = 0; counter < getChildCount();
+ counter++) {
+ TableCellElementInfo cell = (TableCellElementInfo)
+ getChild(counter);
+
+ if (cell.getRowCount() >= rowspan) {
+ cols += cell.getColumnCount();
+ }
+ }
+ return cols;
+ }
+ return 0;
+ }
+ }
+
+ /**
+ * TableCellElementInfo is used to represents the cells of
+ * the table.
+ */
+ private class TableCellElementInfo extends ElementInfo {
+
+ private Accessible accessible;
+ private boolean isHeaderCell;
+
+ TableCellElementInfo(Element e, ElementInfo parent) {
+ super(e, parent);
+ this.isHeaderCell = false;
+ }
+
+ TableCellElementInfo(Element e, ElementInfo parent,
+ boolean isHeaderCell) {
+ super(e, parent);
+ this.isHeaderCell = isHeaderCell;
+ }
+
+ /*
+ * Returns whether this table cell is a header
+ */
+ public boolean isHeaderCell() {
+ return this.isHeaderCell;
+ }
+
+ /*
+ * Returns the Accessible representing this table cell
+ */
+ public Accessible getAccessible() {
+ accessible = null;
+ getAccessible(this);
+ return accessible;
+ }
+
+ /*
+ * Gets the outermost Accessible in the table cell
+ */
+ private void getAccessible(ElementInfo elementInfo) {
+ if (elementInfo instanceof Accessible) {
+ accessible = (Accessible)elementInfo;
+ return;
+ } else {
+ for (int i = 0; i < elementInfo.getChildCount(); i++) {
+ getAccessible(elementInfo.getChild(i));
+ }
+ }
+ }
+
+ /**
+ * Returns the rowspan attribute.
+ */
+ public int getRowCount() {
+ if (validateIfNecessary()) {
+ return Math.max(1, getIntAttr(getAttributes(),
+ HTML.Attribute.ROWSPAN, 1));
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the colspan attribute.
+ */
+ public int getColumnCount() {
+ if (validateIfNecessary()) {
+ return Math.max(1, getIntAttr(getAttributes(),
+ HTML.Attribute.COLSPAN, 1));
+ }
+ return 0;
+ }
+
+ /**
+ * Overriden to invalidate the TableRowElementInfo as well as
+ * the TableCellElementInfo.
+ */
+ protected void invalidate(boolean first) {
+ super.invalidate(first);
+ getParent().invalidate(true);
+ }
+ }
+ }
+
+
+ /**
+ * ElementInfo provides a slim down view of an Element. Each ElementInfo
+ * can have any number of child ElementInfos that are not necessarily
+ * direct children of the Element. As the Document changes various
+ * ElementInfos become invalidated. Before accessing a particular portion
+ * of an ElementInfo you should make sure it is valid by invoking
+ * <code>validateIfNecessary</code>, this will return true if
+ * successful, on the other hand a false return value indicates the
+ * ElementInfo is not valid and can never become valid again (usually
+ * the result of the Element the ElementInfo encapsulates being removed).
+ */
+ private class ElementInfo {
+
+ /**
+ * The children of this ElementInfo.
+ */
+ private ArrayList children;
+ /**
+ * The Element this ElementInfo is providing information for.
+ */
+ private Element element;
+ /**
+ * The parent ElementInfo, will be null for the root.
+ */
+ private ElementInfo parent;
+ /**
+ * Indicates the validity of the ElementInfo.
+ */
+ private boolean isValid;
+ /**
+ * Indicates if the ElementInfo can become valid.
+ */
+ private boolean canBeValid;
+
+
+ /**
+ * Creates the root ElementInfo.
+ */
+ ElementInfo(Element element) {
+ this(element, null);
+ }
+
+ /**
+ * Creates an ElementInfo representing <code>element</code> with
+ * the specified parent.
+ */
+ ElementInfo(Element element, ElementInfo parent) {
+ this.element = element;
+ this.parent = parent;
+ isValid = false;
+ canBeValid = true;
+ }
+
+ /**
+ * Validates the receiver. This recreates the children as well. This
+ * will be invoked within a <code>readLock</code>. If this is overriden
+ * it MUST invoke supers implementation first!
+ */
+ protected void validate() {
+ isValid = true;
+ loadChildren(getElement());
+ }
+
+ /**
+ * Recreates the direct children of <code>info</code>.
+ */
+ protected void loadChildren(Element parent) {
+ if (!parent.isLeaf()) {
+ for (int counter = 0, maxCounter = parent.getElementCount();
+ counter < maxCounter; counter++) {
+ Element e = parent.getElement(counter);
+ ElementInfo childInfo = createElementInfo(e, this);
+
+ if (childInfo != null) {
+ addChild(childInfo);
+ }
+ else {
+ loadChildren(e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the index of the child in the parent, or -1 for the
+ * root or if the parent isn't valid.
+ */
+ public int getIndexInParent() {
+ if (parent == null || !parent.isValid()) {
+ return -1;
+ }
+ return parent.indexOf(this);
+ }
+
+ /**
+ * Returns the Element this <code>ElementInfo</code> represents.
+ */
+ public Element getElement() {
+ return element;
+ }
+
+ /**
+ * Returns the parent of this Element, or null for the root.
+ */
+ public ElementInfo getParent() {
+ return parent;
+ }
+
+ /**
+ * Returns the index of the specified child, or -1 if
+ * <code>child</code> isn't a valid child.
+ */
+ public int indexOf(ElementInfo child) {
+ ArrayList children = this.children;
+
+ if (children != null) {
+ return children.indexOf(child);
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the child ElementInfo at <code>index</code>, or null
+ * if <code>index</code> isn't a valid index.
+ */
+ public ElementInfo getChild(int index) {
+ if (validateIfNecessary()) {
+ ArrayList children = this.children;
+
+ if (children != null && index >= 0 &&
+ index < children.size()) {
+ return (ElementInfo)children.get(index);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the number of children the ElementInfo contains.
+ */
+ public int getChildCount() {
+ validateIfNecessary();
+ return (children == null) ? 0 : children.size();
+ }
+
+ /**
+ * Adds a new child to this ElementInfo.
+ */
+ protected void addChild(ElementInfo child) {
+ if (children == null) {
+ children = new ArrayList();
+ }
+ children.add(child);
+ }
+
+ /**
+ * Returns the View corresponding to this ElementInfo, or null
+ * if the ElementInfo can't be validated.
+ */
+ protected View getView() {
+ if (!validateIfNecessary()) {
+ return null;
+ }
+ Object lock = lock();
+ try {
+ View rootView = getRootView();
+ Element e = getElement();
+ int start = e.getStartOffset();
+
+ if (rootView != null) {
+ return getView(rootView, e, start);
+ }
+ return null;
+ } finally {
+ unlock(lock);
+ }
+ }
+
+ /**
+ * Returns the Bounds for this ElementInfo, or null
+ * if the ElementInfo can't be validated.
+ */
+ public Rectangle getBounds() {
+ if (!validateIfNecessary()) {
+ return null;
+ }
+ Object lock = lock();
+ try {
+ Rectangle bounds = getRootEditorRect();
+ View rootView = getRootView();
+ Element e = getElement();
+
+ if (bounds != null && rootView != null) {
+ try {
+ return rootView.modelToView(e.getStartOffset(),
+ Position.Bias.Forward,
+ e.getEndOffset(),
+ Position.Bias.Backward,
+ bounds).getBounds();
+ } catch (BadLocationException ble) { }
+ }
+ } finally {
+ unlock(lock);
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if this ElementInfo is valid.
+ */
+ protected boolean isValid() {
+ return isValid;
+ }
+
+ /**
+ * Returns the AttributeSet associated with the Element, this will
+ * return null if the ElementInfo can't be validated.
+ */
+ protected AttributeSet getAttributes() {
+ if (validateIfNecessary()) {
+ return getElement().getAttributes();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the AttributeSet associated with the View that is
+ * representing this Element, this will
+ * return null if the ElementInfo can't be validated.
+ */
+ protected AttributeSet getViewAttributes() {
+ if (validateIfNecessary()) {
+ View view = getView();
+
+ if (view != null) {
+ return view.getElement().getAttributes();
+ }
+ return getElement().getAttributes();
+ }
+ return null;
+ }
+
+ /**
+ * Convenience method for getting an integer attribute from the passed
+ * in AttributeSet.
+ */
+ protected int getIntAttr(AttributeSet attrs, Object key, int deflt) {
+ if (attrs != null && attrs.isDefined(key)) {
+ int i;
+ String val = (String)attrs.getAttribute(key);
+ if (val == null) {
+ i = deflt;
+ }
+ else {
+ try {
+ i = Math.max(0, Integer.parseInt(val));
+ } catch (NumberFormatException x) {
+ i = deflt;
+ }
+ }
+ return i;
+ }
+ return deflt;
+ }
+
+ /**
+ * Validates the ElementInfo if necessary. Some ElementInfos may
+ * never be valid again. You should check <code>isValid</code> before
+ * using one. This will reload the children and invoke
+ * <code>validate</code> if the ElementInfo is invalid and can become
+ * valid again. This will return true if the receiver is valid.
+ */
+ protected boolean validateIfNecessary() {
+ if (!isValid() && canBeValid) {
+ children = null;
+ Object lock = lock();
+
+ try {
+ validate();
+ } finally {
+ unlock(lock);
+ }
+ }
+ return isValid();
+ }
+
+ /**
+ * Invalidates the ElementInfo. Subclasses should override this
+ * if they need to reset state once invalid.
+ */
+ protected void invalidate(boolean first) {
+ if (!isValid()) {
+ if (canBeValid && !first) {
+ canBeValid = false;
+ }
+ return;
+ }
+ isValid = false;
+ canBeValid = first;
+ if (children != null) {
+ for (int counter = 0; counter < children.size(); counter++) {
+ ((ElementInfo)children.get(counter)).invalidate(false);
+ }
+ children = null;
+ }
+ }
+
+ private View getView(View parent, Element e, int start) {
+ if (parent.getElement() == e) {
+ return parent;
+ }
+ int index = parent.getViewIndex(start, Position.Bias.Forward);
+
+ if (index != -1 && index < parent.getViewCount()) {
+ return getView(parent.getView(index), e, start);
+ }
+ return null;
+ }
+
+ private int getClosestInfoIndex(int index) {
+ for (int counter = 0; counter < getChildCount(); counter++) {
+ ElementInfo info = getChild(counter);
+
+ if (index < info.getElement().getEndOffset() ||
+ index == info.getElement().getStartOffset()) {
+ return counter;
+ }
+ }
+ return -1;
+ }
+
+ private void update(DocumentEvent e) {
+ if (!isValid()) {
+ return;
+ }
+ ElementInfo parent = getParent();
+ Element element = getElement();
+
+ do {
+ DocumentEvent.ElementChange ec = e.getChange(element);
+ if (ec != null) {
+ if (element == getElement()) {
+ // One of our children changed.
+ invalidate(true);
+ }
+ else if (parent != null) {
+ parent.invalidate(parent == getRootInfo());
+ }
+ return;
+ }
+ element = element.getParentElement();
+ } while (parent != null && element != null &&
+ element != parent.getElement());
+
+ if (getChildCount() > 0) {
+ Element elem = getElement();
+ int pos = e.getOffset();
+ int index0 = getClosestInfoIndex(pos);
+ if (index0 == -1 &&
+ e.getType() == DocumentEvent.EventType.REMOVE &&
+ pos >= elem.getEndOffset()) {
+ // Event beyond our offsets. We may have represented this,
+ // that is the remove may have removed one of our child
+ // Elements that represented this, so, we should foward
+ // to last element.
+ index0 = getChildCount() - 1;
+ }
+ ElementInfo info = (index0 >= 0) ? getChild(index0) : null;
+ if (info != null &&
+ (info.getElement().getStartOffset() == pos) && (pos > 0)) {
+ // If at a boundary, forward the event to the previous
+ // ElementInfo too.
+ index0 = Math.max(index0 - 1, 0);
+ }
+ int index1;
+ if (e.getType() != DocumentEvent.EventType.REMOVE) {
+ index1 = getClosestInfoIndex(pos + e.getLength());
+ if (index1 < 0) {
+ index1 = getChildCount() - 1;
+ }
+ }
+ else {
+ index1 = index0;
+ // A remove may result in empty elements.
+ while ((index1 + 1) < getChildCount() &&
+ getChild(index1 + 1).getElement().getEndOffset() ==
+ getChild(index1 + 1).getElement().getStartOffset()){
+ index1++;
+ }
+ }
+ index0 = Math.max(index0, 0);
+ // The check for isValid is here as in the process of
+ // forwarding update our child may invalidate us.
+ for (int i = index0; i <= index1 && isValid(); i++) {
+ getChild(i).update(e);
+ }
+ }
+ }
+ }
+
+ /**
+ * DocumentListener installed on the current Document. Will invoke
+ * <code>update</code> on the <code>RootInfo</code> in response to
+ * any event.
+ */
+ private class DocumentHandler implements DocumentListener {
+ public void insertUpdate(DocumentEvent e) {
+ getRootInfo().update(e);
+ }
+ public void removeUpdate(DocumentEvent e) {
+ getRootInfo().update(e);
+ }
+ public void changedUpdate(DocumentEvent e) {
+ getRootInfo().update(e);
+ }
+ }
+
+ /*
+ * PropertyChangeListener installed on the editor.
+ */
+ private class PropertyChangeHandler implements PropertyChangeListener {
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (evt.getPropertyName().equals("document")) {
+ // handle the document change
+ setDocument(editor.getDocument());
+ }
+ }
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/BRView.java b/src/share/classes/javax/swing/text/html/BRView.java
new file mode 100644
index 000000000..77aa095c5
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/BRView.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 1998-2004 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import javax.swing.text.*;
+
+/**
+ * Processes the &lt;BR&gt; tag. In other words, forces a line break.
+ *
+ * @author Sunita Mani
+ */
+class BRView extends InlineView {
+
+ /**
+ * Creates a new view that represents a &lt;BR&gt; element.
+ *
+ * @param elem the element to create a view for
+ */
+ public BRView(Element elem) {
+ super(elem);
+ }
+
+ /**
+ * Forces a line break.
+ *
+ * @return View.ForcedBreakWeight
+ */
+ public int getBreakWeight(int axis, float pos, float len) {
+ if (axis == X_AXIS) {
+ return ForcedBreakWeight;
+ } else {
+ return super.getBreakWeight(axis, pos, len);
+ }
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/BlockView.java b/src/share/classes/javax/swing/text/html/BlockView.java
new file mode 100644
index 000000000..8e453bddd
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/BlockView.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.util.Enumeration;
+import java.awt.*;
+import javax.swing.SizeRequirements;
+import javax.swing.border.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.*;
+
+/**
+ * A view implementation to display a block (as a box)
+ * with CSS specifications.
+ *
+ * @author Timothy Prinzing
+ */
+public class BlockView extends BoxView {
+
+ /**
+ * Creates a new view that represents an
+ * html box. This can be used for a number
+ * of elements.
+ *
+ * @param elem the element to create a view for
+ * @param axis either View.X_AXIS or View.Y_AXIS
+ */
+ public BlockView(Element elem, int axis) {
+ super(elem, axis);
+ }
+
+ /**
+ * Establishes the parent view for this view. This is
+ * guaranteed to be called before any other methods if the
+ * parent view is functioning properly.
+ * <p>
+ * This is implemented
+ * to forward to the superclass as well as call the
+ * {@link #setPropertiesFromAttributes()}
+ * method to set the paragraph properties from the css
+ * attributes. The call is made at this time to ensure
+ * the ability to resolve upward through the parents
+ * view attributes.
+ *
+ * @param parent the new parent, or null if the view is
+ * being removed from a parent it was previously added
+ * to
+ */
+ public void setParent(View parent) {
+ super.setParent(parent);
+ if (parent != null) {
+ setPropertiesFromAttributes();
+ }
+ }
+
+ /**
+ * Calculate the requirements of the block along the major
+ * axis (i.e. the axis along with it tiles). This is implemented
+ * to provide the superclass behavior and then adjust it if the
+ * CSS width or height attribute is specified and applicable to
+ * the axis.
+ */
+ protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
+ if (r == null) {
+ r = new SizeRequirements();
+ }
+ if (! spanSetFromAttributes(axis, r, cssWidth, cssHeight)) {
+ r = super.calculateMajorAxisRequirements(axis, r);
+ }
+ else {
+ // Offset by the margins so that pref/min/max return the
+ // right value.
+ SizeRequirements parentR = super.calculateMajorAxisRequirements(
+ axis, null);
+ int margin = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
+ getTopInset() + getBottomInset();
+ r.minimum -= margin;
+ r.preferred -= margin;
+ r.maximum -= margin;
+ constrainSize(axis, r, parentR);
+ }
+ return r;
+ }
+
+ /**
+ * Calculate the requirements of the block along the minor
+ * axis (i.e. the axis orthoginal to the axis along with it tiles).
+ * This is implemented
+ * to provide the superclass behavior and then adjust it if the
+ * CSS width or height attribute is specified and applicable to
+ * the axis.
+ */
+ protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
+ if (r == null) {
+ r = new SizeRequirements();
+ }
+
+ if (! spanSetFromAttributes(axis, r, cssWidth, cssHeight)) {
+
+ /*
+ * The requirements were not directly specified by attributes, so
+ * compute the aggregate of the requirements of the children. The
+ * children that have a percentage value specified will be treated
+ * as completely stretchable since that child is not limited in any
+ * way.
+ */
+/*
+ int min = 0;
+ long pref = 0;
+ int max = 0;
+ int n = getViewCount();
+ for (int i = 0; i < n; i++) {
+ View v = getView(i);
+ min = Math.max((int) v.getMinimumSpan(axis), min);
+ pref = Math.max((int) v.getPreferredSpan(axis), pref);
+ if (
+ max = Math.max((int) v.getMaximumSpan(axis), max);
+
+ }
+ r.preferred = (int) pref;
+ r.minimum = min;
+ r.maximum = max;
+ */
+ r = super.calculateMinorAxisRequirements(axis, r);
+ }
+ else {
+ // Offset by the margins so that pref/min/max return the
+ // right value.
+ SizeRequirements parentR = super.calculateMinorAxisRequirements(
+ axis, null);
+ int margin = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
+ getTopInset() + getBottomInset();
+ r.minimum -= margin;
+ r.preferred -= margin;
+ r.maximum -= margin;
+ constrainSize(axis, r, parentR);
+ }
+
+ /*
+ * Set the alignment based upon the CSS properties if it is
+ * specified. For X_AXIS this would be text-align, for
+ * Y_AXIS this would be vertical-align.
+ */
+ if (axis == X_AXIS) {
+ Object o = getAttributes().getAttribute(CSS.Attribute.TEXT_ALIGN);
+ if (o != null) {
+ String align = o.toString();
+ if (align.equals("center")) {
+ r.alignment = 0.5f;
+ } else if (align.equals("right")) {
+ r.alignment = 1.0f;
+ } else {
+ r.alignment = 0.0f;
+ }
+ }
+ }
+ // Y_AXIS TBD
+ return r;
+ }
+
+ boolean isPercentage(int axis, AttributeSet a) {
+ if (axis == X_AXIS) {
+ if (cssWidth != null) {
+ return cssWidth.isPercentage();
+ }
+ } else {
+ if (cssHeight != null) {
+ return cssHeight.isPercentage();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Adjust the given requirements to the CSS width or height if
+ * it is specified along the applicable axis. Return true if the
+ * size is exactly specified, false if the span is not specified
+ * in an attribute or the size specified is a percentage.
+ */
+ static boolean spanSetFromAttributes(int axis, SizeRequirements r,
+ CSS.LengthValue cssWidth,
+ CSS.LengthValue cssHeight) {
+ if (axis == X_AXIS) {
+ if ((cssWidth != null) && (! cssWidth.isPercentage())) {
+ r.minimum = r.preferred = r.maximum = (int) cssWidth.getValue();
+ return true;
+ }
+ } else {
+ if ((cssHeight != null) && (! cssHeight.isPercentage())) {
+ r.minimum = r.preferred = r.maximum = (int) cssHeight.getValue();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Performs layout for the minor axis of the box (i.e. the
+ * axis orthoginal to the axis that it represents). The results
+ * of the layout (the offset and span for each children) are
+ * placed in the given arrays which represent the allocations to
+ * the children along the minor axis.
+ *
+ * @param targetSpan the total span given to the view, which
+ * whould be used to layout the childre.
+ * @param axis the axis being layed out
+ * @param offsets the offsets from the origin of the view for
+ * each of the child views; this is a return value and is
+ * filled in by the implementation of this method
+ * @param spans the span of each child view; this is a return
+ * value and is filled in by the implementation of this method
+ */
+ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
+ int n = getViewCount();
+ Object key = (axis == X_AXIS) ? CSS.Attribute.WIDTH : CSS.Attribute.HEIGHT;
+ for (int i = 0; i < n; i++) {
+ View v = getView(i);
+ int min = (int) v.getMinimumSpan(axis);
+ int max;
+
+ // check for percentage span
+ AttributeSet a = v.getAttributes();
+ CSS.LengthValue lv = (CSS.LengthValue) a.getAttribute(key);
+ if ((lv != null) && lv.isPercentage()) {
+ // bound the span to the percentage specified
+ min = Math.max((int) lv.getValue(targetSpan), min);
+ max = min;
+ } else {
+ max = (int)v.getMaximumSpan(axis);
+ }
+
+ // assign the offset and span for the child
+ if (max < targetSpan) {
+ // can't make the child this wide, align it
+ float align = v.getAlignment(axis);
+ offsets[i] = (int) ((targetSpan - max) * align);
+ spans[i] = max;
+ } else {
+ // make it the target width, or as small as it can get.
+ offsets[i] = 0;
+ spans[i] = Math.max(min, targetSpan);
+ }
+ }
+ }
+
+
+ /**
+ * Renders using the given rendering surface and area on that
+ * surface. This is implemented to delegate to the css box
+ * painter to paint the border and background prior to the
+ * interior.
+ *
+ * @param g the rendering surface to use
+ * @param allocation the allocated region to render into
+ * @see View#paint
+ */
+ public void paint(Graphics g, Shape allocation) {
+ Rectangle a = (Rectangle) allocation;
+ painter.paint(g, a.x, a.y, a.width, a.height, this);
+ super.paint(g, a);
+ }
+
+ /**
+ * Fetches the attributes to use when rendering. This is
+ * implemented to multiplex the attributes specified in the
+ * model with a StyleSheet.
+ */
+ public AttributeSet getAttributes() {
+ if (attr == null) {
+ StyleSheet sheet = getStyleSheet();
+ attr = sheet.getViewAttributes(this);
+ }
+ return attr;
+ }
+
+ /**
+ * Gets the resize weight.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the weight
+ * @exception IllegalArgumentException for an invalid axis
+ */
+ public int getResizeWeight(int axis) {
+ switch (axis) {
+ case View.X_AXIS:
+ return 1;
+ case View.Y_AXIS:
+ return 0;
+ default:
+ throw new IllegalArgumentException("Invalid axis: " + axis);
+ }
+ }
+
+ /**
+ * Gets the alignment.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the alignment
+ */
+ public float getAlignment(int axis) {
+ switch (axis) {
+ case View.X_AXIS:
+ return 0;
+ case View.Y_AXIS:
+ if (getViewCount() == 0) {
+ return 0;
+ }
+ float span = getPreferredSpan(View.Y_AXIS);
+ View v = getView(0);
+ float above = v.getPreferredSpan(View.Y_AXIS);
+ float a = (((int)span) != 0) ? (above * v.getAlignment(View.Y_AXIS)) / span: 0;
+ return a;
+ default:
+ throw new IllegalArgumentException("Invalid axis: " + axis);
+ }
+ }
+
+ public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
+ super.changedUpdate(changes, a, f);
+ int pos = changes.getOffset();
+ if (pos <= getStartOffset() && (pos + changes.getLength()) >=
+ getEndOffset()) {
+ setPropertiesFromAttributes();
+ }
+ }
+
+ /**
+ * Determines the preferred span for this view along an
+ * axis.
+ *
+ * @param axis may be either <code>View.X_AXIS</code>
+ * or <code>View.Y_AXIS</code>
+ * @return the span the view would like to be rendered into >= 0;
+ * typically the view is told to render into the span
+ * that is returned, although there is no guarantee;
+ * the parent may choose to resize or break the view
+ * @exception IllegalArgumentException for an invalid axis type
+ */
+ public float getPreferredSpan(int axis) {
+ return super.getPreferredSpan(axis);
+ }
+
+ /**
+ * Determines the minimum span for this view along an
+ * axis.
+ *
+ * @param axis may be either <code>View.X_AXIS</code>
+ * or <code>View.Y_AXIS</code>
+ * @return the span the view would like to be rendered into >= 0;
+ * typically the view is told to render into the span
+ * that is returned, although there is no guarantee;
+ * the parent may choose to resize or break the view
+ * @exception IllegalArgumentException for an invalid axis type
+ */
+ public float getMinimumSpan(int axis) {
+ return super.getMinimumSpan(axis);
+ }
+
+ /**
+ * Determines the maximum span for this view along an
+ * axis.
+ *
+ * @param axis may be either <code>View.X_AXIS</code>
+ * or <code>View.Y_AXIS</code>
+ * @return the span the view would like to be rendered into >= 0;
+ * typically the view is told to render into the span
+ * that is returned, although there is no guarantee;
+ * the parent may choose to resize or break the view
+ * @exception IllegalArgumentException for an invalid axis type
+ */
+ public float getMaximumSpan(int axis) {
+ return super.getMaximumSpan(axis);
+ }
+
+ /**
+ * Update any cached values that come from attributes.
+ */
+ protected void setPropertiesFromAttributes() {
+
+ // update attributes
+ StyleSheet sheet = getStyleSheet();
+ attr = sheet.getViewAttributes(this);
+
+ // Reset the painter
+ painter = sheet.getBoxPainter(attr);
+ if (attr != null) {
+ setInsets((short) painter.getInset(TOP, this),
+ (short) painter.getInset(LEFT, this),
+ (short) painter.getInset(BOTTOM, this),
+ (short) painter.getInset(RIGHT, this));
+ }
+
+ // Get the width/height
+ cssWidth = (CSS.LengthValue) attr.getAttribute(CSS.Attribute.WIDTH);
+ cssHeight = (CSS.LengthValue) attr.getAttribute(CSS.Attribute.HEIGHT);
+ }
+
+ protected StyleSheet getStyleSheet() {
+ HTMLDocument doc = (HTMLDocument) getDocument();
+ return doc.getStyleSheet();
+ }
+
+ /**
+ * Constrains <code>want</code> to fit in the minimum size specified
+ * by <code>min</code>.
+ */
+ private void constrainSize(int axis, SizeRequirements want,
+ SizeRequirements min) {
+ if (min.minimum > want.minimum) {
+ want.minimum = want.preferred = min.minimum;
+ want.maximum = Math.max(want.maximum, min.maximum);
+ }
+ }
+
+ private AttributeSet attr;
+ private StyleSheet.BoxPainter painter;
+
+ private CSS.LengthValue cssWidth;
+ private CSS.LengthValue cssHeight;
+
+}
diff --git a/src/share/classes/javax/swing/text/html/CSS.java b/src/share/classes/javax/swing/text/html/CSS.java
new file mode 100644
index 000000000..40b5977e0
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/CSS.java
@@ -0,0 +1,3383 @@
+/*
+ * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.GraphicsEnvironment;
+import java.awt.Toolkit;
+import java.awt.HeadlessException;
+import java.awt.Image;
+import java.io.*;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.MalformedURLException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+import java.util.Locale;
+import javax.swing.ImageIcon;
+import javax.swing.SizeRequirements;
+import javax.swing.text.*;
+
+/**
+ * Defines a set of
+ * <a href="http://www.w3.org/TR/REC-CSS1">CSS attributes</a>
+ * as a typesafe enumeration. The HTML View implementations use
+ * CSS attributes to determine how they will render. This also defines
+ * methods to map between CSS/HTML/StyleConstants. Any shorthand
+ * properties, such as font, are mapped to the intrinsic properties.
+ * <p>The following describes the CSS properties that are suppored by the
+ * rendering engine:
+ * <ul><li>font-family
+ * <li>font-style
+ * <li>font-size (supports relative units)
+ * <li>font-weight
+ * <li>font
+ * <li>color
+ * <li>background-color (with the exception of transparent)
+ * <li>background-image
+ * <li>background-repeat
+ * <li>background-position
+ * <li>background
+ * <li>background-repeat
+ * <li>text-decoration (with the exception of blink and overline)
+ * <li>vertical-align (only sup and super)
+ * <li>text-align (justify is treated as center)
+ * <li>margin-top
+ * <li>margin-right
+ * <li>margin-bottom
+ * <li>margin-left
+ * <li>margin
+ * <li>padding-top
+ * <li>padding-right
+ * <li>padding-bottom
+ * <li>padding-left
+ * <li>border-style (only supports inset, outset and none)
+ * <li>list-style-type
+ * <li>list-style-position
+ * </ul>
+ * The following are modeled, but currently not rendered.
+ * <ul><li>font-variant
+ * <li>background-attachment (background always treated as scroll)
+ * <li>word-spacing
+ * <li>letter-spacing
+ * <li>text-indent
+ * <li>text-transform
+ * <li>line-height
+ * <li>border-top-width (this is used to indicate if a border should be used)
+ * <li>border-right-width
+ * <li>border-bottom-width
+ * <li>border-left-width
+ * <li>border-width
+ * <li>border-top
+ * <li>border-right
+ * <li>border-bottom
+ * <li>border-left
+ * <li>border
+ * <li>width
+ * <li>height
+ * <li>float
+ * <li>clear
+ * <li>display
+ * <li>white-space
+ * <li>list-style
+ * </ul>
+ * <p><b>Note: for the time being we do not fully support relative units,
+ * unless noted, so that
+ * p { margin-top: 10% } will be treated as if no margin-top was specified.
+ *
+ * @author Timothy Prinzing
+ * @author Scott Violet
+ * @see StyleSheet
+ */
+public class CSS implements Serializable {
+
+ /**
+ * Definitions to be used as a key on AttributeSet's
+ * that might hold CSS attributes. Since this is a
+ * closed set (i.e. defined exactly by the specification),
+ * it is final and cannot be extended.
+ */
+ public static final class Attribute {
+
+ private Attribute(String name, String defaultValue, boolean inherited) {
+ this.name = name;
+ this.defaultValue = defaultValue;
+ this.inherited = inherited;
+ }
+
+ /**
+ * The string representation of the attribute. This
+ * should exactly match the string specified in the
+ * CSS specification.
+ */
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * Fetch the default value for the attribute.
+ * If there is no default value (such as for
+ * composite attributes), null will be returned.
+ */
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ /**
+ * Indicates if the attribute should be inherited
+ * from the parent or not.
+ */
+ public boolean isInherited() {
+ return inherited;
+ }
+
+ private String name;
+ private String defaultValue;
+ private boolean inherited;
+
+
+ public static final Attribute BACKGROUND =
+ new Attribute("background", null, false);
+
+ public static final Attribute BACKGROUND_ATTACHMENT =
+ new Attribute("background-attachment", "scroll", false);
+
+ public static final Attribute BACKGROUND_COLOR =
+ new Attribute("background-color", "transparent", false);
+
+ public static final Attribute BACKGROUND_IMAGE =
+ new Attribute("background-image", "none", false);
+
+ public static final Attribute BACKGROUND_POSITION =
+ new Attribute("background-position", null, false);
+
+ public static final Attribute BACKGROUND_REPEAT =
+ new Attribute("background-repeat", "repeat", false);
+
+ public static final Attribute BORDER =
+ new Attribute("border", null, false);
+
+ public static final Attribute BORDER_BOTTOM =
+ new Attribute("border-bottom", null, false);
+
+ public static final Attribute BORDER_BOTTOM_COLOR =
+ new Attribute("border-bottom-color", null, false);
+
+ public static final Attribute BORDER_BOTTOM_STYLE =
+ new Attribute("border-bottom-style", "none", false);
+
+ public static final Attribute BORDER_BOTTOM_WIDTH =
+ new Attribute("border-bottom-width", "medium", false);
+
+ public static final Attribute BORDER_COLOR =
+ new Attribute("border-color", null, false);
+
+ public static final Attribute BORDER_LEFT =
+ new Attribute("border-left", null, false);
+
+ public static final Attribute BORDER_LEFT_COLOR =
+ new Attribute("border-left-color", null, false);
+
+ public static final Attribute BORDER_LEFT_STYLE =
+ new Attribute("border-left-style", "none", false);
+
+ public static final Attribute BORDER_LEFT_WIDTH =
+ new Attribute("border-left-width", "medium", false);
+
+ public static final Attribute BORDER_RIGHT =
+ new Attribute("border-right", null, false);
+
+ public static final Attribute BORDER_RIGHT_COLOR =
+ new Attribute("border-right-color", null, false);
+
+ public static final Attribute BORDER_RIGHT_STYLE =
+ new Attribute("border-right-style", "none", false);
+
+ public static final Attribute BORDER_RIGHT_WIDTH =
+ new Attribute("border-right-width", "medium", false);
+
+ public static final Attribute BORDER_STYLE =
+ new Attribute("border-style", "none", false);
+
+ public static final Attribute BORDER_TOP =
+ new Attribute("border-top", null, false);
+
+ public static final Attribute BORDER_TOP_COLOR =
+ new Attribute("border-top-color", null, false);
+
+ public static final Attribute BORDER_TOP_STYLE =
+ new Attribute("border-top-style", "none", false);
+
+ public static final Attribute BORDER_TOP_WIDTH =
+ new Attribute("border-top-width", "medium", false);
+
+ public static final Attribute BORDER_WIDTH =
+ new Attribute("border-width", "medium", false);
+
+ public static final Attribute CLEAR =
+ new Attribute("clear", "none", false);
+
+ public static final Attribute COLOR =
+ new Attribute("color", "black", true);
+
+ public static final Attribute DISPLAY =
+ new Attribute("display", "block", false);
+
+ public static final Attribute FLOAT =
+ new Attribute("float", "none", false);
+
+ public static final Attribute FONT =
+ new Attribute("font", null, true);
+
+ public static final Attribute FONT_FAMILY =
+ new Attribute("font-family", null, true);
+
+ public static final Attribute FONT_SIZE =
+ new Attribute("font-size", "medium", true);
+
+ public static final Attribute FONT_STYLE =
+ new Attribute("font-style", "normal", true);
+
+ public static final Attribute FONT_VARIANT =
+ new Attribute("font-variant", "normal", true);
+
+ public static final Attribute FONT_WEIGHT =
+ new Attribute("font-weight", "normal", true);
+
+ public static final Attribute HEIGHT =
+ new Attribute("height", "auto", false);
+
+ public static final Attribute LETTER_SPACING =
+ new Attribute("letter-spacing", "normal", true);
+
+ public static final Attribute LINE_HEIGHT =
+ new Attribute("line-height", "normal", true);
+
+ public static final Attribute LIST_STYLE =
+ new Attribute("list-style", null, true);
+
+ public static final Attribute LIST_STYLE_IMAGE =
+ new Attribute("list-style-image", "none", true);
+
+ public static final Attribute LIST_STYLE_POSITION =
+ new Attribute("list-style-position", "outside", true);
+
+ public static final Attribute LIST_STYLE_TYPE =
+ new Attribute("list-style-type", "disc", true);
+
+ public static final Attribute MARGIN =
+ new Attribute("margin", null, false);
+
+ public static final Attribute MARGIN_BOTTOM =
+ new Attribute("margin-bottom", "0", false);
+
+ public static final Attribute MARGIN_LEFT =
+ new Attribute("margin-left", "0", false);
+
+ public static final Attribute MARGIN_RIGHT =
+ new Attribute("margin-right", "0", false);
+
+ /*
+ * made up css attributes to describe orientation depended
+ * margins. used for <dir>, <menu>, <ul> etc. see
+ * 5088268 for more details
+ */
+ static final Attribute MARGIN_LEFT_LTR =
+ new Attribute("margin-left-ltr",
+ Integer.toString(Integer.MIN_VALUE), false);
+
+ static final Attribute MARGIN_LEFT_RTL =
+ new Attribute("margin-left-rtl",
+ Integer.toString(Integer.MIN_VALUE), false);
+
+ static final Attribute MARGIN_RIGHT_LTR =
+ new Attribute("margin-right-ltr",
+ Integer.toString(Integer.MIN_VALUE), false);
+
+ static final Attribute MARGIN_RIGHT_RTL =
+ new Attribute("margin-right-rtl",
+ Integer.toString(Integer.MIN_VALUE), false);
+
+
+ public static final Attribute MARGIN_TOP =
+ new Attribute("margin-top", "0", false);
+
+ public static final Attribute PADDING =
+ new Attribute("padding", null, false);
+
+ public static final Attribute PADDING_BOTTOM =
+ new Attribute("padding-bottom", "0", false);
+
+ public static final Attribute PADDING_LEFT =
+ new Attribute("padding-left", "0", false);
+
+ public static final Attribute PADDING_RIGHT =
+ new Attribute("padding-right", "0", false);
+
+ public static final Attribute PADDING_TOP =
+ new Attribute("padding-top", "0", false);
+
+ public static final Attribute TEXT_ALIGN =
+ new Attribute("text-align", null, true);
+
+ public static final Attribute TEXT_DECORATION =
+ new Attribute("text-decoration", "none", true);
+
+ public static final Attribute TEXT_INDENT =
+ new Attribute("text-indent", "0", true);
+
+ public static final Attribute TEXT_TRANSFORM =
+ new Attribute("text-transform", "none", true);
+
+ public static final Attribute VERTICAL_ALIGN =
+ new Attribute("vertical-align", "baseline", false);
+
+ public static final Attribute WORD_SPACING =
+ new Attribute("word-spacing", "normal", true);
+
+ public static final Attribute WHITE_SPACE =
+ new Attribute("white-space", "normal", true);
+
+ public static final Attribute WIDTH =
+ new Attribute("width", "auto", false);
+
+ /*public*/ static final Attribute BORDER_SPACING =
+ new Attribute("border-spacing", "0", true);
+
+ /*public*/ static final Attribute CAPTION_SIDE =
+ new Attribute("caption-side", "left", true);
+
+ // All possible CSS attribute keys.
+ static final Attribute[] allAttributes = {
+ BACKGROUND, BACKGROUND_ATTACHMENT, BACKGROUND_COLOR,
+ BACKGROUND_IMAGE, BACKGROUND_POSITION, BACKGROUND_REPEAT,
+ BORDER, BORDER_BOTTOM, BORDER_BOTTOM_WIDTH, BORDER_COLOR,
+ BORDER_LEFT, BORDER_LEFT_WIDTH, BORDER_RIGHT, BORDER_RIGHT_WIDTH,
+ BORDER_STYLE, BORDER_TOP, BORDER_TOP_WIDTH, BORDER_WIDTH,
+ BORDER_TOP_STYLE, BORDER_RIGHT_STYLE, BORDER_BOTTOM_STYLE,
+ BORDER_LEFT_STYLE,
+ BORDER_TOP_COLOR, BORDER_RIGHT_COLOR, BORDER_BOTTOM_COLOR,
+ BORDER_LEFT_COLOR,
+ CLEAR, COLOR, DISPLAY, FLOAT, FONT, FONT_FAMILY, FONT_SIZE,
+ FONT_STYLE, FONT_VARIANT, FONT_WEIGHT, HEIGHT, LETTER_SPACING,
+ LINE_HEIGHT, LIST_STYLE, LIST_STYLE_IMAGE, LIST_STYLE_POSITION,
+ LIST_STYLE_TYPE, MARGIN, MARGIN_BOTTOM, MARGIN_LEFT, MARGIN_RIGHT,
+ MARGIN_TOP, PADDING, PADDING_BOTTOM, PADDING_LEFT, PADDING_RIGHT,
+ PADDING_TOP, TEXT_ALIGN, TEXT_DECORATION, TEXT_INDENT, TEXT_TRANSFORM,
+ VERTICAL_ALIGN, WORD_SPACING, WHITE_SPACE, WIDTH,
+ BORDER_SPACING, CAPTION_SIDE,
+ MARGIN_LEFT_LTR, MARGIN_LEFT_RTL, MARGIN_RIGHT_LTR, MARGIN_RIGHT_RTL
+ };
+
+ private static final Attribute[] ALL_MARGINS =
+ { MARGIN_TOP, MARGIN_RIGHT, MARGIN_BOTTOM, MARGIN_LEFT };
+ private static final Attribute[] ALL_PADDING =
+ { PADDING_TOP, PADDING_RIGHT, PADDING_BOTTOM, PADDING_LEFT };
+ private static final Attribute[] ALL_BORDER_WIDTHS =
+ { BORDER_TOP_WIDTH, BORDER_RIGHT_WIDTH, BORDER_BOTTOM_WIDTH,
+ BORDER_LEFT_WIDTH };
+ private static final Attribute[] ALL_BORDER_STYLES =
+ { BORDER_TOP_STYLE, BORDER_RIGHT_STYLE, BORDER_BOTTOM_STYLE,
+ BORDER_LEFT_STYLE };
+ private static final Attribute[] ALL_BORDER_COLORS =
+ { BORDER_TOP_COLOR, BORDER_RIGHT_COLOR, BORDER_BOTTOM_COLOR,
+ BORDER_LEFT_COLOR };
+
+ }
+
+ static final class Value {
+
+ private Value(String name) {
+ this.name = name;
+ }
+
+ /**
+ * The string representation of the attribute. This
+ * should exactly match the string specified in the
+ * CSS specification.
+ */
+ public String toString() {
+ return name;
+ }
+
+ static final Value INHERITED = new Value("inherited");
+ static final Value NONE = new Value("none");
+ static final Value HIDDEN = new Value("hidden");
+ static final Value DOTTED = new Value("dotted");
+ static final Value DASHED = new Value("dashed");
+ static final Value SOLID = new Value("solid");
+ static final Value DOUBLE = new Value("double");
+ static final Value GROOVE = new Value("groove");
+ static final Value RIDGE = new Value("ridge");
+ static final Value INSET = new Value("inset");
+ static final Value OUTSET = new Value("outset");
+ // Lists.
+ static final Value DISC = new Value("disc");
+ static final Value CIRCLE = new Value("circle");
+ static final Value SQUARE = new Value("square");
+ static final Value DECIMAL = new Value("decimal");
+ static final Value LOWER_ROMAN = new Value("lower-roman");
+ static final Value UPPER_ROMAN = new Value("upper-roman");
+ static final Value LOWER_ALPHA = new Value("lower-alpha");
+ static final Value UPPER_ALPHA = new Value("upper-alpha");
+ // background-repeat
+ static final Value BACKGROUND_NO_REPEAT = new Value("no-repeat");
+ static final Value BACKGROUND_REPEAT = new Value("repeat");
+ static final Value BACKGROUND_REPEAT_X = new Value("repeat-x");
+ static final Value BACKGROUND_REPEAT_Y = new Value("repeat-y");
+ // background-attachment
+ static final Value BACKGROUND_SCROLL = new Value("scroll");
+ static final Value BACKGROUND_FIXED = new Value("fixed");
+
+ private String name;
+
+ static final Value[] allValues = {
+ INHERITED, NONE, DOTTED, DASHED, SOLID, DOUBLE, GROOVE,
+ RIDGE, INSET, OUTSET, DISC, CIRCLE, SQUARE, DECIMAL,
+ LOWER_ROMAN, UPPER_ROMAN, LOWER_ALPHA, UPPER_ALPHA,
+ BACKGROUND_NO_REPEAT, BACKGROUND_REPEAT,
+ BACKGROUND_REPEAT_X, BACKGROUND_REPEAT_Y,
+ BACKGROUND_FIXED, BACKGROUND_FIXED
+ };
+ }
+
+ public CSS() {
+ baseFontSize = baseFontSizeIndex + 1;
+ // setup the css conversion table
+ valueConvertor = new Hashtable();
+ valueConvertor.put(CSS.Attribute.FONT_SIZE, new FontSize());
+ valueConvertor.put(CSS.Attribute.FONT_FAMILY, new FontFamily());
+ valueConvertor.put(CSS.Attribute.FONT_WEIGHT, new FontWeight());
+ Object bs = new BorderStyle();
+ valueConvertor.put(CSS.Attribute.BORDER_TOP_STYLE, bs);
+ valueConvertor.put(CSS.Attribute.BORDER_RIGHT_STYLE, bs);
+ valueConvertor.put(CSS.Attribute.BORDER_BOTTOM_STYLE, bs);
+ valueConvertor.put(CSS.Attribute.BORDER_LEFT_STYLE, bs);
+ Object cv = new ColorValue();
+ valueConvertor.put(CSS.Attribute.COLOR, cv);
+ valueConvertor.put(CSS.Attribute.BACKGROUND_COLOR, cv);
+ valueConvertor.put(CSS.Attribute.BORDER_TOP_COLOR, cv);
+ valueConvertor.put(CSS.Attribute.BORDER_RIGHT_COLOR, cv);
+ valueConvertor.put(CSS.Attribute.BORDER_BOTTOM_COLOR, cv);
+ valueConvertor.put(CSS.Attribute.BORDER_LEFT_COLOR, cv);
+ Object lv = new LengthValue();
+ valueConvertor.put(CSS.Attribute.MARGIN_TOP, lv);
+ valueConvertor.put(CSS.Attribute.MARGIN_BOTTOM, lv);
+ valueConvertor.put(CSS.Attribute.MARGIN_LEFT, lv);
+ valueConvertor.put(CSS.Attribute.MARGIN_LEFT_LTR, lv);
+ valueConvertor.put(CSS.Attribute.MARGIN_LEFT_RTL, lv);
+ valueConvertor.put(CSS.Attribute.MARGIN_RIGHT, lv);
+ valueConvertor.put(CSS.Attribute.MARGIN_RIGHT_LTR, lv);
+ valueConvertor.put(CSS.Attribute.MARGIN_RIGHT_RTL, lv);
+ valueConvertor.put(CSS.Attribute.PADDING_TOP, lv);
+ valueConvertor.put(CSS.Attribute.PADDING_BOTTOM, lv);
+ valueConvertor.put(CSS.Attribute.PADDING_LEFT, lv);
+ valueConvertor.put(CSS.Attribute.PADDING_RIGHT, lv);
+ Object bv = new BorderWidthValue(null, 0);
+ valueConvertor.put(CSS.Attribute.BORDER_TOP_WIDTH, bv);
+ valueConvertor.put(CSS.Attribute.BORDER_BOTTOM_WIDTH, bv);
+ valueConvertor.put(CSS.Attribute.BORDER_LEFT_WIDTH, bv);
+ valueConvertor.put(CSS.Attribute.BORDER_RIGHT_WIDTH, bv);
+ Object nlv = new LengthValue(true);
+ valueConvertor.put(CSS.Attribute.TEXT_INDENT, nlv);
+ valueConvertor.put(CSS.Attribute.WIDTH, lv);
+ valueConvertor.put(CSS.Attribute.HEIGHT, lv);
+ valueConvertor.put(CSS.Attribute.BORDER_SPACING, lv);
+ Object sv = new StringValue();
+ valueConvertor.put(CSS.Attribute.FONT_STYLE, sv);
+ valueConvertor.put(CSS.Attribute.TEXT_DECORATION, sv);
+ valueConvertor.put(CSS.Attribute.TEXT_ALIGN, sv);
+ valueConvertor.put(CSS.Attribute.VERTICAL_ALIGN, sv);
+ Object valueMapper = new CssValueMapper();
+ valueConvertor.put(CSS.Attribute.LIST_STYLE_TYPE,
+ valueMapper);
+ valueConvertor.put(CSS.Attribute.BACKGROUND_IMAGE,
+ new BackgroundImage());
+ valueConvertor.put(CSS.Attribute.BACKGROUND_POSITION,
+ new BackgroundPosition());
+ valueConvertor.put(CSS.Attribute.BACKGROUND_REPEAT,
+ valueMapper);
+ valueConvertor.put(CSS.Attribute.BACKGROUND_ATTACHMENT,
+ valueMapper);
+ Object generic = new CssValue();
+ int n = CSS.Attribute.allAttributes.length;
+ for (int i = 0; i < n; i++) {
+ CSS.Attribute key = CSS.Attribute.allAttributes[i];
+ if (valueConvertor.get(key) == null) {
+ valueConvertor.put(key, generic);
+ }
+ }
+ }
+
+ /**
+ * Sets the base font size. <code>sz</code> is a CSS value, and is
+ * not necessarily the point size. Use getPointSize to determine the
+ * point size corresponding to <code>sz</code>.
+ */
+ void setBaseFontSize(int sz) {
+ if (sz < 1)
+ baseFontSize = 0;
+ else if (sz > 7)
+ baseFontSize = 7;
+ else
+ baseFontSize = sz;
+ }
+
+ /**
+ * Sets the base font size from the passed in string.
+ */
+ void setBaseFontSize(String size) {
+ int relSize, absSize, diff;
+
+ if (size != null) {
+ if (size.startsWith("+")) {
+ relSize = Integer.valueOf(size.substring(1)).intValue();
+ setBaseFontSize(baseFontSize + relSize);
+ } else if (size.startsWith("-")) {
+ relSize = -Integer.valueOf(size.substring(1)).intValue();
+ setBaseFontSize(baseFontSize + relSize);
+ } else {
+ setBaseFontSize(Integer.valueOf(size).intValue());
+ }
+ }
+ }
+
+ /**
+ * Returns the base font size.
+ */
+ int getBaseFontSize() {
+ return baseFontSize;
+ }
+
+ /**
+ * Parses the CSS property <code>key</code> with value
+ * <code>value</code> placing the result in <code>att</code>.
+ */
+ void addInternalCSSValue(MutableAttributeSet attr,
+ CSS.Attribute key, String value) {
+ if (key == CSS.Attribute.FONT) {
+ ShorthandFontParser.parseShorthandFont(this, value, attr);
+ }
+ else if (key == CSS.Attribute.BACKGROUND) {
+ ShorthandBackgroundParser.parseShorthandBackground
+ (this, value, attr);
+ }
+ else if (key == CSS.Attribute.MARGIN) {
+ ShorthandMarginParser.parseShorthandMargin(this, value, attr,
+ CSS.Attribute.ALL_MARGINS);
+ }
+ else if (key == CSS.Attribute.PADDING) {
+ ShorthandMarginParser.parseShorthandMargin(this, value, attr,
+ CSS.Attribute.ALL_PADDING);
+ }
+ else if (key == CSS.Attribute.BORDER_WIDTH) {
+ ShorthandMarginParser.parseShorthandMargin(this, value, attr,
+ CSS.Attribute.ALL_BORDER_WIDTHS);
+ }
+ else if (key == CSS.Attribute.BORDER_COLOR) {
+ ShorthandMarginParser.parseShorthandMargin(this, value, attr,
+ CSS.Attribute.ALL_BORDER_COLORS);
+ }
+ else if (key == CSS.Attribute.BORDER_STYLE) {
+ ShorthandMarginParser.parseShorthandMargin(this, value, attr,
+ CSS.Attribute.ALL_BORDER_STYLES);
+ }
+ else if ((key == CSS.Attribute.BORDER) ||
+ (key == CSS.Attribute.BORDER_TOP) ||
+ (key == CSS.Attribute.BORDER_RIGHT) ||
+ (key == CSS.Attribute.BORDER_BOTTOM) ||
+ (key == CSS.Attribute.BORDER_LEFT)) {
+ ShorthandBorderParser.parseShorthandBorder(attr, key, value);
+ }
+ else {
+ Object iValue = getInternalCSSValue(key, value);
+ if (iValue != null) {
+ attr.addAttribute(key, iValue);
+ }
+ }
+ }
+
+ /**
+ * Gets the internal CSS representation of <code>value</code> which is
+ * a CSS value of the CSS attribute named <code>key</code>. The receiver
+ * should not modify <code>value</code>, and the first <code>count</code>
+ * strings are valid.
+ */
+ Object getInternalCSSValue(CSS.Attribute key, String value) {
+ CssValue conv = (CssValue) valueConvertor.get(key);
+ Object r = conv.parseCssValue(value);
+ return r != null ? r : conv.parseCssValue(key.getDefaultValue());
+ }
+
+ /**
+ * Maps from a StyleConstants to a CSS Attribute.
+ */
+ Attribute styleConstantsKeyToCSSKey(StyleConstants sc) {
+ return (Attribute)styleConstantToCssMap.get(sc);
+ }
+
+ /**
+ * Maps from a StyleConstants value to a CSS value.
+ */
+ Object styleConstantsValueToCSSValue(StyleConstants sc,
+ Object styleValue) {
+ Object cssKey = styleConstantsKeyToCSSKey(sc);
+ if (cssKey != null) {
+ CssValue conv = (CssValue)valueConvertor.get(cssKey);
+ return conv.fromStyleConstants(sc, styleValue);
+ }
+ return null;
+ }
+
+ /**
+ * Converts the passed in CSS value to a StyleConstants value.
+ * <code>key</code> identifies the CSS attribute being mapped.
+ */
+ Object cssValueToStyleConstantsValue(StyleConstants key, Object value) {
+ if (value instanceof CssValue) {
+ return ((CssValue)value).toStyleConstants((StyleConstants)key,
+ null);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the font for the values in the passed in AttributeSet.
+ * It is assumed the keys will be CSS.Attribute keys.
+ * <code>sc</code> is the StyleContext that will be messaged to get
+ * the font once the size, name and style have been determined.
+ */
+ Font getFont(StyleContext sc, AttributeSet a, int defaultSize, StyleSheet ss) {
+ ss = getStyleSheet(ss);
+ int size = getFontSize(a, defaultSize, ss);
+
+ /*
+ * If the vertical alignment is set to either superscirpt or
+ * subscript we reduce the font size by 2 points.
+ */
+ StringValue vAlignV = (StringValue)a.getAttribute
+ (CSS.Attribute.VERTICAL_ALIGN);
+ if ((vAlignV != null)) {
+ String vAlign = vAlignV.toString();
+ if ((vAlign.indexOf("sup") >= 0) ||
+ (vAlign.indexOf("sub") >= 0)) {
+ size -= 2;
+ }
+ }
+
+ FontFamily familyValue = (FontFamily)a.getAttribute
+ (CSS.Attribute.FONT_FAMILY);
+ String family = (familyValue != null) ? familyValue.getValue() :
+ Font.SANS_SERIF;
+ int style = Font.PLAIN;
+ FontWeight weightValue = (FontWeight) a.getAttribute
+ (CSS.Attribute.FONT_WEIGHT);
+ if ((weightValue != null) && (weightValue.getValue() > 400)) {
+ style |= Font.BOLD;
+ }
+ Object fs = a.getAttribute(CSS.Attribute.FONT_STYLE);
+ if ((fs != null) && (fs.toString().indexOf("italic") >= 0)) {
+ style |= Font.ITALIC;
+ }
+ if (family.equalsIgnoreCase("monospace")) {
+ family = Font.MONOSPACED;
+ }
+ Font f = sc.getFont(family, style, size);
+ if (f == null
+ || (f.getFamily().equals(Font.DIALOG)
+ && ! family.equalsIgnoreCase(Font.DIALOG))) {
+ family = Font.SANS_SERIF;
+ f = sc.getFont(family, style, size);
+ }
+ return f;
+ }
+
+ static int getFontSize(AttributeSet attr, int defaultSize, StyleSheet ss) {
+ // PENDING(prinz) this is a 1.1 based implementation, need to also
+ // have a 1.2 version.
+ FontSize sizeValue = (FontSize)attr.getAttribute(CSS.Attribute.
+ FONT_SIZE);
+
+ return (sizeValue != null) ? sizeValue.getValue(attr, ss)
+ : defaultSize;
+ }
+
+ /**
+ * Takes a set of attributes and turn it into a color
+ * specification. This might be used to specify things
+ * like brighter, more hue, etc.
+ * This will return null if there is no value for <code>key</code>.
+ *
+ * @param key CSS.Attribute identifying where color is stored.
+ * @param a the set of attributes
+ * @return the color
+ */
+ Color getColor(AttributeSet a, CSS.Attribute key) {
+ ColorValue cv = (ColorValue) a.getAttribute(key);
+ if (cv != null) {
+ return cv.getValue();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the size of a font from the passed in string.
+ *
+ * @param size CSS string describing font size
+ * @param baseFontSize size to use for relative units.
+ */
+ float getPointSize(String size, StyleSheet ss) {
+ int relSize, absSize, diff, index;
+ ss = getStyleSheet(ss);
+ if (size != null) {
+ if (size.startsWith("+")) {
+ relSize = Integer.valueOf(size.substring(1)).intValue();
+ return getPointSize(baseFontSize + relSize, ss);
+ } else if (size.startsWith("-")) {
+ relSize = -Integer.valueOf(size.substring(1)).intValue();
+ return getPointSize(baseFontSize + relSize, ss);
+ } else {
+ absSize = Integer.valueOf(size).intValue();
+ return getPointSize(absSize, ss);
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the length of the attribute in <code>a</code> with
+ * key <code>key</code>.
+ */
+ float getLength(AttributeSet a, CSS.Attribute key, StyleSheet ss) {
+ ss = getStyleSheet(ss);
+ LengthValue lv = (LengthValue) a.getAttribute(key);
+ boolean isW3CLengthUnits = (ss == null) ? false : ss.isW3CLengthUnits();
+ float len = (lv != null) ? lv.getValue(isW3CLengthUnits) : 0;
+ return len;
+ }
+
+ /**
+ * Convert a set of HTML attributes to an equivalent
+ * set of CSS attributes.
+ *
+ * @param AttributeSet containing the HTML attributes.
+ * @return AttributeSet containing the corresponding CSS attributes.
+ * The AttributeSet will be empty if there are no mapping
+ * CSS attributes.
+ */
+ AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
+ MutableAttributeSet cssAttrSet = new SimpleAttributeSet();
+ Element elem = (Element)htmlAttrSet;
+ HTML.Tag tag = getHTMLTag(htmlAttrSet);
+ if ((tag == HTML.Tag.TD) || (tag == HTML.Tag.TH)) {
+ // translate border width into the cells, if it has non-zero value.
+ AttributeSet tableAttr = elem.getParentElement().
+ getParentElement().getAttributes();
+ int borderWidth;
+ try {
+ borderWidth = Integer.parseInt(
+ (String) tableAttr.getAttribute(HTML.Attribute.BORDER));
+ } catch (NumberFormatException e) {
+ borderWidth = 0;
+ }
+ if (borderWidth > 0) {
+ translateAttribute(HTML.Attribute.BORDER, tableAttr, cssAttrSet);
+ }
+ String pad = (String)tableAttr.getAttribute(HTML.Attribute.CELLPADDING);
+ if (pad != null) {
+ LengthValue v =
+ (LengthValue)getInternalCSSValue(CSS.Attribute.PADDING_TOP, pad);
+ v.span = (v.span < 0) ? 0 : v.span;
+ cssAttrSet.addAttribute(CSS.Attribute.PADDING_TOP, v);
+ cssAttrSet.addAttribute(CSS.Attribute.PADDING_BOTTOM, v);
+ cssAttrSet.addAttribute(CSS.Attribute.PADDING_LEFT, v);
+ cssAttrSet.addAttribute(CSS.Attribute.PADDING_RIGHT, v);
+ }
+ }
+ if (elem.isLeaf()) {
+ translateEmbeddedAttributes(htmlAttrSet, cssAttrSet);
+ } else {
+ translateAttributes(tag, htmlAttrSet, cssAttrSet);
+ }
+ if (tag == HTML.Tag.CAPTION) {
+ /*
+ * Navigator uses ALIGN for caption placement and IE uses VALIGN.
+ */
+ Object v = htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
+ if ((v != null) && (v.equals("top") || v.equals("bottom"))) {
+ cssAttrSet.addAttribute(CSS.Attribute.CAPTION_SIDE, v);
+ cssAttrSet.removeAttribute(CSS.Attribute.TEXT_ALIGN);
+ } else {
+ v = htmlAttrSet.getAttribute(HTML.Attribute.VALIGN);
+ if (v != null) {
+ cssAttrSet.addAttribute(CSS.Attribute.CAPTION_SIDE, v);
+ }
+ }
+ }
+ return cssAttrSet;
+ }
+
+ private static final Hashtable attributeMap = new Hashtable();
+ private static final Hashtable valueMap = new Hashtable();
+
+ /**
+ * The hashtable and the static initalization block below,
+ * set up a mapping from well-known HTML attributes to
+ * CSS attributes. For the most part, there is a 1-1 mapping
+ * between the two. However in the case of certain HTML
+ * attributes for example HTML.Attribute.VSPACE or
+ * HTML.Attribute.HSPACE, end up mapping to two CSS.Attribute's.
+ * Therefore, the value associated with each HTML.Attribute.
+ * key ends up being an array of CSS.Attribute.* objects.
+ */
+ private static final Hashtable htmlAttrToCssAttrMap = new Hashtable(20);
+
+ /**
+ * The hashtable and static initialization that follows sets
+ * up a translation from StyleConstants (i.e. the <em>well known</em>
+ * attributes) to the associated CSS attributes.
+ */
+ private static final Hashtable styleConstantToCssMap = new Hashtable(17);
+ /** Maps from HTML value to a CSS value. Used in internal mapping. */
+ private static final Hashtable htmlValueToCssValueMap = new Hashtable(8);
+ /** Maps from CSS value (string) to internal value. */
+ private static final Hashtable cssValueToInternalValueMap = new Hashtable(13);
+
+ static {
+ // load the attribute map
+ for (int i = 0; i < Attribute.allAttributes.length; i++ ) {
+ attributeMap.put(Attribute.allAttributes[i].toString(),
+ Attribute.allAttributes[i]);
+ }
+ // load the value map
+ for (int i = 0; i < Value.allValues.length; i++ ) {
+ valueMap.put(Value.allValues[i].toString(),
+ Value.allValues[i]);
+ }
+
+ htmlAttrToCssAttrMap.put(HTML.Attribute.COLOR,
+ new CSS.Attribute[]{CSS.Attribute.COLOR});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.TEXT,
+ new CSS.Attribute[]{CSS.Attribute.COLOR});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.CLEAR,
+ new CSS.Attribute[]{CSS.Attribute.CLEAR});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.BACKGROUND,
+ new CSS.Attribute[]{CSS.Attribute.BACKGROUND_IMAGE});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.BGCOLOR,
+ new CSS.Attribute[]{CSS.Attribute.BACKGROUND_COLOR});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.WIDTH,
+ new CSS.Attribute[]{CSS.Attribute.WIDTH});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.HEIGHT,
+ new CSS.Attribute[]{CSS.Attribute.HEIGHT});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.BORDER,
+ new CSS.Attribute[]{CSS.Attribute.BORDER_TOP_WIDTH, CSS.Attribute.BORDER_RIGHT_WIDTH, CSS.Attribute.BORDER_BOTTOM_WIDTH, CSS.Attribute.BORDER_LEFT_WIDTH});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.CELLPADDING,
+ new CSS.Attribute[]{CSS.Attribute.PADDING});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.CELLSPACING,
+ new CSS.Attribute[]{CSS.Attribute.BORDER_SPACING});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.MARGINWIDTH,
+ new CSS.Attribute[]{CSS.Attribute.MARGIN_LEFT,
+ CSS.Attribute.MARGIN_RIGHT});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.MARGINHEIGHT,
+ new CSS.Attribute[]{CSS.Attribute.MARGIN_TOP,
+ CSS.Attribute.MARGIN_BOTTOM});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.HSPACE,
+ new CSS.Attribute[]{CSS.Attribute.PADDING_LEFT,
+ CSS.Attribute.PADDING_RIGHT});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.VSPACE,
+ new CSS.Attribute[]{CSS.Attribute.PADDING_BOTTOM,
+ CSS.Attribute.PADDING_TOP});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.FACE,
+ new CSS.Attribute[]{CSS.Attribute.FONT_FAMILY});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.SIZE,
+ new CSS.Attribute[]{CSS.Attribute.FONT_SIZE});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.VALIGN,
+ new CSS.Attribute[]{CSS.Attribute.VERTICAL_ALIGN});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.ALIGN,
+ new CSS.Attribute[]{CSS.Attribute.VERTICAL_ALIGN,
+ CSS.Attribute.TEXT_ALIGN,
+ CSS.Attribute.FLOAT});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.TYPE,
+ new CSS.Attribute[]{CSS.Attribute.LIST_STYLE_TYPE});
+ htmlAttrToCssAttrMap.put(HTML.Attribute.NOWRAP,
+ new CSS.Attribute[]{CSS.Attribute.WHITE_SPACE});
+
+ // initialize StyleConstants mapping
+ styleConstantToCssMap.put(StyleConstants.FontFamily,
+ CSS.Attribute.FONT_FAMILY);
+ styleConstantToCssMap.put(StyleConstants.FontSize,
+ CSS.Attribute.FONT_SIZE);
+ styleConstantToCssMap.put(StyleConstants.Bold,
+ CSS.Attribute.FONT_WEIGHT);
+ styleConstantToCssMap.put(StyleConstants.Italic,
+ CSS.Attribute.FONT_STYLE);
+ styleConstantToCssMap.put(StyleConstants.Underline,
+ CSS.Attribute.TEXT_DECORATION);
+ styleConstantToCssMap.put(StyleConstants.StrikeThrough,
+ CSS.Attribute.TEXT_DECORATION);
+ styleConstantToCssMap.put(StyleConstants.Superscript,
+ CSS.Attribute.VERTICAL_ALIGN);
+ styleConstantToCssMap.put(StyleConstants.Subscript,
+ CSS.Attribute.VERTICAL_ALIGN);
+ styleConstantToCssMap.put(StyleConstants.Foreground,
+ CSS.Attribute.COLOR);
+ styleConstantToCssMap.put(StyleConstants.Background,
+ CSS.Attribute.BACKGROUND_COLOR);
+ styleConstantToCssMap.put(StyleConstants.FirstLineIndent,
+ CSS.Attribute.TEXT_INDENT);
+ styleConstantToCssMap.put(StyleConstants.LeftIndent,
+ CSS.Attribute.MARGIN_LEFT);
+ styleConstantToCssMap.put(StyleConstants.RightIndent,
+ CSS.Attribute.MARGIN_RIGHT);
+ styleConstantToCssMap.put(StyleConstants.SpaceAbove,
+ CSS.Attribute.MARGIN_TOP);
+ styleConstantToCssMap.put(StyleConstants.SpaceBelow,
+ CSS.Attribute.MARGIN_BOTTOM);
+ styleConstantToCssMap.put(StyleConstants.Alignment,
+ CSS.Attribute.TEXT_ALIGN);
+
+ // HTML->CSS
+ htmlValueToCssValueMap.put("disc", CSS.Value.DISC);
+ htmlValueToCssValueMap.put("square", CSS.Value.SQUARE);
+ htmlValueToCssValueMap.put("circle", CSS.Value.CIRCLE);
+ htmlValueToCssValueMap.put("1", CSS.Value.DECIMAL);
+ htmlValueToCssValueMap.put("a", CSS.Value.LOWER_ALPHA);
+ htmlValueToCssValueMap.put("A", CSS.Value.UPPER_ALPHA);
+ htmlValueToCssValueMap.put("i", CSS.Value.LOWER_ROMAN);
+ htmlValueToCssValueMap.put("I", CSS.Value.UPPER_ROMAN);
+
+ // CSS-> internal CSS
+ cssValueToInternalValueMap.put("none", CSS.Value.NONE);
+ cssValueToInternalValueMap.put("disc", CSS.Value.DISC);
+ cssValueToInternalValueMap.put("square", CSS.Value.SQUARE);
+ cssValueToInternalValueMap.put("circle", CSS.Value.CIRCLE);
+ cssValueToInternalValueMap.put("decimal", CSS.Value.DECIMAL);
+ cssValueToInternalValueMap.put("lower-roman", CSS.Value.LOWER_ROMAN);
+ cssValueToInternalValueMap.put("upper-roman", CSS.Value.UPPER_ROMAN);
+ cssValueToInternalValueMap.put("lower-alpha", CSS.Value.LOWER_ALPHA);
+ cssValueToInternalValueMap.put("upper-alpha", CSS.Value.UPPER_ALPHA);
+ cssValueToInternalValueMap.put("repeat", CSS.Value.BACKGROUND_REPEAT);
+ cssValueToInternalValueMap.put("no-repeat",
+ CSS.Value.BACKGROUND_NO_REPEAT);
+ cssValueToInternalValueMap.put("repeat-x",
+ CSS.Value.BACKGROUND_REPEAT_X);
+ cssValueToInternalValueMap.put("repeat-y",
+ CSS.Value.BACKGROUND_REPEAT_Y);
+ cssValueToInternalValueMap.put("scroll",
+ CSS.Value.BACKGROUND_SCROLL);
+ cssValueToInternalValueMap.put("fixed",
+ CSS.Value.BACKGROUND_FIXED);
+
+ // Register all the CSS attribute keys for archival/unarchival
+ Object[] keys = CSS.Attribute.allAttributes;
+ try {
+ for (int i = 0; i < keys.length; i++) {
+ StyleContext.registerStaticAttributeKey(keys[i]);
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+
+ // Register all the CSS Values for archival/unarchival
+ keys = CSS.Value.allValues;
+ try {
+ for (int i = 0; i < keys.length; i++) {
+ StyleContext.registerStaticAttributeKey(keys[i]);
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Return the set of all possible CSS attribute keys.
+ */
+ public static Attribute[] getAllAttributeKeys() {
+ Attribute[] keys = new Attribute[Attribute.allAttributes.length];
+ System.arraycopy(Attribute.allAttributes, 0, keys, 0, Attribute.allAttributes.length);
+ return keys;
+ }
+
+ /**
+ * Translates a string to a <code>CSS.Attribute</code> object.
+ * This will return <code>null</code> if there is no attribute
+ * by the given name.
+ *
+ * @param name the name of the CSS attribute to fetch the
+ * typesafe enumeration for
+ * @return the <code>CSS.Attribute</code> object,
+ * or <code>null</code> if the string
+ * doesn't represent a valid attribute key
+ */
+ public static final Attribute getAttribute(String name) {
+ return (Attribute) attributeMap.get(name);
+ }
+
+ /**
+ * Translates a string to a <code>CSS.Value</code> object.
+ * This will return <code>null</code> if there is no value
+ * by the given name.
+ *
+ * @param name the name of the CSS value to fetch the
+ * typesafe enumeration for
+ * @return the <code>CSS.Value</code> object,
+ * or <code>null</code> if the string
+ * doesn't represent a valid CSS value name; this does
+ * not mean that it doesn't represent a valid CSS value
+ */
+ static final Value getValue(String name) {
+ return (Value) valueMap.get(name);
+ }
+
+
+ //
+ // Conversion related methods/classes
+ //
+
+ /**
+ * Returns a URL for the given CSS url string. If relative,
+ * <code>base</code> is used as the parent. If a valid URL can not
+ * be found, this will not throw a MalformedURLException, instead
+ * null will be returned.
+ */
+ static URL getURL(URL base, String cssString) {
+ if (cssString == null) {
+ return null;
+ }
+ if (cssString.startsWith("url(") &&
+ cssString.endsWith(")")) {
+ cssString = cssString.substring(4, cssString.length() - 1);
+ }
+ // Absolute first
+ try {
+ URL url = new URL(cssString);
+ if (url != null) {
+ return url;
+ }
+ } catch (MalformedURLException mue) {
+ }
+ // Then relative
+ if (base != null) {
+ // Relative URL, try from base
+ try {
+ URL url = new URL(base, cssString);
+ return url;
+ }
+ catch (MalformedURLException muee) {
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Converts a type Color to a hex string
+ * in the format "#RRGGBB"
+ */
+ static String colorToHex(Color color) {
+
+ String colorstr = new String("#");
+
+ // Red
+ String str = Integer.toHexString(color.getRed());
+ if (str.length() > 2)
+ str = str.substring(0, 2);
+ else if (str.length() < 2)
+ colorstr += "0" + str;
+ else
+ colorstr += str;
+
+ // Green
+ str = Integer.toHexString(color.getGreen());
+ if (str.length() > 2)
+ str = str.substring(0, 2);
+ else if (str.length() < 2)
+ colorstr += "0" + str;
+ else
+ colorstr += str;
+
+ // Blue
+ str = Integer.toHexString(color.getBlue());
+ if (str.length() > 2)
+ str = str.substring(0, 2);
+ else if (str.length() < 2)
+ colorstr += "0" + str;
+ else
+ colorstr += str;
+
+ return colorstr;
+ }
+
+ /**
+ * Convert a "#FFFFFF" hex string to a Color.
+ * If the color specification is bad, an attempt
+ * will be made to fix it up.
+ */
+ static final Color hexToColor(String value) {
+ String digits;
+ int n = value.length();
+ if (value.startsWith("#")) {
+ digits = value.substring(1, Math.min(value.length(), 7));
+ } else {
+ digits = value;
+ }
+ String hstr = "0x" + digits;
+ Color c;
+ try {
+ c = Color.decode(hstr);
+ } catch (NumberFormatException nfe) {
+ c = null;
+ }
+ return c;
+ }
+
+ /**
+ * Convert a color string such as "RED" or "#NNNNNN" or "rgb(r, g, b)"
+ * to a Color.
+ */
+ static Color stringToColor(String str) {
+ Color color = null;
+
+ if (str == null) {
+ return null;
+ }
+ if (str.length() == 0)
+ color = Color.black;
+ else if (str.startsWith("rgb(")) {
+ color = parseRGB(str);
+ }
+ else if (str.charAt(0) == '#')
+ color = hexToColor(str);
+ else if (str.equalsIgnoreCase("Black"))
+ color = hexToColor("#000000");
+ else if(str.equalsIgnoreCase("Silver"))
+ color = hexToColor("#C0C0C0");
+ else if(str.equalsIgnoreCase("Gray"))
+ color = hexToColor("#808080");
+ else if(str.equalsIgnoreCase("White"))
+ color = hexToColor("#FFFFFF");
+ else if(str.equalsIgnoreCase("Maroon"))
+ color = hexToColor("#800000");
+ else if(str.equalsIgnoreCase("Red"))
+ color = hexToColor("#FF0000");
+ else if(str.equalsIgnoreCase("Purple"))
+ color = hexToColor("#800080");
+ else if(str.equalsIgnoreCase("Fuchsia"))
+ color = hexToColor("#FF00FF");
+ else if(str.equalsIgnoreCase("Green"))
+ color = hexToColor("#008000");
+ else if(str.equalsIgnoreCase("Lime"))
+ color = hexToColor("#00FF00");
+ else if(str.equalsIgnoreCase("Olive"))
+ color = hexToColor("#808000");
+ else if(str.equalsIgnoreCase("Yellow"))
+ color = hexToColor("#FFFF00");
+ else if(str.equalsIgnoreCase("Navy"))
+ color = hexToColor("#000080");
+ else if(str.equalsIgnoreCase("Blue"))
+ color = hexToColor("#0000FF");
+ else if(str.equalsIgnoreCase("Teal"))
+ color = hexToColor("#008080");
+ else if(str.equalsIgnoreCase("Aqua"))
+ color = hexToColor("#00FFFF");
+ else if(str.equalsIgnoreCase("Orange"))
+ color = hexToColor("#FF8000");
+ else
+ color = hexToColor(str); // sometimes get specified without leading #
+ return color;
+ }
+
+ /**
+ * Parses a String in the format <code>rgb(r, g, b)</code> where
+ * each of the Color components is either an integer, or a floating number
+ * with a % after indicating a percentage value of 255. Values are
+ * constrained to fit with 0-255. The resulting Color is returned.
+ */
+ private static Color parseRGB(String string) {
+ // Find the next numeric char
+ int[] index = new int[1];
+
+ index[0] = 4;
+ int red = getColorComponent(string, index);
+ int green = getColorComponent(string, index);
+ int blue = getColorComponent(string, index);
+
+ return new Color(red, green, blue);
+ }
+
+ /**
+ * Returns the next integer value from <code>string</code> starting
+ * at <code>index[0]</code>. The value can either can an integer, or
+ * a percentage (floating number ending with %), in which case it is
+ * multiplied by 255.
+ */
+ private static int getColorComponent(String string, int[] index) {
+ int length = string.length();
+ char aChar;
+
+ // Skip non-decimal chars
+ while(index[0] < length && (aChar = string.charAt(index[0])) != '-' &&
+ !Character.isDigit(aChar) && aChar != '.') {
+ index[0]++;
+ }
+
+ int start = index[0];
+
+ if (start < length && string.charAt(index[0]) == '-') {
+ index[0]++;
+ }
+ while(index[0] < length &&
+ Character.isDigit(string.charAt(index[0]))) {
+ index[0]++;
+ }
+ if (index[0] < length && string.charAt(index[0]) == '.') {
+ // Decimal value
+ index[0]++;
+ while(index[0] < length &&
+ Character.isDigit(string.charAt(index[0]))) {
+ index[0]++;
+ }
+ }
+ if (start != index[0]) {
+ try {
+ float value = Float.parseFloat(string.substring
+ (start, index[0]));
+
+ if (index[0] < length && string.charAt(index[0]) == '%') {
+ index[0]++;
+ value = value * 255f / 100f;
+ }
+ return Math.min(255, Math.max(0, (int)value));
+ } catch (NumberFormatException nfe) {
+ // Treat as 0
+ }
+ }
+ return 0;
+ }
+
+ static int getIndexOfSize(float pt, int[] sizeMap) {
+ for (int i = 0; i < sizeMap.length; i ++ )
+ if (pt <= sizeMap[i])
+ return i + 1;
+ return sizeMap.length;
+ }
+
+ static int getIndexOfSize(float pt, StyleSheet ss) {
+ int[] sizeMap = (ss != null) ? ss.getSizeMap() :
+ StyleSheet.sizeMapDefault;
+ return getIndexOfSize(pt, sizeMap);
+ }
+
+
+ /**
+ * @return an array of all the strings in <code>value</code>
+ * that are separated by whitespace.
+ */
+ static String[] parseStrings(String value) {
+ int current, last;
+ int length = (value == null) ? 0 : value.length();
+ Vector temp = new Vector(4);
+
+ current = 0;
+ while (current < length) {
+ // Skip ws
+ while (current < length && Character.isWhitespace
+ (value.charAt(current))) {
+ current++;
+ }
+ last = current;
+ while (current < length && !Character.isWhitespace
+ (value.charAt(current))) {
+ current++;
+ }
+ if (last != current) {
+ temp.addElement(value.substring(last, current));
+ }
+ current++;
+ }
+ String[] retValue = new String[temp.size()];
+ temp.copyInto(retValue);
+ return retValue;
+ }
+
+ /**
+ * Return the point size, given a size index. Legal HTML index sizes
+ * are 1-7.
+ */
+ float getPointSize(int index, StyleSheet ss) {
+ ss = getStyleSheet(ss);
+ int[] sizeMap = (ss != null) ? ss.getSizeMap() :
+ StyleSheet.sizeMapDefault;
+ --index;
+ if (index < 0)
+ return sizeMap[0];
+ else if (index > sizeMap.length - 1)
+ return sizeMap[sizeMap.length - 1];
+ else
+ return sizeMap[index];
+ }
+
+
+ private void translateEmbeddedAttributes(AttributeSet htmlAttrSet,
+ MutableAttributeSet cssAttrSet) {
+ Enumeration keys = htmlAttrSet.getAttributeNames();
+ if (htmlAttrSet.getAttribute(StyleConstants.NameAttribute) ==
+ HTML.Tag.HR) {
+ // HR needs special handling due to us treating it as a leaf.
+ translateAttributes(HTML.Tag.HR, htmlAttrSet, cssAttrSet);
+ }
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ if (key instanceof HTML.Tag) {
+ HTML.Tag tag = (HTML.Tag)key;
+ Object o = htmlAttrSet.getAttribute(tag);
+ if (o != null && o instanceof AttributeSet) {
+ translateAttributes(tag, (AttributeSet)o, cssAttrSet);
+ }
+ } else if (key instanceof CSS.Attribute) {
+ cssAttrSet.addAttribute(key, htmlAttrSet.getAttribute(key));
+ }
+ }
+ }
+
+ private void translateAttributes(HTML.Tag tag,
+ AttributeSet htmlAttrSet,
+ MutableAttributeSet cssAttrSet) {
+ Enumeration names = htmlAttrSet.getAttributeNames();
+ while (names.hasMoreElements()) {
+ Object name = names.nextElement();
+
+ if (name instanceof HTML.Attribute) {
+ HTML.Attribute key = (HTML.Attribute)name;
+
+ /*
+ * HTML.Attribute.ALIGN needs special processing.
+ * It can map to to 1 of many(3) possible CSS attributes
+ * depending on the nature of the tag the attribute is
+ * part off and depending on the value of the attribute.
+ */
+ if (key == HTML.Attribute.ALIGN) {
+ String htmlAttrValue = (String)htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
+ if (htmlAttrValue != null) {
+ CSS.Attribute cssAttr = getCssAlignAttribute(tag, htmlAttrSet);
+ if (cssAttr != null) {
+ Object o = getCssValue(cssAttr, htmlAttrValue);
+ if (o != null) {
+ cssAttrSet.addAttribute(cssAttr, o);
+ }
+ }
+ }
+ } else {
+
+ /*
+ * The html size attribute has a mapping in the CSS world only
+ * if it is par of a font or base font tag.
+ */
+
+ if (key == HTML.Attribute.SIZE && !isHTMLFontTag(tag)) {
+ continue;
+ }
+
+ translateAttribute(key, htmlAttrSet, cssAttrSet);
+ }
+ } else if (name instanceof CSS.Attribute) {
+ cssAttrSet.addAttribute(name, htmlAttrSet.getAttribute(name));
+ }
+ }
+ }
+
+ private void translateAttribute(HTML.Attribute key,
+ AttributeSet htmlAttrSet,
+ MutableAttributeSet cssAttrSet) {
+ /*
+ * In the case of all remaining HTML.Attribute's they
+ * map to 1 or more CCS.Attribute.
+ */
+ CSS.Attribute[] cssAttrList = getCssAttribute(key);
+
+ String htmlAttrValue = (String)htmlAttrSet.getAttribute(key);
+
+ if (cssAttrList == null || htmlAttrValue == null) {
+ return;
+ }
+ for (int i = 0; i < cssAttrList.length; i++) {
+ Object o = getCssValue(cssAttrList[i], htmlAttrValue);
+ if (o != null) {
+ cssAttrSet.addAttribute(cssAttrList[i], o);
+ }
+ }
+ }
+
+ /**
+ * Given a CSS.Attribute object and its corresponding HTML.Attribute's
+ * value, this method returns a CssValue object to associate with the
+ * CSS attribute.
+ *
+ * @param the CSS.Attribute
+ * @param a String containing the value associated HTML.Attribtue.
+ */
+ Object getCssValue(CSS.Attribute cssAttr, String htmlAttrValue) {
+ CssValue value = (CssValue)valueConvertor.get(cssAttr);
+ Object o = value.parseHtmlValue(htmlAttrValue);
+ return o;
+ }
+
+ /**
+ * Maps an HTML.Attribute object to its appropriate CSS.Attributes.
+ *
+ * @param HTML.Attribute
+ * @return CSS.Attribute[]
+ */
+ private CSS.Attribute[] getCssAttribute(HTML.Attribute hAttr) {
+ return (CSS.Attribute[])htmlAttrToCssAttrMap.get(hAttr);
+ }
+
+ /**
+ * Maps HTML.Attribute.ALIGN to either:
+ * CSS.Attribute.TEXT_ALIGN
+ * CSS.Attribute.FLOAT
+ * CSS.Attribute.VERTICAL_ALIGN
+ * based on the tag associated with the attribute and the
+ * value of the attribute.
+ *
+ * @param AttributeSet containing HTML attributes.
+ * @return CSS.Attribute mapping for HTML.Attribute.ALIGN.
+ */
+ private CSS.Attribute getCssAlignAttribute(HTML.Tag tag,
+ AttributeSet htmlAttrSet) {
+ return CSS.Attribute.TEXT_ALIGN;
+/*
+ String htmlAttrValue = (String)htmlAttrSet.getAttribute(HTML.Attribute.ALIGN);
+ CSS.Attribute cssAttr = CSS.Attribute.TEXT_ALIGN;
+ if (htmlAttrValue != null && htmlAttrSet instanceof Element) {
+ Element elem = (Element)htmlAttrSet;
+ if (!elem.isLeaf() && tag.isBlock() && validTextAlignValue(htmlAttrValue)) {
+ return CSS.Attribute.TEXT_ALIGN;
+ } else if (isFloater(htmlAttrValue)) {
+ return CSS.Attribute.FLOAT;
+ } else if (elem.isLeaf()) {
+ return CSS.Attribute.VERTICAL_ALIGN;
+ }
+ }
+ return null;
+ */
+ }
+
+ /**
+ * Fetches the tag associated with the HTML AttributeSet.
+ *
+ * @param AttributeSet containing the HTML attributes.
+ * @return HTML.Tag
+ */
+ private HTML.Tag getHTMLTag(AttributeSet htmlAttrSet) {
+ Object o = htmlAttrSet.getAttribute(StyleConstants.NameAttribute);
+ if (o instanceof HTML.Tag) {
+ HTML.Tag tag = (HTML.Tag) o;
+ return tag;
+ }
+ return null;
+ }
+
+
+ private boolean isHTMLFontTag(HTML.Tag tag) {
+ return (tag != null && ((tag == HTML.Tag.FONT) || (tag == HTML.Tag.BASEFONT)));
+ }
+
+
+ private boolean isFloater(String alignValue) {
+ return (alignValue.equals("left") || alignValue.equals("right"));
+ }
+
+ private boolean validTextAlignValue(String alignValue) {
+ return (isFloater(alignValue) || alignValue.equals("center"));
+ }
+
+ /**
+ * Base class to CSS values in the attribute sets. This
+ * is intended to act as a convertor to/from other attribute
+ * formats.
+ * <p>
+ * The CSS parser uses the parseCssValue method to convert
+ * a string to whatever format is appropriate a given key
+ * (i.e. these convertors are stored in a map using the
+ * CSS.Attribute as a key and the CssValue as the value).
+ * <p>
+ * The HTML to CSS conversion process first converts the
+ * HTML.Attribute to a CSS.Attribute, and then calls
+ * the parseHtmlValue method on the value of the HTML
+ * attribute to produce the corresponding CSS value.
+ * <p>
+ * The StyleConstants to CSS conversion process first
+ * converts the StyleConstants attribute to a
+ * CSS.Attribute, and then calls the fromStyleConstants
+ * method to convert the StyleConstants value to a
+ * CSS value.
+ * <p>
+ * The CSS to StyleConstants conversion process first
+ * converts the StyleConstants attribute to a
+ * CSS.Attribute, and then calls the toStyleConstants
+ * method to convert the CSS value to a StyleConstants
+ * value.
+ */
+ static class CssValue implements Serializable {
+
+ /**
+ * Convert a CSS value string to the internal format
+ * (for fast processing) used in the attribute sets.
+ * The fallback storage for any value that we don't
+ * have a special binary format for is a String.
+ */
+ Object parseCssValue(String value) {
+ return value;
+ }
+
+ /**
+ * Convert an HTML attribute value to a CSS attribute
+ * value. If there is no conversion, return null.
+ * This is implemented to simply forward to the CSS
+ * parsing by default (since some of the attribute
+ * values are the same). If the attribute value
+ * isn't recognized as a CSS value it is generally
+ * returned as null.
+ */
+ Object parseHtmlValue(String value) {
+ return parseCssValue(value);
+ }
+
+ /**
+ * Converts a <code>StyleConstants</code> attribute value to
+ * a CSS attribute value. If there is no conversion,
+ * returns <code>null</code>. By default, there is no conversion.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @param value the value of a <code>StyleConstants</code>
+ * attribute to be converted
+ * @return the CSS value that represents the
+ * <code>StyleConstants</code> value
+ */
+ Object fromStyleConstants(StyleConstants key, Object value) {
+ return null;
+ }
+
+ /**
+ * Converts a CSS attribute value to a
+ * <code>StyleConstants</code>
+ * value. If there is no conversion, returns
+ * <code>null</code>.
+ * By default, there is no conversion.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @param v the view containing <code>AttributeSet</code>
+ * @return the <code>StyleConstants</code> attribute value that
+ * represents the CSS attribute value
+ */
+ Object toStyleConstants(StyleConstants key, View v) {
+ return null;
+ }
+
+ /**
+ * Return the CSS format of the value
+ */
+ public String toString() {
+ return svalue;
+ }
+
+ /**
+ * The value as a string... before conversion to a
+ * binary format.
+ */
+ String svalue;
+ }
+
+ /**
+ * By default CSS attributes are represented as simple
+ * strings. They also have no conversion to/from
+ * StyleConstants by default. This class represents the
+ * value as a string (via the superclass), but
+ * provides StyleConstants conversion support for the
+ * CSS attributes that are held as strings.
+ */
+ static class StringValue extends CssValue {
+
+ /**
+ * Convert a CSS value string to the internal format
+ * (for fast processing) used in the attribute sets.
+ * This produces a StringValue, so that it can be
+ * used to convert from CSS to StyleConstants values.
+ */
+ Object parseCssValue(String value) {
+ StringValue sv = new StringValue();
+ sv.svalue = value;
+ return sv;
+ }
+
+ /**
+ * Converts a <code>StyleConstants</code> attribute value to
+ * a CSS attribute value. If there is no conversion
+ * returns <code>null</code>.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @param value the value of a <code>StyleConstants</code>
+ * attribute to be converted
+ * @return the CSS value that represents the
+ * <code>StyleConstants</code> value
+ */
+ Object fromStyleConstants(StyleConstants key, Object value) {
+ if (key == StyleConstants.Italic) {
+ if (value.equals(Boolean.TRUE)) {
+ return parseCssValue("italic");
+ }
+ return parseCssValue("");
+ } else if (key == StyleConstants.Underline) {
+ if (value.equals(Boolean.TRUE)) {
+ return parseCssValue("underline");
+ }
+ return parseCssValue("");
+ } else if (key == StyleConstants.Alignment) {
+ int align = ((Integer)value).intValue();
+ String ta;
+ switch(align) {
+ case StyleConstants.ALIGN_LEFT:
+ ta = "left";
+ break;
+ case StyleConstants.ALIGN_RIGHT:
+ ta = "right";
+ break;
+ case StyleConstants.ALIGN_CENTER:
+ ta = "center";
+ break;
+ case StyleConstants.ALIGN_JUSTIFIED:
+ ta = "justify";
+ break;
+ default:
+ ta = "left";
+ }
+ return parseCssValue(ta);
+ } else if (key == StyleConstants.StrikeThrough) {
+ if (value.equals(Boolean.TRUE)) {
+ return parseCssValue("line-through");
+ }
+ return parseCssValue("");
+ } else if (key == StyleConstants.Superscript) {
+ if (value.equals(Boolean.TRUE)) {
+ return parseCssValue("super");
+ }
+ return parseCssValue("");
+ } else if (key == StyleConstants.Subscript) {
+ if (value.equals(Boolean.TRUE)) {
+ return parseCssValue("sub");
+ }
+ return parseCssValue("");
+ }
+ return null;
+ }
+
+ /**
+ * Converts a CSS attribute value to a
+ * <code>StyleConstants</code> value.
+ * If there is no conversion, returns <code>null</code>.
+ * By default, there is no conversion.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @return the <code>StyleConstants</code> attribute value that
+ * represents the CSS attribute value
+ */
+ Object toStyleConstants(StyleConstants key, View v) {
+ if (key == StyleConstants.Italic) {
+ if (svalue.indexOf("italic") >= 0) {
+ return Boolean.TRUE;
+ }
+ return Boolean.FALSE;
+ } else if (key == StyleConstants.Underline) {
+ if (svalue.indexOf("underline") >= 0) {
+ return Boolean.TRUE;
+ }
+ return Boolean.FALSE;
+ } else if (key == StyleConstants.Alignment) {
+ if (svalue.equals("right")) {
+ return new Integer(StyleConstants.ALIGN_RIGHT);
+ } else if (svalue.equals("center")) {
+ return new Integer(StyleConstants.ALIGN_CENTER);
+ } else if (svalue.equals("justify")) {
+ return new Integer(StyleConstants.ALIGN_JUSTIFIED);
+ }
+ return new Integer(StyleConstants.ALIGN_LEFT);
+ } else if (key == StyleConstants.StrikeThrough) {
+ if (svalue.indexOf("line-through") >= 0) {
+ return Boolean.TRUE;
+ }
+ return Boolean.FALSE;
+ } else if (key == StyleConstants.Superscript) {
+ if (svalue.indexOf("super") >= 0) {
+ return Boolean.TRUE;
+ }
+ return Boolean.FALSE;
+ } else if (key == StyleConstants.Subscript) {
+ if (svalue.indexOf("sub") >= 0) {
+ return Boolean.TRUE;
+ }
+ return Boolean.FALSE;
+ }
+ return null;
+ }
+
+ // Used by ViewAttributeSet
+ boolean isItalic() {
+ return (svalue.indexOf("italic") != -1);
+ }
+
+ boolean isStrike() {
+ return (svalue.indexOf("line-through") != -1);
+ }
+
+ boolean isUnderline() {
+ return (svalue.indexOf("underline") != -1);
+ }
+
+ boolean isSub() {
+ return (svalue.indexOf("sub") != -1);
+ }
+
+ boolean isSup() {
+ return (svalue.indexOf("sup") != -1);
+ }
+ }
+
+ /**
+ * Represents a value for the CSS.FONT_SIZE attribute.
+ * The binary format of the value can be one of several
+ * types. If the type is Float,
+ * the value is specified in terms of point or
+ * percentage, depending upon the ending of the
+ * associated string.
+ * If the type is Integer, the value is specified
+ * in terms of a size index.
+ */
+ class FontSize extends CssValue {
+
+ /**
+ * Returns the size in points. This is ultimately
+ * what we need for the purpose of creating/fetching
+ * a Font object.
+ *
+ * @param a the attribute set the value is being
+ * requested from. We may need to walk up the
+ * resolve hierarchy if it's relative.
+ */
+ int getValue(AttributeSet a, StyleSheet ss) {
+ ss = getStyleSheet(ss);
+ if (index) {
+ // it's an index, translate from size table
+ return Math.round(getPointSize((int) value, ss));
+ }
+ else if (lu == null) {
+ return Math.round(value);
+ }
+ else {
+ if (lu.type == 0) {
+ boolean isW3CLengthUnits = (ss == null) ? false : ss.isW3CLengthUnits();
+ return Math.round(lu.getValue(isW3CLengthUnits));
+ }
+ if (a != null) {
+ AttributeSet resolveParent = a.getResolveParent();
+
+ if (resolveParent != null) {
+ int pValue = StyleConstants.getFontSize(resolveParent);
+
+ float retValue;
+ if (lu.type == 1 || lu.type == 3) {
+ retValue = lu.value * (float)pValue;
+ }
+ else {
+ retValue = lu.value + (float)pValue;
+ }
+ return Math.round(retValue);
+ }
+ }
+ // a is null, or no resolve parent.
+ return 12;
+ }
+ }
+
+ Object parseCssValue(String value) {
+ FontSize fs = new FontSize();
+ fs.svalue = value;
+ try {
+ if (value.equals("xx-small")) {
+ fs.value = 1;
+ fs.index = true;
+ } else if (value.equals("x-small")) {
+ fs.value = 2;
+ fs.index = true;
+ } else if (value.equals("small")) {
+ fs.value = 3;
+ fs.index = true;
+ } else if (value.equals("medium")) {
+ fs.value = 4;
+ fs.index = true;
+ } else if (value.equals("large")) {
+ fs.value = 5;
+ fs.index = true;
+ } else if (value.equals("x-large")) {
+ fs.value = 6;
+ fs.index = true;
+ } else if (value.equals("xx-large")) {
+ fs.value = 7;
+ fs.index = true;
+ } else {
+ fs.lu = new LengthUnit(value, (short)1, 1f);
+ }
+ // relative sizes, larger | smaller (adjust from parent by
+ // 1.5 pixels)
+ // em, ex refer to parent sizes
+ // lengths: pt, mm, cm, pc, in, px
+ // em (font height 3em would be 3 times font height)
+ // ex (height of X)
+ // lengths are (+/-) followed by a number and two letter
+ // unit identifier
+ } catch (NumberFormatException nfe) {
+ fs = null;
+ }
+ return fs;
+ }
+
+ Object parseHtmlValue(String value) {
+ if ((value == null) || (value.length() == 0)) {
+ return null;
+ }
+ FontSize fs = new FontSize();
+ fs.svalue = value;
+
+ try {
+ /*
+ * relative sizes in the size attribute are relative
+ * to the <basefont>'s size.
+ */
+ int baseFontSize = getBaseFontSize();
+ if (value.charAt(0) == '+') {
+ int relSize = Integer.valueOf(value.substring(1)).intValue();
+ fs.value = baseFontSize + relSize;
+ fs.index = true;
+ } else if (value.charAt(0) == '-') {
+ int relSize = -Integer.valueOf(value.substring(1)).intValue();
+ fs.value = baseFontSize + relSize;
+ fs.index = true;
+ } else {
+ fs.value = Integer.parseInt(value);
+ if (fs.value > 7) {
+ fs.value = 7;
+ } else if (fs.value < 0) {
+ fs.value = 0;
+ }
+ fs.index = true;
+ }
+
+ } catch (NumberFormatException nfe) {
+ fs = null;
+ }
+ return fs;
+ }
+
+ /**
+ * Converts a <code>StyleConstants</code> attribute value to
+ * a CSS attribute value. If there is no conversion
+ * returns <code>null</code>. By default, there is no conversion.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @param value the value of a <code>StyleConstants</code>
+ * attribute to be converted
+ * @return the CSS value that represents the
+ * <code>StyleConstants</code> value
+ */
+ Object fromStyleConstants(StyleConstants key, Object value) {
+ if (value instanceof Number) {
+ FontSize fs = new FontSize();
+
+ fs.value = getIndexOfSize(((Number)value).floatValue(), StyleSheet.sizeMapDefault);
+ fs.svalue = Integer.toString((int)fs.value);
+ fs.index = true;
+ return fs;
+ }
+ return parseCssValue(value.toString());
+ }
+
+ /**
+ * Converts a CSS attribute value to a <code>StyleConstants</code>
+ * value. If there is no conversion, returns <code>null</code>.
+ * By default, there is no conversion.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @return the <code>StyleConstants</code> attribute value that
+ * represents the CSS attribute value
+ */
+ Object toStyleConstants(StyleConstants key, View v) {
+ if (v != null) {
+ return Integer.valueOf(getValue(v.getAttributes(), null));
+ }
+ return Integer.valueOf(getValue(null, null));
+ }
+
+ float value;
+ boolean index;
+ LengthUnit lu;
+ }
+
+ static class FontFamily extends CssValue {
+
+ /**
+ * Returns the font family to use.
+ */
+ String getValue() {
+ return family;
+ }
+
+ Object parseCssValue(String value) {
+ int cIndex = value.indexOf(',');
+ FontFamily ff = new FontFamily();
+ ff.svalue = value;
+ ff.family = null;
+
+ if (cIndex == -1) {
+ setFontName(ff, value);
+ }
+ else {
+ boolean done = false;
+ int lastIndex;
+ int length = value.length();
+ cIndex = 0;
+ while (!done) {
+ // skip ws.
+ while (cIndex < length &&
+ Character.isWhitespace(value.charAt(cIndex)))
+ cIndex++;
+ // Find next ','
+ lastIndex = cIndex;
+ cIndex = value.indexOf(',', cIndex);
+ if (cIndex == -1) {
+ cIndex = length;
+ }
+ if (lastIndex < length) {
+ if (lastIndex != cIndex) {
+ int lastCharIndex = cIndex;
+ if (cIndex > 0 && value.charAt(cIndex - 1) == ' '){
+ lastCharIndex--;
+ }
+ setFontName(ff, value.substring
+ (lastIndex, lastCharIndex));
+ done = (ff.family != null);
+ }
+ cIndex++;
+ }
+ else {
+ done = true;
+ }
+ }
+ }
+ if (ff.family == null) {
+ ff.family = Font.SANS_SERIF;
+ }
+ return ff;
+ }
+
+ private void setFontName(FontFamily ff, String fontName) {
+ ff.family = fontName;
+ }
+
+ Object parseHtmlValue(String value) {
+ // TBD
+ return parseCssValue(value);
+ }
+
+ /**
+ * Converts a <code>StyleConstants</code> attribute value to
+ * a CSS attribute value. If there is no conversion
+ * returns <code>null</code>. By default, there is no conversion.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @param value the value of a <code>StyleConstants</code>
+ * attribute to be converted
+ * @return the CSS value that represents the
+ * <code>StyleConstants</code> value
+ */
+ Object fromStyleConstants(StyleConstants key, Object value) {
+ return parseCssValue(value.toString());
+ }
+
+ /**
+ * Converts a CSS attribute value to a <code>StyleConstants</code>
+ * value. If there is no conversion, returns <code>null</code>.
+ * By default, there is no conversion.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @return the <code>StyleConstants</code> attribute value that
+ * represents the CSS attribute value
+ */
+ Object toStyleConstants(StyleConstants key, View v) {
+ return family;
+ }
+
+ String family;
+ }
+
+ static class FontWeight extends CssValue {
+
+ int getValue() {
+ return weight;
+ }
+
+ Object parseCssValue(String value) {
+ FontWeight fw = new FontWeight();
+ fw.svalue = value;
+ if (value.equals("bold")) {
+ fw.weight = 700;
+ } else if (value.equals("normal")) {
+ fw.weight = 400;
+ } else {
+ // PENDING(prinz) add support for relative values
+ try {
+ fw.weight = Integer.parseInt(value);
+ } catch (NumberFormatException nfe) {
+ fw = null;
+ }
+ }
+ return fw;
+ }
+
+ /**
+ * Converts a <code>StyleConstants</code> attribute value to
+ * a CSS attribute value. If there is no conversion
+ * returns <code>null</code>. By default, there is no conversion.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @param value the value of a <code>StyleConstants</code>
+ * attribute to be converted
+ * @return the CSS value that represents the
+ * <code>StyleConstants</code> value
+ */
+ Object fromStyleConstants(StyleConstants key, Object value) {
+ if (value.equals(Boolean.TRUE)) {
+ return parseCssValue("bold");
+ }
+ return parseCssValue("normal");
+ }
+
+ /**
+ * Converts a CSS attribute value to a <code>StyleConstants</code>
+ * value. If there is no conversion, returns <code>null</code>.
+ * By default, there is no conversion.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @return the <code>StyleConstants</code> attribute value that
+ * represents the CSS attribute value
+ */
+ Object toStyleConstants(StyleConstants key, View v) {
+ return (weight > 500) ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ boolean isBold() {
+ return (weight > 500);
+ }
+
+ int weight;
+ }
+
+ static class ColorValue extends CssValue {
+
+ /**
+ * Returns the color to use.
+ */
+ Color getValue() {
+ return c;
+ }
+
+ Object parseCssValue(String value) {
+
+ Color c = stringToColor(value);
+ if (c != null) {
+ ColorValue cv = new ColorValue();
+ cv.svalue = value;
+ cv.c = c;
+ return cv;
+ }
+ return null;
+ }
+
+ Object parseHtmlValue(String value) {
+ return parseCssValue(value);
+ }
+
+ /**
+ * Converts a <code>StyleConstants</code> attribute value to
+ * a CSS attribute value. If there is no conversion
+ * returns <code>null</code>. By default, there is no conversion.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @param value the value of a <code>StyleConstants</code>
+ * attribute to be converted
+ * @return the CSS value that represents the
+ * <code>StyleConstants</code> value
+ */
+ Object fromStyleConstants(StyleConstants key, Object value) {
+ ColorValue colorValue = new ColorValue();
+ colorValue.c = (Color)value;
+ colorValue.svalue = colorToHex(colorValue.c);
+ return colorValue;
+ }
+
+ /**
+ * Converts a CSS attribute value to a <code>StyleConstants</code>
+ * value. If there is no conversion, returns <code>null</code>.
+ * By default, there is no conversion.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @return the <code>StyleConstants</code> attribute value that
+ * represents the CSS attribute value
+ */
+ Object toStyleConstants(StyleConstants key, View v) {
+ return c;
+ }
+
+ Color c;
+ }
+
+ static class BorderStyle extends CssValue {
+
+ CSS.Value getValue() {
+ return style;
+ }
+
+ Object parseCssValue(String value) {
+ CSS.Value cssv = CSS.getValue(value);
+ if (cssv != null) {
+ if ((cssv == CSS.Value.INSET) ||
+ (cssv == CSS.Value.OUTSET) ||
+ (cssv == CSS.Value.NONE) ||
+ (cssv == CSS.Value.DOTTED) ||
+ (cssv == CSS.Value.DASHED) ||
+ (cssv == CSS.Value.SOLID) ||
+ (cssv == CSS.Value.DOUBLE) ||
+ (cssv == CSS.Value.GROOVE) ||
+ (cssv == CSS.Value.RIDGE)) {
+
+ BorderStyle bs = new BorderStyle();
+ bs.svalue = value;
+ bs.style = cssv;
+ return bs;
+ }
+ }
+ return null;
+ }
+
+ private void writeObject(java.io.ObjectOutputStream s)
+ throws IOException {
+ s.defaultWriteObject();
+ if (style == null) {
+ s.writeObject(null);
+ }
+ else {
+ s.writeObject(style.toString());
+ }
+ }
+
+ private void readObject(ObjectInputStream s)
+ throws ClassNotFoundException, IOException {
+ s.defaultReadObject();
+ Object value = s.readObject();
+ if (value != null) {
+ style = CSS.getValue((String)value);
+ }
+ }
+
+ // CSS.Values are static, don't archive it.
+ transient private CSS.Value style;
+ }
+
+ static class LengthValue extends CssValue {
+
+ /**
+ * if this length value may be negative.
+ */
+ boolean mayBeNegative;
+
+ LengthValue() {
+ this(false);
+ }
+
+ LengthValue(boolean mayBeNegative) {
+ this.mayBeNegative = mayBeNegative;
+ }
+
+ /**
+ * Returns the length (span) to use.
+ */
+ float getValue() {
+ return getValue(false);
+ }
+
+ float getValue(boolean isW3CLengthUnits) {
+ return getValue(0, isW3CLengthUnits);
+ }
+
+ /**
+ * Returns the length (span) to use. If the value represents
+ * a percentage, it is scaled based on <code>currentValue</code>.
+ */
+ float getValue(float currentValue) {
+ return getValue(currentValue, false);
+ }
+ float getValue(float currentValue, boolean isW3CLengthUnits) {
+ if (percentage) {
+ return span * currentValue;
+ }
+ return LengthUnit.getValue(span, units, isW3CLengthUnits);
+ }
+
+ /**
+ * Returns true if the length represents a percentage of the
+ * containing box.
+ */
+ boolean isPercentage() {
+ return percentage;
+ }
+
+ Object parseCssValue(String value) {
+ LengthValue lv;
+ try {
+ // Assume pixels
+ float absolute = Float.valueOf(value).floatValue();
+ lv = new LengthValue();
+ lv.span = absolute;
+ } catch (NumberFormatException nfe) {
+ // Not pixels, use LengthUnit
+ LengthUnit lu = new LengthUnit(value,
+ LengthUnit.UNINITALIZED_LENGTH,
+ 0);
+
+ // PENDING: currently, we only support absolute values and
+ // percentages.
+ switch (lu.type) {
+ case 0:
+ // Absolute
+ lv = new LengthValue();
+ lv.span =
+ (mayBeNegative) ? lu.value : Math.max(0, lu.value);
+ lv.units = lu.units;
+ break;
+ case 1:
+ // %
+ lv = new LengthValue();
+ lv.span = Math.max(0, Math.min(1, lu.value));
+ lv.percentage = true;
+ break;
+ default:
+ return null;
+ }
+ }
+ lv.svalue = value;
+ return lv;
+ }
+
+ Object parseHtmlValue(String value) {
+ if (value.equals(HTML.NULL_ATTRIBUTE_VALUE)) {
+ value = "1";
+ }
+ return parseCssValue(value);
+ }
+ /**
+ * Converts a <code>StyleConstants</code> attribute value to
+ * a CSS attribute value. If there is no conversion,
+ * returns <code>null</code>. By default, there is no conversion.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @param value the value of a <code>StyleConstants</code>
+ * attribute to be converted
+ * @return the CSS value that represents the
+ * <code>StyleConstants</code> value
+ */
+ Object fromStyleConstants(StyleConstants key, Object value) {
+ LengthValue v = new LengthValue();
+ v.svalue = value.toString();
+ v.span = ((Float)value).floatValue();
+ return v;
+ }
+
+ /**
+ * Converts a CSS attribute value to a <code>StyleConstants</code>
+ * value. If there is no conversion, returns <code>null</code>.
+ * By default, there is no conversion.
+ *
+ * @param key the <code>StyleConstants</code> attribute
+ * @return the <code>StyleConstants</code> attribute value that
+ * represents the CSS attribute value
+ */
+ Object toStyleConstants(StyleConstants key, View v) {
+ return new Float(getValue(false));
+ }
+
+ /** If true, span is a percentage value, and that to determine
+ * the length another value needs to be passed in. */
+ boolean percentage;
+ /** Either the absolute value (percentage == false) or
+ * a percentage value. */
+ float span;
+
+ String units = null;
+ }
+
+
+ /**
+ * BorderWidthValue is used to model BORDER_XXX_WIDTH and adds support
+ * for the thin/medium/thick values.
+ */
+ static class BorderWidthValue extends LengthValue {
+ BorderWidthValue(String svalue, int index) {
+ this.svalue = svalue;
+ span = values[index];
+ percentage = false;
+ }
+
+ Object parseCssValue(String value) {
+ if (value != null) {
+ if (value.equals("thick")) {
+ return new BorderWidthValue(value, 2);
+ }
+ else if (value.equals("medium")) {
+ return new BorderWidthValue(value, 1);
+ }
+ else if (value.equals("thin")) {
+ return new BorderWidthValue(value, 0);
+ }
+ }
+ // Assume its a length.
+ return super.parseCssValue(value);
+ }
+
+ Object parseHtmlValue(String value) {
+ if (value == HTML.NULL_ATTRIBUTE_VALUE) {
+ return parseCssValue("medium");
+ }
+ return parseCssValue(value);
+ }
+
+ /** Values used to represent border width. */
+ private static final float[] values = { 1, 2, 4 };
+ }
+
+
+ /**
+ * Handles uniquing of CSS values, like lists, and background image
+ * repeating.
+ */
+ static class CssValueMapper extends CssValue {
+ Object parseCssValue(String value) {
+ Object retValue = cssValueToInternalValueMap.get(value);
+ if (retValue == null) {
+ retValue = cssValueToInternalValueMap.get(value.toLowerCase());
+ }
+ return retValue;
+ }
+
+
+ Object parseHtmlValue(String value) {
+ Object retValue = htmlValueToCssValueMap.get(value);
+ if (retValue == null) {
+ retValue = htmlValueToCssValueMap.get(value.toLowerCase());
+ }
+ return retValue;
+ }
+ }
+
+
+ /**
+ * Used for background images, to represent the position.
+ */
+ static class BackgroundPosition extends CssValue {
+ float horizontalPosition;
+ float verticalPosition;
+ // bitmask: bit 0, horizontal relative, bit 1 horizontal relative to
+ // font size, 2 vertical relative to size, 3 vertical relative to
+ // font size.
+ //
+ short relative;
+
+ Object parseCssValue(String value) {
+ // 'top left' and 'left top' both mean the same as '0% 0%'.
+ // 'top', 'top center' and 'center top' mean the same as '50% 0%'.
+ // 'right top' and 'top right' mean the same as '100% 0%'.
+ // 'left', 'left center' and 'center left' mean the same as
+ // '0% 50%'.
+ // 'center' and 'center center' mean the same as '50% 50%'.
+ // 'right', 'right center' and 'center right' mean the same as
+ // '100% 50%'.
+ // 'bottom left' and 'left bottom' mean the same as '0% 100%'.
+ // 'bottom', 'bottom center' and 'center bottom' mean the same as
+ // '50% 100%'.
+ // 'bottom right' and 'right bottom' mean the same as '100% 100%'.
+ String[] strings = CSS.parseStrings(value);
+ int count = strings.length;
+ BackgroundPosition bp = new BackgroundPosition();
+ bp.relative = 5;
+ bp.svalue = value;
+
+ if (count > 0) {
+ // bit 0 for vert, 1 hor, 2 for center
+ short found = 0;
+ int index = 0;
+ while (index < count) {
+ // First, check for keywords
+ String string = strings[index++];
+ if (string.equals("center")) {
+ found |= 4;
+ continue;
+ }
+ else {
+ if ((found & 1) == 0) {
+ if (string.equals("top")) {
+ found |= 1;
+ }
+ else if (string.equals("bottom")) {
+ found |= 1;
+ bp.verticalPosition = 1;
+ continue;
+ }
+ }
+ if ((found & 2) == 0) {
+ if (string.equals("left")) {
+ found |= 2;
+ bp.horizontalPosition = 0;
+ }
+ else if (string.equals("right")) {
+ found |= 2;
+ bp.horizontalPosition = 1;
+ }
+ }
+ }
+ }
+ if (found != 0) {
+ if ((found & 1) == 1) {
+ if ((found & 2) == 0) {
+ // vert and no horiz.
+ bp.horizontalPosition = .5f;
+ }
+ }
+ else if ((found & 2) == 2) {
+ // horiz and no vert.
+ bp.verticalPosition = .5f;
+ }
+ else {
+ // no horiz, no vert, but center
+ bp.horizontalPosition = bp.verticalPosition = .5f;
+ }
+ }
+ else {
+ // Assume lengths
+ LengthUnit lu = new LengthUnit(strings[0], (short)0, 0f);
+
+ if (lu.type == 0) {
+ bp.horizontalPosition = lu.value;
+ bp.relative = (short)(1 ^ bp.relative);
+ }
+ else if (lu.type == 1) {
+ bp.horizontalPosition = lu.value;
+ }
+ else if (lu.type == 3) {
+ bp.horizontalPosition = lu.value;
+ bp.relative = (short)((1 ^ bp.relative) | 2);
+ }
+ if (count > 1) {
+ lu = new LengthUnit(strings[1], (short)0, 0f);
+
+ if (lu.type == 0) {
+ bp.verticalPosition = lu.value;
+ bp.relative = (short)(4 ^ bp.relative);
+ }
+ else if (lu.type == 1) {
+ bp.verticalPosition = lu.value;
+ }
+ else if (lu.type == 3) {
+ bp.verticalPosition = lu.value;
+ bp.relative = (short)((4 ^ bp.relative) | 8);
+ }
+ }
+ else {
+ bp.verticalPosition = .5f;
+ }
+ }
+ }
+ return bp;
+ }
+
+ boolean isHorizontalPositionRelativeToSize() {
+ return ((relative & 1) == 1);
+ }
+
+ boolean isHorizontalPositionRelativeToFontSize() {
+ return ((relative & 2) == 2);
+ }
+
+ float getHorizontalPosition() {
+ return horizontalPosition;
+ }
+
+ boolean isVerticalPositionRelativeToSize() {
+ return ((relative & 4) == 4);
+ }
+
+ boolean isVerticalPositionRelativeToFontSize() {
+ return ((relative & 8) == 8);
+ }
+
+ float getVerticalPosition() {
+ return verticalPosition;
+ }
+ }
+
+
+ /**
+ * Used for BackgroundImages.
+ */
+ static class BackgroundImage extends CssValue {
+ private boolean loadedImage;
+ private ImageIcon image;
+
+ Object parseCssValue(String value) {
+ BackgroundImage retValue = new BackgroundImage();
+ retValue.svalue = value;
+ return retValue;
+ }
+
+ Object parseHtmlValue(String value) {
+ return parseCssValue(value);
+ }
+
+ // PENDING: this base is wrong for linked style sheets.
+ ImageIcon getImage(URL base) {
+ if (!loadedImage) {
+ synchronized(this) {
+ if (!loadedImage) {
+ URL url = CSS.getURL(base, svalue);
+ loadedImage = true;
+ if (url != null) {
+ image = new ImageIcon();
+ Image tmpImg = Toolkit.getDefaultToolkit().createImage(url);
+ if (tmpImg != null) {
+ image.setImage(tmpImg);
+ }
+ }
+ }
+ }
+ }
+ return image;
+ }
+ }
+
+ /**
+ * Parses a length value, this is used internally, and never added
+ * to an AttributeSet or returned to the developer.
+ */
+ static class LengthUnit implements Serializable {
+ static Hashtable lengthMapping = new Hashtable(6);
+ static Hashtable w3cLengthMapping = new Hashtable(6);
+ static {
+ lengthMapping.put("pt", new Float(1f));
+ // Not sure about 1.3, determined by experiementation.
+ lengthMapping.put("px", new Float(1.3f));
+ lengthMapping.put("mm", new Float(2.83464f));
+ lengthMapping.put("cm", new Float(28.3464f));
+ lengthMapping.put("pc", new Float(12f));
+ lengthMapping.put("in", new Float(72f));
+ int res = 72;
+ try {
+ res = Toolkit.getDefaultToolkit().getScreenResolution();
+ } catch (HeadlessException e) {
+ }
+ // mapping according to the CSS2 spec
+ w3cLengthMapping.put("pt", new Float(res/72f));
+ w3cLengthMapping.put("px", new Float(1f));
+ w3cLengthMapping.put("mm", new Float(res/25.4f));
+ w3cLengthMapping.put("cm", new Float(res/2.54f));
+ w3cLengthMapping.put("pc", new Float(res/6f));
+ w3cLengthMapping.put("in", new Float(res));
+ }
+
+ LengthUnit(String value, short defaultType, float defaultValue) {
+ parse(value, defaultType, defaultValue);
+ }
+
+ void parse(String value, short defaultType, float defaultValue) {
+ type = defaultType;
+ this.value = defaultValue;
+
+ int length = value.length();
+ if (length > 0 && value.charAt(length - 1) == '%') {
+ try {
+ this.value = Float.valueOf(value.substring(0, length - 1)).
+ floatValue() / 100.0f;
+ type = 1;
+ }
+ catch (NumberFormatException nfe) { }
+ }
+ if (length >= 2) {
+ units = value.substring(length - 2, length);
+ Float scale = (Float)lengthMapping.get(units);
+ if (scale != null) {
+ try {
+ this.value = Float.valueOf(value.substring(0,
+ length - 2)).floatValue();
+ type = 0;
+ }
+ catch (NumberFormatException nfe) { }
+ }
+ else if (units.equals("em") ||
+ units.equals("ex")) {
+ try {
+ this.value = Float.valueOf(value.substring(0,
+ length - 2)).floatValue();
+ type = 3;
+ }
+ catch (NumberFormatException nfe) { }
+ }
+ else if (value.equals("larger")) {
+ this.value = 2f;
+ type = 2;
+ }
+ else if (value.equals("smaller")) {
+ this.value = -2;
+ type = 2;
+ }
+ else {
+ // treat like points.
+ try {
+ this.value = Float.valueOf(value).floatValue();
+ type = 0;
+ } catch (NumberFormatException nfe) {}
+ }
+ }
+ else if (length > 0) {
+ // treat like points.
+ try {
+ this.value = Float.valueOf(value).floatValue();
+ type = 0;
+ } catch (NumberFormatException nfe) {}
+ }
+ }
+
+ float getValue(boolean w3cLengthUnits) {
+ Hashtable mapping = (w3cLengthUnits) ? w3cLengthMapping : lengthMapping;
+ float scale = 1;
+ if (units != null) {
+ Float scaleFloat = (Float)mapping.get(units);
+ if (scaleFloat != null) {
+ scale = scaleFloat.floatValue();
+ }
+ }
+ return this.value * scale;
+
+ }
+
+ static float getValue(float value, String units, Boolean w3cLengthUnits) {
+ Hashtable mapping = (w3cLengthUnits) ? w3cLengthMapping : lengthMapping;
+ float scale = 1;
+ if (units != null) {
+ Float scaleFloat = (Float)mapping.get(units);
+ if (scaleFloat != null) {
+ scale = scaleFloat.floatValue();
+ }
+ }
+ return value * scale;
+ }
+
+ public String toString() {
+ return type + " " + value;
+ }
+
+ // 0 - value indicates real value
+ // 1 - % value, value relative to depends upon key.
+ // 50% will have a value = .5
+ // 2 - add value to parent value.
+ // 3 - em/ex relative to font size of element (except for
+ // font-size, which is relative to parent).
+ short type;
+ float value;
+ String units = null;
+
+
+ static final short UNINITALIZED_LENGTH = (short)10;
+ }
+
+
+ /**
+ * Class used to parse font property. The font property is shorthand
+ * for the other font properties. This expands the properties, placing
+ * them in the attributeset.
+ */
+ static class ShorthandFontParser {
+ /**
+ * Parses the shorthand font string <code>value</code>, placing the
+ * result in <code>attr</code>.
+ */
+ static void parseShorthandFont(CSS css, String value,
+ MutableAttributeSet attr) {
+ // font is of the form:
+ // [ <font-style> || <font-variant> || <font-weight> ]? <font-size>
+ // [ / <line-height> ]? <font-family>
+ String[] strings = CSS.parseStrings(value);
+ int count = strings.length;
+ int index = 0;
+ // bitmask, 1 for style, 2 for variant, 3 for weight
+ short found = 0;
+ int maxC = Math.min(3, count);
+
+ // Check for font-style font-variant font-weight
+ while (index < maxC) {
+ if ((found & 1) == 0 && isFontStyle(strings[index])) {
+ css.addInternalCSSValue(attr, CSS.Attribute.FONT_STYLE,
+ strings[index++]);
+ found |= 1;
+ }
+ else if ((found & 2) == 0 && isFontVariant(strings[index])) {
+ css.addInternalCSSValue(attr, CSS.Attribute.FONT_VARIANT,
+ strings[index++]);
+ found |= 2;
+ }
+ else if ((found & 4) == 0 && isFontWeight(strings[index])) {
+ css.addInternalCSSValue(attr, CSS.Attribute.FONT_WEIGHT,
+ strings[index++]);
+ found |= 4;
+ }
+ else if (strings[index].equals("normal")) {
+ index++;
+ }
+ else {
+ break;
+ }
+ }
+ if ((found & 1) == 0) {
+ css.addInternalCSSValue(attr, CSS.Attribute.FONT_STYLE,
+ "normal");
+ }
+ if ((found & 2) == 0) {
+ css.addInternalCSSValue(attr, CSS.Attribute.FONT_VARIANT,
+ "normal");
+ }
+ if ((found & 4) == 0) {
+ css.addInternalCSSValue(attr, CSS.Attribute.FONT_WEIGHT,
+ "normal");
+ }
+
+ // string at index should be the font-size
+ if (index < count) {
+ String fontSize = strings[index];
+ int slashIndex = fontSize.indexOf('/');
+
+ if (slashIndex != -1) {
+ fontSize = fontSize.substring(0, slashIndex);
+ strings[index] = strings[index].substring(slashIndex);
+ }
+ else {
+ index++;
+ }
+ css.addInternalCSSValue(attr, CSS.Attribute.FONT_SIZE,
+ fontSize);
+ }
+ else {
+ css.addInternalCSSValue(attr, CSS.Attribute.FONT_SIZE,
+ "medium");
+ }
+
+ // Check for line height
+ if (index < count && strings[index].startsWith("/")) {
+ String lineHeight = null;
+ if (strings[index].equals("/")) {
+ if (++index < count) {
+ lineHeight = strings[index++];
+ }
+ }
+ else {
+ lineHeight = strings[index++].substring(1);
+ }
+ // line height
+ if (lineHeight != null) {
+ css.addInternalCSSValue(attr, CSS.Attribute.LINE_HEIGHT,
+ lineHeight);
+ }
+ else {
+ css.addInternalCSSValue(attr, CSS.Attribute.LINE_HEIGHT,
+ "normal");
+ }
+ }
+ else {
+ css.addInternalCSSValue(attr, CSS.Attribute.LINE_HEIGHT,
+ "normal");
+ }
+
+ // remainder of strings are font-family
+ if (index < count) {
+ String family = strings[index++];
+
+ while (index < count) {
+ family += " " + strings[index++];
+ }
+ css.addInternalCSSValue(attr, CSS.Attribute.FONT_FAMILY,
+ family);
+ }
+ else {
+ css.addInternalCSSValue(attr, CSS.Attribute.FONT_FAMILY,
+ Font.SANS_SERIF);
+ }
+ }
+
+ private static boolean isFontStyle(String string) {
+ return (string.equals("italic") ||
+ string.equals("oblique"));
+ }
+
+ private static boolean isFontVariant(String string) {
+ return (string.equals("small-caps"));
+ }
+
+ private static boolean isFontWeight(String string) {
+ if (string.equals("bold") || string.equals("bolder") ||
+ string.equals("italic") || string.equals("lighter")) {
+ return true;
+ }
+ // test for 100-900
+ return (string.length() == 3 &&
+ string.charAt(0) >= '1' && string.charAt(0) <= '9' &&
+ string.charAt(1) == '0' && string.charAt(2) == '0');
+ }
+
+ }
+
+
+ /**
+ * Parses the background property into its intrinsic values.
+ */
+ static class ShorthandBackgroundParser {
+ /**
+ * Parses the shorthand font string <code>value</code>, placing the
+ * result in <code>attr</code>.
+ */
+ static void parseShorthandBackground(CSS css, String value,
+ MutableAttributeSet attr) {
+ String[] strings = parseStrings(value);
+ int count = strings.length;
+ int index = 0;
+ // bitmask: 0 for image, 1 repeat, 2 attachment, 3 position,
+ // 4 color
+ short found = 0;
+
+ while (index < count) {
+ String string = strings[index++];
+ if ((found & 1) == 0 && isImage(string)) {
+ css.addInternalCSSValue(attr, CSS.Attribute.
+ BACKGROUND_IMAGE, string);
+ found |= 1;
+ }
+ else if ((found & 2) == 0 && isRepeat(string)) {
+ css.addInternalCSSValue(attr, CSS.Attribute.
+ BACKGROUND_REPEAT, string);
+ found |= 2;
+ }
+ else if ((found & 4) == 0 && isAttachment(string)) {
+ css.addInternalCSSValue(attr, CSS.Attribute.
+ BACKGROUND_ATTACHMENT, string);
+ found |= 4;
+ }
+ else if ((found & 8) == 0 && isPosition(string)) {
+ if (index < count && isPosition(strings[index])) {
+ css.addInternalCSSValue(attr, CSS.Attribute.
+ BACKGROUND_POSITION,
+ string + " " +
+ strings[index++]);
+ }
+ else {
+ css.addInternalCSSValue(attr, CSS.Attribute.
+ BACKGROUND_POSITION, string);
+ }
+ found |= 8;
+ }
+ else if ((found & 16) == 0 && isColor(string)) {
+ css.addInternalCSSValue(attr, CSS.Attribute.
+ BACKGROUND_COLOR, string);
+ found |= 16;
+ }
+ }
+ if ((found & 1) == 0) {
+ css.addInternalCSSValue(attr, CSS.Attribute.BACKGROUND_IMAGE,
+ null);
+ }
+ if ((found & 2) == 0) {
+ css.addInternalCSSValue(attr, CSS.Attribute.BACKGROUND_REPEAT,
+ "repeat");
+ }
+ if ((found & 4) == 0) {
+ css.addInternalCSSValue(attr, CSS.Attribute.
+ BACKGROUND_ATTACHMENT, "scroll");
+ }
+ if ((found & 8) == 0) {
+ css.addInternalCSSValue(attr, CSS.Attribute.
+ BACKGROUND_POSITION, null);
+ }
+ // Currently, there is no good way to express this.
+ /*
+ if ((found & 16) == 0) {
+ css.addInternalCSSValue(attr, CSS.Attribute.BACKGROUND_COLOR,
+ null);
+ }
+ */
+ }
+
+ static boolean isImage(String string) {
+ return (string.startsWith("url(") && string.endsWith(")"));
+ }
+
+ static boolean isRepeat(String string) {
+ return (string.equals("repeat-x") || string.equals("repeat-y") ||
+ string.equals("repeat") || string.equals("no-repeat"));
+ }
+
+ static boolean isAttachment(String string) {
+ return (string.equals("fixed") || string.equals("scroll"));
+ }
+
+ static boolean isPosition(String string) {
+ return (string.equals("top") || string.equals("bottom") ||
+ string.equals("left") || string.equals("right") ||
+ string.equals("center") ||
+ (string.length() > 0 &&
+ Character.isDigit(string.charAt(0))));
+ }
+
+ static boolean isColor(String string) {
+ return (CSS.stringToColor(string) != null);
+ }
+ }
+
+
+ /**
+ * Used to parser margin and padding.
+ */
+ static class ShorthandMarginParser {
+ /**
+ * Parses the shorthand margin/padding/border string
+ * <code>value</code>, placing the result in <code>attr</code>.
+ * <code>names</code> give the 4 instrinsic property names.
+ */
+ static void parseShorthandMargin(CSS css, String value,
+ MutableAttributeSet attr,
+ CSS.Attribute[] names) {
+ String[] strings = parseStrings(value);
+ int count = strings.length;
+ int index = 0;
+ switch (count) {
+ case 0:
+ // empty string
+ return;
+ case 1:
+ // Identifies all values.
+ for (int counter = 0; counter < 4; counter++) {
+ css.addInternalCSSValue(attr, names[counter], strings[0]);
+ }
+ break;
+ case 2:
+ // 0 & 2 = strings[0], 1 & 3 = strings[1]
+ css.addInternalCSSValue(attr, names[0], strings[0]);
+ css.addInternalCSSValue(attr, names[2], strings[0]);
+ css.addInternalCSSValue(attr, names[1], strings[1]);
+ css.addInternalCSSValue(attr, names[3], strings[1]);
+ break;
+ case 3:
+ css.addInternalCSSValue(attr, names[0], strings[0]);
+ css.addInternalCSSValue(attr, names[1], strings[1]);
+ css.addInternalCSSValue(attr, names[2], strings[2]);
+ css.addInternalCSSValue(attr, names[3], strings[1]);
+ break;
+ default:
+ for (int counter = 0; counter < 4; counter++) {
+ css.addInternalCSSValue(attr, names[counter],
+ strings[counter]);
+ }
+ break;
+ }
+ }
+ }
+
+ static class ShorthandBorderParser {
+ static Attribute[] keys = {
+ Attribute.BORDER_TOP, Attribute.BORDER_RIGHT,
+ Attribute.BORDER_BOTTOM, Attribute.BORDER_LEFT,
+ };
+
+ static void parseShorthandBorder(MutableAttributeSet attributes,
+ CSS.Attribute key, String value) {
+ Object[] parts = new Object[CSSBorder.PARSERS.length];
+ String[] strings = parseStrings(value);
+ for (String s : strings) {
+ boolean valid = false;
+ for (int i = 0; i < parts.length; i++) {
+ Object v = CSSBorder.PARSERS[i].parseCssValue(s);
+ if (v != null) {
+ if (parts[i] == null) {
+ parts[i] = v;
+ valid = true;
+ }
+ break;
+ }
+ }
+ if (!valid) {
+ // Part is non-parseable or occured more than once.
+ return;
+ }
+ }
+
+ // Unspecified parts get default values.
+ for (int i = 0; i < parts.length; i++) {
+ if (parts[i] == null) {
+ parts[i] = CSSBorder.DEFAULTS[i];
+ }
+ }
+
+ // Dispatch collected values to individual properties.
+ for (int i = 0; i < keys.length; i++) {
+ if ((key == Attribute.BORDER) || (key == keys[i])) {
+ for (int k = 0; k < parts.length; k++) {
+ attributes.addAttribute(
+ CSSBorder.ATTRIBUTES[k][i], parts[k]);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Calculate the requirements needed to tile the requirements
+ * given by the iterator that would be tiled. The calculation
+ * takes into consideration margin and border spacing.
+ */
+ static SizeRequirements calculateTiledRequirements(LayoutIterator iter, SizeRequirements r) {
+ long minimum = 0;
+ long maximum = 0;
+ long preferred = 0;
+ int lastMargin = 0;
+ int totalSpacing = 0;
+ int n = iter.getCount();
+ for (int i = 0; i < n; i++) {
+ iter.setIndex(i);
+ int margin0 = lastMargin;
+ int margin1 = (int) iter.getLeadingCollapseSpan();
+ totalSpacing += Math.max(margin0, margin1);;
+ preferred += (int) iter.getPreferredSpan(0);
+ minimum += iter.getMinimumSpan(0);
+ maximum += iter.getMaximumSpan(0);
+
+ lastMargin = (int) iter.getTrailingCollapseSpan();
+ }
+ totalSpacing += lastMargin;
+ totalSpacing += 2 * iter.getBorderWidth();
+
+ // adjust for the spacing area
+ minimum += totalSpacing;
+ preferred += totalSpacing;
+ maximum += totalSpacing;
+
+ // set return value
+ if (r == null) {
+ r = new SizeRequirements();
+ }
+ r.minimum = (minimum > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)minimum;
+ r.preferred = (preferred > Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int) preferred;
+ r.maximum = (maximum > Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int) maximum;
+ return r;
+ }
+
+ /**
+ * Calculate a tiled layout for the given iterator.
+ * This should be done collapsing the neighboring
+ * margins to be a total of the maximum of the two
+ * neighboring margin areas as described in the CSS spec.
+ */
+ static void calculateTiledLayout(LayoutIterator iter, int targetSpan) {
+
+ /*
+ * first pass, calculate the preferred sizes, adjustments needed because
+ * of margin collapsing, and the flexibility to adjust the sizes.
+ */
+ long preferred = 0;
+ long currentPreferred = 0;
+ int lastMargin = 0;
+ int totalSpacing = 0;
+ int n = iter.getCount();
+ int adjustmentWeightsCount = LayoutIterator.WorstAdjustmentWeight + 1;
+ //max gain we can get adjusting elements with adjustmentWeight <= i
+ long gain[] = new long[adjustmentWeightsCount];
+ //max loss we can get adjusting elements with adjustmentWeight <= i
+ long loss[] = new long[adjustmentWeightsCount];
+
+ for (int i = 0; i < adjustmentWeightsCount; i++) {
+ gain[i] = loss[i] = 0;
+ }
+ for (int i = 0; i < n; i++) {
+ iter.setIndex(i);
+ int margin0 = lastMargin;
+ int margin1 = (int) iter.getLeadingCollapseSpan();
+
+ iter.setOffset(Math.max(margin0, margin1));
+ totalSpacing += iter.getOffset();
+
+ currentPreferred = (long)iter.getPreferredSpan(targetSpan);
+ iter.setSpan((int) currentPreferred);
+ preferred += currentPreferred;
+ gain[iter.getAdjustmentWeight()] +=
+ (long)iter.getMaximumSpan(targetSpan) - currentPreferred;
+ loss[iter.getAdjustmentWeight()] +=
+ currentPreferred - (long)iter.getMinimumSpan(targetSpan);
+ lastMargin = (int) iter.getTrailingCollapseSpan();
+ }
+ totalSpacing += lastMargin;
+ totalSpacing += 2 * iter.getBorderWidth();
+
+ for (int i = 1; i < adjustmentWeightsCount; i++) {
+ gain[i] += gain[i - 1];
+ loss[i] += loss[i - 1];
+ }
+
+ /*
+ * Second pass, expand or contract by as much as possible to reach
+ * the target span. This takes the margin collapsing into account
+ * prior to adjusting the span.
+ */
+
+ // determine the adjustment to be made
+ int allocated = targetSpan - totalSpacing;
+ long desiredAdjustment = allocated - preferred;
+ long adjustmentsArray[] = (desiredAdjustment > 0) ? gain : loss;
+ desiredAdjustment = Math.abs(desiredAdjustment);
+ int adjustmentLevel = 0;
+ for (;adjustmentLevel <= LayoutIterator.WorstAdjustmentWeight;
+ adjustmentLevel++) {
+ // adjustmentsArray[] is sorted. I do not bother about
+ // binary search though
+ if (adjustmentsArray[adjustmentLevel] >= desiredAdjustment) {
+ break;
+ }
+ }
+ float adjustmentFactor = 0.0f;
+ if (adjustmentLevel <= LayoutIterator.WorstAdjustmentWeight) {
+ desiredAdjustment -= (adjustmentLevel > 0) ?
+ adjustmentsArray[adjustmentLevel - 1] : 0;
+ if (desiredAdjustment != 0) {
+ float maximumAdjustment =
+ adjustmentsArray[adjustmentLevel] -
+ ((adjustmentLevel > 0) ?
+ adjustmentsArray[adjustmentLevel - 1] : 0
+ );
+ adjustmentFactor = desiredAdjustment / maximumAdjustment;
+ }
+ }
+ // make the adjustments
+ int totalOffset = (int)iter.getBorderWidth();;
+ for (int i = 0; i < n; i++) {
+ iter.setIndex(i);
+ iter.setOffset( iter.getOffset() + totalOffset);
+ if (iter.getAdjustmentWeight() < adjustmentLevel) {
+ iter.setSpan((int)
+ ((allocated > preferred) ?
+ Math.floor(iter.getMaximumSpan(targetSpan)) :
+ Math.ceil(iter.getMinimumSpan(targetSpan))
+ )
+ );
+ } else if (iter.getAdjustmentWeight() == adjustmentLevel) {
+ int availableSpan = (allocated > preferred) ?
+ (int) iter.getMaximumSpan(targetSpan) - iter.getSpan() :
+ iter.getSpan() - (int) iter.getMinimumSpan(targetSpan);
+ int adj = (int)Math.floor(adjustmentFactor * availableSpan);
+ iter.setSpan(iter.getSpan() +
+ ((allocated > preferred) ? adj : -adj));
+ }
+ totalOffset = (int) Math.min((long) iter.getOffset() +
+ (long) iter.getSpan(),
+ Integer.MAX_VALUE);
+ }
+
+ // while rounding we could lose several pixels.
+ int roundError = targetSpan - totalOffset -
+ (int)iter.getTrailingCollapseSpan() -
+ (int)iter.getBorderWidth();
+ int adj = (roundError > 0) ? 1 : -1;
+ roundError *= adj;
+
+ boolean canAdjust = true;
+ while (roundError > 0 && canAdjust) {
+ // check for infinite loop
+ canAdjust = false;
+ int offsetAdjust = 0;
+ // try to distribute roundError. one pixel per cell
+ for (int i = 0; i < n; i++) {
+ iter.setIndex(i);
+ iter.setOffset(iter.getOffset() + offsetAdjust);
+ int curSpan = iter.getSpan();
+ if (roundError > 0) {
+ int boundGap = (adj > 0) ?
+ (int)Math.floor(iter.getMaximumSpan(targetSpan)) - curSpan :
+ curSpan - (int)Math.ceil(iter.getMinimumSpan(targetSpan));
+ if (boundGap >= 1) {
+ canAdjust = true;
+ iter.setSpan(curSpan + adj);
+ offsetAdjust += adj;
+ roundError--;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * An iterator to express the requirements to use when computing
+ * layout.
+ */
+ interface LayoutIterator {
+
+ void setOffset(int offs);
+
+ int getOffset();
+
+ void setSpan(int span);
+
+ int getSpan();
+
+ int getCount();
+
+ void setIndex(int i);
+
+ float getMinimumSpan(float parentSpan);
+
+ float getPreferredSpan(float parentSpan);
+
+ float getMaximumSpan(float parentSpan);
+
+ int getAdjustmentWeight(); //0 is the best weight WorstAdjustmentWeight is a worst one
+
+ //float getAlignment();
+
+ float getBorderWidth();
+
+ float getLeadingCollapseSpan();
+
+ float getTrailingCollapseSpan();
+ public static final int WorstAdjustmentWeight = 2;
+ }
+
+ //
+ // Serialization support
+ //
+
+ private void writeObject(java.io.ObjectOutputStream s)
+ throws IOException
+ {
+ s.defaultWriteObject();
+
+ // Determine what values in valueConvertor need to be written out.
+ Enumeration keys = valueConvertor.keys();
+ s.writeInt(valueConvertor.size());
+ if (keys != null) {
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ Object value = valueConvertor.get(key);
+ if (!(key instanceof Serializable) &&
+ (key = StyleContext.getStaticAttributeKey(key)) == null) {
+ // Should we throw an exception here?
+ key = null;
+ value = null;
+ }
+ else if (!(value instanceof Serializable) &&
+ (value = StyleContext.getStaticAttributeKey(value)) == null){
+ // Should we throw an exception here?
+ key = null;
+ value = null;
+ }
+ s.writeObject(key);
+ s.writeObject(value);
+ }
+ }
+ }
+
+ private void readObject(ObjectInputStream s)
+ throws ClassNotFoundException, IOException
+ {
+ s.defaultReadObject();
+ // Reconstruct the hashtable.
+ int numValues = s.readInt();
+ valueConvertor = new Hashtable(Math.max(1, numValues));
+ while (numValues-- > 0) {
+ Object key = s.readObject();
+ Object value = s.readObject();
+ Object staticKey = StyleContext.getStaticAttribute(key);
+ if (staticKey != null) {
+ key = staticKey;
+ }
+ Object staticValue = StyleContext.getStaticAttribute(value);
+ if (staticValue != null) {
+ value = staticValue;
+ }
+ if (key != null && value != null) {
+ valueConvertor.put(key, value);
+ }
+ }
+ }
+
+
+ /*
+ * we need StyleSheet for resolving lenght units. (see
+ * isW3CLengthUnits)
+ * we can not pass stylesheet for handling relative sizes. (do not
+ * think changing public API is necessary)
+ * CSS is not likely to be accessed from more then one thread.
+ * Having local storage for StyleSheet for resolving relative
+ * sizes is safe
+ *
+ * idk 08/30/2004
+ */
+ private StyleSheet getStyleSheet(StyleSheet ss) {
+ if (ss != null) {
+ styleSheet = ss;
+ }
+ return styleSheet;
+ }
+ //
+ // Instance variables
+ //
+
+ /** Maps from CSS key to CssValue. */
+ private transient Hashtable valueConvertor;
+
+ /** Size used for relative units. */
+ private int baseFontSize;
+
+ private transient StyleSheet styleSheet = null;
+
+ static int baseFontSizeIndex = 3;
+}
diff --git a/src/share/classes/javax/swing/text/html/CSSBorder.java b/src/share/classes/javax/swing/text/html/CSSBorder.java
new file mode 100644
index 000000000..a24afd7f6
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/CSSBorder.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.util.HashMap;
+import java.util.Map;
+import javax.swing.border.AbstractBorder;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.View;
+import javax.swing.text.html.CSS.Attribute;
+import javax.swing.text.html.CSS.BorderStyle;
+import javax.swing.text.html.CSS.BorderWidthValue;
+import javax.swing.text.html.CSS.ColorValue;
+import javax.swing.text.html.CSS.CssValue;
+import javax.swing.text.html.CSS.LengthValue;
+import javax.swing.text.html.CSS.Value;
+
+/**
+ * CSS-style borders for HTML elements.
+ *
+ * @author Sergey Groznyh
+ */
+class CSSBorder extends AbstractBorder {
+
+ /** Indices for the attribute groups. */
+ final static int COLOR = 0, STYLE = 1, WIDTH = 2;
+
+ /** Indices for the box sides within the attribute group. */
+ final static int TOP = 0, RIGHT = 1, BOTTOM = 2, LEFT = 3;
+
+ /** The attribute groups. */
+ final static Attribute[][] ATTRIBUTES = {
+ { Attribute.BORDER_TOP_COLOR, Attribute.BORDER_RIGHT_COLOR,
+ Attribute.BORDER_BOTTOM_COLOR, Attribute.BORDER_LEFT_COLOR, },
+ { Attribute.BORDER_TOP_STYLE, Attribute.BORDER_RIGHT_STYLE,
+ Attribute.BORDER_BOTTOM_STYLE, Attribute.BORDER_LEFT_STYLE, },
+ { Attribute.BORDER_TOP_WIDTH, Attribute.BORDER_RIGHT_WIDTH,
+ Attribute.BORDER_BOTTOM_WIDTH, Attribute.BORDER_LEFT_WIDTH, },
+ };
+
+ /** Parsers for the border properties. */
+ final static CssValue PARSERS[] = {
+ new ColorValue(), new BorderStyle(), new BorderWidthValue(null, 0),
+ };
+
+ /** Default values for the border properties. */
+ final static Object[] DEFAULTS = {
+ Attribute.BORDER_COLOR, // marker: value will be computed on request
+ PARSERS[1].parseCssValue(Attribute.BORDER_STYLE.getDefaultValue()),
+ PARSERS[2].parseCssValue(Attribute.BORDER_WIDTH.getDefaultValue()),
+ };
+
+ /** Attribute set containing border properties. */
+ final AttributeSet attrs;
+
+ /**
+ * Initialize the attribute set.
+ */
+ CSSBorder(AttributeSet attrs) {
+ this.attrs = attrs;
+ }
+
+ /**
+ * Return the border color for the given side.
+ */
+ private Color getBorderColor(int side) {
+ Object o = attrs.getAttribute(ATTRIBUTES[COLOR][side]);
+ ColorValue cv;
+ if (o instanceof ColorValue) {
+ cv = (ColorValue) o;
+ } else {
+ // Marker for the default value. Use 'color' property value as the
+ // computed value of the 'border-color' property (CSS2 8.5.2)
+ cv = (ColorValue) attrs.getAttribute(Attribute.COLOR);
+ if (cv == null) {
+ cv = (ColorValue) PARSERS[COLOR].parseCssValue(
+ Attribute.COLOR.getDefaultValue());
+ }
+ }
+ return cv.getValue();
+ }
+
+ /**
+ * Return the border width for the given side.
+ */
+ private int getBorderWidth(int side) {
+ int width = 0;
+ BorderStyle bs = (BorderStyle) attrs.getAttribute(
+ ATTRIBUTES[STYLE][side]);
+ if ((bs != null) && (bs.getValue() != Value.NONE)) {
+ // The 'border-style' value of "none" forces the computed value
+ // of 'border-width' to be 0 (CSS2 8.5.3)
+ LengthValue bw = (LengthValue) attrs.getAttribute(
+ ATTRIBUTES[WIDTH][side]);
+ if (bw == null) {
+ bw = (LengthValue) DEFAULTS[WIDTH];
+ }
+ width = (int) bw.getValue(true);
+ }
+ return width;
+ }
+
+ /**
+ * Return an array of border widths in the TOP, RIGHT, BOTTOM, LEFT order.
+ */
+ private int[] getWidths() {
+ int[] widths = new int[4];
+ for (int i = 0; i < widths.length; i++) {
+ widths[i] = getBorderWidth(i);
+ }
+ return widths;
+ }
+
+ /**
+ * Return the border style for the given side.
+ */
+ private Value getBorderStyle(int side) {
+ BorderStyle style =
+ (BorderStyle) attrs.getAttribute(ATTRIBUTES[STYLE][side]);
+ if (style == null) {
+ style = (BorderStyle) DEFAULTS[STYLE];
+ }
+ return style.getValue();
+ }
+
+ /**
+ * Return border shape for {@code side} as if the border has zero interior
+ * length. Shape start is at (0,0); points are added clockwise.
+ */
+ private Polygon getBorderShape(int side) {
+ Polygon shape = null;
+ int[] widths = getWidths();
+ if (widths[side] != 0) {
+ shape = new Polygon(new int[4], new int[4], 0);
+ shape.addPoint(0, 0);
+ shape.addPoint(-widths[(side + 3) % 4], -widths[side]);
+ shape.addPoint(widths[(side + 1) % 4], -widths[side]);
+ shape.addPoint(0, 0);
+ }
+ return shape;
+ }
+
+ /**
+ * Return the border painter appropriate for the given side.
+ */
+ private BorderPainter getBorderPainter(int side) {
+ Value style = getBorderStyle(side);
+ return borderPainters.get(style);
+ }
+
+ /**
+ * Return the color with brightness adjusted by the specified factor.
+ *
+ * The factor values are between 0.0 (no change) and 1.0 (turn into white).
+ * Negative factor values decrease brigthness (ie, 1.0 turns into black).
+ */
+ static Color getAdjustedColor(Color c, double factor) {
+ double f = 1 - Math.min(Math.abs(factor), 1);
+ double inc = (factor > 0 ? 255 * (1 - f) : 0);
+ return new Color((int) (c.getRed() * f + inc),
+ (int) (c.getGreen() * f + inc),
+ (int) (c.getBlue() * f + inc));
+ }
+
+
+ /* The javax.swing.border.Border methods. */
+
+ public Insets getBorderInsets(Component c, Insets insets) {
+ int[] widths = getWidths();
+ insets.set(widths[TOP], widths[LEFT], widths[BOTTOM], widths[RIGHT]);
+ return insets;
+ }
+
+ public void paintBorder(Component c, Graphics g,
+ int x, int y, int width, int height) {
+ assert (g instanceof Graphics2D) : "need Graphics2D instanse";
+ Graphics2D g2 = (Graphics2D) g;
+ Color savedColor = g2.getColor();
+ Shape savedClip = g2.getClip();
+
+ int[] widths = getWidths();
+
+ // Position and size of the border interior.
+ int intX = x + widths[LEFT];
+ int intY = y + widths[TOP];
+ int intWidth = width - (widths[RIGHT] + widths[LEFT]);
+ int intHeight = height - (widths[TOP] + widths[BOTTOM]);
+
+ // Coordinates of the interior corners, from NW clockwise.
+ int[][] intCorners = {
+ { intX, intY },
+ { intX + intWidth, intY },
+ { intX + intWidth, intY + intHeight },
+ { intX, intY + intHeight, },
+ };
+
+ // Draw the borders for all sides.
+ for (int i = 0; i < 4; i++) {
+ Value style = getBorderStyle(i);
+ Polygon shape = getBorderShape(i);
+ if ((style != Value.NONE) && (shape != null)) {
+ int sideLength = (i % 2 == 0 ? intWidth : intHeight);
+
+ // "stretch" the border shape by the interior area dimension
+ shape.xpoints[2] += sideLength;
+ shape.xpoints[3] += sideLength;
+ Color color = getBorderColor(i);
+ BorderPainter painter = getBorderPainter(i);
+
+ double angle = i * Math.PI / 2;
+ g2.translate(intCorners[i][0], intCorners[i][1]);
+ g2.rotate(angle);
+ g2.setClip(shape);
+ painter.paint(shape, g, color, i);
+ g2.rotate(-angle);
+ g2.translate(-intCorners[i][0], -intCorners[i][1]);
+ }
+ }
+ g2.setColor(savedColor);
+ g2.setClip(savedClip);
+ }
+
+
+ /* Border painters. */
+
+ interface BorderPainter {
+ /**
+ * The painter should paint the border as if it were at the top and the
+ * coordinates of the NW corner of the interior area is (0, 0). The
+ * caller is responsible for the appropriate affine transformations.
+ *
+ * Clip is set by the caller to the exact border shape so it's safe to
+ * simply draw into the shape's bounding rectangle.
+ */
+ void paint(Polygon shape, Graphics g, Color color, int side);
+ }
+
+ /**
+ * Painter for the "none" and "hidden" CSS border styles.
+ */
+ static class NullPainter implements BorderPainter {
+ public void paint(Polygon shape, Graphics g, Color color, int side) {
+ // Do nothing.
+ }
+ }
+
+ /**
+ * Painter for the "solid" CSS border style.
+ */
+ static class SolidPainter implements BorderPainter {
+ public void paint(Polygon shape, Graphics g, Color color, int side) {
+ g.setColor(color);
+ g.fillPolygon(shape);
+ }
+ }
+
+ /**
+ * Defines a method for painting strokes in the specified direction using
+ * the given length and color patterns.
+ */
+ abstract static class StrokePainter implements BorderPainter {
+ /**
+ * Paint strokes repeatedly using the given length and color patterns.
+ */
+ void paintStrokes(Rectangle r, Graphics g, int axis,
+ int[] lengthPattern, Color[] colorPattern) {
+ boolean xAxis = (axis == View.X_AXIS);
+ int start = 0;
+ int end = (xAxis ? r.width : r.height);
+ while (start < end) {
+ for (int i = 0; i < lengthPattern.length; i++) {
+ if (start >= end) {
+ break;
+ }
+ int length = lengthPattern[i];
+ Color c = colorPattern[i];
+ if (c != null) {
+ int x = r.x + (xAxis ? start : 0);
+ int y = r.y + (xAxis ? 0 : start);
+ int width = xAxis ? length : r.width;
+ int height = xAxis ? r.height : length;
+ g.setColor(c);
+ g.fillRect(x, y, width, height);
+ }
+ start += length;
+ }
+ }
+ }
+ }
+
+ /**
+ * Painter for the "double" CSS border style.
+ */
+ static class DoublePainter extends StrokePainter {
+ public void paint(Polygon shape, Graphics g, Color color, int side) {
+ Rectangle r = shape.getBounds();
+ int length = Math.max(r.height / 3, 1);
+ int[] lengthPattern = { length, length };
+ Color[] colorPattern = { color, null };
+ paintStrokes(r, g, View.Y_AXIS, lengthPattern, colorPattern);
+ }
+ }
+
+ /**
+ * Painter for the "dotted" and "dashed" CSS border styles.
+ */
+ static class DottedDashedPainter extends StrokePainter {
+ final int factor;
+
+ DottedDashedPainter(int factor) {
+ this.factor = factor;
+ }
+
+ public void paint(Polygon shape, Graphics g, Color color, int side) {
+ Rectangle r = shape.getBounds();
+ int length = r.height * factor;
+ int[] lengthPattern = { length, length };
+ Color[] colorPattern = { color, null };
+ paintStrokes(r, g, View.X_AXIS, lengthPattern, colorPattern);
+ }
+ }
+
+ /**
+ * Painter that defines colors for "shadow" and "light" border sides.
+ */
+ abstract static class ShadowLightPainter extends StrokePainter {
+ /**
+ * Return the "shadow" border side color.
+ */
+ static Color getShadowColor(Color c) {
+ return CSSBorder.getAdjustedColor(c, -0.3);
+ }
+
+ /**
+ * Return the "light" border side color.
+ */
+ static Color getLightColor(Color c) {
+ return CSSBorder.getAdjustedColor(c, 0.7);
+ }
+ }
+
+ /**
+ * Painter for the "groove" and "ridge" CSS border styles.
+ */
+ static class GrooveRidgePainter extends ShadowLightPainter {
+ final Value type;
+
+ GrooveRidgePainter(Value type) {
+ this.type = type;
+ }
+
+ public void paint(Polygon shape, Graphics g, Color color, int side) {
+ Rectangle r = shape.getBounds();
+ int length = Math.max(r.height / 2, 1);
+ int[] lengthPattern = { length, length };
+ Color[] colorPattern =
+ ((side + 1) % 4 < 2) == (type == Value.GROOVE) ?
+ new Color[] { getShadowColor(color), getLightColor(color) } :
+ new Color[] { getLightColor(color), getShadowColor(color) };
+ paintStrokes(r, g, View.Y_AXIS, lengthPattern, colorPattern);
+ }
+ }
+
+ /**
+ * Painter for the "inset" and "outset" CSS border styles.
+ */
+ static class InsetOutsetPainter extends ShadowLightPainter {
+ Value type;
+
+ InsetOutsetPainter(Value type) {
+ this.type = type;
+ }
+
+ public void paint(Polygon shape, Graphics g, Color color, int side) {
+ g.setColor(((side + 1) % 4 < 2) == (type == Value.INSET) ?
+ getShadowColor(color) : getLightColor(color));
+ g.fillPolygon(shape);
+ }
+ }
+
+ /**
+ * Add the specified painter to the painters map.
+ */
+ static void registerBorderPainter(Value style, BorderPainter painter) {
+ borderPainters.put(style, painter);
+ }
+
+ /** Map the border style values to the border painter objects. */
+ static Map<Value, BorderPainter> borderPainters =
+ new HashMap<Value, BorderPainter>();
+
+ /* Initialize the border painters map with the pre-defined values. */
+ static {
+ registerBorderPainter(Value.NONE, new NullPainter());
+ registerBorderPainter(Value.HIDDEN, new NullPainter());
+ registerBorderPainter(Value.SOLID, new SolidPainter());
+ registerBorderPainter(Value.DOUBLE, new DoublePainter());
+ registerBorderPainter(Value.DOTTED, new DottedDashedPainter(1));
+ registerBorderPainter(Value.DASHED, new DottedDashedPainter(3));
+ registerBorderPainter(Value.GROOVE, new GrooveRidgePainter(Value.GROOVE));
+ registerBorderPainter(Value.RIDGE, new GrooveRidgePainter(Value.RIDGE));
+ registerBorderPainter(Value.INSET, new InsetOutsetPainter(Value.INSET));
+ registerBorderPainter(Value.OUTSET, new InsetOutsetPainter(Value.OUTSET));
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/CSSParser.java b/src/share/classes/javax/swing/text/html/CSSParser.java
new file mode 100644
index 000000000..00251a740
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/CSSParser.java
@@ -0,0 +1,854 @@
+/*
+ * Copyright 1999-2000 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.io.*;
+
+/**
+ * A CSS parser. This works by way of a delegate that implements the
+ * CSSParserCallback interface. The delegate is notified of the following
+ * events:
+ * <ul>
+ * <li>Import statement: <code>handleImport</code>
+ * <li>Selectors <code>handleSelector</code>. This is invoked for each
+ * string. For example if the Reader contained p, bar , a {}, the delegate
+ * would be notified 4 times, for 'p,' 'bar' ',' and 'a'.
+ * <li>When a rule starts, <code>startRule</code>
+ * <li>Properties in the rule via the <code>handleProperty</code>. This
+ * is invoked one per property/value key, eg font size: foo;, would
+ * cause the delegate to be notified once with a value of 'font size'.
+ * <li>Values in the rule via the <code>handleValue</code>, this is notified
+ * for the total value.
+ * <li>When a rule ends, <code>endRule</code>
+ * </ul>
+ * This will parse much more than CSS 1, and loosely implements the
+ * recommendation for <i>Forward-compatible parsing</i> in section
+ * 7.1 of the CSS spec found at:
+ * <a href=http://www.w3.org/TR/REC-CSS1>http://www.w3.org/TR/REC-CSS1</a>.
+ * If an error results in parsing, a RuntimeException will be thrown.
+ * <p>
+ * This will preserve case. If the callback wishes to treat certain poritions
+ * case insensitively (such as selectors), it should use toLowerCase, or
+ * something similar.
+ *
+ * @author Scott Violet
+ */
+class CSSParser {
+ // Parsing something like the following:
+ // (@rule | ruleset | block)*
+ //
+ // @rule (block | identifier)*; (block with {} ends @rule)
+ // block matching [] () {} (that is, [()] is a block, [(){}{[]}]
+ // is a block, ()[] is two blocks)
+ // identifier "*" | '*' | anything but a [](){} and whitespace
+ //
+ // ruleset selector decblock
+ // selector (identifier | (block, except block '{}') )*
+ // declblock declaration* block*
+ // declaration (identifier* stopping when identifier ends with :)
+ // (identifier* stopping when identifier ends with ;)
+ //
+ // comments /* */ can appear any where, and are stripped.
+
+
+ // identifier - letters, digits, dashes and escaped characters
+ // block starts with { ends with matching }, () [] and {} always occur
+ // in matching pairs, '' and "" also occur in pairs, except " may be
+
+
+ // Indicates the type of token being parsed.
+ private static final int IDENTIFIER = 1;
+ private static final int BRACKET_OPEN = 2;
+ private static final int BRACKET_CLOSE = 3;
+ private static final int BRACE_OPEN = 4;
+ private static final int BRACE_CLOSE = 5;
+ private static final int PAREN_OPEN = 6;
+ private static final int PAREN_CLOSE = 7;
+ private static final int END = -1;
+
+ private static final char[] charMapping = { 0, 0, '[', ']', '{', '}', '(',
+ ')', 0};
+
+
+ /** Set to true if one character has been read ahead. */
+ private boolean didPushChar;
+ /** The read ahead character. */
+ private int pushedChar;
+ /** Temporary place to hold identifiers. */
+ private StringBuffer unitBuffer;
+ /** Used to indicate blocks. */
+ private int[] unitStack;
+ /** Number of valid blocks. */
+ private int stackCount;
+ /** Holds the incoming CSS rules. */
+ private Reader reader;
+ /** Set to true when the first non @ rule is encountered. */
+ private boolean encounteredRuleSet;
+ /** Notified of state. */
+ private CSSParserCallback callback;
+ /** nextToken() inserts the string here. */
+ private char[] tokenBuffer;
+ /** Current number of chars in tokenBufferLength. */
+ private int tokenBufferLength;
+ /** Set to true if any whitespace is read. */
+ private boolean readWS;
+
+
+ // The delegate interface.
+ static interface CSSParserCallback {
+ /** Called when an @import is encountered. */
+ void handleImport(String importString);
+ // There is currently no way to distinguish between '"foo,"' and
+ // 'foo,'. But this generally isn't valid CSS. If it becomes
+ // a problem, handleSelector will have to be told if the string is
+ // quoted.
+ void handleSelector(String selector);
+ void startRule();
+ // Property names are mapped to lower case before being passed to
+ // the delegate.
+ void handleProperty(String property);
+ void handleValue(String value);
+ void endRule();
+ }
+
+ CSSParser() {
+ unitStack = new int[2];
+ tokenBuffer = new char[80];
+ unitBuffer = new StringBuffer();
+ }
+
+ void parse(Reader reader, CSSParserCallback callback,
+ boolean inRule) throws IOException {
+ this.callback = callback;
+ stackCount = tokenBufferLength = 0;
+ this.reader = reader;
+ encounteredRuleSet = false;
+ try {
+ if (inRule) {
+ parseDeclarationBlock();
+ }
+ else {
+ while (getNextStatement());
+ }
+ } finally {
+ callback = null;
+ reader = null;
+ }
+ }
+
+ /**
+ * Gets the next statement, returning false if the end is reached. A
+ * statement is either an @rule, or a ruleset.
+ */
+ private boolean getNextStatement() throws IOException {
+ unitBuffer.setLength(0);
+
+ int token = nextToken((char)0);
+
+ switch (token) {
+ case IDENTIFIER:
+ if (tokenBufferLength > 0) {
+ if (tokenBuffer[0] == '@') {
+ parseAtRule();
+ }
+ else {
+ encounteredRuleSet = true;
+ parseRuleSet();
+ }
+ }
+ return true;
+ case BRACKET_OPEN:
+ case BRACE_OPEN:
+ case PAREN_OPEN:
+ parseTillClosed(token);
+ return true;
+
+ case BRACKET_CLOSE:
+ case BRACE_CLOSE:
+ case PAREN_CLOSE:
+ // Shouldn't happen...
+ throw new RuntimeException("Unexpected top level block close");
+
+ case END:
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Parses an @ rule, stopping at a matching brace pair, or ;.
+ */
+ private void parseAtRule() throws IOException {
+ // PENDING: make this more effecient.
+ boolean done = false;
+ boolean isImport = (tokenBufferLength == 7 &&
+ tokenBuffer[0] == '@' && tokenBuffer[1] == 'i' &&
+ tokenBuffer[2] == 'm' && tokenBuffer[3] == 'p' &&
+ tokenBuffer[4] == 'o' && tokenBuffer[5] == 'r' &&
+ tokenBuffer[6] == 't');
+
+ unitBuffer.setLength(0);
+ while (!done) {
+ int nextToken = nextToken(';');
+
+ switch (nextToken) {
+ case IDENTIFIER:
+ if (tokenBufferLength > 0 &&
+ tokenBuffer[tokenBufferLength - 1] == ';') {
+ --tokenBufferLength;
+ done = true;
+ }
+ if (tokenBufferLength > 0) {
+ if (unitBuffer.length() > 0 && readWS) {
+ unitBuffer.append(' ');
+ }
+ unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
+ }
+ break;
+
+ case BRACE_OPEN:
+ if (unitBuffer.length() > 0 && readWS) {
+ unitBuffer.append(' ');
+ }
+ unitBuffer.append(charMapping[nextToken]);
+ parseTillClosed(nextToken);
+ done = true;
+ // Skip a tailing ';', not really to spec.
+ {
+ int nextChar = readWS();
+ if (nextChar != -1 && nextChar != ';') {
+ pushChar(nextChar);
+ }
+ }
+ break;
+
+ case BRACKET_OPEN: case PAREN_OPEN:
+ unitBuffer.append(charMapping[nextToken]);
+ parseTillClosed(nextToken);
+ break;
+
+ case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:
+ throw new RuntimeException("Unexpected close in @ rule");
+
+ case END:
+ done = true;
+ break;
+ }
+ }
+ if (isImport && !encounteredRuleSet) {
+ callback.handleImport(unitBuffer.toString());
+ }
+ }
+
+ /**
+ * Parses the next rule set, which is a selector followed by a
+ * declaration block.
+ */
+ private void parseRuleSet() throws IOException {
+ if (parseSelectors()) {
+ callback.startRule();
+ parseDeclarationBlock();
+ callback.endRule();
+ }
+ }
+
+ /**
+ * Parses a set of selectors, returning false if the end of the stream
+ * is reached.
+ */
+ private boolean parseSelectors() throws IOException {
+ // Parse the selectors
+ int nextToken;
+
+ if (tokenBufferLength > 0) {
+ callback.handleSelector(new String(tokenBuffer, 0,
+ tokenBufferLength));
+ }
+
+ unitBuffer.setLength(0);
+ for (;;) {
+ while ((nextToken = nextToken((char)0)) == IDENTIFIER) {
+ if (tokenBufferLength > 0) {
+ callback.handleSelector(new String(tokenBuffer, 0,
+ tokenBufferLength));
+ }
+ }
+ switch (nextToken) {
+ case BRACE_OPEN:
+ return true;
+
+ case BRACKET_OPEN: case PAREN_OPEN:
+ parseTillClosed(nextToken);
+ // Not too sure about this, how we handle this isn't very
+ // well spec'd.
+ unitBuffer.setLength(0);
+ break;
+
+ case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:
+ throw new RuntimeException("Unexpected block close in selector");
+
+ case END:
+ // Prematurely hit end.
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Parses a declaration block. Which a number of declarations followed
+ * by a })].
+ */
+ private void parseDeclarationBlock() throws IOException {
+ for (;;) {
+ int token = parseDeclaration();
+ switch (token) {
+ case END: case BRACE_CLOSE:
+ return;
+
+ case BRACKET_CLOSE: case PAREN_CLOSE:
+ // Bail
+ throw new RuntimeException("Unexpected close in declaration block");
+ case IDENTIFIER:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Parses a single declaration, which is an identifier a : and another
+ * identifier. This returns the last token seen.
+ */
+ // identifier+: identifier* ;|}
+ private int parseDeclaration() throws IOException {
+ int token;
+
+ if ((token = parseIdentifiers(':', false)) != IDENTIFIER) {
+ return token;
+ }
+ // Make the property name to lowercase
+ for (int counter = unitBuffer.length() - 1; counter >= 0; counter--) {
+ unitBuffer.setCharAt(counter, Character.toLowerCase
+ (unitBuffer.charAt(counter)));
+ }
+ callback.handleProperty(unitBuffer.toString());
+
+ token = parseIdentifiers(';', true);
+ callback.handleValue(unitBuffer.toString());
+ return token;
+ }
+
+ /**
+ * Parses identifiers until <code>extraChar</code> is encountered,
+ * returning the ending token, which will be IDENTIFIER if extraChar
+ * is found.
+ */
+ private int parseIdentifiers(char extraChar,
+ boolean wantsBlocks) throws IOException {
+ int nextToken;
+ int ubl;
+
+ unitBuffer.setLength(0);
+ for (;;) {
+ nextToken = nextToken(extraChar);
+
+ switch (nextToken) {
+ case IDENTIFIER:
+ if (tokenBufferLength > 0) {
+ if (tokenBuffer[tokenBufferLength - 1] == extraChar) {
+ if (--tokenBufferLength > 0) {
+ if (readWS && unitBuffer.length() > 0) {
+ unitBuffer.append(' ');
+ }
+ unitBuffer.append(tokenBuffer, 0,
+ tokenBufferLength);
+ }
+ return IDENTIFIER;
+ }
+ if (readWS && unitBuffer.length() > 0) {
+ unitBuffer.append(' ');
+ }
+ unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
+ }
+ break;
+
+ case BRACKET_OPEN:
+ case BRACE_OPEN:
+ case PAREN_OPEN:
+ ubl = unitBuffer.length();
+ if (wantsBlocks) {
+ unitBuffer.append(charMapping[nextToken]);
+ }
+ parseTillClosed(nextToken);
+ if (!wantsBlocks) {
+ unitBuffer.setLength(ubl);
+ }
+ break;
+
+ case BRACE_CLOSE:
+ // No need to throw for these two, we return token and
+ // caller can do whatever.
+ case BRACKET_CLOSE:
+ case PAREN_CLOSE:
+ case END:
+ // Hit the end
+ return nextToken;
+ }
+ }
+ }
+
+ /**
+ * Parses till a matching block close is encountered. This is only
+ * appropriate to be called at the top level (no nesting).
+ */
+ private void parseTillClosed(int openToken) throws IOException {
+ int nextToken;
+ boolean done = false;
+
+ startBlock(openToken);
+ while (!done) {
+ nextToken = nextToken((char)0);
+ switch (nextToken) {
+ case IDENTIFIER:
+ if (unitBuffer.length() > 0 && readWS) {
+ unitBuffer.append(' ');
+ }
+ if (tokenBufferLength > 0) {
+ unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
+ }
+ break;
+
+ case BRACKET_OPEN: case BRACE_OPEN: case PAREN_OPEN:
+ if (unitBuffer.length() > 0 && readWS) {
+ unitBuffer.append(' ');
+ }
+ unitBuffer.append(charMapping[nextToken]);
+ startBlock(nextToken);
+ break;
+
+ case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:
+ if (unitBuffer.length() > 0 && readWS) {
+ unitBuffer.append(' ');
+ }
+ unitBuffer.append(charMapping[nextToken]);
+ endBlock(nextToken);
+ if (!inBlock()) {
+ done = true;
+ }
+ break;
+
+ case END:
+ // Prematurely hit end.
+ throw new RuntimeException("Unclosed block");
+ }
+ }
+ }
+
+ /**
+ * Fetches the next token.
+ */
+ private int nextToken(char idChar) throws IOException {
+ readWS = false;
+
+ int nextChar = readWS();
+
+ switch (nextChar) {
+ case '\'':
+ readTill('\'');
+ if (tokenBufferLength > 0) {
+ tokenBufferLength--;
+ }
+ return IDENTIFIER;
+ case '"':
+ readTill('"');
+ if (tokenBufferLength > 0) {
+ tokenBufferLength--;
+ }
+ return IDENTIFIER;
+ case '[':
+ return BRACKET_OPEN;
+ case ']':
+ return BRACKET_CLOSE;
+ case '{':
+ return BRACE_OPEN;
+ case '}':
+ return BRACE_CLOSE;
+ case '(':
+ return PAREN_OPEN;
+ case ')':
+ return PAREN_CLOSE;
+ case -1:
+ return END;
+ default:
+ pushChar(nextChar);
+ getIdentifier(idChar);
+ return IDENTIFIER;
+ }
+ }
+
+ /**
+ * Gets an identifier, returning true if the length of the string is greater than 0,
+ * stopping when <code>stopChar</code>, whitespace, or one of {}()[] is
+ * hit.
+ */
+ // NOTE: this could be combined with readTill, as they contain somewhat
+ // similiar functionality.
+ private boolean getIdentifier(char stopChar) throws IOException {
+ boolean lastWasEscape = false;
+ boolean done = false;
+ int escapeCount = 0;
+ int escapeChar = 0;
+ int nextChar;
+ int intStopChar = (int)stopChar;
+ // 1 for '\', 2 for valid escape char [0-9a-fA-F], 3 for
+ // stop character (white space, ()[]{}) 0 otherwise
+ short type;
+ int escapeOffset = 0;
+
+ tokenBufferLength = 0;
+ while (!done) {
+ nextChar = readChar();
+ switch (nextChar) {
+ case '\\':
+ type = 1;
+ break;
+
+ case '0': case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9':
+ type = 2;
+ escapeOffset = nextChar - '0';
+ break;
+
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ type = 2;
+ escapeOffset = nextChar - 'a' + 10;
+ break;
+
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ type = 2;
+ escapeOffset = nextChar - 'A' + 10;
+ break;
+
+ case '\'': case '"': case '[': case ']': case '{': case '}':
+ case '(': case ')':
+ case ' ': case '\n': case '\t': case '\r':
+ type = 3;
+ break;
+
+ case '/':
+ type = 4;
+ break;
+
+ case -1:
+ // Reached the end
+ done = true;
+ type = 0;
+ break;
+
+ default:
+ type = 0;
+ break;
+ }
+ if (lastWasEscape) {
+ if (type == 2) {
+ // Continue with escape.
+ escapeChar = escapeChar * 16 + escapeOffset;
+ if (++escapeCount == 4) {
+ lastWasEscape = false;
+ append((char)escapeChar);
+ }
+ }
+ else {
+ // no longer escaped
+ lastWasEscape = false;
+ if (escapeCount > 0) {
+ append((char)escapeChar);
+ // Make this simpler, reprocess the character.
+ pushChar(nextChar);
+ }
+ else if (!done) {
+ append((char)nextChar);
+ }
+ }
+ }
+ else if (!done) {
+ if (type == 1) {
+ lastWasEscape = true;
+ escapeChar = escapeCount = 0;
+ }
+ else if (type == 3) {
+ done = true;
+ pushChar(nextChar);
+ }
+ else if (type == 4) {
+ // Potential comment
+ nextChar = readChar();
+ if (nextChar == '*') {
+ done = true;
+ readComment();
+ readWS = true;
+ }
+ else {
+ append('/');
+ if (nextChar == -1) {
+ done = true;
+ }
+ else {
+ pushChar(nextChar);
+ }
+ }
+ }
+ else {
+ append((char)nextChar);
+ if (nextChar == intStopChar) {
+ done = true;
+ }
+ }
+ }
+ }
+ return (tokenBufferLength > 0);
+ }
+
+ /**
+ * Reads till a <code>stopChar</code> is encountered, escaping characters
+ * as necessary.
+ */
+ private void readTill(char stopChar) throws IOException {
+ boolean lastWasEscape = false;
+ int escapeCount = 0;
+ int escapeChar = 0;
+ int nextChar;
+ boolean done = false;
+ int intStopChar = (int)stopChar;
+ // 1 for '\', 2 for valid escape char [0-9a-fA-F], 0 otherwise
+ short type;
+ int escapeOffset = 0;
+
+ tokenBufferLength = 0;
+ while (!done) {
+ nextChar = readChar();
+ switch (nextChar) {
+ case '\\':
+ type = 1;
+ break;
+
+ case '0': case '1': case '2': case '3': case '4':case '5':
+ case '6': case '7': case '8': case '9':
+ type = 2;
+ escapeOffset = nextChar - '0';
+ break;
+
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ type = 2;
+ escapeOffset = nextChar - 'a' + 10;
+ break;
+
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ type = 2;
+ escapeOffset = nextChar - 'A' + 10;
+ break;
+
+ case -1:
+ // Prematurely reached the end!
+ throw new RuntimeException("Unclosed " + stopChar);
+
+ default:
+ type = 0;
+ break;
+ }
+ if (lastWasEscape) {
+ if (type == 2) {
+ // Continue with escape.
+ escapeChar = escapeChar * 16 + escapeOffset;
+ if (++escapeCount == 4) {
+ lastWasEscape = false;
+ append((char)escapeChar);
+ }
+ }
+ else {
+ // no longer escaped
+ if (escapeCount > 0) {
+ append((char)escapeChar);
+ if (type == 1) {
+ lastWasEscape = true;
+ escapeChar = escapeCount = 0;
+ }
+ else {
+ if (nextChar == intStopChar) {
+ done = true;
+ }
+ append((char)nextChar);
+ lastWasEscape = false;
+ }
+ }
+ else {
+ append((char)nextChar);
+ lastWasEscape = false;
+ }
+ }
+ }
+ else if (type == 1) {
+ lastWasEscape = true;
+ escapeChar = escapeCount = 0;
+ }
+ else {
+ if (nextChar == intStopChar) {
+ done = true;
+ }
+ append((char)nextChar);
+ }
+ }
+ }
+
+ private void append(char character) {
+ if (tokenBufferLength == tokenBuffer.length) {
+ char[] newBuffer = new char[tokenBuffer.length * 2];
+ System.arraycopy(tokenBuffer, 0, newBuffer, 0, tokenBuffer.length);
+ tokenBuffer = newBuffer;
+ }
+ tokenBuffer[tokenBufferLength++] = character;
+ }
+
+ /**
+ * Parses a comment block.
+ */
+ private void readComment() throws IOException {
+ int nextChar;
+
+ for(;;) {
+ nextChar = readChar();
+ switch (nextChar) {
+ case -1:
+ throw new RuntimeException("Unclosed comment");
+ case '*':
+ nextChar = readChar();
+ if (nextChar == '/') {
+ return;
+ }
+ else if (nextChar == -1) {
+ throw new RuntimeException("Unclosed comment");
+ }
+ else {
+ pushChar(nextChar);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Called when a block start is encountered ({[.
+ */
+ private void startBlock(int startToken) {
+ if (stackCount == unitStack.length) {
+ int[] newUS = new int[stackCount * 2];
+
+ System.arraycopy(unitStack, 0, newUS, 0, stackCount);
+ unitStack = newUS;
+ }
+ unitStack[stackCount++] = startToken;
+ }
+
+ /**
+ * Called when an end block is encountered )]}
+ */
+ private void endBlock(int endToken) {
+ int startToken;
+
+ switch (endToken) {
+ case BRACKET_CLOSE:
+ startToken = BRACKET_OPEN;
+ break;
+ case BRACE_CLOSE:
+ startToken = BRACE_OPEN;
+ break;
+ case PAREN_CLOSE:
+ startToken = PAREN_OPEN;
+ break;
+ default:
+ // Will never happen.
+ startToken = -1;
+ break;
+ }
+ if (stackCount > 0 && unitStack[stackCount - 1] == startToken) {
+ stackCount--;
+ }
+ else {
+ // Invalid state, should do something.
+ throw new RuntimeException("Unmatched block");
+ }
+ }
+
+ /**
+ * @return true if currently in a block.
+ */
+ private boolean inBlock() {
+ return (stackCount > 0);
+ }
+
+ /**
+ * Skips any white space, returning the character after the white space.
+ */
+ private int readWS() throws IOException {
+ int nextChar;
+ while ((nextChar = readChar()) != -1 &&
+ Character.isWhitespace((char)nextChar)) {
+ readWS = true;
+ }
+ return nextChar;
+ }
+
+ /**
+ * Reads a character from the stream.
+ */
+ private int readChar() throws IOException {
+ if (didPushChar) {
+ didPushChar = false;
+ return pushedChar;
+ }
+ return reader.read();
+ // Uncomment the following to do case insensitive parsing.
+ /*
+ if (retValue != -1) {
+ return (int)Character.toLowerCase((char)retValue);
+ }
+ return retValue;
+ */
+ }
+
+ /**
+ * Supports one character look ahead, this will throw if called twice
+ * in a row.
+ */
+ private void pushChar(int tempChar) {
+ if (didPushChar) {
+ // Should never happen.
+ throw new RuntimeException("Can not handle look ahead of more than one character");
+ }
+ didPushChar = true;
+ pushedChar = tempChar;
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/CommentView.java b/src/share/classes/javax/swing/text/html/CommentView.java
new file mode 100644
index 000000000..54e1c1c6b
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/CommentView.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 1998-2004 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import javax.swing.text.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.event.*;
+import java.util.*;
+
+/**
+ * CommentView subclasses HiddenTagView to contain a JTextArea showing
+ * a comment. When the textarea is edited the comment is
+ * reset. As this inherits from EditableView if the JTextComponent is
+ * not editable, the textarea will not be visible.
+ *
+ * @author Scott Violet
+ */
+class CommentView extends HiddenTagView {
+ CommentView(Element e) {
+ super(e);
+ }
+
+ protected Component createComponent() {
+ Container host = getContainer();
+ if (host != null && !((JTextComponent)host).isEditable()) {
+ return null;
+ }
+ JTextArea ta = new JTextArea(getRepresentedText());
+ Document doc = getDocument();
+ Font font;
+ if (doc instanceof StyledDocument) {
+ font = ((StyledDocument)doc).getFont(getAttributes());
+ ta.setFont(font);
+ }
+ else {
+ font = ta.getFont();
+ }
+ updateYAlign(font);
+ ta.setBorder(CBorder);
+ ta.getDocument().addDocumentListener(this);
+ ta.setFocusable(isVisible());
+ return ta;
+ }
+
+ void resetBorder() {
+ }
+
+ /**
+ * This is subclassed to put the text on the Comment attribute of
+ * the Element's AttributeSet.
+ */
+ void _updateModelFromText() {
+ JTextComponent textC = getTextComponent();
+ Document doc = getDocument();
+ if (textC != null && doc != null) {
+ String text = textC.getText();
+ SimpleAttributeSet sas = new SimpleAttributeSet();
+ isSettingAttributes = true;
+ try {
+ sas.addAttribute(HTML.Attribute.COMMENT, text);
+ ((StyledDocument)doc).setCharacterAttributes
+ (getStartOffset(), getEndOffset() -
+ getStartOffset(), sas, false);
+ }
+ finally {
+ isSettingAttributes = false;
+ }
+ }
+ }
+
+ JTextComponent getTextComponent() {
+ return (JTextComponent)getComponent();
+ }
+
+ String getRepresentedText() {
+ AttributeSet as = getElement().getAttributes();
+ if (as != null) {
+ Object comment = as.getAttribute(HTML.Attribute.COMMENT);
+ if (comment instanceof String) {
+ return (String)comment;
+ }
+ }
+ return "";
+ }
+
+ static final Border CBorder = new CommentBorder();
+ static final int commentPadding = 3;
+ static final int commentPaddingD = commentPadding * 3;
+
+ static class CommentBorder extends LineBorder {
+ CommentBorder() {
+ super(Color.black, 1);
+ }
+
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ super.paintBorder(c, g, x + commentPadding, y,
+ width - commentPaddingD, height);
+ }
+
+ public Insets getBorderInsets(Component c, Insets insets) {
+ Insets retI = super.getBorderInsets(c, insets);
+
+ retI.left += commentPadding;
+ retI.right += commentPadding;
+ return retI;
+ }
+
+ public boolean isBorderOpaque() {
+ return false;
+ }
+ } // End of class CommentView.CommentBorder
+} // End of CommentView
diff --git a/src/share/classes/javax/swing/text/html/EditableView.java b/src/share/classes/javax/swing/text/html/EditableView.java
new file mode 100644
index 000000000..b1958bafa
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/EditableView.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 1998-2004 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import javax.swing.text.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.event.*;
+import java.util.*;
+
+/**
+ * EditableView sets the view it contains to be visible only when the
+ * JTextComponent the view is contained in is editable. The min/pref/max
+ * size is 0 when not visible.
+ *
+ * @author Scott Violet
+ */
+class EditableView extends ComponentView {
+
+ EditableView(Element e) {
+ super(e);
+ }
+
+ public float getMinimumSpan(int axis) {
+ if (isVisible) {
+ return super.getMinimumSpan(axis);
+ }
+ return 0;
+ }
+
+ public float getPreferredSpan(int axis) {
+ if (isVisible) {
+ return super.getPreferredSpan(axis);
+ }
+ return 0;
+ }
+
+ public float getMaximumSpan(int axis) {
+ if (isVisible) {
+ return super.getMaximumSpan(axis);
+ }
+ return 0;
+ }
+
+ public void paint(Graphics g, Shape allocation) {
+ Component c = getComponent();
+ Container host = getContainer();
+
+ if (host != null &&
+ isVisible != ((JTextComponent)host).isEditable()) {
+ isVisible = ((JTextComponent)host).isEditable();
+ preferenceChanged(null, true, true);
+ host.repaint();
+ }
+ /*
+ * Note: we cannot tweak the visible state of the
+ * component in createComponent() even though it
+ * gets called after the setParent() call where
+ * the value of the boolean is set. This
+ * because, the setComponentParent() in the
+ * superclass, always does a setVisible(false)
+ * after calling createComponent(). We therefore
+ * use this flag in the paint() method to
+ * setVisible() to true if required.
+ */
+ if (isVisible) {
+ super.paint(g, allocation);
+ }
+ else {
+ setSize(0, 0);
+ }
+ if (c != null) {
+ c.setFocusable(isVisible);
+ }
+ }
+
+ public void setParent(View parent) {
+ if (parent != null) {
+ Container host = parent.getContainer();
+ if (host != null) {
+ if (host instanceof JTextComponent) {
+ isVisible = ((JTextComponent)host).isEditable();
+ } else {
+ isVisible = false;
+ }
+ }
+ }
+ super.setParent(parent);
+ }
+
+ /**
+ * @return true if the Component is visible.
+ */
+ public boolean isVisible() {
+ return isVisible;
+ }
+
+ /** Set to true if the component is visible. This is based off the
+ * editability of the container. */
+ private boolean isVisible;
+} // End of EditableView
diff --git a/src/share/classes/javax/swing/text/html/FormSubmitEvent.java b/src/share/classes/javax/swing/text/html/FormSubmitEvent.java
new file mode 100644
index 000000000..bc0925207
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/FormSubmitEvent.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import javax.swing.text.*;
+import java.net.URL;
+
+/**
+ * FormSubmitEvent is used to notify interested
+ * parties that a form was submited.
+ *
+ * @since 1.5
+ * @author Denis Sharypov
+ */
+
+public class FormSubmitEvent extends HTMLFrameHyperlinkEvent {
+
+ /**
+ * Represents an HTML form method type.
+ * <UL>
+ * <LI><code>GET</code> corresponds to the GET form method</LI>
+ * <LI><code>POST</code> corresponds to the POST from method</LI>
+ * </UL>
+ * @since 1.5
+ */
+ public enum MethodType { GET, POST };
+
+ /**
+ * Creates a new object representing an html form submit event.
+ *
+ * @param source the object responsible for the event
+ * @param type the event type
+ * @param actionURL the form action URL
+ * @param sourceElement the element that corresponds to the source
+ * of the event
+ * @param targetFrame the Frame to display the document in
+ * @param method the form method type
+ * @param data the form submission data
+ */
+ FormSubmitEvent(Object source, EventType type, URL targetURL,
+ Element sourceElement, String targetFrame,
+ MethodType method, String data) {
+ super(source, type, targetURL, sourceElement, targetFrame);
+ this.method = method;
+ this.data = data;
+ }
+
+
+ /**
+ * Gets the form method type.
+ *
+ * @return the form method type, either
+ * <code>Method.GET</code> or <code>Method.POST</code>.
+ */
+ public MethodType getMethod() {
+ return method;
+ }
+
+ /**
+ * Gets the form submission data.
+ *
+ * @return the string representing the form submission data.
+ */
+ public String getData() {
+ return data;
+ }
+
+ private MethodType method;
+ private String data;
+}
diff --git a/src/share/classes/javax/swing/text/html/FormView.java b/src/share/classes/javax/swing/text/html/FormView.java
new file mode 100644
index 000000000..d2a6d081f
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/FormView.java
@@ -0,0 +1,932 @@
+/*
+ * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.net.*;
+import java.io.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.*;
+
+/**
+ * Component decorator that implements the view interface
+ * for form elements, &lt;input&gt;, &lt;textarea&gt;,
+ * and &lt;select&gt;. The model for the component is stored
+ * as an attribute of the the element (using StyleConstants.ModelAttribute),
+ * and is used to build the component of the view. The type
+ * of the model is assumed to of the type that would be set by
+ * <code>HTMLDocument.HTMLReader.FormAction</code>. If there are
+ * multiple views mapped over the document, they will share the
+ * embedded component models.
+ * <p>
+ * The following table shows what components get built
+ * by this view.
+ * <table summary="shows what components get built by this view">
+ * <tr>
+ * <th>Element Type</th>
+ * <th>Component built</th>
+ * </tr>
+ * <tr>
+ * <td>input, type button</td>
+ * <td>JButton</td>
+ * </tr>
+ * <tr>
+ * <td>input, type checkbox</td>
+ * <td>JCheckBox</td>
+ * </tr>
+ * <tr>
+ * <td>input, type image</td>
+ * <td>JButton</td>
+ * </tr>
+ * <tr>
+ * <td>input, type password</td>
+ * <td>JPasswordField</td>
+ * </tr>
+ * <tr>
+ * <td>input, type radio</td>
+ * <td>JRadioButton</td>
+ * </tr>
+ * <tr>
+ * <td>input, type reset</td>
+ * <td>JButton</td>
+ * </tr>
+ * <tr>
+ * <td>input, type submit</td>
+ * <td>JButton</td>
+ * </tr>
+ * <tr>
+ * <td>input, type text</td>
+ * <td>JTextField</td>
+ * </tr>
+ * <tr>
+ * <td>select, size &gt; 1 or multiple attribute defined</td>
+ * <td>JList in a JScrollPane</td>
+ * </tr>
+ * <tr>
+ * <td>select, size unspecified or 1</td>
+ * <td>JComboBox</td>
+ * </tr>
+ * <tr>
+ * <td>textarea</td>
+ * <td>JTextArea in a JScrollPane</td>
+ * </tr>
+ * <tr>
+ * <td>input, type file</td>
+ * <td>JTextField</td>
+ * </tr>
+ * </table>
+ *
+ * @author Timothy Prinzing
+ * @author Sunita Mani
+ */
+public class FormView extends ComponentView implements ActionListener {
+
+ /**
+ * If a value attribute is not specified for a FORM input element
+ * of type "submit", then this default string is used.
+ *
+ * @deprecated As of 1.3, value now comes from UIManager property
+ * FormView.submitButtonText
+ */
+ @Deprecated
+ public static final String SUBMIT = new String("Submit Query");
+ /**
+ * If a value attribute is not specified for a FORM input element
+ * of type "reset", then this default string is used.
+ *
+ * @deprecated As of 1.3, value comes from UIManager UIManager property
+ * FormView.resetButtonText
+ */
+ @Deprecated
+ public static final String RESET = new String("Reset");
+
+ /**
+ * Document attribute name for storing POST data. JEditorPane.getPostData()
+ * uses the same name, should be kept in sync.
+ */
+ final static String PostDataProperty = "javax.swing.JEditorPane.postdata";
+
+ /**
+ * Used to indicate if the maximum span should be the same as the
+ * preferred span. This is used so that the Component's size doesn't
+ * change if there is extra room on a line. The first bit is used for
+ * the X direction, and the second for the y direction.
+ */
+ private short maxIsPreferred;
+
+ /**
+ * Creates a new FormView object.
+ *
+ * @param elem the element to decorate
+ */
+ public FormView(Element elem) {
+ super(elem);
+ }
+
+ /**
+ * Create the component. This is basically a
+ * big switch statement based upon the tag type
+ * and html attributes of the associated element.
+ */
+ protected Component createComponent() {
+ AttributeSet attr = getElement().getAttributes();
+ HTML.Tag t = (HTML.Tag)
+ attr.getAttribute(StyleConstants.NameAttribute);
+ JComponent c = null;
+ Object model = attr.getAttribute(StyleConstants.ModelAttribute);
+ if (t == HTML.Tag.INPUT) {
+ c = createInputComponent(attr, model);
+ } else if (t == HTML.Tag.SELECT) {
+
+ if (model instanceof OptionListModel) {
+
+ JList list = new JList((ListModel) model);
+ int size = HTML.getIntegerAttributeValue(attr,
+ HTML.Attribute.SIZE,
+ 1);
+ list.setVisibleRowCount(size);
+ list.setSelectionModel((ListSelectionModel)model);
+ c = new JScrollPane(list);
+ } else {
+ c = new JComboBox((ComboBoxModel) model);
+ maxIsPreferred = 3;
+ }
+ } else if (t == HTML.Tag.TEXTAREA) {
+ JTextArea area = new JTextArea((Document) model);
+ int rows = HTML.getIntegerAttributeValue(attr,
+ HTML.Attribute.ROWS,
+ 1);
+ area.setRows(rows);
+ int cols = HTML.getIntegerAttributeValue(attr,
+ HTML.Attribute.COLS,
+ 20);
+ maxIsPreferred = 3;
+ area.setColumns(cols);
+ c = new JScrollPane(area,
+ JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
+ JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+
+ if (c != null) {
+ c.setAlignmentY(1.0f);
+ }
+ return c;
+ }
+
+
+ /**
+ * Creates a component for an &lt;INPUT&gt; element based on the
+ * value of the "type" attribute.
+ *
+ * @param set of attributes associated with the &lt;INPUT&gt; element.
+ * @param model the value of the StyleConstants.ModelAttribute
+ * @return the component.
+ */
+ private JComponent createInputComponent(AttributeSet attr, Object model) {
+ JComponent c = null;
+ String type = (String) attr.getAttribute(HTML.Attribute.TYPE);
+
+ if (type.equals("submit") || type.equals("reset")) {
+ String value = (String)
+ attr.getAttribute(HTML.Attribute.VALUE);
+ if (value == null) {
+ if (type.equals("submit")) {
+ value = UIManager.getString("FormView.submitButtonText");
+ } else {
+ value = UIManager.getString("FormView.resetButtonText");
+ }
+ }
+ JButton button = new JButton(value);
+ if (model != null) {
+ button.setModel((ButtonModel)model);
+ button.addActionListener(this);
+ }
+ c = button;
+ maxIsPreferred = 3;
+ } else if (type.equals("image")) {
+ String srcAtt = (String) attr.getAttribute(HTML.Attribute.SRC);
+ JButton button;
+ try {
+ URL base = ((HTMLDocument)getElement().getDocument()).getBase();
+ URL srcURL = new URL(base, srcAtt);
+ Icon icon = new ImageIcon(srcURL);
+ button = new JButton(icon);
+ } catch (MalformedURLException e) {
+ button = new JButton(srcAtt);
+ }
+ if (model != null) {
+ button.setModel((ButtonModel)model);
+ button.addMouseListener(new MouseEventListener());
+ }
+ c = button;
+ maxIsPreferred = 3;
+ } else if (type.equals("checkbox")) {
+ c = new JCheckBox();
+ if (model != null) {
+ ((JCheckBox)c).setModel((JToggleButton.ToggleButtonModel) model);
+ }
+ maxIsPreferred = 3;
+ } else if (type.equals("radio")) {
+ c = new JRadioButton();
+ if (model != null) {
+ ((JRadioButton)c).setModel((JToggleButton.ToggleButtonModel)model);
+ }
+ maxIsPreferred = 3;
+ } else if (type.equals("text")) {
+ int size = HTML.getIntegerAttributeValue(attr,
+ HTML.Attribute.SIZE,
+ -1);
+ JTextField field;
+ if (size > 0) {
+ field = new JTextField();
+ field.setColumns(size);
+ }
+ else {
+ field = new JTextField();
+ field.setColumns(20);
+ }
+ c = field;
+ if (model != null) {
+ field.setDocument((Document) model);
+ }
+ field.addActionListener(this);
+ maxIsPreferred = 3;
+ } else if (type.equals("password")) {
+ JPasswordField field = new JPasswordField();
+ c = field;
+ if (model != null) {
+ field.setDocument((Document) model);
+ }
+ int size = HTML.getIntegerAttributeValue(attr,
+ HTML.Attribute.SIZE,
+ -1);
+ field.setColumns((size > 0) ? size : 20);
+ field.addActionListener(this);
+ maxIsPreferred = 3;
+ } else if (type.equals("file")) {
+ JTextField field = new JTextField();
+ if (model != null) {
+ field.setDocument((Document)model);
+ }
+ int size = HTML.getIntegerAttributeValue(attr, HTML.Attribute.SIZE,
+ -1);
+ field.setColumns((size > 0) ? size : 20);
+ JButton browseButton = new JButton(UIManager.getString
+ ("FormView.browseFileButtonText"));
+ Box box = Box.createHorizontalBox();
+ box.add(field);
+ box.add(Box.createHorizontalStrut(5));
+ box.add(browseButton);
+ browseButton.addActionListener(new BrowseFileAction(
+ attr, (Document)model));
+ c = box;
+ maxIsPreferred = 3;
+ }
+ return c;
+ }
+
+
+ /**
+ * Determines the maximum span for this view along an
+ * axis. For certain components, the maximum and preferred span are the
+ * same. For others this will return the value
+ * returned by Component.getMaximumSize along the
+ * axis of interest.
+ *
+ * @param axis may be either View.X_AXIS or View.Y_AXIS
+ * @return the span the view would like to be rendered into >= 0.
+ * Typically the view is told to render into the span
+ * that is returned, although there is no guarantee.
+ * The parent may choose to resize or break the view.
+ * @exception IllegalArgumentException for an invalid axis
+ */
+ public float getMaximumSpan(int axis) {
+ switch (axis) {
+ case View.X_AXIS:
+ if ((maxIsPreferred & 1) == 1) {
+ super.getMaximumSpan(axis);
+ return getPreferredSpan(axis);
+ }
+ return super.getMaximumSpan(axis);
+ case View.Y_AXIS:
+ if ((maxIsPreferred & 2) == 2) {
+ super.getMaximumSpan(axis);
+ return getPreferredSpan(axis);
+ }
+ return super.getMaximumSpan(axis);
+ default:
+ break;
+ }
+ return super.getMaximumSpan(axis);
+ }
+
+
+ /**
+ * Responsible for processeing the ActionEvent.
+ * If the element associated with the FormView,
+ * has a type of "submit", "reset", "text" or "password"
+ * then the action is processed. In the case of a "submit"
+ * the form is submitted. In the case of a "reset"
+ * the form is reset to its original state.
+ * In the case of "text" or "password", if the
+ * element is the last one of type "text" or "password",
+ * the form is submitted. Otherwise, focus is transferred
+ * to the next component in the form.
+ *
+ * @param evt the ActionEvent.
+ */
+ public void actionPerformed(ActionEvent evt) {
+ Element element = getElement();
+ StringBuffer dataBuffer = new StringBuffer();
+ HTMLDocument doc = (HTMLDocument)getDocument();
+ AttributeSet attr = element.getAttributes();
+
+ String type = (String) attr.getAttribute(HTML.Attribute.TYPE);
+
+ if (type.equals("submit")) {
+ getFormData(dataBuffer);
+ submitData(dataBuffer.toString());
+ } else if (type.equals("reset")) {
+ resetForm();
+ } else if (type.equals("text") || type.equals("password")) {
+ if (isLastTextOrPasswordField()) {
+ getFormData(dataBuffer);
+ submitData(dataBuffer.toString());
+ } else {
+ getComponent().transferFocus();
+ }
+ }
+ }
+
+
+ /**
+ * This method is responsible for submitting the form data.
+ * A thread is forked to undertake the submission.
+ */
+ protected void submitData(String data) {
+ Element form = getFormElement();
+ AttributeSet attrs = form.getAttributes();
+ HTMLDocument doc = (HTMLDocument) form.getDocument();
+ URL base = doc.getBase();
+
+ String target = (String) attrs.getAttribute(HTML.Attribute.TARGET);
+ if (target == null) {
+ target = "_self";
+ }
+
+ String method = (String) attrs.getAttribute(HTML.Attribute.METHOD);
+ if (method == null) {
+ method = "GET";
+ }
+ method = method.toLowerCase();
+ boolean isPostMethod = method.equals("post");
+ if (isPostMethod) {
+ storePostData(doc, target, data);
+ }
+
+ String action = (String) attrs.getAttribute(HTML.Attribute.ACTION);
+ URL actionURL;
+ try {
+ actionURL = (action == null)
+ ? new URL(base.getProtocol(), base.getHost(),
+ base.getPort(), base.getFile())
+ : new URL(base, action);
+ if (!isPostMethod) {
+ String query = data.toString();
+ actionURL = new URL(actionURL + "?" + query);
+ }
+ } catch (MalformedURLException e) {
+ actionURL = null;
+ }
+ final JEditorPane c = (JEditorPane) getContainer();
+ HTMLEditorKit kit = (HTMLEditorKit) c.getEditorKit();
+
+ FormSubmitEvent formEvent = null;
+ if (!kit.isAutoFormSubmission() || doc.isFrameDocument()) {
+ FormSubmitEvent.MethodType methodType = isPostMethod
+ ? FormSubmitEvent.MethodType.POST
+ : FormSubmitEvent.MethodType.GET;
+ formEvent = new FormSubmitEvent(
+ FormView.this, HyperlinkEvent.EventType.ACTIVATED,
+ actionURL, form, target, methodType, data);
+
+ }
+ // setPage() may take significant time so schedule it to run later.
+ final FormSubmitEvent fse = formEvent;
+ final URL url = actionURL;
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ if (fse != null) {
+ c.fireHyperlinkUpdate(fse);
+ } else {
+ try {
+ c.setPage(url);
+ } catch (IOException e) {
+ UIManager.getLookAndFeel().provideErrorFeedback(c);
+ }
+ }
+ }
+ });
+ }
+
+ private void storePostData(HTMLDocument doc, String target, String data) {
+
+ /* POST data is stored into the document property named by constant
+ * PostDataProperty from where it is later retrieved by method
+ * JEditorPane.getPostData(). If the current document is in a frame,
+ * the data is initially put into the toplevel (frameset) document
+ * property (named <PostDataProperty>.<Target frame name>). It is the
+ * responsibility of FrameView which updates the target frame
+ * to move data from the frameset document property into the frame
+ * document property.
+ */
+
+ Document propDoc = doc;
+ String propName = PostDataProperty;
+
+ if (doc.isFrameDocument()) {
+ // find the top-most JEditorPane holding the frameset view.
+ FrameView.FrameEditorPane p =
+ (FrameView.FrameEditorPane) getContainer();
+ FrameView v = p.getFrameView();
+ JEditorPane c = v.getOutermostJEditorPane();
+ if (c != null) {
+ propDoc = c.getDocument();
+ propName += ("." + target);
+ }
+ }
+
+ propDoc.putProperty(propName, data);
+ }
+
+ /**
+ * MouseEventListener class to handle form submissions when
+ * an input with type equal to image is clicked on.
+ * A MouseListener is necessary since along with the image
+ * data the coordinates associated with the mouse click
+ * need to be submitted.
+ */
+ protected class MouseEventListener extends MouseAdapter {
+
+ public void mouseReleased(MouseEvent evt) {
+ String imageData = getImageData(evt.getPoint());
+ imageSubmit(imageData);
+ }
+ }
+
+ /**
+ * This method is called to submit a form in response
+ * to a click on an image -- an &lt;INPUT&gt; form
+ * element of type "image".
+ *
+ * @param imageData the mouse click coordinates.
+ */
+ protected void imageSubmit(String imageData) {
+
+ StringBuffer dataBuffer = new StringBuffer();
+ Element elem = getElement();
+ HTMLDocument hdoc = (HTMLDocument)elem.getDocument();
+ getFormData(dataBuffer);
+ if (dataBuffer.length() > 0) {
+ dataBuffer.append('&');
+ }
+ dataBuffer.append(imageData);
+ submitData(dataBuffer.toString());
+ return;
+ }
+
+ /**
+ * Extracts the value of the name attribute
+ * associated with the input element of type
+ * image. If name is defined it is encoded using
+ * the URLEncoder.encode() method and the
+ * image data is returned in the following format:
+ * name + ".x" +"="+ x +"&"+ name +".y"+"="+ y
+ * otherwise,
+ * "x="+ x +"&y="+ y
+ *
+ * @param point associated with the mouse click.
+ * @return the image data.
+ */
+ private String getImageData(Point point) {
+
+ String mouseCoords = point.x + ":" + point.y;
+ int sep = mouseCoords.indexOf(':');
+ String x = mouseCoords.substring(0, sep);
+ String y = mouseCoords.substring(++sep);
+ String name = (String) getElement().getAttributes().getAttribute(HTML.Attribute.NAME);
+
+ String data;
+ if (name == null || name.equals("")) {
+ data = "x="+ x +"&y="+ y;
+ } else {
+ name = URLEncoder.encode(name);
+ data = name + ".x" +"="+ x +"&"+ name +".y"+"="+ y;
+ }
+ return data;
+ }
+
+
+ /**
+ * The following methods provide functionality required to
+ * iterate over a the elements of the form and in the case
+ * of a form submission, extract the data from each model
+ * that is associated with each form element, and in the
+ * case of reset, reinitialize the each model to its
+ * initial state.
+ */
+
+
+ /**
+ * Returns the Element representing the <code>FORM</code>.
+ */
+ private Element getFormElement() {
+ Element elem = getElement();
+ while (elem != null) {
+ if (elem.getAttributes().getAttribute
+ (StyleConstants.NameAttribute) == HTML.Tag.FORM) {
+ return elem;
+ }
+ elem = elem.getParentElement();
+ }
+ return null;
+ }
+
+ /**
+ * Iterates over the
+ * element hierarchy, extracting data from the
+ * models associated with the relevant form elements.
+ * "Relevant" means the form elements that are part
+ * of the same form whose element triggered the submit
+ * action.
+ *
+ * @param buffer the buffer that contains that data to submit
+ * @param targetElement the element that triggered the
+ * form submission
+ */
+ void getFormData(StringBuffer buffer) {
+ Element formE = getFormElement();
+ if (formE != null) {
+ ElementIterator it = new ElementIterator(formE);
+ Element next;
+
+ while ((next = it.next()) != null) {
+ if (isControl(next)) {
+ String type = (String)next.getAttributes().getAttribute
+ (HTML.Attribute.TYPE);
+
+ if (type != null && type.equals("submit") &&
+ next != getElement()) {
+ // do nothing - this submit isnt the trigger
+ } else if (type == null || !type.equals("image")) {
+ // images only result in data if they triggered
+ // the submit and they require that the mouse click
+ // coords be appended to the data. Hence its
+ // processing is handled by the view.
+ loadElementDataIntoBuffer(next, buffer);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Loads the data
+ * associated with the element into the buffer.
+ * The format in which data is appended depends
+ * on the type of the form element. Essentially
+ * data is loaded in name/value pairs.
+ *
+ */
+ private void loadElementDataIntoBuffer(Element elem, StringBuffer buffer) {
+
+ AttributeSet attr = elem.getAttributes();
+ String name = (String)attr.getAttribute(HTML.Attribute.NAME);
+ if (name == null) {
+ return;
+ }
+ String value = null;
+ HTML.Tag tag = (HTML.Tag)elem.getAttributes().getAttribute
+ (StyleConstants.NameAttribute);
+
+ if (tag == HTML.Tag.INPUT) {
+ value = getInputElementData(attr);
+ } else if (tag == HTML.Tag.TEXTAREA) {
+ value = getTextAreaData(attr);
+ } else if (tag == HTML.Tag.SELECT) {
+ loadSelectData(attr, buffer);
+ }
+
+ if (name != null && value != null) {
+ appendBuffer(buffer, name, value);
+ }
+ }
+
+
+ /**
+ * Returns the data associated with an &lt;INPUT&gt; form
+ * element. The value of "type" attributes is
+ * used to determine the type of the model associated
+ * with the element and then the relevant data is
+ * extracted.
+ */
+ private String getInputElementData(AttributeSet attr) {
+
+ Object model = attr.getAttribute(StyleConstants.ModelAttribute);
+ String type = (String) attr.getAttribute(HTML.Attribute.TYPE);
+ String value = null;
+
+ if (type.equals("text") || type.equals("password")) {
+ Document doc = (Document)model;
+ try {
+ value = doc.getText(0, doc.getLength());
+ } catch (BadLocationException e) {
+ value = null;
+ }
+ } else if (type.equals("submit") || type.equals("hidden")) {
+ value = (String) attr.getAttribute(HTML.Attribute.VALUE);
+ if (value == null) {
+ value = "";
+ }
+ } else if (type.equals("radio") || type.equals("checkbox")) {
+ ButtonModel m = (ButtonModel)model;
+ if (m.isSelected()) {
+ value = (String) attr.getAttribute(HTML.Attribute.VALUE);
+ if (value == null) {
+ value = "on";
+ }
+ }
+ } else if (type.equals("file")) {
+ Document doc = (Document)model;
+ String path;
+
+ try {
+ path = doc.getText(0, doc.getLength());
+ } catch (BadLocationException e) {
+ path = null;
+ }
+ if (path != null && path.length() > 0) {
+ value = path;
+/*
+
+ try {
+ Reader reader = new BufferedReader(new FileReader(path));
+ StringBuffer buffer = new StringBuffer();
+ char[] cBuff = new char[1024];
+ int read;
+
+ try {
+ while ((read = reader.read(cBuff)) != -1) {
+ buffer.append(cBuff, 0, read);
+ }
+ } catch (IOException ioe) {
+ buffer = null;
+ }
+ try {
+ reader.close();
+ } catch (IOException ioe) {}
+ if (buffer != null) {
+ value = buffer.toString();
+ }
+ } catch (IOException ioe) {}
+*/
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Returns the data associated with the &lt;TEXTAREA&gt; form
+ * element. This is done by getting the text stored in the
+ * Document model.
+ */
+ private String getTextAreaData(AttributeSet attr) {
+ Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute);
+ try {
+ return doc.getText(0, doc.getLength());
+ } catch (BadLocationException e) {
+ return null;
+ }
+ }
+
+
+ /**
+ * Loads the buffer with the data associated with the Select
+ * form element. Basically, only items that are selected
+ * and have their name attribute set are added to the buffer.
+ */
+ private void loadSelectData(AttributeSet attr, StringBuffer buffer) {
+
+ String name = (String)attr.getAttribute(HTML.Attribute.NAME);
+ if (name == null) {
+ return;
+ }
+ Object m = attr.getAttribute(StyleConstants.ModelAttribute);
+ if (m instanceof OptionListModel) {
+ OptionListModel model = (OptionListModel)m;
+
+ for (int i = 0; i < model.getSize(); i++) {
+ if (model.isSelectedIndex(i)) {
+ Option option = (Option) model.getElementAt(i);
+ appendBuffer(buffer, name, option.getValue());
+ }
+ }
+ } else if (m instanceof ComboBoxModel) {
+ ComboBoxModel model = (ComboBoxModel)m;
+ Option option = (Option)model.getSelectedItem();
+ if (option != null) {
+ appendBuffer(buffer, name, option.getValue());
+ }
+ }
+ }
+
+ /**
+ * Appends name / value pairs into the
+ * buffer. Both names and values are encoded using the
+ * URLEncoder.encode() method before being added to the
+ * buffer.
+ */
+ private void appendBuffer(StringBuffer buffer, String name, String value) {
+ if (buffer.length() > 0) {
+ buffer.append('&');
+ }
+ String encodedName = URLEncoder.encode(name);
+ buffer.append(encodedName);
+ buffer.append('=');
+ String encodedValue = URLEncoder.encode(value);
+ buffer.append(encodedValue);
+ }
+
+ /**
+ * Returns true if the Element <code>elem</code> represents a control.
+ */
+ private boolean isControl(Element elem) {
+ return elem.isLeaf();
+ }
+
+ /**
+ * Iterates over the element hierarchy to determine if
+ * the element parameter, which is assumed to be an
+ * &lt;INPUT&gt; element of type password or text, is the last
+ * one of either kind, in the form to which it belongs.
+ */
+ boolean isLastTextOrPasswordField() {
+ Element parent = getFormElement();
+ Element elem = getElement();
+
+ if (parent != null) {
+ ElementIterator it = new ElementIterator(parent);
+ Element next;
+ boolean found = false;
+
+ while ((next = it.next()) != null) {
+ if (next == elem) {
+ found = true;
+ }
+ else if (found && isControl(next)) {
+ AttributeSet elemAttr = next.getAttributes();
+
+ if (HTMLDocument.matchNameAttribute
+ (elemAttr, HTML.Tag.INPUT)) {
+ String type = (String)elemAttr.getAttribute
+ (HTML.Attribute.TYPE);
+
+ if ("text".equals(type) || "password".equals(type)) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Resets the form
+ * to its initial state by reinitializing the models
+ * associated with each form element to their initial
+ * values.
+ *
+ * param elem the element that triggered the reset
+ */
+ void resetForm() {
+ Element parent = getFormElement();
+
+ if (parent != null) {
+ ElementIterator it = new ElementIterator(parent);
+ Element next;
+
+ while((next = it.next()) != null) {
+ if (isControl(next)) {
+ AttributeSet elemAttr = next.getAttributes();
+ Object m = elemAttr.getAttribute(StyleConstants.
+ ModelAttribute);
+ if (m instanceof TextAreaDocument) {
+ TextAreaDocument doc = (TextAreaDocument)m;
+ doc.reset();
+ } else if (m instanceof PlainDocument) {
+ try {
+ PlainDocument doc = (PlainDocument)m;
+ doc.remove(0, doc.getLength());
+ if (HTMLDocument.matchNameAttribute
+ (elemAttr, HTML.Tag.INPUT)) {
+ String value = (String)elemAttr.
+ getAttribute(HTML.Attribute.VALUE);
+ if (value != null) {
+ doc.insertString(0, value, null);
+ }
+ }
+ } catch (BadLocationException e) {
+ }
+ } else if (m instanceof OptionListModel) {
+ OptionListModel model = (OptionListModel) m;
+ int size = model.getSize();
+ for (int i = 0; i < size; i++) {
+ model.removeIndexInterval(i, i);
+ }
+ BitSet selectionRange = model.getInitialSelection();
+ for (int i = 0; i < selectionRange.size(); i++) {
+ if (selectionRange.get(i)) {
+ model.addSelectionInterval(i, i);
+ }
+ }
+ } else if (m instanceof OptionComboBoxModel) {
+ OptionComboBoxModel model = (OptionComboBoxModel) m;
+ Option option = model.getInitialSelection();
+ if (option != null) {
+ model.setSelectedItem(option);
+ }
+ } else if (m instanceof JToggleButton.ToggleButtonModel) {
+ boolean checked = ((String)elemAttr.getAttribute
+ (HTML.Attribute.CHECKED) != null);
+ JToggleButton.ToggleButtonModel model =
+ (JToggleButton.ToggleButtonModel)m;
+ model.setSelected(checked);
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * BrowseFileAction is used for input type == file. When the user
+ * clicks the button a JFileChooser is brought up allowing the user
+ * to select a file in the file system. The resulting path to the selected
+ * file is set in the text field (actually an instance of Document).
+ */
+ private class BrowseFileAction implements ActionListener {
+ private AttributeSet attrs;
+ private Document model;
+
+ BrowseFileAction(AttributeSet attrs, Document model) {
+ this.attrs = attrs;
+ this.model = model;
+ }
+
+ public void actionPerformed(ActionEvent ae) {
+ // PENDING: When mime support is added to JFileChooser use the
+ // accept value of attrs.
+ JFileChooser fc = new JFileChooser();
+ fc.setMultiSelectionEnabled(false);
+ if (fc.showOpenDialog(getContainer()) ==
+ JFileChooser.APPROVE_OPTION) {
+ File selected = fc.getSelectedFile();
+
+ if (selected != null) {
+ try {
+ if (model.getLength() > 0) {
+ model.remove(0, model.getLength());
+ }
+ model.insertString(0, selected.getPath(), null);
+ } catch (BadLocationException ble) {}
+ }
+ }
+ }
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/FrameSetView.java b/src/share/classes/javax/swing/text/html/FrameSetView.java
new file mode 100644
index 000000000..b653d32ee
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/FrameSetView.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright 1998-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.text.*;
+import javax.swing.event.*;
+
+/**
+ * Implements a FrameSetView, intended to support the HTML
+ * &lt;FRAMESET&gt; tag. Supports the ROWS and COLS attributes.
+ *
+ * @author Sunita Mani
+ *
+ * Credit also to the hotjava browser engineers that
+ * worked on making the allocation of space algorithms
+ * conform to the HTML 4.0 standard and also be netscape
+ * compatible.
+ *
+ */
+
+class FrameSetView extends javax.swing.text.BoxView {
+
+ String[] children;
+ int[] percentChildren;
+ int[] absoluteChildren;
+ int[] relativeChildren;
+ int percentTotals;
+ int absoluteTotals;
+ int relativeTotals;
+
+ /**
+ * Constructs a FrameSetView for the given element.
+ *
+ * @param elem the element that this view is responsible for
+ */
+ public FrameSetView(Element elem, int axis) {
+ super(elem, axis);
+ children = null;
+ }
+
+ /**
+ * Parses the ROW or COL attributes and returns
+ * an array of strings that represent the space
+ * distribution.
+ *
+ */
+ private String[] parseRowColSpec(HTML.Attribute key) {
+
+ AttributeSet attributes = getElement().getAttributes();
+ String spec = "*";
+ if (attributes != null) {
+ if (attributes.getAttribute(key) != null) {
+ spec = (String)attributes.getAttribute(key);
+ }
+ }
+
+ StringTokenizer tokenizer = new StringTokenizer(spec, ",");
+ int nTokens = tokenizer.countTokens();
+ int n = getViewCount();
+ String[] items = new String[Math.max(nTokens, n)];
+ int i = 0;
+ for (; i < nTokens; i++) {
+ items[i] = tokenizer.nextToken().trim();
+ // As per the spec, 100% is the same as *
+ // hence the mapping.
+ //
+ if (items[i].equals("100%")) {
+ items[i] = "*";
+ }
+ }
+ // extend spec if we have more children than specified
+ // in ROWS or COLS attribute
+ for (; i < items.length; i++) {
+ items[i] = "*";
+ }
+ return items;
+ }
+
+
+ /**
+ * Initializes a number of internal state variables
+ * that store information about space allocation
+ * for the frames contained within the frameset.
+ */
+ private void init() {
+ if (getAxis() == View.Y_AXIS) {
+ children = parseRowColSpec(HTML.Attribute.ROWS);
+ } else {
+ children = parseRowColSpec(HTML.Attribute.COLS);
+ }
+ percentChildren = new int[children.length];
+ relativeChildren = new int[children.length];
+ absoluteChildren = new int[children.length];
+
+ for (int i = 0; i < children.length; i++) {
+ percentChildren[i] = -1;
+ relativeChildren[i] = -1;
+ absoluteChildren[i] = -1;
+
+ if (children[i].endsWith("*")) {
+ if (children[i].length() > 1) {
+ relativeChildren[i] =
+ Integer.parseInt(children[i].substring(
+ 0, children[i].length()-1));
+ relativeTotals += relativeChildren[i];
+ } else {
+ relativeChildren[i] = 1;
+ relativeTotals += 1;
+ }
+ } else if (children[i].indexOf('%') != -1) {
+ percentChildren[i] = parseDigits(children[i]);
+ percentTotals += percentChildren[i];
+ } else {
+ absoluteChildren[i] = Integer.parseInt(children[i]);
+ }
+ }
+ if (percentTotals > 100) {
+ for (int i = 0; i < percentChildren.length; i++) {
+ if (percentChildren[i] > 0) {
+ percentChildren[i] =
+ (percentChildren[i] * 100) / percentTotals;
+ }
+ }
+ percentTotals = 100;
+ }
+ }
+
+ /**
+ * Perform layout for the major axis of the box (i.e. the
+ * axis that it represents). The results of the layout should
+ * be placed in the given arrays which represent the allocations
+ * to the children along the major axis.
+ *
+ * @param targetSpan the total span given to the view, which
+ * whould be used to layout the children
+ * @param axis the axis being layed out
+ * @param offsets the offsets from the origin of the view for
+ * each of the child views; this is a return value and is
+ * filled in by the implementation of this method
+ * @param spans the span of each child view; this is a return
+ * value and is filled in by the implementation of this method
+ * @return the offset and span for each child view in the
+ * offsets and spans parameters
+ */
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
+ int[] spans) {
+ if (children == null) {
+ init();
+ }
+ SizeRequirements.calculateTiledPositions(targetSpan, null,
+ getChildRequests(targetSpan,
+ axis),
+ offsets, spans);
+ }
+
+ protected SizeRequirements[] getChildRequests(int targetSpan, int axis) {
+
+ int span[] = new int[children.length];
+
+ spread(targetSpan, span);
+ int n = getViewCount();
+ SizeRequirements[] reqs = new SizeRequirements[n];
+ for (int i = 0, sIndex = 0; i < n; i++) {
+ View v = getView(i);
+ if ((v instanceof FrameView) || (v instanceof FrameSetView)) {
+ reqs[i] = new SizeRequirements((int) v.getMinimumSpan(axis),
+ span[sIndex],
+ (int) v.getMaximumSpan(axis),
+ 0.5f);
+ sIndex++;
+ } else {
+ int min = (int) v.getMinimumSpan(axis);
+ int pref = (int) v.getPreferredSpan(axis);
+ int max = (int) v.getMaximumSpan(axis);
+ float a = v.getAlignment(axis);
+ reqs[i] = new SizeRequirements(min, pref, max, a);
+ }
+ }
+ return reqs;
+ }
+
+
+ /**
+ * This method is responsible for returning in span[] the
+ * span for each child view along the major axis. it
+ * computes this based on the information that extracted
+ * from the value of the ROW/COL attribute.
+ */
+ private void spread(int targetSpan, int span[]) {
+
+ if (targetSpan == 0) {
+ return;
+ }
+
+ int tempSpace = 0;
+ int remainingSpace = targetSpan;
+
+ // allocate the absolute's first, they have
+ // precedence
+ //
+ for (int i = 0; i < span.length; i++) {
+ if (absoluteChildren[i] > 0) {
+ span[i] = absoluteChildren[i];
+ remainingSpace -= span[i];
+ }
+ }
+
+ // then deal with percents.
+ //
+ tempSpace = remainingSpace;
+ for (int i = 0; i < span.length; i++) {
+ if (percentChildren[i] > 0 && tempSpace > 0) {
+ span[i] = (percentChildren[i] * tempSpace) / 100;
+ remainingSpace -= span[i];
+ } else if (percentChildren[i] > 0 && tempSpace <= 0) {
+ span[i] = targetSpan / span.length;
+ remainingSpace -= span[i];
+ }
+ }
+
+ // allocate remainingSpace to relative
+ if (remainingSpace > 0 && relativeTotals > 0) {
+ for (int i = 0; i < span.length; i++) {
+ if (relativeChildren[i] > 0) {
+ span[i] = (remainingSpace *
+ relativeChildren[i]) / relativeTotals;
+ }
+ }
+ } else if (remainingSpace > 0) {
+ // There are no relative columns and the space has been
+ // under- or overallocated. In this case, turn all the
+ // percentage and pixel specified columns to percentage
+ // columns based on the ratio of their pixel count to the
+ // total "virtual" size. (In the case of percentage columns,
+ // the pixel count would equal the specified percentage
+ // of the screen size.
+
+ // This action is in accordance with the HTML
+ // 4.0 spec (see section 8.3, the end of the discussion of
+ // the FRAMESET tag). The precedence of percentage and pixel
+ // specified columns is unclear (spec seems to indicate that
+ // they share priority, however, unspecified what happens when
+ // overallocation occurs.)
+
+ // addendum is that we behave similiar to netscape in that specified
+ // widths have precedance over percentage widths...
+
+ float vTotal = (float)(targetSpan - remainingSpace);
+ float[] tempPercents = new float[span.length];
+ remainingSpace = targetSpan;
+ for (int i = 0; i < span.length; i++) {
+ // ok we know what our total space is, and we know how large each
+ // column should be relative to each other... therefore we can use
+ // that relative information to deduce their percentages of a whole
+ // and then scale them appropriately for the correct size
+ tempPercents[i] = ((float)span[i] / vTotal) * 100.00f;
+ span[i] = (int) ( ((float)targetSpan * tempPercents[i]) / 100.00f);
+ remainingSpace -= span[i];
+ }
+
+
+ // this is for just in case there is something left over.. if there is we just
+ // add it one pixel at a time to the frames in order.. We shouldn't really ever get
+ // here and if we do it shouldn't be with more than 1 pixel, maybe two.
+ int i = 0;
+ while (remainingSpace != 0) {
+ if (remainingSpace < 0) {
+ span[i++]--;
+ remainingSpace++;
+ }
+ else {
+ span[i++]++;
+ remainingSpace--;
+ }
+
+ // just in case there are more pixels than frames...should never happen..
+ if (i == span.length)i = 0;
+ }
+ }
+ }
+
+ /*
+ * Users have been known to type things like "%25" and "25 %". Deal
+ * with it.
+ */
+ private int parseDigits(String mixedStr) {
+ int result = 0;
+ for (int i = 0; i < mixedStr.length(); i++) {
+ char ch = mixedStr.charAt(i);
+ if (Character.isDigit(ch)) {
+ result = (result * 10) + Character.digit(ch, 10);
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/src/share/classes/javax/swing/text/html/FrameView.java b/src/share/classes/javax/swing/text/html/FrameView.java
new file mode 100644
index 000000000..f66a5531c
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/FrameView.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.*;
+import java.util.*;
+import java.net.*;
+import java.io.*;
+import javax.swing.*;
+import javax.swing.text.*;
+import javax.swing.event.*;
+
+import sun.swing.text.html.FrameEditorPaneTag;
+
+/**
+ * Implements a FrameView, intended to support the HTML
+ * &lt;FRAME&gt; tag. Supports the frameborder, scrolling,
+ * marginwidth and marginheight attributes.
+ *
+ * @author Sunita Mani
+ */
+
+class FrameView extends ComponentView implements HyperlinkListener {
+
+
+ JEditorPane htmlPane;
+ JScrollPane scroller;
+ boolean editable;
+ float width;
+ float height;
+ URL src;
+ /** Set to true when the component has been created. */
+ private boolean createdComponent;
+
+ /**
+ * Creates a new Frame.
+ *
+ * @param elem the element to represent.
+ */
+ public FrameView(Element elem) {
+ super(elem);
+ }
+
+ protected Component createComponent() {
+
+ Element elem = getElement();
+ AttributeSet attributes = elem.getAttributes();
+ String srcAtt = (String)attributes.getAttribute(HTML.Attribute.SRC);
+
+ if ((srcAtt != null) && (!srcAtt.equals(""))) {
+ try {
+ URL base = ((HTMLDocument)elem.getDocument()).getBase();
+ src = new URL(base, srcAtt);
+ htmlPane = new FrameEditorPane();
+ htmlPane.addHyperlinkListener(this);
+ JEditorPane host = getHostPane();
+ boolean isAutoFormSubmission = true;
+ if (host != null) {
+ htmlPane.setEditable(host.isEditable());
+ String charset = (String) host.getClientProperty("charset");
+ if (charset != null) {
+ htmlPane.putClientProperty("charset", charset);
+ }
+ HTMLEditorKit hostKit = (HTMLEditorKit)host.getEditorKit();
+ if (hostKit != null) {
+ isAutoFormSubmission = hostKit.isAutoFormSubmission();
+ }
+ }
+ htmlPane.setPage(src);
+ HTMLEditorKit kit = (HTMLEditorKit)htmlPane.getEditorKit();
+ if (kit != null) {
+ kit.setAutoFormSubmission(isAutoFormSubmission);
+ }
+
+ Document doc = htmlPane.getDocument();
+ if (doc instanceof HTMLDocument) {
+ ((HTMLDocument)doc).setFrameDocumentState(true);
+ }
+ setMargin();
+ createScrollPane();
+ setBorder();
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ }
+ createdComponent = true;
+ return scroller;
+ }
+
+ JEditorPane getHostPane() {
+ Container c = getContainer();
+ while ((c != null) && ! (c instanceof JEditorPane)) {
+ c = c.getParent();
+ }
+ return (JEditorPane) c;
+ }
+
+
+ /**
+ * Sets the parent view for the FrameView.
+ * Also determines if the FrameView should be editable
+ * or not based on whether the JTextComponent that
+ * contains it is editable.
+ *
+ * @param parent View
+ */
+ public void setParent(View parent) {
+ if (parent != null) {
+ JTextComponent t = (JTextComponent)parent.getContainer();
+ editable = t.isEditable();
+ }
+ super.setParent(parent);
+ }
+
+
+ /**
+ * Also determines if the FrameView should be editable
+ * or not based on whether the JTextComponent that
+ * contains it is editable. And then proceeds to call
+ * the superclass to do the paint().
+ *
+ * @param parent View
+ * @see text.ComponentView#paint
+ */
+ public void paint(Graphics g, Shape allocation) {
+
+ Container host = getContainer();
+ if (host != null && htmlPane != null &&
+ htmlPane.isEditable() != ((JTextComponent)host).isEditable()) {
+ editable = ((JTextComponent)host).isEditable();
+ htmlPane.setEditable(editable);
+ }
+ super.paint(g, allocation);
+ }
+
+
+ /**
+ * If the marginwidth or marginheight attributes have been specified,
+ * then the JEditorPane's margin's are set to the new values.
+ */
+ private void setMargin() {
+ int margin = 0;
+ Insets in = htmlPane.getMargin();
+ Insets newInsets;
+ boolean modified = false;
+ AttributeSet attributes = getElement().getAttributes();
+ String marginStr = (String)attributes.getAttribute(HTML.Attribute.MARGINWIDTH);
+ if ( in != null) {
+ newInsets = new Insets(in.top, in.left, in.right, in.bottom);
+ } else {
+ newInsets = new Insets(0,0,0,0);
+ }
+ if (marginStr != null) {
+ margin = Integer.parseInt(marginStr);
+ if (margin > 0) {
+ newInsets.left = margin;
+ newInsets.right = margin;
+ modified = true;
+ }
+ }
+ marginStr = (String)attributes.getAttribute(HTML.Attribute.MARGINHEIGHT);
+ if (marginStr != null) {
+ margin = Integer.parseInt(marginStr);
+ if (margin > 0) {
+ newInsets.top = margin;
+ newInsets.bottom = margin;
+ modified = true;
+ }
+ }
+ if (modified) {
+ htmlPane.setMargin(newInsets);
+ }
+ }
+
+ /**
+ * If the frameborder attribute has been specified, either in the frame,
+ * or by the frames enclosing frameset, the JScrollPane's setBorder()
+ * method is invoked to achieve the desired look.
+ */
+ private void setBorder() {
+
+ AttributeSet attributes = getElement().getAttributes();
+ String frameBorder = (String)attributes.getAttribute(HTML.Attribute.FRAMEBORDER);
+ if ((frameBorder != null) &&
+ (frameBorder.equals("no") || frameBorder.equals("0"))) {
+ // make invisible borders.
+ scroller.setBorder(null);
+ }
+ }
+
+
+ /**
+ * This method creates the JScrollPane. The scrollbar policy is determined by
+ * the scrolling attribute. If not defined, the default is "auto" which
+ * maps to the scrollbar's being displayed as needed.
+ */
+ private void createScrollPane() {
+ AttributeSet attributes = getElement().getAttributes();
+ String scrolling = (String)attributes.getAttribute(HTML.Attribute.SCROLLING);
+ if (scrolling == null) {
+ scrolling = "auto";
+ }
+
+ if (!scrolling.equals("no")) {
+ if (scrolling.equals("yes")) {
+ scroller = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
+ JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ } else {
+ // scrollbars will be displayed if needed
+ //
+ scroller = new JScrollPane();
+ }
+ } else {
+ scroller = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_NEVER,
+ JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+
+ JViewport vp = scroller.getViewport();
+ vp.add(htmlPane);
+ vp.setBackingStoreEnabled(true);
+ scroller.setMinimumSize(new Dimension(5,5));
+ scroller.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
+ }
+
+
+ /**
+ * Finds the outermost FrameSetView. It then
+ * returns that FrameSetView's container.
+ */
+ JEditorPane getOutermostJEditorPane() {
+
+ View parent = getParent();
+ FrameSetView frameSetView = null;
+ while (parent != null) {
+ if (parent instanceof FrameSetView) {
+ frameSetView = (FrameSetView)parent;
+ }
+ parent = parent.getParent();
+ }
+ if (frameSetView != null) {
+ return (JEditorPane)frameSetView.getContainer();
+ }
+ return null;
+ }
+
+
+ /**
+ * Returns true if this frame is contained within
+ * a nested frameset.
+ */
+ private boolean inNestedFrameSet() {
+ FrameSetView parent = (FrameSetView)getParent();
+ return (parent.getParent() instanceof FrameSetView);
+ }
+
+
+ /**
+ * Notification of a change relative to a
+ * hyperlink. This method searches for the outermost
+ * JEditorPane, and then fires an HTMLFrameHyperlinkEvent
+ * to that frame. In addition, if the target is _parent,
+ * and there is not nested framesets then the target is
+ * reset to _top. If the target is _top, in addition to
+ * firing the event to the outermost JEditorPane, this
+ * method also invokes the setPage() method and explicitly
+ * replaces the current document with the destination url.
+ *
+ * @param HyperlinkEvent
+ */
+ public void hyperlinkUpdate(HyperlinkEvent evt) {
+
+ JEditorPane c = getOutermostJEditorPane();
+ if (c == null) {
+ return;
+ }
+
+ if (!(evt instanceof HTMLFrameHyperlinkEvent)) {
+ c.fireHyperlinkUpdate(evt);
+ return;
+ }
+
+ HTMLFrameHyperlinkEvent e = (HTMLFrameHyperlinkEvent)evt;
+
+ if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+ String target = e.getTarget();
+ String postTarget = target;
+
+ if (target.equals("_parent") && !inNestedFrameSet()){
+ target = "_top";
+ }
+
+ if (evt instanceof FormSubmitEvent) {
+ HTMLEditorKit kit = (HTMLEditorKit)c.getEditorKit();
+ if (kit != null && kit.isAutoFormSubmission()) {
+ if (target.equals("_top")) {
+ try {
+ movePostData(c, postTarget);
+ c.setPage(e.getURL());
+ } catch (IOException ex) {
+ // Need a way to handle exceptions
+ }
+ } else {
+ HTMLDocument doc = (HTMLDocument)c.getDocument();
+ doc.processHTMLFrameHyperlinkEvent(e);
+ }
+ } else {
+ c.fireHyperlinkUpdate(evt);
+ }
+ return;
+ }
+
+ if (target.equals("_top")) {
+ try {
+ c.setPage(e.getURL());
+ } catch (IOException ex) {
+ // Need a way to handle exceptions
+ // ex.printStackTrace();
+ }
+ }
+ if (!c.isEditable()) {
+ c.fireHyperlinkUpdate(new HTMLFrameHyperlinkEvent(c,
+ e.getEventType(),
+ e.getURL(),
+ e.getDescription(),
+ getElement(),
+ e.getInputEvent(),
+ target));
+ }
+ }
+ }
+
+ /**
+ * Gives notification from the document that attributes were changed
+ * in a location that this view is responsible for. Currently this view
+ * handles changes to its SRC attribute.
+ *
+ * @param e the change information from the associated document
+ * @param a the current allocation of the view
+ * @param f the factory to use to rebuild if the view has children
+ *
+ */
+ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+
+ Element elem = getElement();
+ AttributeSet attributes = elem.getAttributes();
+
+ URL oldPage = src;
+
+ String srcAtt = (String)attributes.getAttribute(HTML.Attribute.SRC);
+ URL base = ((HTMLDocument)elem.getDocument()).getBase();
+ try {
+ if (!createdComponent) {
+ return;
+ }
+
+ Object postData = movePostData(htmlPane, null);
+ src = new URL(base, srcAtt);
+ if (oldPage.equals(src) && (src.getRef() == null) && (postData == null)) {
+ return;
+ }
+
+ htmlPane.setPage(src);
+ Document newDoc = htmlPane.getDocument();
+ if (newDoc instanceof HTMLDocument) {
+ ((HTMLDocument)newDoc).setFrameDocumentState(true);
+ }
+ } catch (MalformedURLException e1) {
+ // Need a way to handle exceptions
+ //e1.printStackTrace();
+ } catch (IOException e2) {
+ // Need a way to handle exceptions
+ //e2.printStackTrace();
+ }
+ }
+
+ /**
+ * Move POST data from temporary storage into the target document property.
+ *
+ * @return the POST data or null if no data found
+ */
+ private Object movePostData(JEditorPane targetPane, String frameName) {
+ Object postData = null;
+ JEditorPane p = getOutermostJEditorPane();
+ if (p != null) {
+ if (frameName == null) {
+ frameName = (String) getElement().getAttributes().getAttribute(
+ HTML.Attribute.NAME);
+ }
+ if (frameName != null) {
+ String propName = FormView.PostDataProperty + "." + frameName;
+ Document d = p.getDocument();
+ postData = d.getProperty(propName);
+ if (postData != null) {
+ targetPane.getDocument().putProperty(
+ FormView.PostDataProperty, postData);
+ d.putProperty(propName, null);
+ }
+ }
+ }
+
+ return postData;
+ }
+
+ /**
+ * Determines the minimum span for this view along an
+ * axis.
+ *
+ * @param axis may be either <code>View.X_AXIS</code> or
+ * <code>View.Y_AXIS</code>
+ * @return the preferred span; given that we do not
+ * support resizing of frames, the minimum span returned
+ * is the same as the preferred span
+ *
+ */
+ public float getMinimumSpan(int axis) {
+ return 5;
+ }
+
+ /**
+ * Determines the maximum span for this view along an
+ * axis.
+ *
+ * @param axis may be either <code>View.X_AXIS</code> or
+ * <code>View.Y_AXIS</code>
+ * @return the preferred span; given that we do not
+ * support resizing of frames, the maximum span returned
+ * is the same as the preferred span
+ *
+ */
+ public float getMaximumSpan(int axis) {
+ return Integer.MAX_VALUE;
+ }
+
+ /** Editor pane rendering frame of HTML document
+ * It uses the same editor kits classes as outermost JEditorPane
+ */
+ class FrameEditorPane extends JEditorPane implements FrameEditorPaneTag {
+ public EditorKit getEditorKitForContentType(String type) {
+ EditorKit editorKit = super.getEditorKitForContentType(type);
+ JEditorPane outerMostJEditorPane = null;
+ if ((outerMostJEditorPane = getOutermostJEditorPane()) != null) {
+ EditorKit inheritedEditorKit = outerMostJEditorPane.getEditorKitForContentType(type);
+ if (! editorKit.getClass().equals(inheritedEditorKit.getClass())) {
+ editorKit = (EditorKit) inheritedEditorKit.clone();
+ setEditorKitForContentType(type, editorKit);
+ }
+ }
+ return editorKit;
+ }
+
+ FrameView getFrameView() {
+ return FrameView.this;
+ }
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/HRuleView.java b/src/share/classes/javax/swing/text/html/HRuleView.java
new file mode 100644
index 000000000..d345b348d
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/HRuleView.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright 1997-2002 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.*;
+import java.util.Enumeration;
+import java.lang.Integer;
+
+/**
+ * A view implementation to display an html horizontal
+ * rule.
+ *
+ * @author Timothy Prinzing
+ * @author Sara Swanson
+ */
+class HRuleView extends View {
+
+ /**
+ * Creates a new view that represents an &lt;hr&gt; element.
+ *
+ * @param elem the element to create a view for
+ */
+ public HRuleView(Element elem) {
+ super(elem);
+ setPropertiesFromAttributes();
+ }
+
+ /**
+ * Update any cached values that come from attributes.
+ */
+ protected void setPropertiesFromAttributes() {
+ StyleSheet sheet = ((HTMLDocument)getDocument()).getStyleSheet();
+ AttributeSet eAttr = getElement().getAttributes();
+ attr = sheet.getViewAttributes(this);
+
+ alignment = StyleConstants.ALIGN_CENTER;
+ size = 0;
+ noshade = null;
+ widthValue = null;
+
+ if (attr != null) {
+ // getAlignment() returns ALIGN_LEFT by default, and HR should
+ // use ALIGN_CENTER by default, so we check if the alignment
+ // attribute is actually defined
+ if (attr.getAttribute(StyleConstants.Alignment) != null) {
+ alignment = StyleConstants.getAlignment(attr);
+ }
+
+ noshade = (String)eAttr.getAttribute(HTML.Attribute.NOSHADE);
+ Object value = eAttr.getAttribute(HTML.Attribute.SIZE);
+ if (value != null && (value instanceof String))
+ size = Integer.parseInt((String)value);
+ value = attr.getAttribute(CSS.Attribute.WIDTH);
+ if (value != null && (value instanceof CSS.LengthValue)) {
+ widthValue = (CSS.LengthValue)value;
+ }
+ topMargin = getLength(CSS.Attribute.MARGIN_TOP, attr);
+ bottomMargin = getLength(CSS.Attribute.MARGIN_BOTTOM, attr);
+ leftMargin = getLength(CSS.Attribute.MARGIN_LEFT, attr);
+ rightMargin = getLength(CSS.Attribute.MARGIN_RIGHT, attr);
+ }
+ else {
+ topMargin = bottomMargin = leftMargin = rightMargin = 0;
+ }
+ size = Math.max(2, size);
+ }
+
+ // This will be removed and centralized at some point, need to unify this
+ // and avoid private classes.
+ private float getLength(CSS.Attribute key, AttributeSet a) {
+ CSS.LengthValue lv = (CSS.LengthValue) a.getAttribute(key);
+ float len = (lv != null) ? lv.getValue() : 0;
+ return len;
+ }
+
+ // --- View methods ---------------------------------------------
+
+ /**
+ * Paints the view.
+ *
+ * @param g the graphics context
+ * @param a the allocation region for the view
+ * @see View#paint
+ */
+ public void paint(Graphics g, Shape a) {
+ Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a :
+ a.getBounds();
+ int x = 0;
+ int y = alloc.y + SPACE_ABOVE + (int)topMargin;
+ int width = alloc.width - (int)(leftMargin + rightMargin);
+ if (widthValue != null) {
+ width = (int)widthValue.getValue((float)width);
+ }
+ int height = alloc.height - (SPACE_ABOVE + SPACE_BELOW +
+ (int)topMargin + (int)bottomMargin);
+ if (size > 0)
+ height = size;
+
+ // Align the rule horizontally.
+ switch (alignment) {
+ case StyleConstants.ALIGN_CENTER:
+ x = alloc.x + (alloc.width / 2) - (width / 2);
+ break;
+ case StyleConstants.ALIGN_RIGHT:
+ x = alloc.x + alloc.width - width - (int)rightMargin;
+ break;
+ case StyleConstants.ALIGN_LEFT:
+ default:
+ x = alloc.x + (int)leftMargin;
+ break;
+ }
+
+ // Paint either a shaded rule or a solid line.
+ if (noshade != null) {
+ g.setColor(Color.black);
+ g.fillRect(x, y, width, height);
+ }
+ else {
+ Color bg = getContainer().getBackground();
+ Color bottom, top;
+ if (bg == null || bg.equals(Color.white)) {
+ top = Color.darkGray;
+ bottom = Color.lightGray;
+ }
+ else {
+ top = Color.darkGray;
+ bottom = Color.white;
+ }
+ g.setColor(bottom);
+ g.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
+ g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
+ g.setColor(top);
+ g.drawLine(x, y, x + width - 1, y);
+ g.drawLine(x, y, x, y + height - 1);
+ }
+
+ }
+
+
+ /**
+ * Calculates the desired shape of the rule... this is
+ * basically the preferred size of the border.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the desired span
+ * @see View#getPreferredSpan
+ */
+ public float getPreferredSpan(int axis) {
+ switch (axis) {
+ case View.X_AXIS:
+ return 1;
+ case View.Y_AXIS:
+ if (size > 0) {
+ return size + SPACE_ABOVE + SPACE_BELOW + topMargin +
+ bottomMargin;
+ } else {
+ if (noshade != null) {
+ return 2 + SPACE_ABOVE + SPACE_BELOW + topMargin +
+ bottomMargin;
+ } else {
+ return SPACE_ABOVE + SPACE_BELOW + topMargin +bottomMargin;
+ }
+ }
+ default:
+ throw new IllegalArgumentException("Invalid axis: " + axis);
+ }
+ }
+
+ /**
+ * Gets the resize weight for the axis.
+ * The rule is: rigid vertically and flexible horizontally.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the weight
+ */
+ public int getResizeWeight(int axis) {
+ if (axis == View.X_AXIS) {
+ return 1;
+ } else if (axis == View.Y_AXIS) {
+ return 0;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Determines how attractive a break opportunity in
+ * this view is. This is implemented to request a forced break.
+ *
+ * @param axis may be either View.X_AXIS or View.Y_AXIS
+ * @param pos the potential location of the start of the
+ * broken view (greater than or equal to zero).
+ * This may be useful for calculating tab
+ * positions.
+ * @param len specifies the relative length from <em>pos</em>
+ * where a potential break is desired. The value must be greater
+ * than or equal to zero.
+ * @return the weight, which should be a value between
+ * ForcedBreakWeight and BadBreakWeight.
+ */
+ public int getBreakWeight(int axis, float pos, float len) {
+ if (axis == X_AXIS) {
+ return ForcedBreakWeight;
+ }
+ return BadBreakWeight;
+ }
+
+ public View breakView(int axis, int offset, float pos, float len) {
+ return null;
+ }
+
+ /**
+ * Provides a mapping from the document model coordinate space
+ * to the coordinate space of the view mapped to it.
+ *
+ * @param pos the position to convert
+ * @param a the allocated region to render into
+ * @return the bounding box of the given position
+ * @exception BadLocationException if the given position does not
+ * represent a valid location in the associated document
+ * @see View#modelToView
+ */
+ public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
+ int p0 = getStartOffset();
+ int p1 = getEndOffset();
+ if ((pos >= p0) && (pos <= p1)) {
+ Rectangle r = a.getBounds();
+ if (pos == p1) {
+ r.x += r.width;
+ }
+ r.width = 0;
+ return r;
+ }
+ return null;
+ }
+
+ /**
+ * Provides a mapping from the view coordinate space to the logical
+ * coordinate space of the model.
+ *
+ * @param x the X coordinate
+ * @param y the Y coordinate
+ * @param a the allocated region to render into
+ * @return the location within the model that best represents the
+ * given point of view
+ * @see View#viewToModel
+ */
+ public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
+ Rectangle alloc = (Rectangle) a;
+ if (x < alloc.x + (alloc.width / 2)) {
+ bias[0] = Position.Bias.Forward;
+ return getStartOffset();
+ }
+ bias[0] = Position.Bias.Backward;
+ return getEndOffset();
+ }
+
+ /**
+ * Fetches the attributes to use when rendering. This is
+ * implemented to multiplex the attributes specified in the
+ * model with a StyleSheet.
+ */
+ public AttributeSet getAttributes() {
+ return attr;
+ }
+
+ public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
+ super.changedUpdate(changes, a, f);
+ int pos = changes.getOffset();
+ if (pos <= getStartOffset() && (pos + changes.getLength()) >=
+ getEndOffset()) {
+ setPropertiesFromAttributes();
+ }
+ }
+
+ // --- variables ------------------------------------------------
+
+ private float topMargin;
+ private float bottomMargin;
+ private float leftMargin;
+ private float rightMargin;
+ private int alignment = StyleConstants.ALIGN_CENTER;
+ private String noshade = null;
+ private int size = 0;
+ private CSS.LengthValue widthValue;
+
+ private static final int SPACE_ABOVE = 3;
+ private static final int SPACE_BELOW = 3;
+
+ /** View Attributes. */
+ private AttributeSet attr;
+}
diff --git a/src/share/classes/javax/swing/text/html/HTML.java b/src/share/classes/javax/swing/text/html/HTML.java
new file mode 100644
index 000000000..1976c16fa
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/HTML.java
@@ -0,0 +1,697 @@
+/*
+ * Copyright 1998-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.io.*;
+import java.util.Hashtable;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyleContext;
+
+/**
+ * Constants used in the <code>HTMLDocument</code>. These
+ * are basically tag and attribute definitions.
+ *
+ * @author Timothy Prinzing
+ * @author Sunita Mani
+ *
+ */
+public class HTML {
+
+ /**
+ * Typesafe enumeration for an HTML tag. Although the
+ * set of HTML tags is a closed set, we have left the
+ * set open so that people can add their own tag types
+ * to their custom parser and still communicate to the
+ * reader.
+ */
+ public static class Tag {
+
+ /** @since 1.3 */
+ public Tag() {}
+
+ /**
+ * Creates a new <code>Tag</code> with the specified <code>id</code>,
+ * and with <code>causesBreak</code> and <code>isBlock</code>
+ * set to <code>false</code>.
+ *
+ * @param id the id of the new tag
+ */
+ protected Tag(String id) {
+ this(id, false, false);
+ }
+
+ /**
+ * Creates a new <code>Tag</code> with the specified <code>id</code>;
+ * <code>causesBreak</code> and <code>isBlock</code> are defined
+ * by the user.
+ *
+ * @param id the id of the new tag
+ * @param causesBreak <code>true</code> if this tag
+ * causes a break to the flow of data
+ * @param isBlock <code>true</code> if the tag is used
+ * to add structure to a document
+ */
+ protected Tag(String id, boolean causesBreak, boolean isBlock) {
+ name = id;
+ this.breakTag = causesBreak;
+ this.blockTag = isBlock;
+ }
+
+ /**
+ * Returns <code>true</code> if this tag is a block
+ * tag, which is a tag used to add structure to a
+ * document.
+ *
+ * @return <code>true</code> if this tag is a block
+ * tag, otherwise returns <code>false</code>
+ */
+ public boolean isBlock() {
+ return blockTag;
+ }
+
+ /**
+ * Returns <code>true</code> if this tag causes a
+ * line break to the flow of data, otherwise returns
+ * <code>false</code>.
+ *
+ * @return <code>true</code> if this tag causes a
+ * line break to the flow of data, otherwise returns
+ * <code>false</code>
+ */
+ public boolean breaksFlow() {
+ return breakTag;
+ }
+
+ /**
+ * Returns <code>true</code> if this tag is pre-formatted,
+ * which is true if the tag is either <code>PRE</code> or
+ * <code>TEXTAREA</code>.
+ *
+ * @return <code>true</code> if this tag is pre-formatted,
+ * otherwise returns <code>false</code>
+ */
+ public boolean isPreformatted() {
+ return (this == PRE || this == TEXTAREA);
+ }
+
+ /**
+ * Returns the string representation of the
+ * tag.
+ *
+ * @return the <code>String</code> representation of the tag
+ */
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * Returns <code>true</code> if this tag is considered to be a paragraph
+ * in the internal HTML model. <code>false</code> - otherwise.
+ *
+ * @return <code>true</code> if this tag is considered to be a paragraph
+ * in the internal HTML model. <code>false</code> - otherwise.
+ * @see javax.swing.text.html.HTMLDocument#HTMLReader#ParagraphAction
+ */
+ boolean isParagraph() {
+ return (
+ this == P
+ || this == IMPLIED
+ || this == DT
+ || this == H1
+ || this == H2
+ || this == H3
+ || this == H4
+ || this == H5
+ || this == H6
+ );
+ }
+
+ boolean blockTag;
+ boolean breakTag;
+ String name;
+ boolean unknown;
+
+ // --- Tag Names -----------------------------------
+
+ public static final Tag A = new Tag("a");
+ public static final Tag ADDRESS = new Tag("address");
+ public static final Tag APPLET = new Tag("applet");
+ public static final Tag AREA = new Tag("area");
+ public static final Tag B = new Tag("b");
+ public static final Tag BASE = new Tag("base");
+ public static final Tag BASEFONT = new Tag("basefont");
+ public static final Tag BIG = new Tag("big");
+ public static final Tag BLOCKQUOTE = new Tag("blockquote", true, true);
+ public static final Tag BODY = new Tag("body", true, true);
+ public static final Tag BR = new Tag("br", true, false);
+ public static final Tag CAPTION = new Tag("caption");
+ public static final Tag CENTER = new Tag("center", true, false);
+ public static final Tag CITE = new Tag("cite");
+ public static final Tag CODE = new Tag("code");
+ public static final Tag DD = new Tag("dd", true, true);
+ public static final Tag DFN = new Tag("dfn");
+ public static final Tag DIR = new Tag("dir", true, true);
+ public static final Tag DIV = new Tag("div", true, true);
+ public static final Tag DL = new Tag("dl", true, true);
+ public static final Tag DT = new Tag("dt", true, true);
+ public static final Tag EM = new Tag("em");
+ public static final Tag FONT = new Tag("font");
+ public static final Tag FORM = new Tag("form", true, false);
+ public static final Tag FRAME = new Tag("frame");
+ public static final Tag FRAMESET = new Tag("frameset");
+ public static final Tag H1 = new Tag("h1", true, true);
+ public static final Tag H2 = new Tag("h2", true, true);
+ public static final Tag H3 = new Tag("h3", true, true);
+ public static final Tag H4 = new Tag("h4", true, true);
+ public static final Tag H5 = new Tag("h5", true, true);
+ public static final Tag H6 = new Tag("h6", true, true);
+ public static final Tag HEAD = new Tag("head", true, true);
+ public static final Tag HR = new Tag("hr", true, false);
+ public static final Tag HTML = new Tag("html", true, false);
+ public static final Tag I = new Tag("i");
+ public static final Tag IMG = new Tag("img");
+ public static final Tag INPUT = new Tag("input");
+ public static final Tag ISINDEX = new Tag("isindex", true, false);
+ public static final Tag KBD = new Tag("kbd");
+ public static final Tag LI = new Tag("li", true, true);
+ public static final Tag LINK = new Tag("link");
+ public static final Tag MAP = new Tag("map");
+ public static final Tag MENU = new Tag("menu", true, true);
+ public static final Tag META = new Tag("meta");
+ /*public*/ static final Tag NOBR = new Tag("nobr");
+ public static final Tag NOFRAMES = new Tag("noframes", true, true);
+ public static final Tag OBJECT = new Tag("object");
+ public static final Tag OL = new Tag("ol", true, true);
+ public static final Tag OPTION = new Tag("option");
+ public static final Tag P = new Tag("p", true, true);
+ public static final Tag PARAM = new Tag("param");
+ public static final Tag PRE = new Tag("pre", true, true);
+ public static final Tag SAMP = new Tag("samp");
+ public static final Tag SCRIPT = new Tag("script");
+ public static final Tag SELECT = new Tag("select");
+ public static final Tag SMALL = new Tag("small");
+ public static final Tag SPAN = new Tag("span");
+ public static final Tag STRIKE = new Tag("strike");
+ public static final Tag S = new Tag("s");
+ public static final Tag STRONG = new Tag("strong");
+ public static final Tag STYLE = new Tag("style");
+ public static final Tag SUB = new Tag("sub");
+ public static final Tag SUP = new Tag("sup");
+ public static final Tag TABLE = new Tag("table", false, true);
+ public static final Tag TD = new Tag("td", true, true);
+ public static final Tag TEXTAREA = new Tag("textarea");
+ public static final Tag TH = new Tag("th", true, true);
+ public static final Tag TITLE = new Tag("title", true, true);
+ public static final Tag TR = new Tag("tr", false, true);
+ public static final Tag TT = new Tag("tt");
+ public static final Tag U = new Tag("u");
+ public static final Tag UL = new Tag("ul", true, true);
+ public static final Tag VAR = new Tag("var");
+
+ /**
+ * All text content must be in a paragraph element.
+ * If a paragraph didn't exist when content was
+ * encountered, a paragraph is manufactured.
+ * <p>
+ * This is a tag synthesized by the HTML reader.
+ * Since elements are identified by their tag type,
+ * we create a some fake tag types to mark the elements
+ * that were manufactured.
+ */
+ public static final Tag IMPLIED = new Tag("p-implied");
+
+ /**
+ * All text content is labeled with this tag.
+ * <p>
+ * This is a tag synthesized by the HTML reader.
+ * Since elements are identified by their tag type,
+ * we create a some fake tag types to mark the elements
+ * that were manufactured.
+ */
+ public static final Tag CONTENT = new Tag("content");
+
+ /**
+ * All comments are labeled with this tag.
+ * <p>
+ * This is a tag synthesized by the HTML reader.
+ * Since elements are identified by their tag type,
+ * we create a some fake tag types to mark the elements
+ * that were manufactured.
+ */
+ public static final Tag COMMENT = new Tag("comment");
+
+ static final Tag allTags[] = {
+ A, ADDRESS, APPLET, AREA, B, BASE, BASEFONT, BIG,
+ BLOCKQUOTE, BODY, BR, CAPTION, CENTER, CITE, CODE,
+ DD, DFN, DIR, DIV, DL, DT, EM, FONT, FORM, FRAME,
+ FRAMESET, H1, H2, H3, H4, H5, H6, HEAD, HR, HTML,
+ I, IMG, INPUT, ISINDEX, KBD, LI, LINK, MAP, MENU,
+ META, NOBR, NOFRAMES, OBJECT, OL, OPTION, P, PARAM,
+ PRE, SAMP, SCRIPT, SELECT, SMALL, SPAN, STRIKE, S,
+ STRONG, STYLE, SUB, SUP, TABLE, TD, TEXTAREA,
+ TH, TITLE, TR, TT, U, UL, VAR
+ };
+
+ static {
+ // Force HTMLs static initialize to be loaded.
+ getTag("html");
+ }
+ }
+
+ // There is no unique instance of UnknownTag, so we allow it to be
+ // Serializable.
+ public static class UnknownTag extends Tag implements Serializable {
+
+ /**
+ * Creates a new <code>UnknownTag</code> with the specified
+ * <code>id</code>.
+ * @param id the id of the new tag
+ */
+ public UnknownTag(String id) {
+ super(id);
+ }
+
+ /**
+ * Returns the hash code which corresponds to the string
+ * for this tag.
+ */
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ /**
+ * Compares this object to the specifed object.
+ * The result is <code>true</code> if and only if the argument is not
+ * <code>null</code> and is an <code>UnknownTag</code> object
+ * with the same name.
+ *
+ * @param obj the object to compare this tag with
+ * @return <code>true</code> if the objects are equal;
+ * <code>false</code> otherwise
+ */
+ public boolean equals(Object obj) {
+ if (obj instanceof UnknownTag) {
+ return toString().equals(obj.toString());
+ }
+ return false;
+ }
+
+ private void writeObject(java.io.ObjectOutputStream s)
+ throws IOException {
+ s.defaultWriteObject();
+ s.writeBoolean(blockTag);
+ s.writeBoolean(breakTag);
+ s.writeBoolean(unknown);
+ s.writeObject(name);
+ }
+
+ private void readObject(ObjectInputStream s)
+ throws ClassNotFoundException, IOException {
+ s.defaultReadObject();
+ blockTag = s.readBoolean();
+ breakTag = s.readBoolean();
+ unknown = s.readBoolean();
+ name = (String)s.readObject();
+ }
+ }
+
+ /**
+ * Typesafe enumeration representing an HTML
+ * attribute.
+ */
+ public static final class Attribute {
+
+ /**
+ * Creates a new <code>Attribute</code> with the specified
+ * <code>id</code>.
+ *
+ * @param id the id of the new <code>Attribute</code>
+ */
+ Attribute(String id) {
+ name = id;
+ }
+
+ /**
+ * Returns the string representation of this attribute.
+ * @return the string representation of this attribute
+ */
+ public String toString() {
+ return name;
+ }
+
+ private String name;
+
+ public static final Attribute SIZE = new Attribute("size");
+ public static final Attribute COLOR = new Attribute("color");
+ public static final Attribute CLEAR = new Attribute("clear");
+ public static final Attribute BACKGROUND = new Attribute("background");
+ public static final Attribute BGCOLOR = new Attribute("bgcolor");
+ public static final Attribute TEXT = new Attribute("text");
+ public static final Attribute LINK = new Attribute("link");
+ public static final Attribute VLINK = new Attribute("vlink");
+ public static final Attribute ALINK = new Attribute("alink");
+ public static final Attribute WIDTH = new Attribute("width");
+ public static final Attribute HEIGHT = new Attribute("height");
+ public static final Attribute ALIGN = new Attribute("align");
+ public static final Attribute NAME = new Attribute("name");
+ public static final Attribute HREF = new Attribute("href");
+ public static final Attribute REL = new Attribute("rel");
+ public static final Attribute REV = new Attribute("rev");
+ public static final Attribute TITLE = new Attribute("title");
+ public static final Attribute TARGET = new Attribute("target");
+ public static final Attribute SHAPE = new Attribute("shape");
+ public static final Attribute COORDS = new Attribute("coords");
+ public static final Attribute ISMAP = new Attribute("ismap");
+ public static final Attribute NOHREF = new Attribute("nohref");
+ public static final Attribute ALT = new Attribute("alt");
+ public static final Attribute ID = new Attribute("id");
+ public static final Attribute SRC = new Attribute("src");
+ public static final Attribute HSPACE = new Attribute("hspace");
+ public static final Attribute VSPACE = new Attribute("vspace");
+ public static final Attribute USEMAP = new Attribute("usemap");
+ public static final Attribute LOWSRC = new Attribute("lowsrc");
+ public static final Attribute CODEBASE = new Attribute("codebase");
+ public static final Attribute CODE = new Attribute("code");
+ public static final Attribute ARCHIVE = new Attribute("archive");
+ public static final Attribute VALUE = new Attribute("value");
+ public static final Attribute VALUETYPE = new Attribute("valuetype");
+ public static final Attribute TYPE = new Attribute("type");
+ public static final Attribute CLASS = new Attribute("class");
+ public static final Attribute STYLE = new Attribute("style");
+ public static final Attribute LANG = new Attribute("lang");
+ public static final Attribute FACE = new Attribute("face");
+ public static final Attribute DIR = new Attribute("dir");
+ public static final Attribute DECLARE = new Attribute("declare");
+ public static final Attribute CLASSID = new Attribute("classid");
+ public static final Attribute DATA = new Attribute("data");
+ public static final Attribute CODETYPE = new Attribute("codetype");
+ public static final Attribute STANDBY = new Attribute("standby");
+ public static final Attribute BORDER = new Attribute("border");
+ public static final Attribute SHAPES = new Attribute("shapes");
+ public static final Attribute NOSHADE = new Attribute("noshade");
+ public static final Attribute COMPACT = new Attribute("compact");
+ public static final Attribute START = new Attribute("start");
+ public static final Attribute ACTION = new Attribute("action");
+ public static final Attribute METHOD = new Attribute("method");
+ public static final Attribute ENCTYPE = new Attribute("enctype");
+ public static final Attribute CHECKED = new Attribute("checked");
+ public static final Attribute MAXLENGTH = new Attribute("maxlength");
+ public static final Attribute MULTIPLE = new Attribute("multiple");
+ public static final Attribute SELECTED = new Attribute("selected");
+ public static final Attribute ROWS = new Attribute("rows");
+ public static final Attribute COLS = new Attribute("cols");
+ public static final Attribute DUMMY = new Attribute("dummy");
+ public static final Attribute CELLSPACING = new Attribute("cellspacing");
+ public static final Attribute CELLPADDING = new Attribute("cellpadding");
+ public static final Attribute VALIGN = new Attribute("valign");
+ public static final Attribute HALIGN = new Attribute("halign");
+ public static final Attribute NOWRAP = new Attribute("nowrap");
+ public static final Attribute ROWSPAN = new Attribute("rowspan");
+ public static final Attribute COLSPAN = new Attribute("colspan");
+ public static final Attribute PROMPT = new Attribute("prompt");
+ public static final Attribute HTTPEQUIV = new Attribute("http-equiv");
+ public static final Attribute CONTENT = new Attribute("content");
+ public static final Attribute LANGUAGE = new Attribute("language");
+ public static final Attribute VERSION = new Attribute("version");
+ public static final Attribute N = new Attribute("n");
+ public static final Attribute FRAMEBORDER = new Attribute("frameborder");
+ public static final Attribute MARGINWIDTH = new Attribute("marginwidth");
+ public static final Attribute MARGINHEIGHT = new Attribute("marginheight");
+ public static final Attribute SCROLLING = new Attribute("scrolling");
+ public static final Attribute NORESIZE = new Attribute("noresize");
+ public static final Attribute ENDTAG = new Attribute("endtag");
+ public static final Attribute COMMENT = new Attribute("comment");
+ static final Attribute MEDIA = new Attribute("media");
+
+ static final Attribute allAttributes[] = {
+ FACE,
+ COMMENT,
+ SIZE,
+ COLOR,
+ CLEAR,
+ BACKGROUND,
+ BGCOLOR,
+ TEXT,
+ LINK,
+ VLINK,
+ ALINK,
+ WIDTH,
+ HEIGHT,
+ ALIGN,
+ NAME,
+ HREF,
+ REL,
+ REV,
+ TITLE,
+ TARGET,
+ SHAPE,
+ COORDS,
+ ISMAP,
+ NOHREF,
+ ALT,
+ ID,
+ SRC,
+ HSPACE,
+ VSPACE,
+ USEMAP,
+ LOWSRC,
+ CODEBASE,
+ CODE,
+ ARCHIVE,
+ VALUE,
+ VALUETYPE,
+ TYPE,
+ CLASS,
+ STYLE,
+ LANG,
+ DIR,
+ DECLARE,
+ CLASSID,
+ DATA,
+ CODETYPE,
+ STANDBY,
+ BORDER,
+ SHAPES,
+ NOSHADE,
+ COMPACT,
+ START,
+ ACTION,
+ METHOD,
+ ENCTYPE,
+ CHECKED,
+ MAXLENGTH,
+ MULTIPLE,
+ SELECTED,
+ ROWS,
+ COLS,
+ DUMMY,
+ CELLSPACING,
+ CELLPADDING,
+ VALIGN,
+ HALIGN,
+ NOWRAP,
+ ROWSPAN,
+ COLSPAN,
+ PROMPT,
+ HTTPEQUIV,
+ CONTENT,
+ LANGUAGE,
+ VERSION,
+ N,
+ FRAMEBORDER,
+ MARGINWIDTH,
+ MARGINHEIGHT,
+ SCROLLING,
+ NORESIZE,
+ MEDIA,
+ ENDTAG
+ };
+ }
+
+ // The secret to 73, is that, given that the Hashtable contents
+ // never change once the static initialization happens, the initial size
+ // that the hashtable grew to was determined, and then that very size
+ // is used.
+ //
+ private static final Hashtable tagHashtable = new Hashtable(73);
+
+ /** Maps from StyleConstant key to HTML.Tag. */
+ private static final Hashtable scMapping = new Hashtable(8);
+
+ static {
+
+ for (int i = 0; i < Tag.allTags.length; i++ ) {
+ tagHashtable.put(Tag.allTags[i].toString(), Tag.allTags[i]);
+ StyleContext.registerStaticAttributeKey(Tag.allTags[i]);
+ }
+ StyleContext.registerStaticAttributeKey(Tag.IMPLIED);
+ StyleContext.registerStaticAttributeKey(Tag.CONTENT);
+ StyleContext.registerStaticAttributeKey(Tag.COMMENT);
+ for (int i = 0; i < Attribute.allAttributes.length; i++) {
+ StyleContext.registerStaticAttributeKey(Attribute.
+ allAttributes[i]);
+ }
+ StyleContext.registerStaticAttributeKey(HTML.NULL_ATTRIBUTE_VALUE);
+ scMapping.put(StyleConstants.Bold, Tag.B);
+ scMapping.put(StyleConstants.Italic, Tag.I);
+ scMapping.put(StyleConstants.Underline, Tag.U);
+ scMapping.put(StyleConstants.StrikeThrough, Tag.STRIKE);
+ scMapping.put(StyleConstants.Superscript, Tag.SUP);
+ scMapping.put(StyleConstants.Subscript, Tag.SUB);
+ scMapping.put(StyleConstants.FontFamily, Tag.FONT);
+ scMapping.put(StyleConstants.FontSize, Tag.FONT);
+ }
+
+ /**
+ * Returns the set of actual HTML tags that
+ * are recognized by the default HTML reader.
+ * This set does not include tags that are
+ * manufactured by the reader.
+ */
+ public static Tag[] getAllTags() {
+ Tag[] tags = new Tag[Tag.allTags.length];
+ System.arraycopy(Tag.allTags, 0, tags, 0, Tag.allTags.length);
+ return tags;
+ }
+
+ /**
+ * Fetches a tag constant for a well-known tag name (i.e. one of
+ * the tags in the set {A, ADDRESS, APPLET, AREA, B,
+ * BASE, BASEFONT, BIG,
+ * BLOCKQUOTE, BODY, BR, CAPTION, CENTER, CITE, CODE,
+ * DD, DFN, DIR, DIV, DL, DT, EM, FONT, FORM, FRAME,
+ * FRAMESET, H1, H2, H3, H4, H5, H6, HEAD, HR, HTML,
+ * I, IMG, INPUT, ISINDEX, KBD, LI, LINK, MAP, MENU,
+ * META, NOBR, NOFRAMES, OBJECT, OL, OPTION, P, PARAM,
+ * PRE, SAMP, SCRIPT, SELECT, SMALL, SPAN, STRIKE, S,
+ * STRONG, STYLE, SUB, SUP, TABLE, TD, TEXTAREA,
+ * TH, TITLE, TR, TT, U, UL, VAR}. If the given
+ * name does not represent one of the well-known tags, then
+ * <code>null</code> will be returned.
+ *
+ * @param tagName the <code>String</code> name requested
+ * @return a tag constant corresponding to the <code>tagName</code>,
+ * or <code>null</code> if not found
+ */
+ public static Tag getTag(String tagName) {
+
+ Object t = tagHashtable.get(tagName);
+ return (t == null ? null : (Tag)t);
+ }
+
+ /**
+ * Returns the HTML <code>Tag</code> associated with the
+ * <code>StyleConstants</code> key <code>sc</code>.
+ * If no matching <code>Tag</code> is found, returns
+ * <code>null</code>.
+ *
+ * @param sc the <code>StyleConstants</code> key
+ * @return tag which corresponds to <code>sc</code>, or
+ * <code>null</code> if not found
+ */
+ static Tag getTagForStyleConstantsKey(StyleConstants sc) {
+ return (Tag)scMapping.get(sc);
+ }
+
+ /**
+ * Fetches an integer attribute value. Attribute values
+ * are stored as a string, and this is a convenience method
+ * to convert to an actual integer.
+ *
+ * @param attr the set of attributes to use to try to fetch a value
+ * @param key the key to use to fetch the value
+ * @param def the default value to use if the attribute isn't
+ * defined or there is an error converting to an integer
+ */
+ public static int getIntegerAttributeValue(AttributeSet attr,
+ Attribute key, int def) {
+ int value = def;
+ String istr = (String) attr.getAttribute(key);
+ if (istr != null) {
+ try {
+ value = Integer.valueOf(istr).intValue();
+ } catch (NumberFormatException e) {
+ value = def;
+ }
+ }
+ return value;
+ }
+
+ // This is used in cases where the value for the attribute has not
+ // been specified.
+ //
+ public static final String NULL_ATTRIBUTE_VALUE = "#DEFAULT";
+
+ // size determined similar to size of tagHashtable
+ private static final Hashtable attHashtable = new Hashtable(77);
+
+ static {
+
+ for (int i = 0; i < Attribute.allAttributes.length; i++ ) {
+ attHashtable.put(Attribute.allAttributes[i].toString(), Attribute.allAttributes[i]);
+ }
+ }
+
+ /**
+ * Returns the set of HTML attributes recognized.
+ * @return the set of HTML attributes recognized
+ */
+ public static Attribute[] getAllAttributeKeys() {
+ Attribute[] attributes = new Attribute[Attribute.allAttributes.length];
+ System.arraycopy(Attribute.allAttributes, 0,
+ attributes, 0, Attribute.allAttributes.length);
+ return attributes;
+ }
+
+ /**
+ * Fetches an attribute constant for a well-known attribute name
+ * (i.e. one of the attributes in the set {FACE, COMMENT, SIZE,
+ * COLOR, CLEAR, BACKGROUND, BGCOLOR, TEXT, LINK, VLINK, ALINK,
+ * WIDTH, HEIGHT, ALIGN, NAME, HREF, REL, REV, TITLE, TARGET,
+ * SHAPE, COORDS, ISMAP, NOHREF, ALT, ID, SRC, HSPACE, VSPACE,
+ * USEMAP, LOWSRC, CODEBASE, CODE, ARCHIVE, VALUE, VALUETYPE,
+ * TYPE, CLASS, STYLE, LANG, DIR, DECLARE, CLASSID, DATA, CODETYPE,
+ * STANDBY, BORDER, SHAPES, NOSHADE, COMPACT, START, ACTION, METHOD,
+ * ENCTYPE, CHECKED, MAXLENGTH, MULTIPLE, SELECTED, ROWS, COLS,
+ * DUMMY, CELLSPACING, CELLPADDING, VALIGN, HALIGN, NOWRAP, ROWSPAN,
+ * COLSPAN, PROMPT, HTTPEQUIV, CONTENT, LANGUAGE, VERSION, N,
+ * FRAMEBORDER, MARGINWIDTH, MARGINHEIGHT, SCROLLING, NORESIZE,
+ * MEDIA, ENDTAG}).
+ * If the given name does not represent one of the well-known attributes,
+ * then <code>null</code> will be returned.
+ *
+ * @param attName the <code>String</code> requested
+ * @return the <code>Attribute</code> corresponding to <code>attName</code>
+ */
+ public static Attribute getAttributeKey(String attName) {
+ Object a = attHashtable.get(attName);
+ if (a == null) {
+ return null;
+ }
+ return (Attribute)a;
+ }
+
+}
diff --git a/src/share/classes/javax/swing/text/html/HTMLDocument.java b/src/share/classes/javax/swing/text/html/HTMLDocument.java
new file mode 100644
index 000000000..871372cb8
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/HTMLDocument.java
@@ -0,0 +1,4185 @@
+/*
+ * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.font.TextAttribute;
+import java.util.*;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.net.MalformedURLException;
+import java.io.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.*;
+import javax.swing.undo.*;
+import java.text.Bidi;
+import sun.swing.SwingUtilities2;
+
+/**
+ * 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 &lt;BASE&gt; 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>
+ * &lt;html>
+ * &lt;head>
+ * &lt;title>An example HTMLDocument&lt;/title>
+ * &lt;style type="text/css">
+ * div { background-color: silver; }
+ * ul { color: red; }
+ * &lt;/style>
+ * &lt;/head>
+ * &lt;body>
+ * &lt;div id="BOX">
+ * &lt;p>Paragraph 1&lt;/p>
+ * &lt;p>Paragraph 2&lt;/p>
+ * &lt;/div>
+ * &lt;/body>
+ * &lt;/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, "&lt;ul>&lt;li>List
+ * Item&lt;/li>&lt;/ul>")</code> inserts the list before the first
+ * paragraph, and <code>d.insertBeforeEnd(e, "&lt;ul>&lt;li>List
+ * Item&lt;/li>&lt;/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,
+ * "&lt;ul>&lt;li>List Item&lt;/li>&lt;/ul>")</code> inserts the list
+ * before the <code>DIV</code> element, and <code>d.insertAfterEnd(e,
+ * "&lt;ul>&lt;li>List Item&lt;/li>&lt;/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, "&lt;ul>&lt;li>List
+ * Item&lt;/li>&lt;/ul>")</code> replaces all children paragraphs with
+ * the list, and <code>d.setOuterHTML(e, "&lt;ul>&lt;li>List
+ * Item&lt;/li>&lt;/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 nowrap="nowrap">
+ * <div style="background-color: silver;">
+ * <p>Paragraph 1</p>
+ * <p>Paragraph 2</p>
+ * </div>
+ * </td>
+ * <!--insertAfterStart-->
+ * <td nowrap="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 nowrap="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 nowrap="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 nowrap="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 nowrap="nowrap">
+ * <div style="background-color: silver;">
+ * <ul style="color: red;">
+ * <li>List Item</li>
+ * </ul>
+ * </div>
+ * </td>
+ * <!--setOuterHTML-->
+ * <td nowrap="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<sup><font
+ * size="-2">TM</font></sup> 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="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
+ * to Use Threads</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 &lt;FRAMESET&gt; element, and inserts a new &lt;FRAME&gt;
+ * element, and sets its <code>HTML.Attribute.SRC</code> attribute
+ * to have a value equal to the destination URL and fire a
+ * <code>RemovedUpdate</code> and <code>InsertUpdate</code>.
+ *<p>
+ * If the target is _top, this method does nothing. In the implementation
+ * of the view for a frame, namely the <code>FrameView</code>,
+ * the processing of _top is handled. Given that _top implies
+ * replacing the entire document, it made sense to handle this outside
+ * of the document that it will replace.
+ *<p>
+ * If the target is a named frame, then the element hierarchy is searched
+ * for an element with a name equal to the target, its
+ * <code>HTML.Attribute.SRC</code> attribute is updated and a
+ * <code>ChangedUpdate</code> event is fired.
+ *
+ * @param e the event
+ */
+ public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent e) {
+ String frameName = e.getTarget();
+ Element element = e.getSourceElement();
+ String urlStr = e.getURL().toString();
+
+ if (frameName.equals("_self")) {
+ /*
+ The source and destination elements
+ are the same.
+ */
+ updateFrame(element, urlStr);
+ } else if (frameName.equals("_parent")) {
+ /*
+ The destination is the parent of the frame.
+ */
+ updateFrameSet(element.getParentElement(), urlStr);
+ } else {
+ /*
+ locate a named frame
+ */
+ Element targetElement = findFrame(frameName);
+ if (targetElement != null) {
+ updateFrame(targetElement, urlStr);
+ }
+ }
+ }
+
+
+ /**
+ * Searches the element hierarchy for an FRAME element
+ * that has its name attribute equal to the <code>frameName</code>.
+ *
+ * @param frameName
+ * @return the element whose NAME attribute has a value of
+ * <code>frameName</code>; returns <code>null</code>
+ * if not found
+ */
+ private Element findFrame(String frameName) {
+ ElementIterator it = new ElementIterator(this);
+ Element next = null;
+
+ while ((next = it.next()) != null) {
+ AttributeSet attr = next.getAttributes();
+ if (matchNameAttribute(attr, HTML.Tag.FRAME)) {
+ String frameTarget = (String)attr.getAttribute(HTML.Attribute.NAME);
+ if (frameTarget != null && frameTarget.equals(frameName)) {
+ break;
+ }
+ }
+ }
+ return next;
+ }
+
+ /**
+ * Returns true if <code>StyleConstants.NameAttribute</code> is
+ * equal to the tag that is passed in as a parameter.
+ *
+ * @param attr the attributes to be matched
+ * @param tag the value to be matched
+ * @return true if there is a match, false otherwise
+ * @see javax.swing.text.html.HTML.Attribute
+ */
+ static boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
+ Object o = attr.getAttribute(StyleConstants.NameAttribute);
+ if (o instanceof HTML.Tag) {
+ HTML.Tag name = (HTML.Tag) o;
+ if (name == tag) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Replaces a frameset branch Element with a frame leaf element.
+ *
+ * @param element the frameset element to remove
+ * @param url the value for the SRC attribute for the
+ * new frame that will replace the frameset
+ */
+ private void updateFrameSet(Element element, String url) {
+ try {
+ int startOffset = element.getStartOffset();
+ int endOffset = Math.min(getLength(), element.getEndOffset());
+ String html = "<frame";
+ if (url != null) {
+ html += " src=\"" + url + "\"";
+ }
+ html += ">";
+ installParserIfNecessary();
+ setOuterHTML(element, html);
+ } catch (BadLocationException e1) {
+ // Should handle this better
+ } catch (IOException ioe) {
+ // Should handle this better
+ }
+ }
+
+
+ /**
+ * Updates the Frame elements <code>HTML.Attribute.SRC attribute</code>
+ * and fires a <code>ChangedUpdate</code> event.
+ *
+ * @param element a FRAME element whose SRC attribute will be updated
+ * @param url a string specifying the new value for the SRC attribute
+ */
+ private void updateFrame(Element element, String url) {
+
+ try {
+ writeLock();
+ DefaultDocumentEvent changes = new DefaultDocumentEvent(element.getStartOffset(),
+ 1,
+ DocumentEvent.EventType.CHANGE);
+ AttributeSet sCopy = element.getAttributes().copyAttributes();
+ MutableAttributeSet attr = (MutableAttributeSet) element.getAttributes();
+ changes.addEdit(new AttributeUndoableEdit(element, sCopy, false));
+ attr.removeAttribute(HTML.Attribute.SRC);
+ attr.addAttribute(HTML.Attribute.SRC, url);
+ changes.end();
+ fireChangedUpdate(changes);
+ fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
+ } finally {
+ writeUnlock();
+ }
+ }
+
+
+ /**
+ * Returns true if the document will be viewed in a frame.
+ * @return true if document will be viewed in a frame, otherwise false
+ */
+ boolean isFrameDocument() {
+ return frameDocument;
+ }
+
+ /**
+ * Sets a boolean state about whether the document will be
+ * viewed in a frame.
+ * @param frameDoc true if the document will be viewed in a frame,
+ * otherwise false
+ */
+ void setFrameDocumentState(boolean frameDoc) {
+ this.frameDocument = frameDoc;
+ }
+
+ /**
+ * Adds the specified map, this will remove a Map that has been
+ * previously registered with the same name.
+ *
+ * @param map the <code>Map</code> to be registered
+ */
+ void addMap(Map map) {
+ String name = map.getName();
+
+ if (name != null) {
+ Object maps = getProperty(MAP_PROPERTY);
+
+ if (maps == null) {
+ maps = new Hashtable(11);
+ putProperty(MAP_PROPERTY, maps);
+ }
+ if (maps instanceof Hashtable) {
+ ((Hashtable)maps).put("#" + name, map);
+ }
+ }
+ }
+
+ /**
+ * Removes a previously registered map.
+ * @param map the <code>Map</code> to be removed
+ */
+ void removeMap(Map map) {
+ String name = map.getName();
+
+ if (name != null) {
+ Object maps = getProperty(MAP_PROPERTY);
+
+ if (maps instanceof Hashtable) {
+ ((Hashtable)maps).remove("#" + name);
+ }
+ }
+ }
+
+ /**
+ * Returns the Map associated with the given name.
+ * @param the name of the desired <code>Map</code>
+ * @return the <code>Map</code> or <code>null</code> if it can't
+ * be found, or if <code>name</code> is <code>null</code>
+ */
+ Map getMap(String name) {
+ if (name != null) {
+ Object maps = getProperty(MAP_PROPERTY);
+
+ if (maps != null && (maps instanceof Hashtable)) {
+ return (Map)((Hashtable)maps).get(name);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns an <code>Enumeration</code> of the possible Maps.
+ * @return the enumerated list of maps, or <code>null</code>
+ * if the maps are not an instance of <code>Hashtable</code>
+ */
+ Enumeration getMaps() {
+ Object maps = getProperty(MAP_PROPERTY);
+
+ if (maps instanceof Hashtable) {
+ return ((Hashtable)maps).elements();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the content type language used for style sheets that do not
+ * explicitly specify the type. The default is text/css.
+ * @param contentType the content type language for the style sheets
+ */
+ /* public */
+ void setDefaultStyleSheetType(String contentType) {
+ putProperty(StyleType, contentType);
+ }
+
+ /**
+ * Returns the content type language used for style sheets. The default
+ * is text/css.
+ * @return the content type language used for the style sheets
+ */
+ /* public */
+ String getDefaultStyleSheetType() {
+ String retValue = (String)getProperty(StyleType);
+ if (retValue == null) {
+ return "text/css";
+ }
+ return retValue;
+ }
+
+ /**
+ * Sets the parser that is used by the methods that insert html
+ * into the existing document, such as <code>setInnerHTML</code>,
+ * and <code>setOuterHTML</code>.
+ * <p>
+ * <code>HTMLEditorKit.createDefaultDocument</code> will set the parser
+ * for you. If you create an <code>HTMLDocument</code> by hand,
+ * be sure and set the parser accordingly.
+ * @param parser the parser to be used for text insertion
+ *
+ * @since 1.3
+ */
+ public void setParser(HTMLEditorKit.Parser parser) {
+ this.parser = parser;
+ putProperty("__PARSER__", null);
+ }
+
+ /**
+ * Returns the parser that is used when inserting HTML into the existing
+ * document.
+ * @return the parser used for text insertion
+ *
+ * @since 1.3
+ */
+ public HTMLEditorKit.Parser getParser() {
+ Object p = getProperty("__PARSER__");
+
+ if (p instanceof HTMLEditorKit.Parser) {
+ return (HTMLEditorKit.Parser)p;
+ }
+ return parser;
+ }
+
+ /**
+ * Replaces the children of the given element with the contents
+ * specified as an HTML string.
+ *
+ * <p>This will be seen as at least two events, n inserts followed by
+ * a remove.</p>
+ *
+ * <p>Consider the following structure (the <code>elem</code>
+ * parameter is <b>in bold</b>).</p>
+ *
+ * <pre>
+ * &lt;body>
+ * |
+ * <b>&lt;div></b>
+ * / \
+ * &lt;p> &lt;p>
+ * </pre>
+ *
+ * <p>Invoking <code>setInnerHTML(elem, "&lt;ul>&lt;li>")</code>
+ * results in the following structure (new elements are <font
+ * color="red">in red</font>).</p>
+ *
+ * <pre>
+ * &lt;body>
+ * |
+ * <b>&lt;div></b>
+ * \
+ * <font color="red">&lt;ul></font>
+ * \
+ * <font color="red">&lt;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 correcty, the document must have an
+ * <code>HTMLEditorKit.Parser</code> set. This will be the case
+ * if the document was created from an HTMLEditorKit via the
+ * <code>createDefaultDocument</code> method.</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 &lt;img&gt; this would create
+ * two elements, one for the image, ane 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>
+ * &lt;body>
+ * |
+ * <b>&lt;div></b>
+ * / \
+ * &lt;p> &lt;p>
+ * </pre>
+ *
+ * <p>Invoking <code>setOuterHTML(elem, "&lt;ul>&lt;li>")</code>
+ * results in the following structure (new elements are <font
+ * color="red">in red</font>).</p>
+ *
+ * <pre>
+ * &lt;body>
+ * |
+ * <font color="red">&lt;ul></font>
+ * \
+ * <font color="red">&lt;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 correcty, the document must have an
+ * HTMLEditorKit.Parser set. This will be the case if the document
+ * was created from an HTMLEditorKit via the
+ * <code>createDefaultDocument</code> method.</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>
+ * &lt;body>
+ * |
+ * <b>&lt;div></b>
+ * / \
+ * &lt;p> &lt;p>
+ * </pre>
+ *
+ * <p>Invoking <code>insertAfterStart(elem,
+ * "&lt;ul>&lt;li>")</code> results in the following structure
+ * (new elements are <font color="red">in red</font>).</p>
+ *
+ * <pre>
+ * &lt;body>
+ * |
+ * <b>&lt;div></b>
+ * / | \
+ * <font color="red">&lt;ul></font> &lt;p> &lt;p>
+ * /
+ * <font color="red">&lt;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 correcty, the document must have an
+ * <code>HTMLEditorKit.Parser</code> set. This will be the case
+ * if the document was created from an HTMLEditorKit via the
+ * <code>createDefaultDocument</code> method.</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 && 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>
+ * &lt;body>
+ * |
+ * <b>&lt;div></b>
+ * / \
+ * &lt;p> &lt;p>
+ * </pre>
+ *
+ * <p>Invoking <code>insertBeforeEnd(elem, "&lt;ul>&lt;li>")</code>
+ * results in the following structure (new elements are <font
+ * color="red">in red</font>).</p>
+ *
+ * <pre>
+ * &lt;body>
+ * |
+ * <b>&lt;div></b>
+ * / | \
+ * &lt;p> &lt;p> <font color="red">&lt;ul></font>
+ * \
+ * <font color="red">&lt;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 correcty, the document must have an
+ * <code>HTMLEditorKit.Parser</code> set. This will be the case
+ * if the document was created from an HTMLEditorKit via the
+ * <code>createDefaultDocument</code> method.</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>
+ * &lt;body>
+ * |
+ * <b>&lt;div></b>
+ * / \
+ * &lt;p> &lt;p>
+ * </pre>
+ *
+ * <p>Invoking <code>insertBeforeStart(elem,
+ * "&lt;ul>&lt;li>")</code> results in the following structure
+ * (new elements are <font color="red">in red</font>).</p>
+ *
+ * <pre>
+ * &lt;body>
+ * / \
+ * <font color="red">&lt;ul></font> <b>&lt;div></b>
+ * / / \
+ * <font color="red">&lt;li></font> &lt;p> &lt;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 correcty, the document must have an
+ * <code>HTMLEditorKit.Parser</code> set. This will be the case
+ * if the document was created from an HTMLEditorKit via the
+ * <code>createDefaultDocument</code> method.</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>
+ * &lt;body>
+ * |
+ * <b>&lt;div></b>
+ * / \
+ * &lt;p> &lt;p>
+ * </pre>
+ *
+ * <p>Invoking <code>insertAfterEnd(elem, "&lt;ul>&lt;li>")</code>
+ * results in the following structure (new elements are <font
+ * color="red">in red</font>).</p>
+ *
+ * <pre>
+ * &lt;body>
+ * / \
+ * <b>&lt;div></b> <font color="red">&lt;ul></font>
+ * / \ \
+ * &lt;p> &lt;p> <font color="red">&lt;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 correcty, the document must have an
+ * <code>HTMLEditorKit.Parser</code> set. This will be the case
+ * if the document was created from an HTMLEditorKit via the
+ * <code>createDefaultDocument</code> method.</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) {
+ int offset = elem.getEndOffset();
+ if (offset > getLength()) {
+ offset--;
+ }
+ else if (elem.isLeaf() && getText(offset - 1, 1).
+ charAt(0) == NEWLINE[0]) {
+ offset--;
+ }
+ insertHTML(parent, offset, htmlText, false);
+ }
+ }
+ }
+
+ /**
+ * Returns the element that has the given id <code>Attribute</code>.
+ * If the element can't be found, <code>null</code> is returned.
+ * Note that this method works on an <code>Attribute</code>,
+ * <i>not</i> a character tag. In the following HTML snippet:
+ * <code>&lt;a id="HelloThere"&gt;</code> the attribute is
+ * 'id' and the character tag is 'a'.
+ * This is a convenience method for
+ * <code>getElement(RootElement, HTML.Attribute.id, id)</code>.
+ * This is not thread-safe.
+ *
+ * @param id the string representing the desired <code>Attribute</code>
+ * @return the element with the specified <code>Attribute</code>
+ * or <code>null</code> if it can't be found,
+ * or <code>null</code> if <code>id</code> is <code>null</code>
+ * @see javax.swing.text.html.HTML.Attribute
+ * @since 1.3
+ */
+ public Element getElement(String id) {
+ if (id == null) {
+ return null;
+ }
+ return getElement(getDefaultRootElement(), HTML.Attribute.ID, id,
+ true);
+ }
+
+ /**
+ * Returns the child element of <code>e</code> that contains the
+ * attribute, <code>attribute</code> with value <code>value</code>, or
+ * <code>null</code> if one isn't found. This is not thread-safe.
+ *
+ * @param e the root element where the search begins
+ * @param attribute the desired <code>Attribute</code>
+ * @param value the values for the specified <code>Attribute</code>
+ * @return the element with the specified <code>Attribute</code>
+ * and the specified <code>value</code>, or <code>null</code>
+ * if it can't be found
+ * @see javax.swing.text.html.HTML.Attribute
+ * @since 1.3
+ */
+ public Element getElement(Element e, Object attribute, Object value) {
+ return getElement(e, attribute, value, true);
+ }
+
+ /**
+ * Returns the child element of <code>e</code> that contains the
+ * attribute, <code>attribute</code> with value <code>value</code>, or
+ * <code>null</code> if one isn't found. This is not thread-safe.
+ * <p>
+ * If <code>searchLeafAttributes</code> is true, and <code>e</code> is
+ * a leaf, any attributes that are instances of <code>HTML.Tag</code>
+ * with a value that is an <code>AttributeSet</code> will also be checked.
+ *
+ * @param e the root element where the search begins
+ * @param attribute the desired <code>Attribute</code>
+ * @param value the values for the specified <code>Attribute</code>
+ * @return the element with the specified <code>Attribute</code>
+ * and the specified <code>value</code>, or <code>null</code>
+ * if it can't be found
+ * @see javax.swing.text.html.HTML.Attribute
+ */
+ private Element getElement(Element e, Object attribute, Object value,
+ boolean searchLeafAttributes) {
+ AttributeSet attr = e.getAttributes();
+
+ if (attr != null && attr.isDefined(attribute)) {
+ if (value.equals(attr.getAttribute(attribute))) {
+ return e;
+ }
+ }
+ if (!e.isLeaf()) {
+ for (int counter = 0, maxCounter = e.getElementCount();
+ counter < maxCounter; counter++) {
+ Element retValue = getElement(e.getElement(counter), attribute,
+ value, searchLeafAttributes);
+
+ if (retValue != null) {
+ return retValue;
+ }
+ }
+ }
+ else if (searchLeafAttributes && attr != null) {
+ // For some leaf elements we store the actual attributes inside
+ // the AttributeSet of the Element (such as anchors).
+ Enumeration names = attr.getAttributeNames();
+ if (names != null) {
+ while (names.hasMoreElements()) {
+ Object name = names.nextElement();
+ if ((name instanceof HTML.Tag) &&
+ (attr.getAttribute(name) instanceof AttributeSet)) {
+
+ AttributeSet check = (AttributeSet)attr.
+ getAttribute(name);
+ if (check.isDefined(attribute) &&
+ value.equals(check.getAttribute(attribute))) {
+ return e;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Verifies the document has an <code>HTMLEditorKit.Parser</code> set.
+ * If <code>getParser</code> returns <code>null</code>, this will throw an
+ * IllegalStateException.
+ *
+ * @throws IllegalStateException if the document does not have a Parser
+ */
+ private void verifyParser() {
+ if (getParser() == null) {
+ throw new IllegalStateException("No HTMLEditorKit.Parser");
+ }
+ }
+
+ /**
+ * Installs a default Parser if one has not been installed yet.
+ */
+ private void installParserIfNecessary() {
+ if (getParser() == null) {
+ setParser(new HTMLEditorKit().getParser());
+ }
+ }
+
+ /**
+ * Inserts a string of HTML into the document at the given position.
+ * <code>parent</code> is used to identify the location to insert the
+ * <code>html</code>. If <code>parent</code> is a leaf this can have
+ * unexpected results.
+ */
+ private void insertHTML(Element parent, int offset, String html,
+ boolean wantsTrailingNewline)
+ throws BadLocationException, IOException {
+ if (parent != null && html != null) {
+ HTMLEditorKit.Parser parser = getParser();
+ if (parser != null) {
+ int lastOffset = Math.max(0, offset - 1);
+ Element charElement = getCharacterElement(lastOffset);
+ Element commonParent = parent;
+ int pop = 0;
+ int push = 0;
+
+ if (parent.getStartOffset() > lastOffset) {
+ while (commonParent != null &&
+ commonParent.getStartOffset() > lastOffset) {
+ commonParent = commonParent.getParentElement();
+ push++;
+ }
+ if (commonParent == null) {
+ throw new BadLocationException("No common parent",
+ offset);
+ }
+ }
+ while (charElement != null && charElement != commonParent) {
+ pop++;
+ charElement = charElement.getParentElement();
+ }
+ if (charElement != null) {
+ // Found it, do the insert.
+ HTMLReader reader = new HTMLReader(offset, pop - 1, push,
+ null, false, true,
+ wantsTrailingNewline);
+
+ parser.parse(new StringReader(html), reader, true);
+ reader.flush();
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes child Elements of the passed in Element <code>e</code>. This
+ * will do the necessary cleanup to ensure the element representing the
+ * end character is correctly created.
+ * <p>This is not a general purpose method, it assumes that <code>e</code>
+ * will still have at least one child after the remove, and it assumes
+ * the character at <code>e.getStartOffset() - 1</code> is a newline and
+ * is of length 1.
+ */
+ private void removeElements(Element e, int index, int count) throws BadLocationException {
+ writeLock();
+ try {
+ int start = e.getElement(index).getStartOffset();
+ int end = e.getElement(index + count - 1).getEndOffset();
+ if (end > getLength()) {
+ removeElementsAtEnd(e, index, count, start, end);
+ }
+ else {
+ removeElements(e, index, count, start, end);
+ }
+ } finally {
+ writeUnlock();
+ }
+ }
+
+ /**
+ * Called to remove child elements of <code>e</code> when one of the
+ * elements to remove is representing the end character.
+ * <p>Since the Content will not allow a removal to the end character
+ * this will do a remove from <code>start - 1</code> to <code>end</code>.
+ * The end Element(s) will be removed, and the element representing
+ * <code>start - 1</code> to <code>start</code> will be recreated. This
+ * Element has to be recreated as after the content removal its offsets
+ * become <code>start - 1</code> to <code>start - 1</code>.
+ */
+ private void removeElementsAtEnd(Element e, int index, int count,
+ int start, int end) throws BadLocationException {
+ // index must be > 0 otherwise no insert would have happened.
+ boolean isLeaf = (e.getElement(index - 1).isLeaf());
+ DefaultDocumentEvent dde = new DefaultDocumentEvent(
+ start - 1, end - start + 1, DocumentEvent.
+ EventType.REMOVE);
+
+ if (isLeaf) {
+ Element endE = getCharacterElement(getLength());
+ // e.getElement(index - 1) should represent the newline.
+ index--;
+ if (endE.getParentElement() != e) {
+ // The hiearchies don't match, we'll have to manually
+ // recreate the leaf at e.getElement(index - 1)
+ replace(dde, e, index, ++count, start, end, true, true);
+ }
+ else {
+ // The hierarchies for the end Element and
+ // e.getElement(index - 1), match, we can safely remove
+ // the Elements and the end content will be aligned
+ // appropriately.
+ replace(dde, e, index, count, start, end, true, false);
+ }
+ }
+ else {
+ // Not a leaf, descend until we find the leaf representing
+ // start - 1 and remove it.
+ Element newLineE = e.getElement(index - 1);
+ while (!newLineE.isLeaf()) {
+ newLineE = newLineE.getElement(newLineE.getElementCount() - 1);
+ }
+ newLineE = newLineE.getParentElement();
+ replace(dde, e, index, count, start, end, false, false);
+ replace(dde, newLineE, newLineE.getElementCount() - 1, 1, start,
+ end, true, true);
+ }
+ postRemoveUpdate(dde);
+ dde.end();
+ fireRemoveUpdate(dde);
+ fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
+ }
+
+ /**
+ * This is used by <code>removeElementsAtEnd</code>, it removes
+ * <code>count</code> elements starting at <code>start</code> from
+ * <code>e</code>. If <code>remove</code> is true text of length
+ * <code>start - 1</code> to <code>end - 1</code> is removed. If
+ * <code>create</code> is true a new leaf is created of length 1.
+ */
+ private void replace(DefaultDocumentEvent dde, Element e, int index,
+ int count, int start, int end, boolean remove,
+ boolean create) throws BadLocationException {
+ Element[] added;
+ AttributeSet attrs = e.getElement(index).getAttributes();
+ Element[] removed = new Element[count];
+
+ for (int counter = 0; counter < count; counter++) {
+ removed[counter] = e.getElement(counter + index);
+ }
+ if (remove) {
+ UndoableEdit u = getContent().remove(start - 1, end - start);
+ if (u != null) {
+ dde.addEdit(u);
+ }
+ }
+ if (create) {
+ added = new Element[1];
+ added[0] = createLeafElement(e, attrs, start - 1, start);
+ }
+ else {
+ added = new Element[0];
+ }
+ dde.addEdit(new ElementEdit(e, index, removed, added));
+ ((AbstractDocument.BranchElement)e).replace(
+ index, removed.length, added);
+ }
+
+ /**
+ * Called to remove child Elements when the end is not touched.
+ */
+ private void removeElements(Element e, int index, int count,
+ int start, int end) throws BadLocationException {
+ Element[] removed = new Element[count];
+ Element[] added = new Element[0];
+ for (int counter = 0; counter < count; counter++) {
+ removed[counter] = e.getElement(counter + index);
+ }
+ DefaultDocumentEvent dde = new DefaultDocumentEvent
+ (start, end - start, DocumentEvent.EventType.REMOVE);
+ ((AbstractDocument.BranchElement)e).replace(index, removed.length,
+ added);
+ dde.addEdit(new ElementEdit(e, index, removed, added));
+ UndoableEdit u = getContent().remove(start, end - start);
+ if (u != null) {
+ dde.addEdit(u);
+ }
+ postRemoveUpdate(dde);
+ dde.end();
+ fireRemoveUpdate(dde);
+ if (u != null) {
+ fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
+ }
+ }
+
+
+ // These two are provided for inner class access. The are named different
+ // than the super class as the super class implementations are final.
+ void obtainLock() {
+ writeLock();
+ }
+
+ void releaseLock() {
+ writeUnlock();
+ }
+
+ //
+ // Provided for inner class access.
+ //
+
+ /**
+ * Notifies all listeners that have registered interest for
+ * notification on this event type. The event instance
+ * is lazily created using the parameters passed into
+ * the fire method.
+ *
+ * @param e the event
+ * @see EventListenerList
+ */
+ protected void fireChangedUpdate(DocumentEvent e) {
+ super.fireChangedUpdate(e);
+ }
+
+ /**
+ * Notifies all listeners that have registered interest for
+ * notification on this event type. The event instance
+ * is lazily created using the parameters passed into
+ * the fire method.
+ *
+ * @param e the event
+ * @see EventListenerList
+ */
+ protected void fireUndoableEditUpdate(UndoableEditEvent e) {
+ super.fireUndoableEditUpdate(e);
+ }
+
+ boolean hasBaseTag() {
+ return hasBaseTag;
+ }
+
+ String getBaseTarget() {
+ return baseTarget;
+ }
+
+ /*
+ * state defines whether the document is a frame document
+ * or not.
+ */
+ private boolean frameDocument = false;
+ private boolean preservesUnknownTags = true;
+
+ /*
+ * Used to store button groups for radio buttons in
+ * a form.
+ */
+ private HashMap radioButtonGroupsMap;
+
+ /**
+ * Document property for the number of tokens to buffer
+ * before building an element subtree to represent them.
+ */
+ static final String TokenThreshold = "token threshold";
+
+ private static final int MaxThreshold = 10000;
+
+ private static final int StepThreshold = 5;
+
+
+ /**
+ * Document property key value. The value for the key will be a Vector
+ * of Strings that are comments not found in the body.
+ */
+ public static final String AdditionalComments = "AdditionalComments";
+
+ /**
+ * Document property key value. The value for the key will be a
+ * String indicating the default type of stylesheet links.
+ */
+ /* public */ static final String StyleType = "StyleType";
+
+ /**
+ * The location to resolve relative URLs against. By
+ * default this will be the document's URL if the document
+ * was loaded from a URL. If a base tag is found and
+ * can be parsed, it will be used as the base location.
+ */
+ URL base;
+
+ /**
+ * does the document have base tag
+ */
+ boolean hasBaseTag = false;
+
+ /**
+ * BASE tag's TARGET attribute value
+ */
+ private String baseTarget = null;
+
+ /**
+ * The parser that is used when inserting html into the existing
+ * document.
+ */
+ private HTMLEditorKit.Parser parser;
+
+ /**
+ * Used for inserts when a null AttributeSet is supplied.
+ */
+ private static AttributeSet contentAttributeSet;
+
+ /**
+ * Property Maps are registered under, will be a Hashtable.
+ */
+ static String MAP_PROPERTY = "__MAP__";
+
+ private static char[] NEWLINE;
+ private static final String IMPLIED_CR = "CR";
+
+ /**
+ * I18N property key.
+ *
+ * @see AbstractDocument.I18NProperty
+ */
+ private static final String I18NProperty = "i18n";
+
+ 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 = (AttributeSet)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, &lt;APPLET&gt;, &lt;PARAM&gt;, &lt;MAP&gt;, &lt;AREA&gt;, &lt;LINK&gt;,
+ * &lt;SCRIPT&gt; and &lt;STYLE&gt; are unsupported.
+ *
+ * <p>
+ * The assignment of the actions described is shown in the
+ * following table for the tags defined in <code>HTML.Tag</code>.<P>
+ * <table border=1 summary="HTML tags and assigned actions">
+ * <tr><th>Tag</th><th>Action</th></tr>
+ * <tr><td><code>HTML.Tag.A</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.ADDRESS</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.APPLET</code> <td>HiddenAction
+ * <tr><td><code>HTML.Tag.AREA</code> <td>AreaAction
+ * <tr><td><code>HTML.Tag.B</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.BASE</code> <td>BaseAction
+ * <tr><td><code>HTML.Tag.BASEFONT</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.BIG</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.BLOCKQUOTE</code><td>BlockAction
+ * <tr><td><code>HTML.Tag.BODY</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.BR</code> <td>SpecialAction
+ * <tr><td><code>HTML.Tag.CAPTION</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.CENTER</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.CITE</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.CODE</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.DD</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.DFN</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.DIR</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.DIV</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.DL</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.DT</code> <td>ParagraphAction
+ * <tr><td><code>HTML.Tag.EM</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.FONT</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.FORM</code> <td>As of 1.4 a BlockAction
+ * <tr><td><code>HTML.Tag.FRAME</code> <td>SpecialAction
+ * <tr><td><code>HTML.Tag.FRAMESET</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.H1</code> <td>ParagraphAction
+ * <tr><td><code>HTML.Tag.H2</code> <td>ParagraphAction
+ * <tr><td><code>HTML.Tag.H3</code> <td>ParagraphAction
+ * <tr><td><code>HTML.Tag.H4</code> <td>ParagraphAction
+ * <tr><td><code>HTML.Tag.H5</code> <td>ParagraphAction
+ * <tr><td><code>HTML.Tag.H6</code> <td>ParagraphAction
+ * <tr><td><code>HTML.Tag.HEAD</code> <td>HeadAction
+ * <tr><td><code>HTML.Tag.HR</code> <td>SpecialAction
+ * <tr><td><code>HTML.Tag.HTML</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.I</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.IMG</code> <td>SpecialAction
+ * <tr><td><code>HTML.Tag.INPUT</code> <td>FormAction
+ * <tr><td><code>HTML.Tag.ISINDEX</code> <td>IsndexAction
+ * <tr><td><code>HTML.Tag.KBD</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.LI</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.LINK</code> <td>LinkAction
+ * <tr><td><code>HTML.Tag.MAP</code> <td>MapAction
+ * <tr><td><code>HTML.Tag.MENU</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.META</code> <td>MetaAction
+ * <tr><td><code>HTML.Tag.NOFRAMES</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.OBJECT</code> <td>SpecialAction
+ * <tr><td><code>HTML.Tag.OL</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.OPTION</code> <td>FormAction
+ * <tr><td><code>HTML.Tag.P</code> <td>ParagraphAction
+ * <tr><td><code>HTML.Tag.PARAM</code> <td>HiddenAction
+ * <tr><td><code>HTML.Tag.PRE</code> <td>PreAction
+ * <tr><td><code>HTML.Tag.SAMP</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.SCRIPT</code> <td>HiddenAction
+ * <tr><td><code>HTML.Tag.SELECT</code> <td>FormAction
+ * <tr><td><code>HTML.Tag.SMALL</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.STRIKE</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.S</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.STRONG</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.STYLE</code> <td>StyleAction
+ * <tr><td><code>HTML.Tag.SUB</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.SUP</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.TABLE</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.TD</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.TEXTAREA</code> <td>FormAction
+ * <tr><td><code>HTML.Tag.TH</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.TITLE</code> <td>TitleAction
+ * <tr><td><code>HTML.Tag.TR</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.TT</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.U</code> <td>CharacterAction
+ * <tr><td><code>HTML.Tag.UL</code> <td>BlockAction
+ * <tr><td><code>HTML.Tag.VAR</code> <td>CharacterAction
+ * </table>
+ * <p>
+ * Once &lt;/html> is encountered, the Actions are no longer notified.
+ */
+ public class HTMLReader extends HTMLEditorKit.ParserCallback {
+
+ public HTMLReader(int offset) {
+ this(offset, 0, 0, null);
+ }
+
+ public HTMLReader(int offset, int popDepth, int pushDepth,
+ HTML.Tag insertTag) {
+ this(offset, popDepth, pushDepth, insertTag, true, false, true);
+ }
+
+ /**
+ * Generates a RuntimeException (will eventually generate
+ * a BadLocationException when API changes are alloced) if inserting
+ * into non empty document, <code>insertTag</code> is
+ * non-<code>null</code>, and <code>offset</code> is not in the body.
+ */
+ // PENDING(sky): Add throws BadLocationException and remove
+ // RuntimeException
+ HTMLReader(int offset, int popDepth, int pushDepth,
+ HTML.Tag insertTag, boolean insertInsertTag,
+ boolean insertAfterImplied, boolean wantsTrailingNewline) {
+ emptyDocument = (getLength() == 0);
+ isStyleCSS = "text/css".equals(getDefaultStyleSheetType());
+ this.offset = offset;
+ threshold = HTMLDocument.this.getTokenThreshold();
+ tagMap = new Hashtable(57);
+ TagAction na = new TagAction();
+ TagAction ba = new BlockAction();
+ TagAction pa = new ParagraphAction();
+ TagAction ca = new CharacterAction();
+ TagAction sa = new SpecialAction();
+ TagAction fa = new FormAction();
+ TagAction ha = new HiddenAction();
+ TagAction conv = new ConvertAction();
+
+ // register handlers for the well known tags
+ tagMap.put(HTML.Tag.A, new AnchorAction());
+ tagMap.put(HTML.Tag.ADDRESS, ca);
+ tagMap.put(HTML.Tag.APPLET, ha);
+ tagMap.put(HTML.Tag.AREA, new AreaAction());
+ tagMap.put(HTML.Tag.B, conv);
+ tagMap.put(HTML.Tag.BASE, new BaseAction());
+ tagMap.put(HTML.Tag.BASEFONT, ca);
+ tagMap.put(HTML.Tag.BIG, ca);
+ tagMap.put(HTML.Tag.BLOCKQUOTE, ba);
+ tagMap.put(HTML.Tag.BODY, ba);
+ tagMap.put(HTML.Tag.BR, sa);
+ tagMap.put(HTML.Tag.CAPTION, ba);
+ tagMap.put(HTML.Tag.CENTER, ba);
+ tagMap.put(HTML.Tag.CITE, ca);
+ tagMap.put(HTML.Tag.CODE, ca);
+ tagMap.put(HTML.Tag.DD, ba);
+ tagMap.put(HTML.Tag.DFN, ca);
+ tagMap.put(HTML.Tag.DIR, ba);
+ tagMap.put(HTML.Tag.DIV, ba);
+ tagMap.put(HTML.Tag.DL, ba);
+ tagMap.put(HTML.Tag.DT, pa);
+ tagMap.put(HTML.Tag.EM, ca);
+ tagMap.put(HTML.Tag.FONT, conv);
+ tagMap.put(HTML.Tag.FORM, new FormTagAction());
+ tagMap.put(HTML.Tag.FRAME, sa);
+ tagMap.put(HTML.Tag.FRAMESET, ba);
+ tagMap.put(HTML.Tag.H1, pa);
+ tagMap.put(HTML.Tag.H2, pa);
+ tagMap.put(HTML.Tag.H3, pa);
+ tagMap.put(HTML.Tag.H4, pa);
+ tagMap.put(HTML.Tag.H5, pa);
+ tagMap.put(HTML.Tag.H6, pa);
+ tagMap.put(HTML.Tag.HEAD, new HeadAction());
+ tagMap.put(HTML.Tag.HR, sa);
+ tagMap.put(HTML.Tag.HTML, ba);
+ tagMap.put(HTML.Tag.I, conv);
+ tagMap.put(HTML.Tag.IMG, sa);
+ tagMap.put(HTML.Tag.INPUT, fa);
+ tagMap.put(HTML.Tag.ISINDEX, new IsindexAction());
+ tagMap.put(HTML.Tag.KBD, ca);
+ tagMap.put(HTML.Tag.LI, ba);
+ tagMap.put(HTML.Tag.LINK, new LinkAction());
+ tagMap.put(HTML.Tag.MAP, new MapAction());
+ tagMap.put(HTML.Tag.MENU, ba);
+ tagMap.put(HTML.Tag.META, new MetaAction());
+ tagMap.put(HTML.Tag.NOBR, ca);
+ tagMap.put(HTML.Tag.NOFRAMES, ba);
+ tagMap.put(HTML.Tag.OBJECT, sa);
+ tagMap.put(HTML.Tag.OL, ba);
+ tagMap.put(HTML.Tag.OPTION, fa);
+ tagMap.put(HTML.Tag.P, pa);
+ tagMap.put(HTML.Tag.PARAM, new ObjectAction());
+ tagMap.put(HTML.Tag.PRE, new PreAction());
+ tagMap.put(HTML.Tag.SAMP, ca);
+ tagMap.put(HTML.Tag.SCRIPT, ha);
+ tagMap.put(HTML.Tag.SELECT, fa);
+ tagMap.put(HTML.Tag.SMALL, ca);
+ tagMap.put(HTML.Tag.SPAN, ca);
+ tagMap.put(HTML.Tag.STRIKE, conv);
+ tagMap.put(HTML.Tag.S, ca);
+ tagMap.put(HTML.Tag.STRONG, ca);
+ tagMap.put(HTML.Tag.STYLE, new StyleAction());
+ tagMap.put(HTML.Tag.SUB, conv);
+ tagMap.put(HTML.Tag.SUP, conv);
+ tagMap.put(HTML.Tag.TABLE, ba);
+ tagMap.put(HTML.Tag.TD, ba);
+ tagMap.put(HTML.Tag.TEXTAREA, fa);
+ tagMap.put(HTML.Tag.TH, ba);
+ tagMap.put(HTML.Tag.TITLE, new TitleAction());
+ tagMap.put(HTML.Tag.TR, ba);
+ tagMap.put(HTML.Tag.TT, ca);
+ tagMap.put(HTML.Tag.U, conv);
+ tagMap.put(HTML.Tag.UL, ba);
+ tagMap.put(HTML.Tag.VAR, ca);
+
+ if (insertTag != null) {
+ this.insertTag = insertTag;
+ this.popDepth = popDepth;
+ this.pushDepth = pushDepth;
+ this.insertInsertTag = insertInsertTag;
+ foundInsertTag = false;
+ }
+ else {
+ foundInsertTag = true;
+ }
+ if (insertAfterImplied) {
+ this.popDepth = popDepth;
+ this.pushDepth = pushDepth;
+ this.insertAfterImplied = true;
+ foundInsertTag = false;
+ midInsert = false;
+ this.insertInsertTag = true;
+ this.wantsTrailingNewline = wantsTrailingNewline;
+ }
+ else {
+ midInsert = (!emptyDocument && insertTag == null);
+ if (midInsert) {
+ generateEndsSpecsForMidInsert();
+ }
+ }
+
+ /**
+ * 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 = null;
+ Element[] added;
+ Element[] removed;
+ int index;
+ // Remove the fake second body.
+ added = new Element[0];
+ removed = new Element[1];
+ index = pPath[0].getElementIndex(length);
+ removed[0] = pPath[0].getElement(index);
+ ((BranchElement)pPath[0]).replace(index, 1, added);
+ ElementEdit firstEdit = new ElementEdit(pPath[0], index,
+ removed, added);
+
+ // Insert a new element to represent the end that the
+ // second body was representing.
+ SimpleAttributeSet sas = new SimpleAttributeSet();
+ sas.addAttribute(StyleConstants.NameAttribute,
+ HTML.Tag.CONTENT);
+ sas.addAttribute(IMPLIED_CR, Boolean.TRUE);
+ added = new Element[1];
+ added[0] = createLeafElement(pPath[pLength - 1],
+ sas, length, length + 1);
+ index = pPath[pLength - 1].getElementCount();
+ ((BranchElement)pPath[pLength - 1]).replace(index, 0,
+ added);
+ event = new DefaultDocumentEvent(length, 1,
+ DocumentEvent.EventType.CHANGE);
+ event.addEdit(new ElementEdit(pPath[pLength - 1],
+ index, new Element[0], added));
+ event.addEdit(firstEdit);
+ event.end();
+ fireChangedUpdate(event);
+ fireUndoableEditUpdate(new UndoableEditEvent(this, event));
+
+ if (lastText.equals("\n")) {
+ // We now have two \n's, one part of the Document.
+ // We need to remove one
+ event = new DefaultDocumentEvent(length - 1, 1,
+ DocumentEvent.EventType.REMOVE);
+ removeUpdate(event);
+ UndoableEdit u = getContent().remove(length - 1, 1);
+ if (u != null) {
+ event.addEdit(u);
+ }
+ postRemoveUpdate(event);
+ // Mark the edit as done.
+ event.end();
+ fireRemoveUpdate(event);
+ fireUndoableEditUpdate(new UndoableEditEvent(
+ this, event));
+ }
+ }
+ }
+ catch (BadLocationException ble) {
+ }
+ finally {
+ releaseLock();
+ }
+ }
+
+ private Element[] getPathTo(int offset) {
+ Stack elements = new Stack();
+ Element e = getDefaultRootElement();
+ int index;
+ while (!e.isLeaf()) {
+ elements.push(e);
+ e = e.getElement(e.getElementIndex(offset));
+ }
+ Element[] retValue = new Element[elements.size()];
+ elements.copyInto(retValue);
+ return retValue;
+ }
+
+ // -- HTMLEditorKit.ParserCallback methods --------------------
+
+ /**
+ * The last method called on the reader. It allows
+ * any pending changes to be flushed into the document.
+ * Since this is currently loading synchronously, the entire
+ * set of changes are pushed in at this point.
+ */
+ public void flush() throws BadLocationException {
+ if (emptyDocument && !insertAfterImplied) {
+ if (HTMLDocument.this.getLength() > 0 ||
+ parseBuffer.size() > 0) {
+ flushBuffer(true);
+ adjustEndElement();
+ }
+ // We won't insert when
+ }
+ else {
+ flushBuffer(true);
+ }
+ }
+
+ /**
+ * Called by the parser to indicate a block of text was
+ * encountered.
+ */
+ public void handleText(char[] data, int pos) {
+ if (receivedEndHTML || (midInsert && !inBody)) {
+ return;
+ }
+
+ // see if complex glyph layout support is needed
+ if(HTMLDocument.this.getProperty(I18NProperty).equals( Boolean.FALSE ) ) {
+ // if a default direction of right-to-left has been specified,
+ // we want complex layout even if the text is all left to right.
+ Object d = getProperty(TextAttribute.RUN_DIRECTION);
+ if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
+ HTMLDocument.this.putProperty( I18NProperty, Boolean.TRUE);
+ } else {
+ if (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;
+ inParagraph = impliedP = true;
+ }
+ if (data.length >= 1) {
+ addContent(data, 0, data.length);
+ }
+ }
+ }
+
+ /**
+ * Callback from the parser. Route to the appropriate
+ * handler for the tag.
+ */
+ public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
+ if (receivedEndHTML) {
+ return;
+ }
+ if (midInsert && !inBody) {
+ if (t == HTML.Tag.BODY) {
+ inBody = true;
+ // Increment inBlock since we know we are in the body,
+ // this is needed incase an implied-p is needed. If
+ // inBlock isn't incremented, and an implied-p is
+ // encountered, addContent won't be called!
+ inBlock++;
+ }
+ return;
+ }
+ if (!inBody && t == HTML.Tag.BODY) {
+ inBody = true;
+ }
+ if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
+ // Map the style attributes.
+ String decl = (String)a.getAttribute(HTML.Attribute.STYLE);
+ a.removeAttribute(HTML.Attribute.STYLE);
+ styleAttributes = getStyleSheet().getDeclaration(decl);
+ a.addAttributes(styleAttributes);
+ }
+ else {
+ styleAttributes = null;
+ }
+ TagAction action = (TagAction) tagMap.get(t);
+
+ if (action != null) {
+ action.start(t, a);
+ }
+ }
+
+ public void handleComment(char[] data, int pos) {
+ if (receivedEndHTML) {
+ addExternalComment(new String(data));
+ return;
+ }
+ if (inStyle) {
+ if (styles != null) {
+ styles.addElement(new String(data));
+ }
+ }
+ else if (getPreservesUnknownTags()) {
+ if (inBlock == 0 && (foundInsertTag ||
+ insertTag != HTML.Tag.COMMENT)) {
+ // Comment outside of body, will not be able to show it,
+ // but can add it as a property on the Document.
+ addExternalComment(new String(data));
+ return;
+ }
+ SimpleAttributeSet sas = new SimpleAttributeSet();
+ sas.addAttribute(HTML.Attribute.COMMENT, new String(data));
+ addSpecialElement(HTML.Tag.COMMENT, sas);
+ }
+
+ TagAction action = (TagAction)tagMap.get(HTML.Tag.COMMENT);
+ if (action != null) {
+ action.start(HTML.Tag.COMMENT, new SimpleAttributeSet());
+ action.end(HTML.Tag.COMMENT);
+ }
+ }
+
+ /**
+ * Adds the comment <code>comment</code> to the set of comments
+ * maintained outside of the scope of elements.
+ */
+ private void addExternalComment(String comment) {
+ Object comments = getProperty(AdditionalComments);
+ if (comments != null && !(comments instanceof Vector)) {
+ // No place to put comment.
+ return;
+ }
+ if (comments == null) {
+ comments = new Vector();
+ putProperty(AdditionalComments, comments);
+ }
+ ((Vector)comments).addElement(comment);
+ }
+
+ /**
+ * Callback from the parser. Route to the appropriate
+ * handler for the tag.
+ */
+ public void handleEndTag(HTML.Tag t, int pos) {
+ if (receivedEndHTML || (midInsert && !inBody)) {
+ return;
+ }
+ if (t == HTML.Tag.HTML) {
+ receivedEndHTML = true;
+ }
+ if (t == HTML.Tag.BODY) {
+ inBody = false;
+ if (midInsert) {
+ inBlock--;
+ }
+ }
+ TagAction action = (TagAction) tagMap.get(t);
+ if (action != null) {
+ action.end(t);
+ }
+ }
+
+ /**
+ * Callback from the parser. Route to the appropriate
+ * handler for the tag.
+ */
+ public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
+ if (receivedEndHTML || (midInsert && !inBody)) {
+ return;
+ }
+
+ if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
+ // Map the style attributes.
+ String decl = (String)a.getAttribute(HTML.Attribute.STYLE);
+ a.removeAttribute(HTML.Attribute.STYLE);
+ styleAttributes = getStyleSheet().getDeclaration(decl);
+ a.addAttributes(styleAttributes);
+ }
+ else {
+ styleAttributes = null;
+ }
+
+ TagAction action = (TagAction) tagMap.get(t);
+ if (action != null) {
+ action.start(t, a);
+ action.end(t);
+ }
+ else if (getPreservesUnknownTags()) {
+ // unknown tag, only add if should preserve it.
+ addSpecialElement(t, a);
+ }
+ }
+
+ /**
+ * This is invoked after the stream has been parsed, but before
+ * <code>flush</code>. <code>eol</code> will be one of \n, \r
+ * or \r\n, which ever is encountered the most in parsing the
+ * stream.
+ *
+ * @since 1.3
+ */
+ public void handleEndOfLineString(String eol) {
+ if (emptyDocument && eol != null) {
+ putProperty(DefaultEditorKit.EndOfLineStringProperty,
+ eol);
+ }
+ }
+
+ // ---- tag handling support ------------------------------
+
+ /**
+ * Registers a handler for the given tag. By default
+ * all of the well-known tags will have been registered.
+ * This can be used to change the handling of a particular
+ * tag or to add support for custom tags.
+ */
+ protected void registerTag(HTML.Tag t, TagAction a) {
+ tagMap.put(t, a);
+ }
+
+ /**
+ * An action to be performed in response
+ * to parsing a tag. This allows customization
+ * of how each tag is handled and avoids a large
+ * switch statement.
+ */
+ public class TagAction {
+
+ /**
+ * Called when a start tag is seen for the
+ * type of tag this action was registered
+ * to. The tag argument indicates the actual
+ * tag for those actions that are shared across
+ * many tags. By default this does nothing and
+ * completely ignores the tag.
+ */
+ public void start(HTML.Tag t, MutableAttributeSet a) {
+ }
+
+ /**
+ * Called when an end tag is seen for the
+ * type of tag this action was registered
+ * to. The tag argument indicates the actual
+ * tag for those actions that are shared across
+ * many tags. By default this does nothing and
+ * completely ignores the tag.
+ */
+ public void end(HTML.Tag t) {
+ }
+
+ }
+
+ public class BlockAction extends TagAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet attr) {
+ blockOpen(t, attr);
+ }
+
+ public void end(HTML.Tag t) {
+ blockClose(t);
+ }
+ }
+
+
+ /**
+ * Action used for the actual element form tag. This is named such
+ * as there was already a public class named FormAction.
+ */
+ private class FormTagAction extends BlockAction {
+ public void start(HTML.Tag t, MutableAttributeSet attr) {
+ super.start(t, attr);
+ // initialize a ButtonGroupsMap when
+ // FORM tag is encountered. This will
+ // be used for any radio buttons that
+ // might be defined in the FORM.
+ // for new group new ButtonGroup will be created (fix for 4529702)
+ // group name is a key in radioButtonGroupsMap
+ radioButtonGroupsMap = new HashMap();
+ }
+
+ public void end(HTML.Tag t) {
+ super.end(t);
+ // reset the button group to null since
+ // the form has ended.
+ radioButtonGroupsMap = null;
+ }
+ }
+
+
+ public class ParagraphAction extends BlockAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet a) {
+ super.start(t, a);
+ inParagraph = true;
+ }
+
+ public void end(HTML.Tag t) {
+ super.end(t);
+ inParagraph = false;
+ }
+ }
+
+ public class SpecialAction extends TagAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet a) {
+ addSpecialElement(t, a);
+ }
+
+ }
+
+ public class IsindexAction extends TagAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet a) {
+ blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
+ addSpecialElement(t, a);
+ blockClose(HTML.Tag.IMPLIED);
+ }
+
+ }
+
+
+ public class HiddenAction extends TagAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet a) {
+ addSpecialElement(t, a);
+ }
+
+ public void end(HTML.Tag t) {
+ if (!isEmpty(t)) {
+ MutableAttributeSet a = new SimpleAttributeSet();
+ a.addAttribute(HTML.Attribute.ENDTAG, "true");
+ addSpecialElement(t, a);
+ }
+ }
+
+ boolean isEmpty(HTML.Tag t) {
+ if (t == HTML.Tag.APPLET ||
+ t == HTML.Tag.SCRIPT) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+
+ /**
+ * Subclass of HiddenAction to set the content type for style sheets,
+ * and to set the name of the default style sheet.
+ */
+ class MetaAction extends HiddenAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet a) {
+ Object equiv = a.getAttribute(HTML.Attribute.HTTPEQUIV);
+ if (equiv != null) {
+ equiv = ((String)equiv).toLowerCase();
+ if (equiv.equals("content-style-type")) {
+ String value = (String)a.getAttribute
+ (HTML.Attribute.CONTENT);
+ setDefaultStyleSheetType(value);
+ isStyleCSS = "text/css".equals
+ (getDefaultStyleSheetType());
+ }
+ else if (equiv.equals("default-style")) {
+ defaultStyle = (String)a.getAttribute
+ (HTML.Attribute.CONTENT);
+ }
+ }
+ super.start(t, a);
+ }
+
+ boolean isEmpty(HTML.Tag t) {
+ return true;
+ }
+ }
+
+
+ /**
+ * End if overridden to create the necessary stylesheets that
+ * are referenced via the link tag. It is done in this manner
+ * as the meta tag can be used to specify an alternate style sheet,
+ * and is not guaranteed to come before the link tags.
+ */
+ class HeadAction extends BlockAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet a) {
+ inHead = true;
+ // This check of the insertTag is put in to avoid considering
+ // the implied-p that is generated for the head. This allows
+ // inserts for HR to work correctly.
+ if ((insertTag == null && !insertAfterImplied) ||
+ (insertTag == HTML.Tag.HEAD) ||
+ (insertAfterImplied &&
+ (foundInsertTag || !a.isDefined(IMPLIED)))) {
+ super.start(t, a);
+ }
+ }
+
+ public void end(HTML.Tag t) {
+ inHead = inStyle = false;
+ // See if there is a StyleSheet to link to.
+ if (styles != null) {
+ boolean isDefaultCSS = isStyleCSS;
+ for (int counter = 0, maxCounter = styles.size();
+ counter < maxCounter;) {
+ Object value = styles.elementAt(counter);
+ if (value == HTML.Tag.LINK) {
+ handleLink((AttributeSet)styles.
+ elementAt(++counter));
+ counter++;
+ }
+ else {
+ // Rule.
+ // First element gives type.
+ String type = (String)styles.elementAt(++counter);
+ boolean isCSS = (type == null) ? isDefaultCSS :
+ type.equals("text/css");
+ while (++counter < maxCounter &&
+ (styles.elementAt(counter)
+ instanceof String)) {
+ if (isCSS) {
+ addCSSRules((String)styles.elementAt
+ (counter));
+ }
+ }
+ }
+ }
+ }
+ if ((insertTag == null && !insertAfterImplied) ||
+ insertTag == HTML.Tag.HEAD ||
+ (insertAfterImplied && foundInsertTag)) {
+ super.end(t);
+ }
+ }
+
+ boolean isEmpty(HTML.Tag t) {
+ return false;
+ }
+
+ private void handleLink(AttributeSet attr) {
+ // Link.
+ String type = (String)attr.getAttribute(HTML.Attribute.TYPE);
+ if (type == null) {
+ type = getDefaultStyleSheetType();
+ }
+ // Only choose if type==text/css
+ // Select link if rel==stylesheet.
+ // Otherwise if rel==alternate stylesheet and
+ // title matches default style.
+ if (type.equals("text/css")) {
+ String rel = (String)attr.getAttribute(HTML.Attribute.REL);
+ String title = (String)attr.getAttribute
+ (HTML.Attribute.TITLE);
+ String media = (String)attr.getAttribute
+ (HTML.Attribute.MEDIA);
+ if (media == null) {
+ media = "all";
+ }
+ else {
+ media = media.toLowerCase();
+ }
+ if (rel != null) {
+ rel = rel.toLowerCase();
+ if ((media.indexOf("all") != -1 ||
+ media.indexOf("screen") != -1) &&
+ (rel.equals("stylesheet") ||
+ (rel.equals("alternate stylesheet") &&
+ title.equals(defaultStyle)))) {
+ linkCSSStyleSheet((String)attr.getAttribute
+ (HTML.Attribute.HREF));
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * A subclass to add the AttributeSet to styles if the
+ * attributes contains an attribute for 'rel' with value
+ * 'stylesheet' or 'alternate stylesheet'.
+ */
+ class LinkAction extends HiddenAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet a) {
+ String rel = (String)a.getAttribute(HTML.Attribute.REL);
+ if (rel != null) {
+ rel = rel.toLowerCase();
+ if (rel.equals("stylesheet") ||
+ rel.equals("alternate stylesheet")) {
+ if (styles == null) {
+ styles = new Vector(3);
+ }
+ styles.addElement(t);
+ styles.addElement(a.copyAttributes());
+ }
+ }
+ super.start(t, a);
+ }
+ }
+
+ class MapAction extends TagAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet a) {
+ lastMap = new Map((String)a.getAttribute(HTML.Attribute.NAME));
+ addMap(lastMap);
+ }
+
+ public void end(HTML.Tag t) {
+ }
+ }
+
+
+ class AreaAction extends TagAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet a) {
+ if (lastMap != null) {
+ lastMap.addArea(a.copyAttributes());
+ }
+ }
+
+ public void end(HTML.Tag t) {
+ }
+ }
+
+
+ class StyleAction extends TagAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet a) {
+ if (inHead) {
+ if (styles == null) {
+ styles = new Vector(3);
+ }
+ styles.addElement(t);
+ styles.addElement(a.getAttribute(HTML.Attribute.TYPE));
+ inStyle = true;
+ }
+ }
+
+ public void end(HTML.Tag t) {
+ inStyle = false;
+ }
+
+ boolean isEmpty(HTML.Tag t) {
+ return false;
+ }
+ }
+
+
+ public class PreAction extends BlockAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet attr) {
+ inPre = true;
+ blockOpen(t, attr);
+ attr.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
+ blockOpen(HTML.Tag.IMPLIED, attr);
+ }
+
+ public void end(HTML.Tag t) {
+ blockClose(HTML.Tag.IMPLIED);
+ // set inPre to false after closing, so that if a newline
+ // is added it won't generate a blockOpen.
+ inPre = false;
+ blockClose(t);
+ }
+ }
+
+ public class CharacterAction extends TagAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet attr) {
+ pushCharacterStyle();
+ if (!foundInsertTag) {
+ // Note that the third argument should really be based off
+ // inParagraph and impliedP. If we're wrong (that is
+ // insertTagDepthDelta shouldn't be changed), we'll end up
+ // removing an extra EndSpec, which won't matter anyway.
+ boolean insert = canInsertTag(t, attr, false);
+ if (foundInsertTag) {
+ if (!inParagraph) {
+ inParagraph = impliedP = true;
+ }
+ }
+ if (!insert) {
+ return;
+ }
+ }
+ if (attr.isDefined(IMPLIED)) {
+ attr.removeAttribute(IMPLIED);
+ }
+ charAttr.addAttribute(t, attr.copyAttributes());
+ if (styleAttributes != null) {
+ charAttr.addAttributes(styleAttributes);
+ }
+ }
+
+ public void end(HTML.Tag t) {
+ popCharacterStyle();
+ }
+ }
+
+ /**
+ * Provides conversion of HTML tag/attribute
+ * mappings that have a corresponding StyleConstants
+ * and CSS mapping. The conversion is to CSS attributes.
+ */
+ class ConvertAction extends TagAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet attr) {
+ pushCharacterStyle();
+ if (!foundInsertTag) {
+ // Note that the third argument should really be based off
+ // inParagraph and impliedP. If we're wrong (that is
+ // insertTagDepthDelta shouldn't be changed), we'll end up
+ // removing an extra EndSpec, which won't matter anyway.
+ boolean insert = canInsertTag(t, attr, false);
+ if (foundInsertTag) {
+ if (!inParagraph) {
+ inParagraph = impliedP = true;
+ }
+ }
+ if (!insert) {
+ return;
+ }
+ }
+ if (attr.isDefined(IMPLIED)) {
+ attr.removeAttribute(IMPLIED);
+ }
+ if (styleAttributes != null) {
+ charAttr.addAttributes(styleAttributes);
+ }
+ // We also need to add attr, otherwise we lose custom
+ // attributes, including class/id for style lookups, and
+ // further confuse style lookup (doesn't have tag).
+ charAttr.addAttribute(t, attr.copyAttributes());
+ StyleSheet sheet = getStyleSheet();
+ if (t == HTML.Tag.B) {
+ sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_WEIGHT, "bold");
+ } else if (t == HTML.Tag.I) {
+ sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_STYLE, "italic");
+ } else if (t == HTML.Tag.U) {
+ Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
+ String value = "underline";
+ value = (v != null) ? value + "," + v.toString() : value;
+ sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
+ } else if (t == HTML.Tag.STRIKE) {
+ Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
+ String value = "line-through";
+ value = (v != null) ? value + "," + v.toString() : value;
+ sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
+ } else if (t == HTML.Tag.SUP) {
+ Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
+ String value = "sup";
+ value = (v != null) ? value + "," + v.toString() : value;
+ sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
+ } else if (t == HTML.Tag.SUB) {
+ Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
+ String value = "sub";
+ value = (v != null) ? value + "," + v.toString() : value;
+ sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
+ } else if (t == HTML.Tag.FONT) {
+ String color = (String) attr.getAttribute(HTML.Attribute.COLOR);
+ if (color != null) {
+ sheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color);
+ }
+ String face = (String) attr.getAttribute(HTML.Attribute.FACE);
+ if (face != null) {
+ sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY, face);
+ }
+ String size = (String) attr.getAttribute(HTML.Attribute.SIZE);
+ if (size != null) {
+ sheet.addCSSAttributeFromHTML(charAttr, CSS.Attribute.FONT_SIZE, size);
+ }
+ }
+ }
+
+ public void end(HTML.Tag t) {
+ popCharacterStyle();
+ }
+
+ }
+
+ class AnchorAction extends CharacterAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet attr) {
+ // set flag to catch empty anchors
+ emptyAnchor = true;
+ super.start(t, attr);
+ }
+
+ public void end(HTML.Tag t) {
+ if (emptyAnchor) {
+ // if the anchor was empty it was probably a
+ // named anchor point and we don't want to throw
+ // it away.
+ char[] one = new char[1];
+ one[0] = '\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 = (ElementSpec) parseBuffer.lastElement();
+ MutableAttributeSet objAttr = (MutableAttributeSet) objSpec.getAttributes();
+ objAttr.addAttribute(name, value);
+ }
+ }
+ }
+
+ /**
+ * Action to support forms by building all of the elements
+ * used to represent form controls. This will process
+ * the &lt;INPUT&gt;, &lt;TEXTAREA&gt;, &lt;SELECT&gt;,
+ * and &lt;OPTION&gt; tags. The element created by
+ * this action is expected to have the attribute
+ * <code>StyleConstants.ModelAttribute</code> set to
+ * the model that holds the state for the form control.
+ * This enables multiple views, and allows document to
+ * be iterated over picking up the data of the form.
+ * The following are the model assignments for the
+ * various type of form elements.
+ * <table summary="model assignments for the various types of form elements">
+ * <tr>
+ * <th>Element Type
+ * <th>Model Type
+ * <tr>
+ * <td>input, type button
+ * <td>{@link DefaultButtonModel}
+ * <tr>
+ * <td>input, type checkbox
+ * <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
+ * <tr>
+ * <td>input, type image
+ * <td>{@link DefaultButtonModel}
+ * <tr>
+ * <td>input, type password
+ * <td>{@link PlainDocument}
+ * <tr>
+ * <td>input, type radio
+ * <td>{@link javax.swing.JToggleButton.ToggleButtonModel}
+ * <tr>
+ * <td>input, type reset
+ * <td>{@link DefaultButtonModel}
+ * <tr>
+ * <td>input, type submit
+ * <td>{@link DefaultButtonModel}
+ * <tr>
+ * <td>input, type text or type is null.
+ * <td>{@link PlainDocument}
+ * <tr>
+ * <td>select
+ * <td>{@link DefaultComboBoxModel} or an {@link DefaultListModel}, with an item type of Option
+ * <tr>
+ * <td>textarea
+ * <td>{@link PlainDocument}
+ * </table>
+ *
+ */
+ public class FormAction extends SpecialAction {
+
+ public void start(HTML.Tag t, MutableAttributeSet attr) {
+ if (t == HTML.Tag.INPUT) {
+ String type = (String)
+ attr.getAttribute(HTML.Attribute.TYPE);
+ /*
+ * if type is not defined teh default is
+ * assumed to be text.
+ */
+ if (type == null) {
+ type = "text";
+ attr.addAttribute(HTML.Attribute.TYPE, "text");
+ }
+ setModel(type, attr);
+ } else if (t == HTML.Tag.TEXTAREA) {
+ inTextArea = true;
+ textAreaDocument = new TextAreaDocument();
+ attr.addAttribute(StyleConstants.ModelAttribute,
+ textAreaDocument);
+ } else if (t == HTML.Tag.SELECT) {
+ int size = HTML.getIntegerAttributeValue(attr,
+ HTML.Attribute.SIZE,
+ 1);
+ boolean multiple = ((String)attr.getAttribute(HTML.Attribute.MULTIPLE) != null);
+ if ((size > 1) || multiple) {
+ OptionListModel m = new OptionListModel();
+ if (multiple) {
+ m.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+ }
+ selectModel = m;
+ } else {
+ selectModel = new OptionComboBoxModel();
+ }
+ attr.addAttribute(StyleConstants.ModelAttribute,
+ selectModel);
+
+ }
+
+ // build the element, unless this is an option.
+ if (t == HTML.Tag.OPTION) {
+ option = new Option(attr);
+
+ if (selectModel instanceof OptionListModel) {
+ OptionListModel m = (OptionListModel)selectModel;
+ m.addElement(option);
+ if (option.isSelected()) {
+ m.addSelectionInterval(optionCount, optionCount);
+ m.setInitialSelection(optionCount);
+ }
+ } else if (selectModel instanceof OptionComboBoxModel) {
+ OptionComboBoxModel m = (OptionComboBoxModel)selectModel;
+ m.addElement(option);
+ if (option.isSelected()) {
+ m.setSelectedItem(option);
+ m.setInitialSelection(option);
+ }
+ }
+ optionCount++;
+ } else {
+ super.start(t, attr);
+ }
+ }
+
+ public void end(HTML.Tag t) {
+ if (t == HTML.Tag.OPTION) {
+ option = null;
+ } else {
+ if (t == HTML.Tag.SELECT) {
+ selectModel = null;
+ optionCount = 0;
+ } else if (t == HTML.Tag.TEXTAREA) {
+ inTextArea = false;
+
+ /* Now that the textarea has ended,
+ * store the entire initial text
+ * of the text area. This will
+ * enable us to restore the initial
+ * state if a reset is requested.
+ */
+ textAreaDocument.storeInitialText();
+ }
+ super.end(t);
+ }
+ }
+
+ void setModel(String type, MutableAttributeSet attr) {
+ if (type.equals("submit") ||
+ type.equals("reset") ||
+ type.equals("image")) {
+
+ // button model
+ attr.addAttribute(StyleConstants.ModelAttribute,
+ new DefaultButtonModel());
+ } else if (type.equals("text") ||
+ type.equals("password")) {
+ // plain text model
+ int maxLength = HTML.getIntegerAttributeValue(
+ attr, HTML.Attribute.MAXLENGTH, -1);
+ Document doc;
+
+ if (maxLength > 0) {
+ doc = new FixedLengthDocument(maxLength);
+ }
+ else {
+ doc = new PlainDocument();
+ }
+ 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();
+ }
+ ButtonGroup radioButtonGroup = (ButtonGroup)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 &lt;SELECT&gt; tag is being processed, this
+ * model will be a reference to the model being filled
+ * with the &lt;OPTION&gt; elements (which produce
+ * objects of type <code>Option</code>.
+ */
+ Object selectModel;
+ int optionCount;
+ }
+
+
+ // --- utility methods used by the reader ------------------
+
+ /**
+ * Pushes the current character style on a stack in preparation
+ * for forming a new nested character style.
+ */
+ protected void pushCharacterStyle() {
+ charAttrStack.push(charAttr.copyAttributes());
+ }
+
+ /**
+ * Pops a previously pushed character style off the stack
+ * to return to a previous style.
+ */
+ protected void popCharacterStyle() {
+ if (!charAttrStack.empty()) {
+ charAttr = (MutableAttributeSet) charAttrStack.peek();
+ charAttrStack.pop();
+ }
+ }
+
+ /**
+ * Adds the given content to the textarea document.
+ * This method gets called when we are in a textarea
+ * context. Therefore all text that is seen belongs
+ * to the text area and is hence added to the
+ * TextAreaDocument associated with the text area.
+ */
+ protected void textAreaContent(char[] data) {
+ try {
+ textAreaDocument.insertString(textAreaDocument.getLength(), new String(data), null);
+ } catch (BadLocationException e) {
+ // Should do something reasonable
+ }
+ }
+
+ /**
+ * Adds the given content that was encountered in a
+ * PRE element. This synthesizes lines to hold the
+ * runs of text, and makes calls to addContent to
+ * actually add the text.
+ */
+ protected void preContent(char[] data) {
+ int last = 0;
+ for (int i = 0; i < data.length; i++) {
+ if (data[i] == '\n') {
+ addContent(data, last, i - last + 1);
+ blockClose(HTML.Tag.IMPLIED);
+ MutableAttributeSet a = new SimpleAttributeSet();
+ a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
+ blockOpen(HTML.Tag.IMPLIED, a);
+ last = i + 1;
+ }
+ }
+ if (last < data.length) {
+ addContent(data, last, data.length - last);
+ }
+ }
+
+ /**
+ * Adds an instruction to the parse buffer to create a
+ * block element with the given attributes.
+ */
+ protected void blockOpen(HTML.Tag t, MutableAttributeSet attr) {
+ if (impliedP) {
+ blockClose(HTML.Tag.IMPLIED);
+ }
+
+ inBlock++;
+
+ if (!canInsertTag(t, attr, true)) {
+ return;
+ }
+ if (attr.isDefined(IMPLIED)) {
+ attr.removeAttribute(IMPLIED);
+ }
+ lastWasNewline = false;
+ attr.addAttribute(StyleConstants.NameAttribute, t);
+ ElementSpec es = new ElementSpec(
+ attr.copyAttributes(), ElementSpec.StartTagType);
+ parseBuffer.addElement(es);
+ }
+
+ /**
+ * Adds an instruction to the parse buffer to close out
+ * a block element of the given type.
+ */
+ protected void blockClose(HTML.Tag t) {
+ inBlock--;
+
+ if (!foundInsertTag) {
+ return;
+ }
+
+ // Add a new line, if the last character wasn't one. This is
+ // needed for proper positioning of the cursor. addContent
+ // with true will force an implied paragraph to be generated if
+ // there isn't one. This may result in a rather bogus structure
+ // (perhaps a table with a child pargraph), but the paragraph
+ // is needed for proper positioning and display.
+ if(!lastWasNewline) {
+ pushCharacterStyle();
+ charAttr.addAttribute(IMPLIED_CR, Boolean.TRUE);
+ addContent(NEWLINE, 0, 1, true);
+ popCharacterStyle();
+ lastWasNewline = true;
+ }
+
+ if (impliedP) {
+ impliedP = false;
+ inParagraph = false;
+ if (t != HTML.Tag.IMPLIED) {
+ blockClose(HTML.Tag.IMPLIED);
+ }
+ }
+ // an open/close with no content will be removed, so we
+ // add a space of content to keep the element being formed.
+ ElementSpec prev = (parseBuffer.size() > 0) ?
+ (ElementSpec) parseBuffer.lastElement() : null;
+ if (prev != null && prev.getType() == ElementSpec.StartTagType) {
+ char[] one = new char[1];
+ one[0] = ' ';
+ addContent(one, 0, 1);
+ }
+ ElementSpec es = new ElementSpec(
+ null, ElementSpec.EndTagType);
+ parseBuffer.addElement(es);
+ }
+
+ /**
+ * Adds some text with the current character attributes.
+ *
+ * @param 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 hiearchy) may not match up.
+ // This attemps to clean it up.
+ int removeCounter = insertTagDepthDelta;
+ while (removeCounter < 0 && size >= 0 &&
+ ((ElementSpec)parseBuffer.elementAt(size - 1)).
+ getType() == ElementSpec.EndTagType) {
+ parseBuffer.removeElementAt(--size);
+ removeCounter++;
+ }
+ }
+ if (flushCount == 0 && (!insertAfterImplied ||
+ !wantsTrailingNewline)) {
+ // If this starts with content (or popDepth > 0 &&
+ // pushDepth > 0) and ends with EndTagTypes, make sure
+ // the last content isn't a \n, otherwise will end up with
+ // an extra \n in the middle of content.
+ int index = 0;
+ if (pushDepth > 0) {
+ if (((ElementSpec)parseBuffer.elementAt(0)).getType() ==
+ ElementSpec.ContentType) {
+ index++;
+ }
+ }
+ index += (popDepth + pushDepth);
+ int cCount = 0;
+ int cStart = index;
+ while (index < size && ((ElementSpec)parseBuffer.elementAt
+ (index)).getType() == ElementSpec.ContentType) {
+ index++;
+ cCount++;
+ }
+ if (cCount > 1) {
+ while (index < size && ((ElementSpec)parseBuffer.elementAt
+ (index)).getType() == ElementSpec.EndTagType) {
+ index++;
+ }
+ if (index == size) {
+ char[] lastText = ((ElementSpec)parseBuffer.elementAt
+ (cStart + cCount - 1)).getArray();
+ if (lastText.length == 1 && lastText[0] == NEWLINE[0]){
+ index = cStart + cCount - 1;
+ while (size > index) {
+ parseBuffer.removeElementAt(--size);
+ }
+ }
+ }
+ }
+ }
+ if (wantsTrailingNewline) {
+ // Make sure there is in fact a newline
+ for (int counter = parseBuffer.size() - 1; counter >= 0;
+ counter--) {
+ ElementSpec spec = (ElementSpec)parseBuffer.
+ elementAt(counter);
+ if (spec.getType() == ElementSpec.ContentType) {
+ if (spec.getArray()[spec.getLength() - 1] != '\n') {
+ SimpleAttributeSet attrs =new SimpleAttributeSet();
+
+ attrs.addAttribute(StyleConstants.NameAttribute,
+ HTML.Tag.CONTENT);
+ parseBuffer.insertElementAt(new ElementSpec(
+ attrs,
+ ElementSpec.ContentType, NEWLINE, 0, 1),
+ counter + 1);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds the CSS rules in <code>rules</code>.
+ */
+ void addCSSRules(String rules) {
+ StyleSheet ss = getStyleSheet();
+ ss.addRule(rules);
+ }
+
+ /**
+ * Adds the CSS stylesheet at <code>href</code> to the known list
+ * of stylesheets.
+ */
+ void linkCSSStyleSheet(String href) {
+ URL url = null;
+ try {
+ url = new URL(base, href);
+ } catch (MalformedURLException mfe) {
+ try {
+ url = new URL(href);
+ } catch (MalformedURLException mfe2) {
+ url = null;
+ }
+ }
+ if (url != null) {
+ getStyleSheet().importStyleSheet(url);
+ }
+ }
+
+ /**
+ * Returns true if can insert starting at <code>t</code>. This
+ * will return false if the insert tag is set, and hasn't been found
+ * yet.
+ */
+ private boolean canInsertTag(HTML.Tag t, AttributeSet attr,
+ boolean isBlockTag) {
+ if (!foundInsertTag) {
+ 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 <html>.
+ */
+ private boolean receivedEndHTML;
+ /** Number of times <code>flushBuffer</code> has been invoked. */
+ private int flushCount;
+ /** If true, behavior is similiar to insertTag, but instead of
+ * waiting for insertTag will wait for first Element without
+ * an 'implied' attribute and begin inserting then. */
+ private boolean insertAfterImplied;
+ /** This is only used if insertAfterImplied is true. If false, only
+ * inserting content, and there is a trailing newline it is removed. */
+ private boolean wantsTrailingNewline;
+ int threshold;
+ int offset;
+ boolean inParagraph = false;
+ boolean impliedP = false;
+ boolean inPre = false;
+ boolean inTextArea = false;
+ TextAreaDocument textAreaDocument = null;
+ boolean inTitle = false;
+ boolean lastWasNewline = true;
+ boolean emptyAnchor;
+ /** True if (!emptyDocument && insertTag == null), this is used so
+ * much it is cached. */
+ boolean midInsert;
+ /** True when the body has been encountered. */
+ boolean inBody;
+ /** If non null, gives parent Tag that insert is to happen at. */
+ HTML.Tag insertTag;
+ /** If true, the insertTag is inserted, otherwise elements after
+ * the insertTag is found are inserted. */
+ boolean insertInsertTag;
+ /** Set to true when insertTag has been found. */
+ boolean foundInsertTag;
+ /** When foundInsertTag is set to true, this will be updated to
+ * reflect the delta between the two structures. That is, it
+ * will be the depth the inserts are happening at minus the
+ * depth of the tags being passed in. A value of 0 (the common
+ * case) indicates the structures match, a value greater than 0 indicates
+ * the insert is happening at a deeper depth than the stream is
+ * parsing, and a value less than 0 indicates the insert is happening earlier
+ * in the tree that the parser thinks and that we will need to remove
+ * EndTagType specs in the flushBuffer method.
+ */
+ int insertTagDepthDelta;
+ /** How many parents to ascend before insert new elements. */
+ int popDepth;
+ /** How many parents to descend (relative to popDepth) before
+ * inserting. */
+ int pushDepth;
+ /** Last Map that was encountered. */
+ Map lastMap;
+ /** Set to true when a style element is encountered. */
+ boolean inStyle = false;
+ /** Name of style to use. Obtained from Meta tag. */
+ String defaultStyle;
+ /** Vector describing styles that should be include. Will consist
+ * of a bunch of HTML.Tags, which will either be:
+ * <p>LINK: in which case it is followed by an AttributeSet
+ * <p>STYLE: in which case the following element is a String
+ * indicating the type (may be null), and the elements following
+ * it until the next HTML.Tag are the rules as Strings.
+ */
+ Vector styles;
+ /** True if inside the head tag. */
+ boolean inHead = false;
+ /** Set to true if the style language is text/css. Since this is
+ * used alot, it is cached. */
+ boolean isStyleCSS;
+ /** True if inserting into an empty document. */
+ boolean emptyDocument;
+ /** Attributes from a style Attribute. */
+ AttributeSet styleAttributes;
+
+ /**
+ * Current option, if in an option element (needed to
+ * load the label.
+ */
+ Option option;
+
+ protected Vector<ElementSpec> parseBuffer = new Vector(); // Vector<ElementSpec>
+ protected MutableAttributeSet charAttr = new TaggedAttributeSet();
+ Stack charAttrStack = new Stack();
+ Hashtable tagMap;
+ int inBlock = 0;
+
+ /**
+ * 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);
+ }
+ }
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/HTMLEditorKit.java b/src/share/classes/javax/swing/text/html/HTMLEditorKit.java
new file mode 100644
index 000000000..980817d96
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/HTMLEditorKit.java
@@ -0,0 +1,2279 @@
+/*
+ * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.lang.reflect.Method;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import javax.swing.text.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.event.*;
+import javax.swing.plaf.TextUI;
+import java.util.*;
+import javax.accessibility.*;
+import java.lang.ref.*;
+
+/**
+ * The Swing JEditorPane text component supports different kinds
+ * of content via a plug-in mechanism called an EditorKit. Because
+ * HTML is a very popular format of content, some support is provided
+ * by default. The default support is provided by this class, which
+ * supports HTML version 3.2 (with some extensions), and is migrating
+ * toward version 4.0.
+ * The &lt;applet&gt; tag is not supported, but some support is provided
+ * for the &lt;object&gt; tag.
+ * <p>
+ * There are several goals of the HTML EditorKit provided, that have
+ * an effect upon the way that HTML is modeled. These
+ * have influenced its design in a substantial way.
+ * <dl>
+ * <p>
+ * <dt>
+ * Support editing
+ * <dd>
+ * It might seem fairly obvious that a plug-in for JEditorPane
+ * should provide editing support, but that fact has several
+ * design considerations. There are a substantial number of HTML
+ * documents that don't properly conform to an HTML specification.
+ * These must be normalized somewhat into a correct form if one
+ * is to edit them. Additionally, users don't like to be presented
+ * with an excessive amount of structure editing, so using traditional
+ * text editing gestures is preferred over using the HTML structure
+ * exactly as defined in the HTML document.
+ * <p>
+ * The modeling of HTML is provided by the class <code>HTMLDocument</code>.
+ * Its documention describes the details of how the HTML is modeled.
+ * The editing support leverages heavily off of the text package.
+ * <p>
+ * <dt>
+ * Extendable/Scalable
+ * <dd>
+ * To maximize the usefulness of this kit, a great deal of effort
+ * has gone into making it extendable. These are some of the
+ * features.
+ * <ol>
+ * <li>
+ * The parser is replacable. The default parser is the Hot Java
+ * parser which is DTD based. A different DTD can be used, or an
+ * entirely different parser can be used. To change the parser,
+ * reimplement the getParser method. The default parser is
+ * dynamically loaded when first asked for, so the class files
+ * will never be loaded if an alternative parser is used. The
+ * default parser is in a separate package called parser below
+ * this package.
+ * <li>
+ * The parser drives the ParserCallback, which is provided by
+ * HTMLDocument. To change the callback, subclass HTMLDocument
+ * and reimplement the createDefaultDocument method to return
+ * document that produces a different reader. The reader controls
+ * how the document is structured. Although the Document provides
+ * HTML support by default, there is nothing preventing support of
+ * non-HTML tags that result in alternative element structures.
+ * <li>
+ * The default view of the models are provided as a hierarchy of
+ * View implementations, so one can easily customize how a particular
+ * element is displayed or add capabilities for new kinds of elements
+ * by providing new View implementations. The default set of views
+ * are provided by the <code>HTMLFactory</code> class. This can
+ * be easily changed by subclassing or replacing the HTMLFactory
+ * and reimplementing the getViewFactory method to return the alternative
+ * factory.
+ * <li>
+ * The View implementations work primarily off of CSS attributes,
+ * which are kept in the views. This makes it possible to have
+ * multiple views mapped over the same model that appear substantially
+ * different. This can be especially useful for printing. For
+ * most HTML attributes, the HTML attributes are converted to CSS
+ * attributes for display. This helps make the View implementations
+ * more general purpose
+ * </ol>
+ * <p>
+ * <dt>
+ * Asynchronous Loading
+ * <dd>
+ * Larger documents involve a lot of parsing and take some time
+ * to load. By default, this kit produces documents that will be
+ * loaded asynchronously if loaded using <code>JEditorPane.setPage</code>.
+ * This is controlled by a property on the document. The method
+ * <a href="#createDefaultDocument">createDefaultDocument</a> can
+ * be overriden to change this. The batching of work is done
+ * by the <code>HTMLDocument.HTMLReader</code> class. The actual
+ * work is done by the <code>DefaultStyledDocument</code> and
+ * <code>AbstractDocument</code> classes in the text package.
+ * <p>
+ * <dt>
+ * Customization from current LAF
+ * <dd>
+ * HTML provides a well known set of features without exactly
+ * specifying the display characteristics. Swing has a theme
+ * mechanism for its look-and-feel implementations. It is desirable
+ * for the look-and-feel to feed display characteristics into the
+ * HTML views. An user with poor vision for example would want
+ * high contrast and larger than typical fonts.
+ * <p>
+ * The support for this is provided by the <code>StyleSheet</code>
+ * class. The presentation of the HTML can be heavily influenced
+ * by the setting of the StyleSheet property on the EditorKit.
+ * <p>
+ * <dt>
+ * Not lossy
+ * <dd>
+ * An EditorKit has the ability to be read and save documents.
+ * It is generally the most pleasing to users if there is no loss
+ * of data between the two operation. The policy of the HTMLEditorKit
+ * will be to store things not recognized or not necessarily visible
+ * so they can be subsequently written out. The model of the HTML document
+ * should therefore contain all information discovered while reading the
+ * document. This is constrained in some ways by the need to support
+ * editing (i.e. incorrect documents sometimes must be normalized).
+ * The guiding principle is that information shouldn't be lost, but
+ * some might be synthesized to produce a more correct model or it might
+ * be rearranged.
+ * </dl>
+ *
+ * @author Timothy Prinzing
+ */
+public class HTMLEditorKit extends StyledEditorKit implements Accessible {
+
+ private JEditorPane theEditor;
+
+ /**
+ * Constructs an HTMLEditorKit, creates a StyleContext,
+ * and loads the style sheet.
+ */
+ public HTMLEditorKit() {
+
+ }
+
+ /**
+ * Get the MIME type of the data that this
+ * kit represents support for. This kit supports
+ * the type <code>text/html</code>.
+ *
+ * @return the type
+ */
+ public String getContentType() {
+ return "text/html";
+ }
+
+ /**
+ * Fetch a factory that is suitable for producing
+ * views of any models that are produced by this
+ * kit.
+ *
+ * @return the factory
+ */
+ public ViewFactory getViewFactory() {
+ return defaultFactory;
+ }
+
+ /**
+ * Create an uninitialized text storage model
+ * that is appropriate for this type of editor.
+ *
+ * @return the model
+ */
+ public Document createDefaultDocument() {
+ StyleSheet styles = getStyleSheet();
+ StyleSheet ss = new StyleSheet();
+
+ ss.addStyleSheet(styles);
+
+ HTMLDocument doc = new HTMLDocument(ss);
+ doc.setParser(getParser());
+ doc.setAsynchronousLoadPriority(4);
+ doc.setTokenThreshold(100);
+ return doc;
+ }
+
+ /**
+ * Try to get an HTML parser from the document. If no parser is set for
+ * the document, return the editor kit's default parser. It is an error
+ * if no parser could be obtained from the editor kit.
+ */
+ private Parser ensureParser(HTMLDocument doc) throws IOException {
+ Parser p = doc.getParser();
+ if (p == null) {
+ p = getParser();
+ }
+ if (p == null) {
+ throw new IOException("Can't load parser");
+ }
+ return p;
+ }
+
+ /**
+ * Inserts content from the given stream. If <code>doc</code> is
+ * an instance of HTMLDocument, this will read
+ * HTML 3.2 text. Inserting HTML into a non-empty document must be inside
+ * the body Element, if you do not insert into the body an exception will
+ * be thrown. When inserting into a non-empty document all tags outside
+ * of the body (head, title) will be dropped.
+ *
+ * @param in the stream to read from
+ * @param doc the destination for the insertion
+ * @param pos the location in the document to place the
+ * content
+ * @exception IOException on any I/O error
+ * @exception BadLocationException if pos represents an invalid
+ * location within the document
+ * @exception RuntimeException (will eventually be a BadLocationException)
+ * if pos is invalid
+ */
+ public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
+
+ if (doc instanceof HTMLDocument) {
+ HTMLDocument hdoc = (HTMLDocument) doc;
+ if (pos > doc.getLength()) {
+ throw new BadLocationException("Invalid location", pos);
+ }
+
+ Parser p = ensureParser(hdoc);
+ ParserCallback receiver = hdoc.getReader(pos);
+ Boolean ignoreCharset = (Boolean)doc.getProperty("IgnoreCharsetDirective");
+ p.parse(in, receiver, (ignoreCharset == null) ? false : ignoreCharset.booleanValue());
+ receiver.flush();
+ } else {
+ super.read(in, doc, pos);
+ }
+ }
+
+ /**
+ * Inserts HTML into an existing document.
+ *
+ * @param doc the document to insert into
+ * @param offset the offset to insert HTML at
+ * @param popDepth the number of ElementSpec.EndTagTypes to generate before
+ * inserting
+ * @param pushDepth the number of ElementSpec.StartTagTypes with a direction
+ * of ElementSpec.JoinNextDirection that should be generated
+ * before inserting, but after the end tags have been generated
+ * @param insertTag the first tag to start inserting into document
+ * @exception RuntimeException (will eventually be a BadLocationException)
+ * if pos is invalid
+ */
+ public void insertHTML(HTMLDocument doc, int offset, String html,
+ int popDepth, int pushDepth,
+ HTML.Tag insertTag) throws
+ BadLocationException, IOException {
+ if (offset > doc.getLength()) {
+ throw new BadLocationException("Invalid location", offset);
+ }
+
+ Parser p = ensureParser(doc);
+ ParserCallback receiver = doc.getReader(offset, popDepth, pushDepth,
+ insertTag);
+ Boolean ignoreCharset = (Boolean)doc.getProperty
+ ("IgnoreCharsetDirective");
+ p.parse(new StringReader(html), receiver, (ignoreCharset == null) ?
+ false : ignoreCharset.booleanValue());
+ receiver.flush();
+ }
+
+ /**
+ * Write content from a document to the given stream
+ * in a format appropriate for this kind of content handler.
+ *
+ * @param out the stream to write to
+ * @param doc the source for the write
+ * @param pos the location in the document to fetch the
+ * content
+ * @param len the amount to write out
+ * @exception IOException on any I/O error
+ * @exception BadLocationException if pos represents an invalid
+ * location within the document
+ */
+ public void write(Writer out, Document doc, int pos, int len)
+ throws IOException, BadLocationException {
+
+ if (doc instanceof HTMLDocument) {
+ HTMLWriter w = new HTMLWriter(out, (HTMLDocument)doc, pos, len);
+ w.write();
+ } else if (doc instanceof StyledDocument) {
+ MinimalHTMLWriter w = new MinimalHTMLWriter(out, (StyledDocument)doc, pos, len);
+ w.write();
+ } else {
+ super.write(out, doc, pos, len);
+ }
+ }
+
+ /**
+ * Called when the kit is being installed into the
+ * a JEditorPane.
+ *
+ * @param c the JEditorPane
+ */
+ public void install(JEditorPane c) {
+ c.addMouseListener(linkHandler);
+ c.addMouseMotionListener(linkHandler);
+ c.addCaretListener(nextLinkAction);
+ super.install(c);
+ theEditor = c;
+ }
+
+ /**
+ * Called when the kit is being removed from the
+ * JEditorPane. This is used to unregister any
+ * listeners that were attached.
+ *
+ * @param c the JEditorPane
+ */
+ public void deinstall(JEditorPane c) {
+ c.removeMouseListener(linkHandler);
+ c.removeMouseMotionListener(linkHandler);
+ c.removeCaretListener(nextLinkAction);
+ super.deinstall(c);
+ theEditor = null;
+ }
+
+ /**
+ * Default Cascading Style Sheet file that sets
+ * up the tag views.
+ */
+ public static final String DEFAULT_CSS = "default.css";
+
+ /**
+ * Set the set of styles to be used to render the various
+ * HTML elements. These styles are specified in terms of
+ * CSS specifications. Each document produced by the kit
+ * will have a copy of the sheet which it can add the
+ * document specific styles to. By default, the StyleSheet
+ * specified is shared by all HTMLEditorKit instances.
+ * This should be reimplemented to provide a finer granularity
+ * if desired.
+ */
+ public void setStyleSheet(StyleSheet s) {
+ defaultStyles = s;
+ }
+
+ /**
+ * Get the set of styles currently being used to render the
+ * HTML elements. By default the resource specified by
+ * DEFAULT_CSS gets loaded, and is shared by all HTMLEditorKit
+ * instances.
+ */
+ public StyleSheet getStyleSheet() {
+ if (defaultStyles == null) {
+ defaultStyles = new StyleSheet();
+ try {
+ InputStream is = HTMLEditorKit.getResourceAsStream(DEFAULT_CSS);
+ Reader r = new BufferedReader(
+ new InputStreamReader(is, "ISO-8859-1"));
+ defaultStyles.loadRules(r, null);
+ r.close();
+ } catch (Throwable e) {
+ // on error we simply have no styles... the html
+ // will look mighty wrong but still function.
+ }
+ }
+ return defaultStyles;
+ }
+
+ /**
+ * Fetch a resource relative to the HTMLEditorKit classfile.
+ * If this is called on 1.2 the loading will occur under the
+ * protection of a doPrivileged call to allow the HTMLEditorKit
+ * to function when used in an applet.
+ *
+ * @param name the name of the resource, relative to the
+ * HTMLEditorKit class
+ * @return a stream representing the resource
+ */
+ static InputStream getResourceAsStream(String name) {
+ try {
+ return ResourceLoader.getResourceAsStream(name);
+ } catch (Throwable e) {
+ // If the class doesn't exist or we have some other
+ // problem we just try to call getResourceAsStream directly.
+ return HTMLEditorKit.class.getResourceAsStream(name);
+ }
+ }
+
+ /**
+ * Fetches the command list for the editor. This is
+ * the list of commands supported by the superclass
+ * augmented by the collection of commands defined
+ * locally for style operations.
+ *
+ * @return the command list
+ */
+ public Action[] getActions() {
+ return TextAction.augmentList(super.getActions(), this.defaultActions);
+ }
+
+ /**
+ * Copies the key/values in <code>element</code>s AttributeSet into
+ * <code>set</code>. This does not copy component, icon, or element
+ * names attributes. Subclasses may wish to refine what is and what
+ * isn't copied here. But be sure to first remove all the attributes that
+ * are in <code>set</code>.<p>
+ * This is called anytime the caret moves over a different location.
+ *
+ */
+ protected void createInputAttributes(Element element,
+ MutableAttributeSet set) {
+ set.removeAttributes(set);
+ set.addAttributes(element.getAttributes());
+ set.removeAttribute(StyleConstants.ComposedTextAttribute);
+
+ Object o = set.getAttribute(StyleConstants.NameAttribute);
+ if (o instanceof HTML.Tag) {
+ HTML.Tag tag = (HTML.Tag)o;
+ // PENDING: we need a better way to express what shouldn't be
+ // copied when editing...
+ if(tag == HTML.Tag.IMG) {
+ // Remove the related image attributes, src, width, height
+ set.removeAttribute(HTML.Attribute.SRC);
+ set.removeAttribute(HTML.Attribute.HEIGHT);
+ set.removeAttribute(HTML.Attribute.WIDTH);
+ set.addAttribute(StyleConstants.NameAttribute,
+ HTML.Tag.CONTENT);
+ }
+ else if (tag == HTML.Tag.HR || tag == HTML.Tag.BR) {
+ // Don't copy HRs or BRs either.
+ set.addAttribute(StyleConstants.NameAttribute,
+ HTML.Tag.CONTENT);
+ }
+ else if (tag == HTML.Tag.COMMENT) {
+ // Don't copy COMMENTs either
+ set.addAttribute(StyleConstants.NameAttribute,
+ HTML.Tag.CONTENT);
+ set.removeAttribute(HTML.Attribute.COMMENT);
+ }
+ else if (tag == HTML.Tag.INPUT) {
+ // or INPUT either
+ set.addAttribute(StyleConstants.NameAttribute,
+ HTML.Tag.CONTENT);
+ set.removeAttribute(HTML.Tag.INPUT);
+ }
+ else if (tag instanceof HTML.UnknownTag) {
+ // Don't copy unknowns either:(
+ set.addAttribute(StyleConstants.NameAttribute,
+ HTML.Tag.CONTENT);
+ set.removeAttribute(HTML.Attribute.ENDTAG);
+ }
+ }
+ }
+
+ /**
+ * Gets the input attributes used for the styled
+ * editing actions.
+ *
+ * @return the attribute set
+ */
+ public MutableAttributeSet getInputAttributes() {
+ if (input == null) {
+ input = getStyleSheet().addStyle(null, null);
+ }
+ return input;
+ }
+
+ /**
+ * Sets the default cursor.
+ *
+ * @since 1.3
+ */
+ public void setDefaultCursor(Cursor cursor) {
+ defaultCursor = cursor;
+ }
+
+ /**
+ * Returns the default cursor.
+ *
+ * @since 1.3
+ */
+ public Cursor getDefaultCursor() {
+ return defaultCursor;
+ }
+
+ /**
+ * Sets the cursor to use over links.
+ *
+ * @since 1.3
+ */
+ public void setLinkCursor(Cursor cursor) {
+ linkCursor = cursor;
+ }
+
+ /**
+ * Returns the cursor to use over hyper links.
+ * @since 1.3
+ */
+ public Cursor getLinkCursor() {
+ return linkCursor;
+ }
+
+ /**
+ * Indicates whether an html form submission is processed automatically
+ * or only <code>FormSubmitEvent</code> is fired.
+ *
+ * @return true if html form submission is processed automatically,
+ * false otherwise.
+ *
+ * @see #setAutoFormSubmission
+ * @since 1.5
+ */
+ public boolean isAutoFormSubmission() {
+ return isAutoFormSubmission;
+ }
+
+ /**
+ * Specifies if an html form submission is processed
+ * automatically or only <code>FormSubmitEvent</code> is fired.
+ * By default it is set to true.
+ *
+ * @see #isAutoFormSubmission
+ * @see FormSubmitEvent
+ * @since 1.5
+ */
+ public void setAutoFormSubmission(boolean isAuto) {
+ isAutoFormSubmission = isAuto;
+ }
+
+ /**
+ * Creates a copy of the editor kit.
+ *
+ * @return the copy
+ */
+ public Object clone() {
+ HTMLEditorKit o = (HTMLEditorKit)super.clone();
+ if (o != null) {
+ o.input = null;
+ o.linkHandler = new LinkController();
+ }
+ return o;
+ }
+
+ /**
+ * Fetch the parser to use for reading HTML streams.
+ * This can be reimplemented to provide a different
+ * parser. The default implementation is loaded dynamically
+ * to avoid the overhead of loading the default parser if
+ * it's not used. The default parser is the HotJava parser
+ * using an HTML 3.2 DTD.
+ */
+ protected Parser getParser() {
+ if (defaultParser == null) {
+ try {
+ Class c = Class.forName("javax.swing.text.html.parser.ParserDelegator");
+ defaultParser = (Parser) c.newInstance();
+ } catch (Throwable e) {
+ }
+ }
+ return defaultParser;
+ }
+
+ // ----- Accessibility support -----
+ private AccessibleContext accessibleContext;
+
+ /**
+ * returns the AccessibleContext associated with this editor kit
+ *
+ * @return the AccessibleContext associated with this editor kit
+ * @since 1.4
+ */
+ public AccessibleContext getAccessibleContext() {
+ if (theEditor == null) {
+ return null;
+ }
+ if (accessibleContext == null) {
+ AccessibleHTML a = new AccessibleHTML(theEditor);
+ accessibleContext = a.getAccessibleContext();
+ }
+ return accessibleContext;
+ }
+
+ // --- variables ------------------------------------------
+
+ private static final Cursor MoveCursor = Cursor.getPredefinedCursor
+ (Cursor.HAND_CURSOR);
+ private static final Cursor DefaultCursor = Cursor.getPredefinedCursor
+ (Cursor.DEFAULT_CURSOR);
+
+ /** Shared factory for creating HTML Views. */
+ private static final ViewFactory defaultFactory = new HTMLFactory();
+
+ MutableAttributeSet input;
+ private static StyleSheet defaultStyles = null;
+ private LinkController linkHandler = new LinkController();
+ private static Parser defaultParser = null;
+ private Cursor defaultCursor = DefaultCursor;
+ private Cursor linkCursor = MoveCursor;
+ private boolean isAutoFormSubmission = true;
+
+ /**
+ * Class to watch the associated component and fire
+ * hyperlink events on it when appropriate.
+ */
+ public static class LinkController extends MouseAdapter implements MouseMotionListener, Serializable {
+ private Element curElem = null;
+ /**
+ * If true, the current element (curElem) represents an image.
+ */
+ private boolean curElemImage = false;
+ private String href = null;
+ /** This is used by viewToModel to avoid allocing a new array each
+ * time. */
+ private transient Position.Bias[] bias = new Position.Bias[1];
+ /**
+ * Current offset.
+ */
+ private int curOffset;
+
+ /**
+ * Called for a mouse click event.
+ * If the component is read-only (ie a browser) then
+ * the clicked event is used to drive an attempt to
+ * follow the reference specified by a link.
+ *
+ * @param e the mouse event
+ * @see MouseListener#mouseClicked
+ */
+ public void mouseClicked(MouseEvent e) {
+ JEditorPane editor = (JEditorPane) e.getSource();
+
+ if (! editor.isEditable() && editor.isEnabled() &&
+ SwingUtilities.isLeftMouseButton(e)) {
+ Point pt = new Point(e.getX(), e.getY());
+ int pos = editor.viewToModel(pt);
+ if (pos >= 0) {
+ activateLink(pos, editor, e);
+ }
+ }
+ }
+
+ // ignore the drags
+ public void mouseDragged(MouseEvent e) {
+ }
+
+ // track the moving of the mouse.
+ public void mouseMoved(MouseEvent e) {
+ JEditorPane editor = (JEditorPane) e.getSource();
+ if (!editor.isEnabled()) {
+ return;
+ }
+
+ HTMLEditorKit kit = (HTMLEditorKit)editor.getEditorKit();
+ boolean adjustCursor = true;
+ Cursor newCursor = kit.getDefaultCursor();
+ if (!editor.isEditable()) {
+ Point pt = new Point(e.getX(), e.getY());
+ int pos = editor.getUI().viewToModel(editor, pt, bias);
+ if (bias[0] == Position.Bias.Backward && pos > 0) {
+ pos--;
+ }
+ if (pos >= 0 &&(editor.getDocument() instanceof HTMLDocument)){
+ HTMLDocument hdoc = (HTMLDocument)editor.getDocument();
+ Element elem = hdoc.getCharacterElement(pos);
+ if (!doesElementContainLocation(editor, elem, pos,
+ e.getX(), e.getY())) {
+ elem = null;
+ }
+ if (curElem != elem || curElemImage) {
+ Element lastElem = curElem;
+ curElem = elem;
+ String href = null;
+ curElemImage = false;
+ if (elem != null) {
+ AttributeSet a = elem.getAttributes();
+ AttributeSet anchor = (AttributeSet)a.
+ getAttribute(HTML.Tag.A);
+ if (anchor == null) {
+ curElemImage = (a.getAttribute(StyleConstants.
+ NameAttribute) == HTML.Tag.IMG);
+ if (curElemImage) {
+ href = getMapHREF(editor, hdoc, elem, a,
+ pos, e.getX(), e.getY());
+ }
+ }
+ else {
+ href = (String)anchor.getAttribute
+ (HTML.Attribute.HREF);
+ }
+ }
+
+ if (href != this.href) {
+ // reference changed, fire event(s)
+ fireEvents(editor, hdoc, href, lastElem, e);
+ this.href = href;
+ if (href != null) {
+ newCursor = kit.getLinkCursor();
+ }
+ }
+ else {
+ adjustCursor = false;
+ }
+ }
+ else {
+ adjustCursor = false;
+ }
+ curOffset = pos;
+ }
+ }
+ if (adjustCursor && editor.getCursor() != newCursor) {
+ editor.setCursor(newCursor);
+ }
+ }
+
+ /**
+ * Returns a string anchor if the passed in element has a
+ * USEMAP that contains the passed in location.
+ */
+ private String getMapHREF(JEditorPane html, HTMLDocument hdoc,
+ Element elem, AttributeSet attr, int offset,
+ int x, int y) {
+ Object useMap = attr.getAttribute(HTML.Attribute.USEMAP);
+ if (useMap != null && (useMap instanceof String)) {
+ Map m = hdoc.getMap((String)useMap);
+ if (m != null && offset < hdoc.getLength()) {
+ Rectangle bounds;
+ TextUI ui = html.getUI();
+ try {
+ Shape lBounds = ui.modelToView(html, offset,
+ Position.Bias.Forward);
+ Shape rBounds = ui.modelToView(html, offset + 1,
+ Position.Bias.Backward);
+ bounds = lBounds.getBounds();
+ bounds.add((rBounds instanceof Rectangle) ?
+ (Rectangle)rBounds : rBounds.getBounds());
+ } catch (BadLocationException ble) {
+ bounds = null;
+ }
+ if (bounds != null) {
+ AttributeSet area = m.getArea(x - bounds.x,
+ y - bounds.y,
+ bounds.width,
+ bounds.height);
+ if (area != null) {
+ return (String)area.getAttribute(HTML.Attribute.
+ HREF);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the View representing <code>e</code> contains
+ * the location <code>x</code>, <code>y</code>. <code>offset</code>
+ * gives the offset into the Document to check for.
+ */
+ private boolean doesElementContainLocation(JEditorPane editor,
+ Element e, int offset,
+ int x, int y) {
+ if (e != null && offset > 0 && e.getStartOffset() == offset) {
+ try {
+ TextUI ui = editor.getUI();
+ Shape s1 = ui.modelToView(editor, offset,
+ Position.Bias.Forward);
+ if (s1 == null) {
+ return false;
+ }
+ Rectangle r1 = (s1 instanceof Rectangle) ? (Rectangle)s1 :
+ s1.getBounds();
+ Shape s2 = ui.modelToView(editor, e.getEndOffset(),
+ Position.Bias.Backward);
+ if (s2 != null) {
+ Rectangle r2 = (s2 instanceof Rectangle) ? (Rectangle)s2 :
+ s2.getBounds();
+ r1.add(r2);
+ }
+ return r1.contains(x, y);
+ } catch (BadLocationException ble) {
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Calls linkActivated on the associated JEditorPane
+ * if the given position represents a link.<p>This is implemented
+ * to forward to the method with the same name, but with the following
+ * args both == -1.
+ *
+ * @param pos the position
+ * @param editor the editor pane
+ */
+ protected void activateLink(int pos, JEditorPane editor) {
+ activateLink(pos, editor, null);
+ }
+
+ /**
+ * Calls linkActivated on the associated JEditorPane
+ * if the given position represents a link. If this was the result
+ * of a mouse click, <code>x</code> and
+ * <code>y</code> will give the location of the mouse, otherwise
+ * they will be < 0.
+ *
+ * @param pos the position
+ * @param html the editor pane
+ */
+ void activateLink(int pos, JEditorPane html, MouseEvent mouseEvent) {
+ Document doc = html.getDocument();
+ if (doc instanceof HTMLDocument) {
+ HTMLDocument hdoc = (HTMLDocument) doc;
+ Element e = hdoc.getCharacterElement(pos);
+ AttributeSet a = e.getAttributes();
+ AttributeSet anchor = (AttributeSet)a.getAttribute(HTML.Tag.A);
+ HyperlinkEvent linkEvent = null;
+ String description;
+ int x = -1;
+ int y = -1;
+
+ if (mouseEvent != null) {
+ x = mouseEvent.getX();
+ y = mouseEvent.getY();
+ }
+
+ if (anchor == null) {
+ href = getMapHREF(html, hdoc, e, a, pos, x, y);
+ }
+ else {
+ href = (String)anchor.getAttribute(HTML.Attribute.HREF);
+ }
+
+ if (href != null) {
+ linkEvent = createHyperlinkEvent(html, hdoc, href, anchor,
+ e, mouseEvent);
+ }
+ if (linkEvent != null) {
+ html.fireHyperlinkUpdate(linkEvent);
+ }
+ }
+ }
+
+ /**
+ * Creates and returns a new instance of HyperlinkEvent. If
+ * <code>hdoc</code> is a frame document a HTMLFrameHyperlinkEvent
+ * will be created.
+ */
+ HyperlinkEvent createHyperlinkEvent(JEditorPane html,
+ HTMLDocument hdoc, String href,
+ AttributeSet anchor,
+ Element element,
+ MouseEvent mouseEvent) {
+ URL u;
+ try {
+ URL base = hdoc.getBase();
+ u = new URL(base, href);
+ // Following is a workaround for 1.2, in which
+ // new URL("file://...", "#...") causes the filename to
+ // be lost.
+ if (href != null && "file".equals(u.getProtocol()) &&
+ href.startsWith("#")) {
+ String baseFile = base.getFile();
+ String newFile = u.getFile();
+ if (baseFile != null && newFile != null &&
+ !newFile.startsWith(baseFile)) {
+ u = new URL(base, baseFile + href);
+ }
+ }
+ } catch (MalformedURLException m) {
+ u = null;
+ }
+ HyperlinkEvent linkEvent = null;
+
+ if (!hdoc.isFrameDocument()) {
+ linkEvent = new HyperlinkEvent(
+ html, HyperlinkEvent.EventType.ACTIVATED, u, href,
+ element, mouseEvent);
+ } else {
+ String target = (anchor != null) ?
+ (String)anchor.getAttribute(HTML.Attribute.TARGET) : null;
+ if ((target == null) || (target.equals(""))) {
+ target = hdoc.getBaseTarget();
+ }
+ if ((target == null) || (target.equals(""))) {
+ target = "_self";
+ }
+ linkEvent = new HTMLFrameHyperlinkEvent(
+ html, HyperlinkEvent.EventType.ACTIVATED, u, href,
+ element, mouseEvent, target);
+ }
+ return linkEvent;
+ }
+
+ void fireEvents(JEditorPane editor, HTMLDocument doc, String href,
+ Element lastElem, MouseEvent mouseEvent) {
+ if (this.href != null) {
+ // fire an exited event on the old link
+ URL u;
+ try {
+ u = new URL(doc.getBase(), this.href);
+ } catch (MalformedURLException m) {
+ u = null;
+ }
+ HyperlinkEvent exit = new HyperlinkEvent(editor,
+ HyperlinkEvent.EventType.EXITED, u, this.href,
+ lastElem, mouseEvent);
+ editor.fireHyperlinkUpdate(exit);
+ }
+ if (href != null) {
+ // fire an entered event on the new link
+ URL u;
+ try {
+ u = new URL(doc.getBase(), href);
+ } catch (MalformedURLException m) {
+ u = null;
+ }
+ HyperlinkEvent entered = new HyperlinkEvent(editor,
+ HyperlinkEvent.EventType.ENTERED,
+ u, href, curElem, mouseEvent);
+ editor.fireHyperlinkUpdate(entered);
+ }
+ }
+ }
+
+ /**
+ * Interface to be supported by the parser. This enables
+ * providing a different parser while reusing some of the
+ * implementation provided by this editor kit.
+ */
+ public static abstract class Parser {
+ /**
+ * Parse the given stream and drive the given callback
+ * with the results of the parse. This method should
+ * be implemented to be thread-safe.
+ */
+ public abstract void parse(Reader r, ParserCallback cb, boolean ignoreCharSet) throws IOException;
+
+ }
+
+ /**
+ * The result of parsing drives these callback methods.
+ * The open and close actions should be balanced. The
+ * <code>flush</code> method will be the last method
+ * called, to give the receiver a chance to flush any
+ * pending data into the document.
+ * <p>Refer to DocumentParser, the default parser used, for further
+ * information on the contents of the AttributeSets, the positions, and
+ * other info.
+ *
+ * @see javax.swing.text.html.parser.DocumentParser
+ */
+ public static class ParserCallback {
+ /**
+ * This is passed as an attribute in the attributeset to indicate
+ * the element is implied eg, the string '&lt;&gt;foo&lt;\t&gt;'
+ * contains an implied html element and an implied body element.
+ *
+ * @since 1.3
+ */
+ public static final Object IMPLIED = "_implied_";
+
+
+ public void flush() throws BadLocationException {
+ }
+
+ public void handleText(char[] data, int pos) {
+ }
+
+ public void handleComment(char[] data, int pos) {
+ }
+
+ public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
+ }
+
+ public void handleEndTag(HTML.Tag t, int pos) {
+ }
+
+ public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
+ }
+
+ public void handleError(String errorMsg, int pos){
+ }
+
+ /**
+ * 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) {
+ }
+ }
+
+ /**
+ * A factory to build views for HTML. The following
+ * table describes what this factory will build by
+ * default.
+ *
+ * <table summary="Describes the tag and view created by this factory by default">
+ * <tr>
+ * <th align=left>Tag<th align=left>View created
+ * </tr><tr>
+ * <td>HTML.Tag.CONTENT<td>InlineView
+ * </tr><tr>
+ * <td>HTML.Tag.IMPLIED<td>javax.swing.text.html.ParagraphView
+ * </tr><tr>
+ * <td>HTML.Tag.P<td>javax.swing.text.html.ParagraphView
+ * </tr><tr>
+ * <td>HTML.Tag.H1<td>javax.swing.text.html.ParagraphView
+ * </tr><tr>
+ * <td>HTML.Tag.H2<td>javax.swing.text.html.ParagraphView
+ * </tr><tr>
+ * <td>HTML.Tag.H3<td>javax.swing.text.html.ParagraphView
+ * </tr><tr>
+ * <td>HTML.Tag.H4<td>javax.swing.text.html.ParagraphView
+ * </tr><tr>
+ * <td>HTML.Tag.H5<td>javax.swing.text.html.ParagraphView
+ * </tr><tr>
+ * <td>HTML.Tag.H6<td>javax.swing.text.html.ParagraphView
+ * </tr><tr>
+ * <td>HTML.Tag.DT<td>javax.swing.text.html.ParagraphView
+ * </tr><tr>
+ * <td>HTML.Tag.MENU<td>ListView
+ * </tr><tr>
+ * <td>HTML.Tag.DIR<td>ListView
+ * </tr><tr>
+ * <td>HTML.Tag.UL<td>ListView
+ * </tr><tr>
+ * <td>HTML.Tag.OL<td>ListView
+ * </tr><tr>
+ * <td>HTML.Tag.LI<td>BlockView
+ * </tr><tr>
+ * <td>HTML.Tag.DL<td>BlockView
+ * </tr><tr>
+ * <td>HTML.Tag.DD<td>BlockView
+ * </tr><tr>
+ * <td>HTML.Tag.BODY<td>BlockView
+ * </tr><tr>
+ * <td>HTML.Tag.HTML<td>BlockView
+ * </tr><tr>
+ * <td>HTML.Tag.CENTER<td>BlockView
+ * </tr><tr>
+ * <td>HTML.Tag.DIV<td>BlockView
+ * </tr><tr>
+ * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
+ * </tr><tr>
+ * <td>HTML.Tag.PRE<td>BlockView
+ * </tr><tr>
+ * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
+ * </tr><tr>
+ * <td>HTML.Tag.PRE<td>BlockView
+ * </tr><tr>
+ * <td>HTML.Tag.IMG<td>ImageView
+ * </tr><tr>
+ * <td>HTML.Tag.HR<td>HRuleView
+ * </tr><tr>
+ * <td>HTML.Tag.BR<td>BRView
+ * </tr><tr>
+ * <td>HTML.Tag.TABLE<td>javax.swing.text.html.TableView
+ * </tr><tr>
+ * <td>HTML.Tag.INPUT<td>FormView
+ * </tr><tr>
+ * <td>HTML.Tag.SELECT<td>FormView
+ * </tr><tr>
+ * <td>HTML.Tag.TEXTAREA<td>FormView
+ * </tr><tr>
+ * <td>HTML.Tag.OBJECT<td>ObjectView
+ * </tr><tr>
+ * <td>HTML.Tag.FRAMESET<td>FrameSetView
+ * </tr><tr>
+ * <td>HTML.Tag.FRAME<td>FrameView
+ * </tr>
+ * </table>
+ */
+ public static class HTMLFactory implements ViewFactory {
+
+ /**
+ * Creates a view from an element.
+ *
+ * @param elem the element
+ * @return the view
+ */
+ public View create(Element elem) {
+ AttributeSet attrs = elem.getAttributes();
+ Object elementName =
+ attrs.getAttribute(AbstractDocument.ElementNameAttribute);
+ Object o = (elementName != null) ?
+ null : attrs.getAttribute(StyleConstants.NameAttribute);
+ if (o instanceof HTML.Tag) {
+ HTML.Tag kind = (HTML.Tag) o;
+ if (kind == HTML.Tag.CONTENT) {
+ return new InlineView(elem);
+ } else if (kind == HTML.Tag.IMPLIED) {
+ String ws = (String) elem.getAttributes().getAttribute(
+ CSS.Attribute.WHITE_SPACE);
+ if ((ws != null) && ws.equals("pre")) {
+ return new LineView(elem);
+ }
+ return new javax.swing.text.html.ParagraphView(elem);
+ } else if ((kind == HTML.Tag.P) ||
+ (kind == HTML.Tag.H1) ||
+ (kind == HTML.Tag.H2) ||
+ (kind == HTML.Tag.H3) ||
+ (kind == HTML.Tag.H4) ||
+ (kind == HTML.Tag.H5) ||
+ (kind == HTML.Tag.H6) ||
+ (kind == HTML.Tag.DT)) {
+ // paragraph
+ return new javax.swing.text.html.ParagraphView(elem);
+ } else if ((kind == HTML.Tag.MENU) ||
+ (kind == HTML.Tag.DIR) ||
+ (kind == HTML.Tag.UL) ||
+ (kind == HTML.Tag.OL)) {
+ return new ListView(elem);
+ } else if (kind == HTML.Tag.BODY) {
+ return new BodyBlockView(elem);
+ } else if (kind == HTML.Tag.HTML) {
+ return new BlockView(elem, View.Y_AXIS);
+ } else if ((kind == HTML.Tag.LI) ||
+ (kind == HTML.Tag.CENTER) ||
+ (kind == HTML.Tag.DL) ||
+ (kind == HTML.Tag.DD) ||
+ (kind == HTML.Tag.DIV) ||
+ (kind == HTML.Tag.BLOCKQUOTE) ||
+ (kind == HTML.Tag.PRE) ||
+ (kind == HTML.Tag.FORM)) {
+ // vertical box
+ return new BlockView(elem, View.Y_AXIS);
+ } else if (kind == HTML.Tag.NOFRAMES) {
+ return new NoFramesView(elem, View.Y_AXIS);
+ } else if (kind==HTML.Tag.IMG) {
+ return new ImageView(elem);
+ } else if (kind == HTML.Tag.ISINDEX) {
+ return new IsindexView(elem);
+ } else if (kind == HTML.Tag.HR) {
+ return new HRuleView(elem);
+ } else if (kind == HTML.Tag.BR) {
+ return new BRView(elem);
+ } else if (kind == HTML.Tag.TABLE) {
+ return new javax.swing.text.html.TableView(elem);
+ } else if ((kind == HTML.Tag.INPUT) ||
+ (kind == HTML.Tag.SELECT) ||
+ (kind == HTML.Tag.TEXTAREA)) {
+ return new FormView(elem);
+ } else if (kind == HTML.Tag.OBJECT) {
+ return new ObjectView(elem);
+ } else if (kind == HTML.Tag.FRAMESET) {
+ if (elem.getAttributes().isDefined(HTML.Attribute.ROWS)) {
+ return new FrameSetView(elem, View.Y_AXIS);
+ } else if (elem.getAttributes().isDefined(HTML.Attribute.COLS)) {
+ return new FrameSetView(elem, View.X_AXIS);
+ }
+ throw new RuntimeException("Can't build a" + kind + ", " + elem + ":" +
+ "no ROWS or COLS defined.");
+ } else if (kind == HTML.Tag.FRAME) {
+ return new FrameView(elem);
+ } else if (kind instanceof HTML.UnknownTag) {
+ return new HiddenTagView(elem);
+ } else if (kind == HTML.Tag.COMMENT) {
+ return new CommentView(elem);
+ } else if (kind == HTML.Tag.HEAD) {
+ // Make the head never visible, and never load its
+ // children. For Cursor positioning,
+ // getNextVisualPositionFrom is overriden to always return
+ // the end offset of the element.
+ return new BlockView(elem, View.X_AXIS) {
+ public float getPreferredSpan(int axis) {
+ return 0;
+ }
+ public float getMinimumSpan(int axis) {
+ return 0;
+ }
+ public float getMaximumSpan(int axis) {
+ return 0;
+ }
+ protected void loadChildren(ViewFactory f) {
+ }
+ public Shape modelToView(int pos, Shape a,
+ Position.Bias b) throws BadLocationException {
+ return a;
+ }
+ public int getNextVisualPositionFrom(int pos,
+ Position.Bias b, Shape a,
+ int direction, Position.Bias[] biasRet) {
+ return getElement().getEndOffset();
+ }
+ };
+ } else if ((kind == HTML.Tag.TITLE) ||
+ (kind == HTML.Tag.META) ||
+ (kind == HTML.Tag.LINK) ||
+ (kind == HTML.Tag.STYLE) ||
+ (kind == HTML.Tag.SCRIPT) ||
+ (kind == HTML.Tag.AREA) ||
+ (kind == HTML.Tag.MAP) ||
+ (kind == HTML.Tag.PARAM) ||
+ (kind == HTML.Tag.APPLET)) {
+ return new HiddenTagView(elem);
+ }
+ }
+ // If we get here, it's either an element we don't know about
+ // or something from StyledDocument that doesn't have a mapping to HTML.
+ String nm = (elementName != null) ? (String)elementName :
+ elem.getName();
+ if (nm != null) {
+ if (nm.equals(AbstractDocument.ContentElementName)) {
+ return new LabelView(elem);
+ } else if (nm.equals(AbstractDocument.ParagraphElementName)) {
+ return new ParagraphView(elem);
+ } else if (nm.equals(AbstractDocument.SectionElementName)) {
+ return new BoxView(elem, View.Y_AXIS);
+ } else if (nm.equals(StyleConstants.ComponentElementName)) {
+ return new ComponentView(elem);
+ } else if (nm.equals(StyleConstants.IconElementName)) {
+ return new IconView(elem);
+ }
+ }
+
+ // default to text display
+ return new LabelView(elem);
+ }
+
+ static class BodyBlockView extends BlockView implements ComponentListener {
+ public BodyBlockView(Element elem) {
+ super(elem,View.Y_AXIS);
+ }
+ // reimplement major axis requirements to indicate that the
+ // block is flexible for the body element... so that it can
+ // be stretched to fill the background properly.
+ protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
+ r = super.calculateMajorAxisRequirements(axis, r);
+ r.maximum = Integer.MAX_VALUE;
+ return r;
+ }
+
+ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
+ Container container = getContainer();
+ Container parentContainer;
+ if (container != null
+ && (container instanceof javax.swing.JEditorPane)
+ && (parentContainer = container.getParent()) != null
+ && (parentContainer instanceof javax.swing.JViewport)) {
+ JViewport viewPort = (JViewport)parentContainer;
+ Object cachedObject;
+ if (cachedViewPort != null) {
+ if ((cachedObject = cachedViewPort.get()) != null) {
+ if (cachedObject != viewPort) {
+ ((JComponent)cachedObject).removeComponentListener(this);
+ }
+ } else {
+ cachedViewPort = null;
+ }
+ }
+ if (cachedViewPort == null) {
+ viewPort.addComponentListener(this);
+ cachedViewPort = new WeakReference(viewPort);
+ }
+
+ componentVisibleWidth = viewPort.getExtentSize().width;
+ if (componentVisibleWidth > 0) {
+ Insets insets = container.getInsets();
+ viewVisibleWidth = componentVisibleWidth - insets.left - getLeftInset();
+ //try to use viewVisibleWidth if it is smaller than targetSpan
+ targetSpan = Math.min(targetSpan, viewVisibleWidth);
+ }
+ } else {
+ if (cachedViewPort != null) {
+ Object cachedObject;
+ if ((cachedObject = cachedViewPort.get()) != null) {
+ ((JComponent)cachedObject).removeComponentListener(this);
+ }
+ cachedViewPort = null;
+ }
+ }
+ super.layoutMinorAxis(targetSpan, axis, offsets, spans);
+ }
+
+ public void setParent(View parent) {
+ //if parent == null unregister component listener
+ if (parent == null) {
+ if (cachedViewPort != null) {
+ Object cachedObject;
+ if ((cachedObject = cachedViewPort.get()) != null) {
+ ((JComponent)cachedObject).removeComponentListener(this);
+ }
+ cachedViewPort = null;
+ }
+ }
+ super.setParent(parent);
+ }
+
+ public void componentResized(ComponentEvent e) {
+ if ( !(e.getSource() instanceof JViewport) ) {
+ return;
+ }
+ JViewport viewPort = (JViewport)e.getSource();
+ if (componentVisibleWidth != viewPort.getExtentSize().width) {
+ Document doc = getDocument();
+ if (doc instanceof AbstractDocument) {
+ AbstractDocument document = (AbstractDocument)getDocument();
+ document.readLock();
+ try {
+ layoutChanged(X_AXIS);
+ preferenceChanged(null, true, true);
+ } finally {
+ document.readUnlock();
+ }
+
+ }
+ }
+ }
+ public void componentHidden(ComponentEvent e) {
+ }
+ public void componentMoved(ComponentEvent e) {
+ }
+ public void componentShown(ComponentEvent e) {
+ }
+ /*
+ * we keep weak reference to viewPort if and only if BodyBoxView is listening for ComponentEvents
+ * only in that case cachedViewPort is not equal to null.
+ * we need to keep this reference in order to remove BodyBoxView from viewPort listeners.
+ *
+ */
+ private Reference cachedViewPort = null;
+ private boolean isListening = false;
+ private int viewVisibleWidth = Integer.MAX_VALUE;
+ private int componentVisibleWidth = Integer.MAX_VALUE;
+ }
+
+ }
+
+ // --- Action implementations ------------------------------
+
+/** The bold action identifier
+*/
+ public static final String BOLD_ACTION = "html-bold-action";
+/** The italic action identifier
+*/
+ public static final String ITALIC_ACTION = "html-italic-action";
+/** The paragraph left indent action identifier
+*/
+ public static final String PARA_INDENT_LEFT = "html-para-indent-left";
+/** The paragraph right indent action identifier
+*/
+ public static final String PARA_INDENT_RIGHT = "html-para-indent-right";
+/** The font size increase to next value action identifier
+*/
+ public static final String FONT_CHANGE_BIGGER = "html-font-bigger";
+/** The font size decrease to next value action identifier
+*/
+ public static final String FONT_CHANGE_SMALLER = "html-font-smaller";
+/** The Color choice action identifier
+ The color is passed as an argument
+*/
+ public static final String COLOR_ACTION = "html-color-action";
+/** The logical style choice action identifier
+ The logical style is passed in as an argument
+*/
+ public static final String LOGICAL_STYLE_ACTION = "html-logical-style-action";
+ /**
+ * Align images at the top.
+ */
+ public static final String IMG_ALIGN_TOP = "html-image-align-top";
+
+ /**
+ * Align images in the middle.
+ */
+ public static final String IMG_ALIGN_MIDDLE = "html-image-align-middle";
+
+ /**
+ * Align images at the bottom.
+ */
+ public static final String IMG_ALIGN_BOTTOM = "html-image-align-bottom";
+
+ /**
+ * Align images at the border.
+ */
+ public static final String IMG_BORDER = "html-image-border";
+
+
+ /** HTML used when inserting tables. */
+ private static final String INSERT_TABLE_HTML = "<table border=1><tr><td></td></tr></table>";
+
+ /** HTML used when inserting unordered lists. */
+ private static final String INSERT_UL_HTML = "<ul><li></li></ul>";
+
+ /** HTML used when inserting ordered lists. */
+ private static final String INSERT_OL_HTML = "<ol><li></li></ol>";
+
+ /** HTML used when inserting hr. */
+ private static final String INSERT_HR_HTML = "<hr>";
+
+ /** HTML used when inserting pre. */
+ private static final String INSERT_PRE_HTML = "<pre></pre>";
+
+ private static final NavigateLinkAction nextLinkAction =
+ new NavigateLinkAction("next-link-action");
+
+ private static final NavigateLinkAction previousLinkAction =
+ new NavigateLinkAction("previous-link-action");
+
+ private static final ActivateLinkAction activateLinkAction =
+ new ActivateLinkAction("activate-link-action");
+
+ private static final Action[] defaultActions = {
+ new InsertHTMLTextAction("InsertTable", INSERT_TABLE_HTML,
+ HTML.Tag.BODY, HTML.Tag.TABLE),
+ new InsertHTMLTextAction("InsertTableRow", INSERT_TABLE_HTML,
+ HTML.Tag.TABLE, HTML.Tag.TR,
+ HTML.Tag.BODY, HTML.Tag.TABLE),
+ new InsertHTMLTextAction("InsertTableDataCell", INSERT_TABLE_HTML,
+ HTML.Tag.TR, HTML.Tag.TD,
+ HTML.Tag.BODY, HTML.Tag.TABLE),
+ new InsertHTMLTextAction("InsertUnorderedList", INSERT_UL_HTML,
+ HTML.Tag.BODY, HTML.Tag.UL),
+ new InsertHTMLTextAction("InsertUnorderedListItem", INSERT_UL_HTML,
+ HTML.Tag.UL, HTML.Tag.LI,
+ HTML.Tag.BODY, HTML.Tag.UL),
+ new InsertHTMLTextAction("InsertOrderedList", INSERT_OL_HTML,
+ HTML.Tag.BODY, HTML.Tag.OL),
+ new InsertHTMLTextAction("InsertOrderedListItem", INSERT_OL_HTML,
+ HTML.Tag.OL, HTML.Tag.LI,
+ HTML.Tag.BODY, HTML.Tag.OL),
+ new InsertHRAction(),
+ new InsertHTMLTextAction("InsertPre", INSERT_PRE_HTML,
+ HTML.Tag.BODY, HTML.Tag.PRE),
+ nextLinkAction, previousLinkAction, activateLinkAction,
+
+ new BeginAction(beginAction, false),
+ new BeginAction(selectionBeginAction, true)
+ };
+
+ // link navigation support
+ private boolean foundLink = false;
+ private int prevHypertextOffset = -1;
+ private Object linkNavigationTag;
+
+
+ /**
+ * An abstract Action providing some convenience methods that may
+ * be useful in inserting HTML into an existing document.
+ * <p>NOTE: None of the convenience methods obtain a lock on the
+ * document. If you have another thread modifying the text these
+ * methods may have inconsistent behavior, or return the wrong thing.
+ */
+ public static abstract class HTMLTextAction extends StyledTextAction {
+ public HTMLTextAction(String name) {
+ super(name);
+ }
+
+ /**
+ * @return HTMLDocument of <code>e</code>.
+ */
+ protected HTMLDocument getHTMLDocument(JEditorPane e) {
+ Document d = e.getDocument();
+ if (d instanceof HTMLDocument) {
+ return (HTMLDocument) d;
+ }
+ throw new IllegalArgumentException("document must be HTMLDocument");
+ }
+
+ /**
+ * @return HTMLEditorKit for <code>e</code>.
+ */
+ protected HTMLEditorKit getHTMLEditorKit(JEditorPane e) {
+ EditorKit k = e.getEditorKit();
+ if (k instanceof HTMLEditorKit) {
+ return (HTMLEditorKit) k;
+ }
+ throw new IllegalArgumentException("EditorKit must be HTMLEditorKit");
+ }
+
+ /**
+ * Returns an array of the Elements that contain <code>offset</code>.
+ * The first elements corresponds to the root.
+ */
+ protected Element[] getElementsAt(HTMLDocument doc, int offset) {
+ return getElementsAt(doc.getDefaultRootElement(), offset, 0);
+ }
+
+ /**
+ * Recursive method used by getElementsAt.
+ */
+ private Element[] getElementsAt(Element parent, int offset,
+ int depth) {
+ if (parent.isLeaf()) {
+ Element[] retValue = new Element[depth + 1];
+ retValue[depth] = parent;
+ return retValue;
+ }
+ Element[] retValue = getElementsAt(parent.getElement
+ (parent.getElementIndex(offset)), offset, depth + 1);
+ retValue[depth] = parent;
+ return retValue;
+ }
+
+ /**
+ * Returns number of elements, starting at the deepest leaf, needed
+ * to get to an element representing <code>tag</code>. This will
+ * return -1 if no elements is found representing <code>tag</code>,
+ * or 0 if the parent of the leaf at <code>offset</code> represents
+ * <code>tag</code>.
+ */
+ protected int elementCountToTag(HTMLDocument doc, int offset,
+ HTML.Tag tag) {
+ int depth = -1;
+ Element e = doc.getCharacterElement(offset);
+ while (e != null && e.getAttributes().getAttribute
+ (StyleConstants.NameAttribute) != tag) {
+ e = e.getParentElement();
+ depth++;
+ }
+ if (e == null) {
+ return -1;
+ }
+ return depth;
+ }
+
+ /**
+ * Returns the deepest element at <code>offset</code> matching
+ * <code>tag</code>.
+ */
+ protected Element findElementMatchingTag(HTMLDocument doc, int offset,
+ HTML.Tag tag) {
+ Element e = doc.getDefaultRootElement();
+ Element lastMatch = null;
+ while (e != null) {
+ if (e.getAttributes().getAttribute
+ (StyleConstants.NameAttribute) == tag) {
+ lastMatch = e;
+ }
+ e = e.getElement(e.getElementIndex(offset));
+ }
+ return lastMatch;
+ }
+ }
+
+
+ /**
+ * InsertHTMLTextAction can be used to insert an arbitrary string of HTML
+ * into an existing HTML document. At least two HTML.Tags need to be
+ * supplied. The first Tag, parentTag, identifies the parent in
+ * the document to add the elements to. The second tag, addTag,
+ * identifies the first tag that should be added to the document as
+ * seen in the HTML string. One important thing to remember, is that
+ * the parser is going to generate all the appropriate tags, even if
+ * they aren't in the HTML string passed in.<p>
+ * For example, lets say you wanted to create an action to insert
+ * a table into the body. The parentTag would be HTML.Tag.BODY,
+ * addTag would be HTML.Tag.TABLE, and the string could be something
+ * like &lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;.
+ * <p>There is also an option to supply an alternate parentTag and
+ * addTag. These will be checked for if there is no parentTag at
+ * offset.
+ */
+ public static class InsertHTMLTextAction extends HTMLTextAction {
+ public InsertHTMLTextAction(String name, String html,
+ HTML.Tag parentTag, HTML.Tag addTag) {
+ this(name, html, parentTag, addTag, null, null);
+ }
+
+ public InsertHTMLTextAction(String name, String html,
+ HTML.Tag parentTag,
+ HTML.Tag addTag,
+ HTML.Tag alternateParentTag,
+ HTML.Tag alternateAddTag) {
+ this(name, html, parentTag, addTag, alternateParentTag,
+ alternateAddTag, true);
+ }
+
+ /* public */
+ InsertHTMLTextAction(String name, String html,
+ HTML.Tag parentTag,
+ HTML.Tag addTag,
+ HTML.Tag alternateParentTag,
+ HTML.Tag alternateAddTag,
+ boolean adjustSelection) {
+ super(name);
+ this.html = html;
+ this.parentTag = parentTag;
+ this.addTag = addTag;
+ this.alternateParentTag = alternateParentTag;
+ this.alternateAddTag = alternateAddTag;
+ this.adjustSelection = adjustSelection;
+ }
+
+ /**
+ * A cover for HTMLEditorKit.insertHTML. If an exception it
+ * thrown it is wrapped in a RuntimeException and thrown.
+ */
+ protected void insertHTML(JEditorPane editor, HTMLDocument doc,
+ int offset, String html, int popDepth,
+ int pushDepth, HTML.Tag addTag) {
+ try {
+ getHTMLEditorKit(editor).insertHTML(doc, offset, html,
+ popDepth, pushDepth,
+ addTag);
+ } catch (IOException ioe) {
+ throw new RuntimeException("Unable to insert: " + ioe);
+ } catch (BadLocationException ble) {
+ throw new RuntimeException("Unable to insert: " + ble);
+ }
+ }
+
+ /**
+ * This is invoked when inserting at a boundary. It determines
+ * the number of pops, and then the number of pushes that need
+ * to be performed, and then invokes insertHTML.
+ * @since 1.3
+ */
+ protected void insertAtBoundary(JEditorPane editor, HTMLDocument doc,
+ int offset, Element insertElement,
+ String html, HTML.Tag parentTag,
+ HTML.Tag addTag) {
+ insertAtBoundry(editor, doc, offset, insertElement, html,
+ parentTag, addTag);
+ }
+
+ /**
+ * This is invoked when inserting at a boundary. It determines
+ * the number of pops, and then the number of pushes that need
+ * to be performed, and then invokes insertHTML.
+ * @deprecated As of Java 2 platform v1.3, use insertAtBoundary
+ */
+ @Deprecated
+ protected void insertAtBoundry(JEditorPane editor, HTMLDocument doc,
+ int offset, Element insertElement,
+ String html, HTML.Tag parentTag,
+ HTML.Tag addTag) {
+ // Find the common parent.
+ Element e;
+ Element commonParent;
+ boolean isFirst = (offset == 0);
+
+ if (offset > 0 || insertElement == null) {
+ e = doc.getDefaultRootElement();
+ while (e != null && e.getStartOffset() != offset &&
+ !e.isLeaf()) {
+ e = e.getElement(e.getElementIndex(offset));
+ }
+ commonParent = (e != null) ? e.getParentElement() : null;
+ }
+ else {
+ // If inserting at the origin, the common parent is the
+ // insertElement.
+ commonParent = insertElement;
+ }
+ if (commonParent != null) {
+ // Determine how many pops to do.
+ int pops = 0;
+ int pushes = 0;
+ if (isFirst && insertElement != null) {
+ e = commonParent;
+ while (e != null && !e.isLeaf()) {
+ e = e.getElement(e.getElementIndex(offset));
+ pops++;
+ }
+ }
+ else {
+ e = commonParent;
+ offset--;
+ while (e != null && !e.isLeaf()) {
+ e = e.getElement(e.getElementIndex(offset));
+ pops++;
+ }
+
+ // And how many pushes
+ e = commonParent;
+ offset++;
+ while (e != null && e != insertElement) {
+ e = e.getElement(e.getElementIndex(offset));
+ pushes++;
+ }
+ }
+ pops = Math.max(0, pops - 1);
+
+ // And insert!
+ insertHTML(editor, doc, offset, html, pops, pushes, addTag);
+ }
+ }
+
+ /**
+ * If there is an Element with name <code>tag</code> at
+ * <code>offset</code>, this will invoke either insertAtBoundary
+ * or <code>insertHTML</code>. This returns true if there is
+ * a match, and one of the inserts is invoked.
+ */
+ /*protected*/
+ boolean insertIntoTag(JEditorPane editor, HTMLDocument doc,
+ int offset, HTML.Tag tag, HTML.Tag addTag) {
+ Element e = findElementMatchingTag(doc, offset, tag);
+ if (e != null && e.getStartOffset() == offset) {
+ insertAtBoundary(editor, doc, offset, e, html,
+ tag, addTag);
+ return true;
+ }
+ else if (offset > 0) {
+ int depth = elementCountToTag(doc, offset - 1, tag);
+ if (depth != -1) {
+ insertHTML(editor, doc, offset, html, depth, 0, addTag);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called after an insertion to adjust the selection.
+ */
+ /* protected */
+ void adjustSelection(JEditorPane pane, HTMLDocument doc,
+ int startOffset, int oldLength) {
+ int newLength = doc.getLength();
+ if (newLength != oldLength && startOffset < newLength) {
+ if (startOffset > 0) {
+ String text;
+ try {
+ text = doc.getText(startOffset - 1, 1);
+ } catch (BadLocationException ble) {
+ text = null;
+ }
+ if (text != null && text.length() > 0 &&
+ text.charAt(0) == '\n') {
+ pane.select(startOffset, startOffset);
+ }
+ else {
+ pane.select(startOffset + 1, startOffset + 1);
+ }
+ }
+ else {
+ pane.select(1, 1);
+ }
+ }
+ }
+
+ /**
+ * Inserts the HTML into the document.
+ *
+ * @param ae the event
+ */
+ public void actionPerformed(ActionEvent ae) {
+ JEditorPane editor = getEditor(ae);
+ if (editor != null) {
+ HTMLDocument doc = getHTMLDocument(editor);
+ int offset = editor.getSelectionStart();
+ int length = doc.getLength();
+ boolean inserted;
+ // Try first choice
+ if (!insertIntoTag(editor, doc, offset, parentTag, addTag) &&
+ alternateParentTag != null) {
+ // Then alternate.
+ inserted = insertIntoTag(editor, doc, offset,
+ alternateParentTag,
+ alternateAddTag);
+ }
+ else {
+ inserted = true;
+ }
+ if (adjustSelection && inserted) {
+ adjustSelection(editor, doc, offset, length);
+ }
+ }
+ }
+
+ /** HTML to insert. */
+ protected String html;
+ /** Tag to check for in the document. */
+ protected HTML.Tag parentTag;
+ /** Tag in HTML to start adding tags from. */
+ protected HTML.Tag addTag;
+ /** Alternate Tag to check for in the document if parentTag is
+ * not found. */
+ protected HTML.Tag alternateParentTag;
+ /** Alternate tag in HTML to start adding tags from if parentTag
+ * is not found and alternateParentTag is found. */
+ protected HTML.Tag alternateAddTag;
+ /** True indicates the selection should be adjusted after an insert. */
+ boolean adjustSelection;
+ }
+
+
+ /**
+ * InsertHRAction is special, at actionPerformed time it will determine
+ * the parent HTML.Tag based on the paragraph element at the selection
+ * start.
+ */
+ static class InsertHRAction extends InsertHTMLTextAction {
+ InsertHRAction() {
+ super("InsertHR", "<hr>", null, HTML.Tag.IMPLIED, null, null,
+ false);
+ }
+
+ /**
+ * Inserts the HTML into the document.
+ *
+ * @param ae the event
+ */
+ public void actionPerformed(ActionEvent ae) {
+ JEditorPane editor = getEditor(ae);
+ if (editor != null) {
+ HTMLDocument doc = getHTMLDocument(editor);
+ int offset = editor.getSelectionStart();
+ Element paragraph = doc.getParagraphElement(offset);
+ if (paragraph.getParentElement() != null) {
+ parentTag = (HTML.Tag)paragraph.getParentElement().
+ getAttributes().getAttribute
+ (StyleConstants.NameAttribute);
+ super.actionPerformed(ae);
+ }
+ }
+ }
+
+ }
+
+ /*
+ * Returns the object in an AttributeSet matching a key
+ */
+ static private Object getAttrValue(AttributeSet attr, HTML.Attribute key) {
+ Enumeration names = attr.getAttributeNames();
+ while (names.hasMoreElements()) {
+ Object nextKey = names.nextElement();
+ Object nextVal = attr.getAttribute(nextKey);
+ if (nextVal instanceof AttributeSet) {
+ Object value = getAttrValue((AttributeSet)nextVal, key);
+ if (value != null) {
+ return value;
+ }
+ } else if (nextKey == key) {
+ return nextVal;
+ }
+ }
+ return null;
+ }
+
+ /*
+ * Action to move the focus on the next or previous hypertext link
+ * or object. TODO: This method relies on support from the
+ * javax.accessibility package. The text package should support
+ * keyboard navigation of text elements directly.
+ */
+ static class NavigateLinkAction extends TextAction implements CaretListener {
+
+ private static final FocusHighlightPainter focusPainter =
+ new FocusHighlightPainter(null);
+ private final boolean focusBack;
+
+ /*
+ * Create this action with the appropriate identifier.
+ */
+ public NavigateLinkAction(String actionName) {
+ super(actionName);
+ focusBack = "previous-link-action".equals(actionName);
+ }
+
+ /**
+ * Called when the caret position is updated.
+ *
+ * @param e the caret event
+ */
+ public void caretUpdate(CaretEvent e) {
+ Object src = e.getSource();
+ if (src instanceof JTextComponent) {
+ JTextComponent comp = (JTextComponent) src;
+ HTMLEditorKit kit = getHTMLEditorKit(comp);
+ if (kit != null && kit.foundLink) {
+ kit.foundLink = false;
+ // TODO: The AccessibleContext for the editor should register
+ // as a listener for CaretEvents and forward the events to
+ // assistive technologies listening for such events.
+ comp.getAccessibleContext().firePropertyChange(
+ AccessibleContext.ACCESSIBLE_HYPERTEXT_OFFSET,
+ new Integer(kit.prevHypertextOffset),
+ new Integer(e.getDot()));
+ }
+ }
+ }
+
+ /*
+ * The operation to perform when this action is triggered.
+ */
+ public void actionPerformed(ActionEvent e) {
+ JTextComponent comp = getTextComponent(e);
+ if (comp == null || comp.isEditable()) {
+ return;
+ }
+
+ Document doc = comp.getDocument();
+ HTMLEditorKit kit = getHTMLEditorKit(comp);
+ if (doc == null || kit == null) {
+ return;
+ }
+
+ // TODO: Should start successive iterations from the
+ // current caret position.
+ ElementIterator ei = new ElementIterator(doc);
+ int currentOffset = comp.getCaretPosition();
+ int prevStartOffset = -1;
+ int prevEndOffset = -1;
+
+ // highlight the next link or object after the current caret position
+ Element nextElement = null;
+ while ((nextElement = ei.next()) != null) {
+ String name = nextElement.getName();
+ AttributeSet attr = nextElement.getAttributes();
+
+ Object href = getAttrValue(attr, HTML.Attribute.HREF);
+ if (!(name.equals(HTML.Tag.OBJECT.toString())) && href == null) {
+ continue;
+ }
+
+ int elementOffset = nextElement.getStartOffset();
+ if (focusBack) {
+ if (elementOffset >= currentOffset &&
+ prevStartOffset >= 0) {
+
+ kit.foundLink = true;
+ comp.setCaretPosition(prevStartOffset);
+ moveCaretPosition(comp, kit, prevStartOffset,
+ prevEndOffset);
+ kit.prevHypertextOffset = prevStartOffset;
+ return;
+ }
+ } else { // focus forward
+ if (elementOffset > currentOffset) {
+
+ kit.foundLink = true;
+ comp.setCaretPosition(elementOffset);
+ moveCaretPosition(comp, kit, elementOffset,
+ nextElement.getEndOffset());
+ kit.prevHypertextOffset = elementOffset;
+ return;
+ }
+ }
+ prevStartOffset = nextElement.getStartOffset();
+ prevEndOffset = nextElement.getEndOffset();
+ }
+ if (focusBack && prevStartOffset >= 0) {
+ kit.foundLink = true;
+ comp.setCaretPosition(prevStartOffset);
+ moveCaretPosition(comp, kit, prevStartOffset, prevEndOffset);
+ kit.prevHypertextOffset = prevStartOffset;
+ return;
+ }
+ }
+
+ /*
+ * Moves the caret from mark to dot
+ */
+ private void moveCaretPosition(JTextComponent comp, HTMLEditorKit kit,
+ int mark, int dot) {
+ Highlighter h = comp.getHighlighter();
+ if (h != null) {
+ int p0 = Math.min(dot, mark);
+ int p1 = Math.max(dot, mark);
+ try {
+ if (kit.linkNavigationTag != null) {
+ h.changeHighlight(kit.linkNavigationTag, p0, p1);
+ } else {
+ kit.linkNavigationTag =
+ h.addHighlight(p0, p1, focusPainter);
+ }
+ } catch (BadLocationException e) {
+ }
+ }
+ }
+
+ private HTMLEditorKit getHTMLEditorKit(JTextComponent comp) {
+ if (comp instanceof JEditorPane) {
+ EditorKit kit = ((JEditorPane) comp).getEditorKit();
+ if (kit instanceof HTMLEditorKit) {
+ return (HTMLEditorKit) kit;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * A highlight painter that draws a one-pixel border around
+ * the highlighted area.
+ */
+ static class FocusHighlightPainter extends
+ DefaultHighlighter.DefaultHighlightPainter {
+
+ FocusHighlightPainter(Color color) {
+ super(color);
+ }
+
+ /**
+ * Paints a portion of a highlight.
+ *
+ * @param g the graphics context
+ * @param offs0 the starting model offset >= 0
+ * @param offs1 the ending model offset >= offs1
+ * @param bounds the bounding box of the view, which is not
+ * necessarily the region to paint.
+ * @param c the editor
+ * @param view View painting for
+ * @return region in which drawing occurred
+ */
+ public Shape paintLayer(Graphics g, int offs0, int offs1,
+ Shape bounds, JTextComponent c, View view) {
+
+ Color color = getColor();
+
+ if (color == null) {
+ g.setColor(c.getSelectionColor());
+ }
+ else {
+ g.setColor(color);
+ }
+ if (offs0 == view.getStartOffset() &&
+ offs1 == view.getEndOffset()) {
+ // Contained in view, can just use bounds.
+ Rectangle alloc;
+ if (bounds instanceof Rectangle) {
+ alloc = (Rectangle)bounds;
+ }
+ else {
+ alloc = bounds.getBounds();
+ }
+ g.drawRect(alloc.x, alloc.y, alloc.width - 1, alloc.height);
+ return alloc;
+ }
+ else {
+ // Should only render part of View.
+ try {
+ // --- determine locations ---
+ Shape shape = view.modelToView(offs0, Position.Bias.Forward,
+ offs1,Position.Bias.Backward,
+ bounds);
+ Rectangle r = (shape instanceof Rectangle) ?
+ (Rectangle)shape : shape.getBounds();
+ g.drawRect(r.x, r.y, r.width - 1, r.height);
+ return r;
+ } catch (BadLocationException e) {
+ // can't render
+ }
+ }
+ // Only if exception
+ return null;
+ }
+ }
+ }
+
+ /*
+ * Action to activate the hypertext link that has focus.
+ * TODO: This method relies on support from the
+ * javax.accessibility package. The text package should support
+ * keyboard navigation of text elements directly.
+ */
+ static class ActivateLinkAction extends TextAction {
+
+ /**
+ * Create this action with the appropriate identifier.
+ */
+ public ActivateLinkAction(String actionName) {
+ super(actionName);
+ }
+
+ /*
+ * activates the hyperlink at offset
+ */
+ private void activateLink(String href, HTMLDocument doc,
+ JEditorPane editor, int offset) {
+ try {
+ URL page =
+ (URL)doc.getProperty(Document.StreamDescriptionProperty);
+ URL url = new URL(page, href);
+ HyperlinkEvent linkEvent = new HyperlinkEvent
+ (editor, HyperlinkEvent.EventType.
+ ACTIVATED, url, url.toExternalForm(),
+ doc.getCharacterElement(offset));
+ editor.fireHyperlinkUpdate(linkEvent);
+ } catch (MalformedURLException m) {
+ }
+ }
+
+ /*
+ * Invokes default action on the object in an element
+ */
+ private void doObjectAction(JEditorPane editor, Element elem) {
+ View view = getView(editor, elem);
+ if (view != null && view instanceof ObjectView) {
+ Component comp = ((ObjectView)view).getComponent();
+ if (comp != null && comp instanceof Accessible) {
+ AccessibleContext ac = ((Accessible)comp).getAccessibleContext();
+ if (ac != null) {
+ AccessibleAction aa = ac.getAccessibleAction();
+ if (aa != null) {
+ aa.doAccessibleAction(0);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Returns the root view for a document
+ */
+ private View getRootView(JEditorPane editor) {
+ return editor.getUI().getRootView(editor);
+ }
+
+ /*
+ * Returns a view associated with an element
+ */
+ private View getView(JEditorPane editor, Element elem) {
+ Object lock = lock(editor);
+ try {
+ View rootView = getRootView(editor);
+ int start = elem.getStartOffset();
+ if (rootView != null) {
+ return getView(rootView, elem, start);
+ }
+ return null;
+ } finally {
+ unlock(lock);
+ }
+ }
+
+ private View getView(View parent, Element elem, int start) {
+ if (parent.getElement() == elem) {
+ return parent;
+ }
+ int index = parent.getViewIndex(start, Position.Bias.Forward);
+
+ if (index != -1 && index < parent.getViewCount()) {
+ return getView(parent.getView(index), elem, start);
+ }
+ return null;
+ }
+
+ /*
+ * If possible acquires a lock on the Document. If a lock has been
+ * obtained a key will be retured that should be passed to
+ * <code>unlock</code>.
+ */
+ private Object lock(JEditorPane editor) {
+ Document document = editor.getDocument();
+
+ if (document instanceof AbstractDocument) {
+ ((AbstractDocument)document).readLock();
+ return document;
+ }
+ return null;
+ }
+
+ /*
+ * Releases a lock previously obtained via <code>lock</code>.
+ */
+ private void unlock(Object key) {
+ if (key != null) {
+ ((AbstractDocument)key).readUnlock();
+ }
+ }
+
+ /*
+ * The operation to perform when this action is triggered.
+ */
+ public void actionPerformed(ActionEvent e) {
+
+ JTextComponent c = getTextComponent(e);
+ if (c.isEditable() || !(c instanceof JEditorPane)) {
+ return;
+ }
+ JEditorPane editor = (JEditorPane)c;
+
+ Document d = editor.getDocument();
+ if (d == null || !(d instanceof HTMLDocument)) {
+ return;
+ }
+ HTMLDocument doc = (HTMLDocument)d;
+
+ ElementIterator ei = new ElementIterator(doc);
+ int currentOffset = editor.getCaretPosition();
+
+ // invoke the next link or object action
+ String urlString = null;
+ String objString = null;
+ Element currentElement = null;
+ while ((currentElement = ei.next()) != null) {
+ String name = currentElement.getName();
+ AttributeSet attr = currentElement.getAttributes();
+
+ Object href = getAttrValue(attr, HTML.Attribute.HREF);
+ if (href != null) {
+ if (currentOffset >= currentElement.getStartOffset() &&
+ currentOffset <= currentElement.getEndOffset()) {
+
+ activateLink((String)href, doc, editor, currentOffset);
+ return;
+ }
+ } else if (name.equals(HTML.Tag.OBJECT.toString())) {
+ Object obj = getAttrValue(attr, HTML.Attribute.CLASSID);
+ if (obj != null) {
+ if (currentOffset >= currentElement.getStartOffset() &&
+ currentOffset <= currentElement.getEndOffset()) {
+
+ doObjectAction(editor, currentElement);
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static int getBodyElementStart(JTextComponent comp) {
+ Element rootElement = comp.getDocument().getRootElements()[0];
+ for (int i = 0; i < rootElement.getElementCount(); i++) {
+ Element currElement = rootElement.getElement(i);
+ if("body".equals(currElement.getName())) {
+ return currElement.getStartOffset();
+ }
+ }
+ return 0;
+ }
+
+ /*
+ * Move the caret to the beginning of the document.
+ * @see DefaultEditorKit#beginAction
+ * @see HTMLEditorKit#getActions
+ */
+
+ static class BeginAction extends TextAction {
+
+ /* Create this object with the appropriate identifier. */
+ BeginAction(String nm, boolean select) {
+ super(nm);
+ this.select = select;
+ }
+
+ /** The operation to perform when this action is triggered. */
+ public void actionPerformed(ActionEvent e) {
+ JTextComponent target = getTextComponent(e);
+ int bodyStart = getBodyElementStart(target);
+
+ if (target != null) {
+ if (select) {
+ target.moveCaretPosition(bodyStart);
+ } else {
+ target.setCaretPosition(bodyStart);
+ }
+ }
+ }
+
+ private boolean select;
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/HTMLFrameHyperlinkEvent.java b/src/share/classes/javax/swing/text/html/HTMLFrameHyperlinkEvent.java
new file mode 100644
index 000000000..b1c97b9e2
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/HTMLFrameHyperlinkEvent.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.event.InputEvent;
+import javax.swing.text.*;
+import javax.swing.event.HyperlinkEvent;
+import java.net.URL;
+
+/**
+ * HTMLFrameHyperlinkEvent is used to notify interested
+ * parties that link was activated in a frame.
+ *
+ * @author Sunita Mani
+ */
+
+public class HTMLFrameHyperlinkEvent extends HyperlinkEvent {
+
+ /**
+ * Creates a new object representing a html frame
+ * hypertext link event.
+ *
+ * @param source the object responsible for the event
+ * @param type the event type
+ * @param targetURL the affected URL
+ * @param targetFrame the Frame to display the document in
+ */
+ public HTMLFrameHyperlinkEvent(Object source, EventType type, URL targetURL,
+ String targetFrame) {
+ super(source, type, targetURL);
+ this.targetFrame = targetFrame;
+ }
+
+
+ /**
+ * Creates a new object representing a hypertext link event.
+ *
+ * @param source the object responsible for the event
+ * @param type the event type
+ * @param targetURL the affected URL
+ * @param desc a description
+ * @param targetFrame the Frame to display the document in
+ */
+ public HTMLFrameHyperlinkEvent(Object source, EventType type, URL targetURL, String desc,
+ String targetFrame) {
+ super(source, type, targetURL, desc);
+ this.targetFrame = targetFrame;
+ }
+
+ /**
+ * Creates a new object representing a hypertext link event.
+ *
+ * @param source the object responsible for the event
+ * @param type the event type
+ * @param targetURL the affected URL
+ * @param sourceElement the element that corresponds to the source
+ * of the event
+ * @param targetFrame the Frame to display the document in
+ */
+ public HTMLFrameHyperlinkEvent(Object source, EventType type, URL targetURL,
+ Element sourceElement, String targetFrame) {
+ super(source, type, targetURL, null, sourceElement);
+ this.targetFrame = targetFrame;
+ }
+
+
+ /**
+ * Creates a new object representing a hypertext link event.
+ *
+ * @param source the object responsible for the event
+ * @param type the event type
+ * @param targetURL the affected URL
+ * @param desc a desription
+ * @param sourceElement the element that corresponds to the source
+ * of the event
+ * @param targetFrame the Frame to display the document in
+ */
+ public HTMLFrameHyperlinkEvent(Object source, EventType type, URL targetURL, String desc,
+ Element sourceElement, String targetFrame) {
+ super(source, type, targetURL, desc, sourceElement);
+ this.targetFrame = targetFrame;
+ }
+
+ /**
+ * Creates a new object representing a hypertext link event.
+ *
+ * @param source the object responsible for the event
+ * @param type the event type
+ * @param targetURL the affected URL
+ * @param desc a desription
+ * @param sourceElement the element that corresponds to the source
+ * of the event
+ * @param inputEvent InputEvent that triggered the hyperlink event
+ * @param targetFrame the Frame to display the document in
+ * @since 1.7
+ */
+ public HTMLFrameHyperlinkEvent(Object source, EventType type, URL targetURL,
+ String desc, Element sourceElement,
+ InputEvent inputEvent, String targetFrame) {
+ super(source, type, targetURL, desc, sourceElement, inputEvent);
+ this.targetFrame = targetFrame;
+ }
+
+ /**
+ * returns the target for the link.
+ */
+ public String getTarget() {
+ return targetFrame;
+ }
+
+ private String targetFrame;
+}
diff --git a/src/share/classes/javax/swing/text/html/HTMLWriter.java b/src/share/classes/javax/swing/text/html/HTMLWriter.java
new file mode 100644
index 000000000..f7fa0e89f
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/HTMLWriter.java
@@ -0,0 +1,1276 @@
+/*
+ * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import javax.swing.text.*;
+import java.io.Writer;
+import java.util.Stack;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.io.IOException;
+import java.util.StringTokenizer;
+import java.util.NoSuchElementException;
+import java.net.URL;
+
+/**
+ * This is a writer for HTMLDocuments.
+ *
+ * @author Sunita Mani
+ */
+
+
+public class HTMLWriter extends AbstractWriter {
+ /*
+ * Stores all elements for which end tags have to
+ * be emitted.
+ */
+ private Stack blockElementStack = new Stack();
+ private boolean inContent = false;
+ private boolean inPre = false;
+ /** When inPre is true, this will indicate the end offset of the pre
+ * element. */
+ private int preEndOffset;
+ private boolean inTextArea = false;
+ private boolean newlineOutputed = false;
+ private boolean completeDoc;
+
+ /*
+ * Stores all embedded tags. Embedded tags are tags that are
+ * stored as attributes in other tags. Generally they're
+ * character level attributes. Examples include
+ * &lt;b&gt;, &lt;i&gt;, &lt;font&gt;, and &lt;a&gt;.
+ */
+ private Vector tags = new Vector(10);
+
+ /**
+ * Values for the tags.
+ */
+ private Vector tagValues = new Vector(10);
+
+ /**
+ * Used when writing out content.
+ */
+ private Segment segment;
+
+ /*
+ * This is used in closeOutUnwantedEmbeddedTags.
+ */
+ private Vector tagsToRemove = new Vector(10);
+
+ /**
+ * Set to true after the head has been output.
+ */
+ private boolean wroteHead;
+
+ /**
+ * Set to true when entities (such as &lt;) should be replaced.
+ */
+ private boolean replaceEntities;
+
+ /**
+ * Temporary buffer.
+ */
+ private char[] tempChars;
+
+
+ /**
+ * Creates a new HTMLWriter.
+ *
+ * @param w a Writer
+ * @param doc an HTMLDocument
+ *
+ */
+ public HTMLWriter(Writer w, HTMLDocument doc) {
+ this(w, doc, 0, doc.getLength());
+ }
+
+ /**
+ * Creates a new HTMLWriter.
+ *
+ * @param w a Writer
+ * @param doc an HTMLDocument
+ * @param pos the document location from which to fetch the content
+ * @param len the amount to write out
+ */
+ public HTMLWriter(Writer w, HTMLDocument doc, int pos, int len) {
+ super(w, doc, pos, len);
+ completeDoc = (pos == 0 && len == doc.getLength());
+ setLineLength(80);
+ }
+
+ /**
+ * Iterates over the
+ * Element tree and controls the writing out of
+ * all the tags and its attributes.
+ *
+ * @exception IOException on any I/O error
+ * @exception BadLocationException if pos represents an invalid
+ * location within the document.
+ *
+ */
+ public void write() throws IOException, BadLocationException {
+ ElementIterator it = getElementIterator();
+ Element current = null;
+ Element next = null;
+
+ wroteHead = false;
+ setCurrentLineLength(0);
+ replaceEntities = false;
+ setCanWrapLines(false);
+ if (segment == null) {
+ segment = new Segment();
+ }
+ inPre = false;
+ boolean forcedBody = false;
+ while ((next = it.next()) != null) {
+ if (!inRange(next)) {
+ if (completeDoc && next.getAttributes().getAttribute(
+ StyleConstants.NameAttribute) == HTML.Tag.BODY) {
+ forcedBody = true;
+ }
+ else {
+ continue;
+ }
+ }
+ if (current != null) {
+
+ /*
+ if next is child of current increment indent
+ */
+
+ if (indentNeedsIncrementing(current, next)) {
+ incrIndent();
+ } else if (current.getParentElement() != next.getParentElement()) {
+ /*
+ next and current are not siblings
+ so emit end tags for items on the stack until the
+ item on top of the stack, is the parent of the
+ next.
+ */
+ Element top = (Element)blockElementStack.peek();
+ while (top != next.getParentElement()) {
+ /*
+ pop() will return top.
+ */
+ blockElementStack.pop();
+ if (!synthesizedElement(top)) {
+ AttributeSet attrs = top.getAttributes();
+ if (!matchNameAttribute(attrs, HTML.Tag.PRE) &&
+ !isFormElementWithContent(attrs)) {
+ decrIndent();
+ }
+ endTag(top);
+ }
+ top = (Element)blockElementStack.peek();
+ }
+ } else if (current.getParentElement() == next.getParentElement()) {
+ /*
+ if next and current are siblings the indent level
+ is correct. But, we need to make sure that if current is
+ on the stack, we pop it off, and put out its end tag.
+ */
+ Element top = (Element)blockElementStack.peek();
+ if (top == current) {
+ blockElementStack.pop();
+ endTag(top);
+ }
+ }
+ }
+ if (!next.isLeaf() || isFormElementWithContent(next.getAttributes())) {
+ blockElementStack.push(next);
+ startTag(next);
+ } else {
+ emptyTag(next);
+ }
+ current = next;
+ }
+ /* Emit all remaining end tags */
+
+ /* A null parameter ensures that all embedded tags
+ currently in the tags vector have their
+ corresponding end tags written out.
+ */
+ closeOutUnwantedEmbeddedTags(null);
+
+ if (forcedBody) {
+ blockElementStack.pop();
+ endTag(current);
+ }
+ while (!blockElementStack.empty()) {
+ current = (Element)blockElementStack.pop();
+ if (!synthesizedElement(current)) {
+ AttributeSet attrs = current.getAttributes();
+ if (!matchNameAttribute(attrs, HTML.Tag.PRE) &&
+ !isFormElementWithContent(attrs)) {
+ decrIndent();
+ }
+ endTag(current);
+ }
+ }
+
+ if (completeDoc) {
+ writeAdditionalComments();
+ }
+
+ segment.array = null;
+ }
+
+
+ /**
+ * Writes out the attribute set. Ignores all
+ * attributes with a key of type HTML.Tag,
+ * attributes with a key of type StyleConstants,
+ * and attributes with a key of type
+ * HTML.Attribute.ENDTAG.
+ *
+ * @param attr an AttributeSet
+ * @exception IOException on any I/O error
+ *
+ */
+ protected void writeAttributes(AttributeSet attr) throws IOException {
+ // translate css attributes to html
+ convAttr.removeAttributes(convAttr);
+ convertToHTML32(attr, convAttr);
+
+ Enumeration names = convAttr.getAttributeNames();
+ while (names.hasMoreElements()) {
+ Object name = names.nextElement();
+ if (name instanceof HTML.Tag ||
+ name instanceof StyleConstants ||
+ name == HTML.Attribute.ENDTAG) {
+ continue;
+ }
+ write(" " + name + "=\"" + convAttr.getAttribute(name) + "\"");
+ }
+ }
+
+ /**
+ * Writes out all empty elements (all tags that have no
+ * corresponding end tag).
+ *
+ * @param elem an Element
+ * @exception IOException on any I/O error
+ * @exception BadLocationException if pos represents an invalid
+ * location within the document.
+ */
+ protected void emptyTag(Element elem) throws BadLocationException, IOException {
+
+ if (!inContent && !inPre) {
+ indentSmart();
+ }
+
+ AttributeSet attr = elem.getAttributes();
+ closeOutUnwantedEmbeddedTags(attr);
+ writeEmbeddedTags(attr);
+
+ if (matchNameAttribute(attr, HTML.Tag.CONTENT)) {
+ inContent = true;
+ text(elem);
+ } else if (matchNameAttribute(attr, HTML.Tag.COMMENT)) {
+ comment(elem);
+ } else {
+ boolean isBlock = isBlockTag(elem.getAttributes());
+ if (inContent && isBlock ) {
+ writeLineSeparator();
+ indentSmart();
+ }
+
+ Object nameTag = (attr != null) ? attr.getAttribute
+ (StyleConstants.NameAttribute) : null;
+ Object endTag = (attr != null) ? attr.getAttribute
+ (HTML.Attribute.ENDTAG) : null;
+
+ boolean outputEndTag = false;
+ // If an instance of an UNKNOWN Tag, or an instance of a
+ // tag that is only visible during editing
+ //
+ if (nameTag != null && endTag != null &&
+ (endTag instanceof String) &&
+ ((String)endTag).equals("true")) {
+ outputEndTag = true;
+ }
+
+ if (completeDoc && matchNameAttribute(attr, HTML.Tag.HEAD)) {
+ if (outputEndTag) {
+ // Write out any styles.
+ writeStyles(((HTMLDocument)getDocument()).getStyleSheet());
+ }
+ wroteHead = true;
+ }
+
+ write('<');
+ if (outputEndTag) {
+ write('/');
+ }
+ write(elem.getName());
+ writeAttributes(attr);
+ write('>');
+ if (matchNameAttribute(attr, HTML.Tag.TITLE) && !outputEndTag) {
+ Document doc = elem.getDocument();
+ String title = (String)doc.getProperty(Document.TitleProperty);
+ write(title);
+ } else if (!inContent || isBlock) {
+ writeLineSeparator();
+ if (isBlock && inContent) {
+ indentSmart();
+ }
+ }
+ }
+ }
+
+ /**
+ * Determines if the HTML.Tag associated with the
+ * element is a block tag.
+ *
+ * @param attr an AttributeSet
+ * @return true if tag is block tag, false otherwise.
+ */
+ protected boolean isBlockTag(AttributeSet attr) {
+ Object o = attr.getAttribute(StyleConstants.NameAttribute);
+ if (o instanceof HTML.Tag) {
+ HTML.Tag name = (HTML.Tag) o;
+ return name.isBlock();
+ }
+ return false;
+ }
+
+
+ /**
+ * Writes out a start tag for the element.
+ * Ignores all synthesized elements.
+ *
+ * @param elem an Element
+ * @exception IOException on any I/O error
+ */
+ protected void startTag(Element elem) throws IOException, BadLocationException {
+
+ if (synthesizedElement(elem)) {
+ return;
+ }
+
+ // Determine the name, as an HTML.Tag.
+ AttributeSet attr = elem.getAttributes();
+ Object nameAttribute = attr.getAttribute(StyleConstants.NameAttribute);
+ HTML.Tag name;
+ if (nameAttribute instanceof HTML.Tag) {
+ name = (HTML.Tag)nameAttribute;
+ }
+ else {
+ name = null;
+ }
+
+ if (name == HTML.Tag.PRE) {
+ inPre = true;
+ preEndOffset = elem.getEndOffset();
+ }
+
+ // write out end tags for item on stack
+ closeOutUnwantedEmbeddedTags(attr);
+
+ if (inContent) {
+ writeLineSeparator();
+ inContent = false;
+ newlineOutputed = false;
+ }
+
+ if (completeDoc && name == HTML.Tag.BODY && !wroteHead) {
+ // If the head has not been output, output it and the styles.
+ wroteHead = true;
+ indentSmart();
+ write("<head>");
+ writeLineSeparator();
+ incrIndent();
+ writeStyles(((HTMLDocument)getDocument()).getStyleSheet());
+ decrIndent();
+ writeLineSeparator();
+ indentSmart();
+ write("</head>");
+ writeLineSeparator();
+ }
+
+ indentSmart();
+ write('<');
+ write(elem.getName());
+ writeAttributes(attr);
+ write('>');
+ if (name != HTML.Tag.PRE) {
+ writeLineSeparator();
+ }
+
+ if (name == HTML.Tag.TEXTAREA) {
+ textAreaContent(elem.getAttributes());
+ } else if (name == HTML.Tag.SELECT) {
+ selectContent(elem.getAttributes());
+ } else if (completeDoc && name == HTML.Tag.BODY) {
+ // Write out the maps, which is not stored as Elements in
+ // the Document.
+ writeMaps(((HTMLDocument)getDocument()).getMaps());
+ }
+ else if (name == HTML.Tag.HEAD) {
+ HTMLDocument document = (HTMLDocument)getDocument();
+ wroteHead = true;
+ incrIndent();
+ writeStyles(document.getStyleSheet());
+ if (document.hasBaseTag()) {
+ indentSmart();
+ write("<base href=\"" + document.getBase() + "\">");
+ writeLineSeparator();
+ }
+ decrIndent();
+ }
+
+ }
+
+
+ /**
+ * Writes out text that is contained in a TEXTAREA form
+ * element.
+ *
+ * @param attr an AttributeSet
+ * @exception IOException on any I/O error
+ * @exception BadLocationException if pos represents an invalid
+ * location within the document.
+ */
+ protected void textAreaContent(AttributeSet attr) throws BadLocationException, IOException {
+ Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute);
+ if (doc != null && doc.getLength() > 0) {
+ if (segment == null) {
+ segment = new Segment();
+ }
+ doc.getText(0, doc.getLength(), segment);
+ if (segment.count > 0) {
+ inTextArea = true;
+ incrIndent();
+ indentSmart();
+ setCanWrapLines(true);
+ replaceEntities = true;
+ write(segment.array, segment.offset, segment.count);
+ replaceEntities = false;
+ setCanWrapLines(false);
+ writeLineSeparator();
+ inTextArea = false;
+ decrIndent();
+ }
+ }
+ }
+
+
+ /**
+ * Writes out text. If a range is specified when the constructor
+ * is invoked, then only the appropriate range of text is written
+ * out.
+ *
+ * @param elem an Element
+ * @exception IOException on any I/O error
+ * @exception BadLocationException if pos represents an invalid
+ * location within the document.
+ */
+ protected void text(Element elem) throws BadLocationException, IOException {
+ int start = Math.max(getStartOffset(), elem.getStartOffset());
+ int end = Math.min(getEndOffset(), elem.getEndOffset());
+ if (start < end) {
+ if (segment == null) {
+ segment = new Segment();
+ }
+ getDocument().getText(start, end - start, segment);
+ newlineOutputed = false;
+ if (segment.count > 0) {
+ if (segment.array[segment.offset + segment.count - 1] == '\n'){
+ newlineOutputed = true;
+ }
+ if (inPre && end == preEndOffset) {
+ if (segment.count > 1) {
+ segment.count--;
+ }
+ else {
+ return;
+ }
+ }
+ replaceEntities = true;
+ setCanWrapLines(!inPre);
+ write(segment.array, segment.offset, segment.count);
+ setCanWrapLines(false);
+ replaceEntities = false;
+ }
+ }
+ }
+
+ /**
+ * Writes out the content of the SELECT form element.
+ *
+ * @param attr the AttributeSet associated with the form element
+ * @exception IOException on any I/O error
+ */
+ protected void selectContent(AttributeSet attr) throws IOException {
+ Object model = attr.getAttribute(StyleConstants.ModelAttribute);
+ incrIndent();
+ if (model instanceof OptionListModel) {
+ OptionListModel listModel = (OptionListModel)model;
+ int size = listModel.getSize();
+ for (int i = 0; i < size; i++) {
+ Option option = (Option)listModel.getElementAt(i);
+ writeOption(option);
+ }
+ } else if (model instanceof OptionComboBoxModel) {
+ OptionComboBoxModel comboBoxModel = (OptionComboBoxModel)model;
+ int size = comboBoxModel.getSize();
+ for (int i = 0; i < size; i++) {
+ Option option = (Option)comboBoxModel.getElementAt(i);
+ writeOption(option);
+ }
+ }
+ decrIndent();
+ }
+
+
+ /**
+ * Writes out the content of the Option form element.
+ * @param option an Option
+ * @exception IOException on any I/O error
+ *
+ */
+ protected void writeOption(Option option) throws IOException {
+
+ indentSmart();
+ write('<');
+ write("option");
+ // PENDING: should this be changed to check for null first?
+ Object value = option.getAttributes().getAttribute
+ (HTML.Attribute.VALUE);
+ if (value != null) {
+ write(" value="+ value);
+ }
+ if (option.isSelected()) {
+ write(" selected");
+ }
+ write('>');
+ if (option.getLabel() != null) {
+ write(option.getLabel());
+ }
+ writeLineSeparator();
+ }
+
+ /**
+ * Writes out an end tag for the element.
+ *
+ * @param elem an Element
+ * @exception IOException on any I/O error
+ */
+ protected void endTag(Element elem) throws IOException {
+ if (synthesizedElement(elem)) {
+ return;
+ }
+
+ // write out end tags for item on stack
+ closeOutUnwantedEmbeddedTags(elem.getAttributes());
+ if (inContent) {
+ if (!newlineOutputed && !inPre) {
+ writeLineSeparator();
+ }
+ newlineOutputed = false;
+ inContent = false;
+ }
+ if (!inPre) {
+ indentSmart();
+ }
+ if (matchNameAttribute(elem.getAttributes(), HTML.Tag.PRE)) {
+ inPre = false;
+ }
+ write('<');
+ write('/');
+ write(elem.getName());
+ write('>');
+ writeLineSeparator();
+ }
+
+
+
+ /**
+ * Writes out comments.
+ *
+ * @param elem an Element
+ * @exception IOException on any I/O error
+ * @exception BadLocationException if pos represents an invalid
+ * location within the document.
+ */
+ protected void comment(Element elem) throws BadLocationException, IOException {
+ AttributeSet as = elem.getAttributes();
+ if (matchNameAttribute(as, HTML.Tag.COMMENT)) {
+ Object comment = as.getAttribute(HTML.Attribute.COMMENT);
+ if (comment instanceof String) {
+ writeComment((String)comment);
+ }
+ else {
+ writeComment(null);
+ }
+ }
+ }
+
+
+ /**
+ * Writes out comment string.
+ *
+ * @param string the comment
+ * @exception IOException on any I/O error
+ * @exception BadLocationException if pos represents an invalid
+ * location within the document.
+ */
+ void writeComment(String string) throws IOException {
+ write("<!--");
+ if (string != null) {
+ write(string);
+ }
+ write("-->");
+ writeLineSeparator();
+ indentSmart();
+ }
+
+
+ /**
+ * Writes out any additional comments (comments outside of the body)
+ * stored under the property HTMLDocument.AdditionalComments.
+ */
+ void writeAdditionalComments() throws IOException {
+ Object comments = getDocument().getProperty
+ (HTMLDocument.AdditionalComments);
+
+ if (comments instanceof Vector) {
+ Vector v = (Vector)comments;
+ for (int counter = 0, maxCounter = v.size(); counter < maxCounter;
+ counter++) {
+ writeComment(v.elementAt(counter).toString());
+ }
+ }
+ }
+
+
+ /**
+ * Returns true if the element is a
+ * synthesized element. Currently we are only testing
+ * for the p-implied tag.
+ */
+ protected boolean synthesizedElement(Element elem) {
+ if (matchNameAttribute(elem.getAttributes(), HTML.Tag.IMPLIED)) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Returns true if the StyleConstants.NameAttribute is
+ * equal to the tag that is passed in as a parameter.
+ */
+ protected 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;
+ }
+
+ /**
+ * Searches for embedded tags in the AttributeSet
+ * and writes them out. It also stores these tags in a vector
+ * so that when appropriate the corresponding end tags can be
+ * written out.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void writeEmbeddedTags(AttributeSet attr) throws IOException {
+
+ // translate css attributes to html
+ attr = convertToHTML(attr, oConvAttr);
+
+ Enumeration names = attr.getAttributeNames();
+ while (names.hasMoreElements()) {
+ Object name = names.nextElement();
+ if (name instanceof HTML.Tag) {
+ HTML.Tag tag = (HTML.Tag)name;
+ if (tag == HTML.Tag.FORM || tags.contains(tag)) {
+ continue;
+ }
+ write('<');
+ write(tag.toString());
+ Object o = attr.getAttribute(tag);
+ if (o != null && o instanceof AttributeSet) {
+ writeAttributes((AttributeSet)o);
+ }
+ write('>');
+ tags.addElement(tag);
+ tagValues.addElement(o);
+ }
+ }
+ }
+
+
+ /**
+ * Searches the attribute set for a tag, both of which
+ * are passed in as a parameter. Returns true if no match is found
+ * and false otherwise.
+ */
+ private boolean noMatchForTagInAttributes(AttributeSet attr, HTML.Tag t,
+ Object tagValue) {
+ if (attr != null && attr.isDefined(t)) {
+ Object newValue = attr.getAttribute(t);
+
+ if ((tagValue == null) ? (newValue == null) :
+ (newValue != null && tagValue.equals(newValue))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Searches the attribute set and for each tag
+ * that is stored in the tag vector. If the tag isnt found,
+ * then the tag is removed from the vector and a corresponding
+ * end tag is written out.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void closeOutUnwantedEmbeddedTags(AttributeSet attr) throws IOException {
+
+ tagsToRemove.removeAllElements();
+
+ // translate css attributes to html
+ attr = convertToHTML(attr, null);
+
+ HTML.Tag t;
+ Object tValue;
+ int firstIndex = -1;
+ int size = tags.size();
+ // First, find all the tags that need to be removed.
+ for (int i = size - 1; i >= 0; i--) {
+ t = (HTML.Tag)tags.elementAt(i);
+ tValue = tagValues.elementAt(i);
+ if ((attr == null) || noMatchForTagInAttributes(attr, t, tValue)) {
+ firstIndex = i;
+ tagsToRemove.addElement(t);
+ }
+ }
+ if (firstIndex != -1) {
+ // Then close them out.
+ boolean removeAll = ((size - firstIndex) == tagsToRemove.size());
+ for (int i = size - 1; i >= firstIndex; i--) {
+ t = (HTML.Tag)tags.elementAt(i);
+ if (removeAll || tagsToRemove.contains(t)) {
+ tags.removeElementAt(i);
+ tagValues.removeElementAt(i);
+ }
+ write('<');
+ write('/');
+ write(t.toString());
+ write('>');
+ }
+ // Have to output any tags after firstIndex that still remaing,
+ // as we closed them out, but they should remain open.
+ size = tags.size();
+ for (int i = firstIndex; i < size; i++) {
+ t = (HTML.Tag)tags.elementAt(i);
+ write('<');
+ write(t.toString());
+ Object o = tagValues.elementAt(i);
+ if (o != null && o instanceof AttributeSet) {
+ writeAttributes((AttributeSet)o);
+ }
+ write('>');
+ }
+ }
+ }
+
+
+ /**
+ * Determines if the element associated with the attributeset
+ * is a TEXTAREA or SELECT. If true, returns true else
+ * false
+ */
+ private boolean isFormElementWithContent(AttributeSet attr) {
+ if (matchNameAttribute(attr, HTML.Tag.TEXTAREA) ||
+ matchNameAttribute(attr, HTML.Tag.SELECT)) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Determines whether a the indentation needs to be
+ * incremented. Basically, if next is a child of current, and
+ * next is NOT a synthesized element, the indent level will be
+ * incremented. If there is a parent-child relationship and "next"
+ * is a synthesized element, then its children must be indented.
+ * This state is maintained by the indentNext boolean.
+ *
+ * @return boolean that's true if indent level
+ * needs incrementing.
+ */
+ private boolean indentNext = false;
+ private boolean indentNeedsIncrementing(Element current, Element next) {
+ if ((next.getParentElement() == current) && !inPre) {
+ if (indentNext) {
+ indentNext = false;
+ return true;
+ } else if (synthesizedElement(next)) {
+ indentNext = true;
+ } else if (!synthesizedElement(current)){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Outputs the maps as elements. Maps are not stored as elements in
+ * the document, and as such this is used to output them.
+ */
+ void writeMaps(Enumeration maps) throws IOException {
+ if (maps != null) {
+ while(maps.hasMoreElements()) {
+ Map map = (Map)maps.nextElement();
+ String name = map.getName();
+
+ incrIndent();
+ indentSmart();
+ write("<map");
+ if (name != null) {
+ write(" name=\"");
+ write(name);
+ write("\">");
+ }
+ else {
+ write('>');
+ }
+ writeLineSeparator();
+ incrIndent();
+
+ // Output the areas
+ AttributeSet[] areas = map.getAreas();
+ if (areas != null) {
+ for (int counter = 0, maxCounter = areas.length;
+ counter < maxCounter; counter++) {
+ indentSmart();
+ write("<area");
+ writeAttributes(areas[counter]);
+ write("></area>");
+ writeLineSeparator();
+ }
+ }
+ decrIndent();
+ indentSmart();
+ write("</map>");
+ writeLineSeparator();
+ decrIndent();
+ }
+ }
+ }
+
+ /**
+ * Outputs the styles as a single element. Styles are not stored as
+ * elements, but part of the document. For the time being styles are
+ * written out as a comment, inside a style tag.
+ */
+ void writeStyles(StyleSheet sheet) throws IOException {
+ if (sheet != null) {
+ Enumeration styles = sheet.getStyleNames();
+ if (styles != null) {
+ boolean outputStyle = false;
+ while (styles.hasMoreElements()) {
+ String name = (String)styles.nextElement();
+ // Don't write out the default style.
+ if (!StyleContext.DEFAULT_STYLE.equals(name) &&
+ writeStyle(name, sheet.getStyle(name), outputStyle)) {
+ outputStyle = true;
+ }
+ }
+ if (outputStyle) {
+ writeStyleEndTag();
+ }
+ }
+ }
+ }
+
+ /**
+ * Outputs the named style. <code>outputStyle</code> indicates
+ * whether or not a style has been output yet. This will return
+ * true if a style is written.
+ */
+ boolean writeStyle(String name, Style style, boolean outputStyle)
+ throws IOException{
+ boolean didOutputStyle = false;
+ Enumeration attributes = style.getAttributeNames();
+ if (attributes != null) {
+ while (attributes.hasMoreElements()) {
+ Object attribute = attributes.nextElement();
+ if (attribute instanceof CSS.Attribute) {
+ String value = style.getAttribute(attribute).toString();
+ if (value != null) {
+ if (!outputStyle) {
+ writeStyleStartTag();
+ outputStyle = true;
+ }
+ if (!didOutputStyle) {
+ didOutputStyle = true;
+ indentSmart();
+ write(name);
+ write(" {");
+ }
+ else {
+ write(";");
+ }
+ write(' ');
+ write(attribute.toString());
+ write(": ");
+ write(value);
+ }
+ }
+ }
+ }
+ if (didOutputStyle) {
+ write(" }");
+ writeLineSeparator();
+ }
+ return didOutputStyle;
+ }
+
+ void writeStyleStartTag() throws IOException {
+ indentSmart();
+ write("<style type=\"text/css\">");
+ incrIndent();
+ writeLineSeparator();
+ indentSmart();
+ write("<!--");
+ incrIndent();
+ writeLineSeparator();
+ }
+
+ void writeStyleEndTag() throws IOException {
+ decrIndent();
+ indentSmart();
+ write("-->");
+ writeLineSeparator();
+ decrIndent();
+ indentSmart();
+ write("</style>");
+ writeLineSeparator();
+ indentSmart();
+ }
+
+ // --- conversion support ---------------------------
+
+ /**
+ * Convert the give set of attributes to be html for
+ * the purpose of writing them out. Any keys that
+ * have been converted will not appear in the resultant
+ * set. Any keys not converted will appear in the
+ * resultant set the same as the received set.<p>
+ * This will put the converted values into <code>to</code>, unless
+ * it is null in which case a temporary AttributeSet will be returned.
+ */
+ AttributeSet convertToHTML(AttributeSet from, MutableAttributeSet to) {
+ if (to == null) {
+ to = convAttr;
+ }
+ to.removeAttributes(to);
+ if (writeCSS) {
+ convertToHTML40(from, to);
+ } else {
+ convertToHTML32(from, to);
+ }
+ return to;
+ }
+
+ /**
+ * If true, the writer will emit CSS attributes in preference
+ * to HTML tags/attributes (i.e. It will emit an HTML 4.0
+ * style).
+ */
+ private boolean writeCSS = false;
+
+ /**
+ * Buffer for the purpose of attribute conversion
+ */
+ private MutableAttributeSet convAttr = new SimpleAttributeSet();
+
+ /**
+ * Buffer for the purpose of attribute conversion. This can be
+ * used if convAttr is being used.
+ */
+ private MutableAttributeSet oConvAttr = new SimpleAttributeSet();
+
+ /**
+ * Create an older style of HTML attributes. This will
+ * convert character level attributes that have a StyleConstants
+ * mapping over to an HTML tag/attribute. Other CSS attributes
+ * will be placed in an HTML style attribute.
+ */
+ private static void convertToHTML32(AttributeSet from, MutableAttributeSet to) {
+ if (from == null) {
+ return;
+ }
+ Enumeration keys = from.getAttributeNames();
+ String value = "";
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ if (key instanceof CSS.Attribute) {
+ if ((key == CSS.Attribute.FONT_FAMILY) ||
+ (key == CSS.Attribute.FONT_SIZE) ||
+ (key == CSS.Attribute.COLOR)) {
+
+ createFontAttribute((CSS.Attribute)key, from, to);
+ } else if (key == CSS.Attribute.FONT_WEIGHT) {
+ // add a bold tag is weight is bold
+ CSS.FontWeight weightValue = (CSS.FontWeight)
+ from.getAttribute(CSS.Attribute.FONT_WEIGHT);
+ if ((weightValue != null) && (weightValue.getValue() > 400)) {
+ addAttribute(to, HTML.Tag.B, SimpleAttributeSet.EMPTY);
+ }
+ } else if (key == CSS.Attribute.FONT_STYLE) {
+ String s = from.getAttribute(key).toString();
+ if (s.indexOf("italic") >= 0) {
+ addAttribute(to, HTML.Tag.I, SimpleAttributeSet.EMPTY);
+ }
+ } else if (key == CSS.Attribute.TEXT_DECORATION) {
+ String decor = from.getAttribute(key).toString();
+ if (decor.indexOf("underline") >= 0) {
+ addAttribute(to, HTML.Tag.U, SimpleAttributeSet.EMPTY);
+ }
+ if (decor.indexOf("line-through") >= 0) {
+ addAttribute(to, HTML.Tag.STRIKE, SimpleAttributeSet.EMPTY);
+ }
+ } else if (key == CSS.Attribute.VERTICAL_ALIGN) {
+ String vAlign = from.getAttribute(key).toString();
+ if (vAlign.indexOf("sup") >= 0) {
+ addAttribute(to, HTML.Tag.SUP, SimpleAttributeSet.EMPTY);
+ }
+ if (vAlign.indexOf("sub") >= 0) {
+ addAttribute(to, HTML.Tag.SUB, SimpleAttributeSet.EMPTY);
+ }
+ } else if (key == CSS.Attribute.TEXT_ALIGN) {
+ addAttribute(to, HTML.Attribute.ALIGN,
+ from.getAttribute(key).toString());
+ } else {
+ // default is to store in a HTML style attribute
+ if (value.length() > 0) {
+ value = value + "; ";
+ }
+ value = value + key + ": " + from.getAttribute(key);
+ }
+ } else {
+ Object attr = from.getAttribute(key);
+ if (attr instanceof AttributeSet) {
+ attr = ((AttributeSet)attr).copyAttributes();
+ }
+ addAttribute(to, key, attr);
+ }
+ }
+ if (value.length() > 0) {
+ to.addAttribute(HTML.Attribute.STYLE, value);
+ }
+ }
+
+ /**
+ * Add an attribute only if it doesn't exist so that we don't
+ * loose information replacing it with SimpleAttributeSet.EMPTY
+ */
+ private static void addAttribute(MutableAttributeSet to, Object key, Object value) {
+ Object attr = to.getAttribute(key);
+ if (attr == null || attr == SimpleAttributeSet.EMPTY) {
+ to.addAttribute(key, value);
+ } else {
+ if (attr instanceof MutableAttributeSet &&
+ value instanceof AttributeSet) {
+ ((MutableAttributeSet)attr).addAttributes((AttributeSet)value);
+ }
+ }
+ }
+
+ /**
+ * Create/update an HTML &lt;font&gt; tag attribute. The
+ * value of the attribute should be a MutableAttributeSet so
+ * that the attributes can be updated as they are discovered.
+ */
+ private static void createFontAttribute(CSS.Attribute a, AttributeSet from,
+ MutableAttributeSet to) {
+ MutableAttributeSet fontAttr = (MutableAttributeSet)
+ to.getAttribute(HTML.Tag.FONT);
+ if (fontAttr == null) {
+ fontAttr = new SimpleAttributeSet();
+ to.addAttribute(HTML.Tag.FONT, fontAttr);
+ }
+ // edit the parameters to the font tag
+ String htmlValue = from.getAttribute(a).toString();
+ if (a == CSS.Attribute.FONT_FAMILY) {
+ fontAttr.addAttribute(HTML.Attribute.FACE, htmlValue);
+ } else if (a == CSS.Attribute.FONT_SIZE) {
+ fontAttr.addAttribute(HTML.Attribute.SIZE, htmlValue);
+ } else if (a == CSS.Attribute.COLOR) {
+ fontAttr.addAttribute(HTML.Attribute.COLOR, htmlValue);
+ }
+ }
+
+ /**
+ * Copies the given AttributeSet to a new set, converting
+ * any CSS attributes found to arguments of an HTML style
+ * attribute.
+ */
+ private static void convertToHTML40(AttributeSet from, MutableAttributeSet to) {
+ Enumeration keys = from.getAttributeNames();
+ String value = "";
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ if (key instanceof CSS.Attribute) {
+ value = value + " " + key + "=" + from.getAttribute(key) + ";";
+ } else {
+ to.addAttribute(key, from.getAttribute(key));
+ }
+ }
+ if (value.length() > 0) {
+ to.addAttribute(HTML.Attribute.STYLE, value);
+ }
+ }
+
+ //
+ // Overrides the writing methods to only break a string when
+ // canBreakString is true.
+ // In a future release it is likely AbstractWriter will get this
+ // functionality.
+ //
+
+ /**
+ * Writes the line separator. This is overriden to make sure we don't
+ * replace the newline content in case it is outside normal ascii.
+ * @since 1.3
+ */
+ protected void writeLineSeparator() throws IOException {
+ boolean oldReplace = replaceEntities;
+ replaceEntities = false;
+ super.writeLineSeparator();
+ replaceEntities = oldReplace;
+ indented = false;
+ }
+
+ /**
+ * This method is overriden to map any character entities, such as
+ * &lt; to &amp;lt;. <code>super.output</code> will be invoked to
+ * write the content.
+ * @since 1.3
+ */
+ protected void output(char[] chars, int start, int length)
+ throws IOException {
+ if (!replaceEntities) {
+ super.output(chars, start, length);
+ return;
+ }
+ int last = start;
+ length += start;
+ for (int counter = start; counter < length; counter++) {
+ // This will change, we need better support character level
+ // entities.
+ switch(chars[counter]) {
+ // Character level entities.
+ case '<':
+ if (counter > last) {
+ super.output(chars, last, counter - last);
+ }
+ last = counter + 1;
+ output("&lt;");
+ break;
+ case '>':
+ if (counter > last) {
+ super.output(chars, last, counter - last);
+ }
+ last = counter + 1;
+ output("&gt;");
+ break;
+ case '&':
+ if (counter > last) {
+ super.output(chars, last, counter - last);
+ }
+ last = counter + 1;
+ output("&amp;");
+ break;
+ case '"':
+ if (counter > last) {
+ super.output(chars, last, counter - last);
+ }
+ last = counter + 1;
+ output("&quot;");
+ break;
+ // Special characters
+ case '\n':
+ case '\t':
+ case '\r':
+ break;
+ default:
+ if (chars[counter] < ' ' || chars[counter] > 127) {
+ if (counter > last) {
+ super.output(chars, last, counter - last);
+ }
+ last = counter + 1;
+ // If the character is outside of ascii, write the
+ // numeric value.
+ output("&#");
+ output(String.valueOf((int)chars[counter]));
+ output(";");
+ }
+ break;
+ }
+ }
+ if (last < length) {
+ super.output(chars, last, length - last);
+ }
+ }
+
+ /**
+ * This directly invokes super's <code>output</code> after converting
+ * <code>string</code> to a char[].
+ */
+ private void output(String string) throws IOException {
+ int length = string.length();
+ if (tempChars == null || tempChars.length < length) {
+ tempChars = new char[length];
+ }
+ string.getChars(0, length, tempChars, 0);
+ super.output(tempChars, 0, length);
+ }
+
+ private boolean indented = false;
+
+ /**
+ * Writes indent only once per line.
+ */
+ private void indentSmart() throws IOException {
+ if (!indented) {
+ indent();
+ indented = true;
+ }
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/HiddenTagView.java b/src/share/classes/javax/swing/text/html/HiddenTagView.java
new file mode 100644
index 000000000..7d46b2836
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/HiddenTagView.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import javax.swing.text.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.event.*;
+import java.util.*;
+
+/**
+ * HiddenTagView subclasses EditableView to contain a JTextField showing
+ * the element name. When the textfield is edited the element name is
+ * reset. As this inherits from EditableView if the JTextComponent is
+ * not editable, the textfield will not be visible.
+ *
+ * @author Scott Violet
+ */
+class HiddenTagView extends EditableView implements DocumentListener {
+ HiddenTagView(Element e) {
+ super(e);
+ yAlign = 1;
+ }
+
+ protected Component createComponent() {
+ JTextField tf = new JTextField(getElement().getName());
+ Document doc = getDocument();
+ Font font;
+ if (doc instanceof StyledDocument) {
+ font = ((StyledDocument)doc).getFont(getAttributes());
+ tf.setFont(font);
+ }
+ else {
+ font = tf.getFont();
+ }
+ tf.getDocument().addDocumentListener(this);
+ updateYAlign(font);
+
+ // Create a panel to wrap the textfield so that the textfields
+ // laf border shows through.
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setBackground(null);
+ if (isEndTag()) {
+ panel.setBorder(EndBorder);
+ }
+ else {
+ panel.setBorder(StartBorder);
+ }
+ panel.add(tf);
+ return panel;
+ }
+
+ public float getAlignment(int axis) {
+ if (axis == View.Y_AXIS) {
+ return yAlign;
+ }
+ return 0.5f;
+ }
+
+ public float getMinimumSpan(int axis) {
+ if (axis == View.X_AXIS && isVisible()) {
+ // Default to preferred.
+ return Math.max(30, super.getPreferredSpan(axis));
+ }
+ return super.getMinimumSpan(axis);
+ }
+
+ public float getPreferredSpan(int axis) {
+ if (axis == View.X_AXIS && isVisible()) {
+ return Math.max(30, super.getPreferredSpan(axis));
+ }
+ return super.getPreferredSpan(axis);
+ }
+
+ public float getMaximumSpan(int axis) {
+ if (axis == View.X_AXIS && isVisible()) {
+ // Default to preferred.
+ return Math.max(30, super.getMaximumSpan(axis));
+ }
+ return super.getMaximumSpan(axis);
+ }
+
+ // DocumentListener methods
+ public void insertUpdate(DocumentEvent e) {
+ updateModelFromText();
+ }
+
+ public void removeUpdate(DocumentEvent e) {
+ updateModelFromText();
+ }
+
+ public void changedUpdate(DocumentEvent e) {
+ updateModelFromText();
+ }
+
+ // View method
+ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+ if (!isSettingAttributes) {
+ setTextFromModel();
+ }
+ }
+
+ // local methods
+
+ void updateYAlign(Font font) {
+ Container c = getContainer();
+ FontMetrics fm = (c != null) ? c.getFontMetrics(font) :
+ Toolkit.getDefaultToolkit().getFontMetrics(font);
+ float h = fm.getHeight();
+ float d = fm.getDescent();
+ yAlign = (h > 0) ? (h - d) / h : 0;
+ }
+
+ void resetBorder() {
+ Component comp = getComponent();
+
+ if (comp != null) {
+ if (isEndTag()) {
+ ((JPanel)comp).setBorder(EndBorder);
+ }
+ else {
+ ((JPanel)comp).setBorder(StartBorder);
+ }
+ }
+ }
+
+ /**
+ * This resets the text on the text component we created to match
+ * that of the AttributeSet for the Element we represent.
+ * <p>If this is invoked on the event dispatching thread, this
+ * directly invokes <code>_setTextFromModel</code>, otherwise
+ * <code>SwingUtilities.invokeLater</code> is used to schedule execution
+ * of <code>_setTextFromModel</code>.
+ */
+ void setTextFromModel() {
+ if (SwingUtilities.isEventDispatchThread()) {
+ _setTextFromModel();
+ }
+ else {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ _setTextFromModel();
+ }
+ });
+ }
+ }
+
+ /**
+ * This resets the text on the text component we created to match
+ * that of the AttributeSet for the Element we represent.
+ */
+ void _setTextFromModel() {
+ Document doc = getDocument();
+ try {
+ isSettingAttributes = true;
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readLock();
+ }
+ JTextComponent text = getTextComponent();
+ if (text != null) {
+ text.setText(getRepresentedText());
+ resetBorder();
+ Container host = getContainer();
+ if (host != null) {
+ preferenceChanged(this, true, true);
+ host.repaint();
+ }
+ }
+ }
+ finally {
+ isSettingAttributes = false;
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readUnlock();
+ }
+ }
+ }
+
+ /**
+ * This copies the text from the text component we've created
+ * to the Element's AttributeSet we represent.
+ * <p>If this is invoked on the event dispatching thread, this
+ * directly invokes <code>_updateModelFromText</code>, otherwise
+ * <code>SwingUtilities.invokeLater</code> is used to schedule execution
+ * of <code>_updateModelFromText</code>.
+ */
+ void updateModelFromText() {
+ if (!isSettingAttributes) {
+ if (SwingUtilities.isEventDispatchThread()) {
+ _updateModelFromText();
+ }
+ else {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ _updateModelFromText();
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * This copies the text from the text component we've created
+ * to the Element's AttributeSet we represent.
+ */
+ void _updateModelFromText() {
+ Document doc = getDocument();
+ Object name = getElement().getAttributes().getAttribute
+ (StyleConstants.NameAttribute);
+ if ((name instanceof HTML.UnknownTag) &&
+ (doc instanceof StyledDocument)) {
+ SimpleAttributeSet sas = new SimpleAttributeSet();
+ JTextComponent textComponent = getTextComponent();
+ if (textComponent != null) {
+ String text = textComponent.getText();
+ isSettingAttributes = true;
+ try {
+ sas.addAttribute(StyleConstants.NameAttribute,
+ new HTML.UnknownTag(text));
+ ((StyledDocument)doc).setCharacterAttributes
+ (getStartOffset(), getEndOffset() -
+ getStartOffset(), sas, false);
+ }
+ finally {
+ isSettingAttributes = false;
+ }
+ }
+ }
+ }
+
+ JTextComponent getTextComponent() {
+ Component comp = getComponent();
+
+ return (comp == null) ? null : (JTextComponent)((Container)comp).
+ getComponent(0);
+ }
+
+ String getRepresentedText() {
+ String retValue = getElement().getName();
+ return (retValue == null) ? "" : retValue;
+ }
+
+ boolean isEndTag() {
+ AttributeSet as = getElement().getAttributes();
+ if (as != null) {
+ Object end = as.getAttribute(HTML.Attribute.ENDTAG);
+ if (end != null && (end instanceof String) &&
+ ((String)end).equals("true")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Alignment along the y axis, based on the font of the textfield. */
+ float yAlign;
+ /** Set to true when setting attributes. */
+ boolean isSettingAttributes;
+
+
+ // Following are for Borders that used for Unknown tags and comments.
+ //
+ // Border defines
+ static final int circleR = 3;
+ static final int circleD = circleR * 2;
+ static final int tagSize = 6;
+ static final int padding = 3;
+ static final Color UnknownTagBorderColor = Color.black;
+ static final Border StartBorder = new StartTagBorder();
+ static final Border EndBorder = new EndTagBorder();
+
+
+ static class StartTagBorder implements Border, Serializable {
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ g.setColor(UnknownTagBorderColor);
+ x += padding;
+ width -= (padding * 2);
+ g.drawLine(x, y + circleR,
+ x, y + height - circleR);
+ g.drawArc(x, y + height - circleD - 1,
+ circleD, circleD, 180, 90);
+ g.drawArc(x, y, circleD, circleD, 90, 90);
+ g.drawLine(x + circleR, y, x + width - tagSize, y);
+ g.drawLine(x + circleR, y + height - 1,
+ x + width - tagSize, y + height - 1);
+
+ g.drawLine(x + width - tagSize, y,
+ x + width - 1, y + height / 2);
+ g.drawLine(x + width - tagSize, y + height,
+ x + width - 1, y + height / 2);
+ }
+
+ public Insets getBorderInsets(Component c) {
+ return new Insets(2, 2 + padding, 2, tagSize + 2 + padding);
+ }
+
+ public boolean isBorderOpaque() {
+ return false;
+ }
+ } // End of class HiddenTagView.StartTagBorder
+
+
+ static class EndTagBorder implements Border, Serializable {
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ g.setColor(UnknownTagBorderColor);
+ x += padding;
+ width -= (padding * 2);
+ g.drawLine(x + width - 1, y + circleR,
+ x + width - 1, y + height - circleR);
+ g.drawArc(x + width - circleD - 1, y + height - circleD - 1,
+ circleD, circleD, 270, 90);
+ g.drawArc(x + width - circleD - 1, y, circleD, circleD, 0, 90);
+ g.drawLine(x + tagSize, y, x + width - circleR, y);
+ g.drawLine(x + tagSize, y + height - 1,
+ x + width - circleR, y + height - 1);
+
+ g.drawLine(x + tagSize, y,
+ x, y + height / 2);
+ g.drawLine(x + tagSize, y + height,
+ x, y + height / 2);
+ }
+
+ public Insets getBorderInsets(Component c) {
+ return new Insets(2, tagSize + 2 + padding, 2, 2 + padding);
+ }
+
+ public boolean isBorderOpaque() {
+ return false;
+ }
+ } // End of class HiddenTagView.EndTagBorder
+
+
+} // End of HiddenTagView
diff --git a/src/share/classes/javax/swing/text/html/ImageView.java b/src/share/classes/javax/swing/text/html/ImageView.java
new file mode 100644
index 000000000..4b645b6a7
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/ImageView.java
@@ -0,0 +1,997 @@
+/*
+ * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.ImageObserver;
+import java.io.*;
+import java.net.*;
+import java.util.Dictionary;
+import javax.swing.*;
+import javax.swing.text.*;
+import javax.swing.event.*;
+
+/**
+ * View of an Image, intended to support the HTML &lt;IMG&gt; tag.
+ * Supports scaling via the HEIGHT and WIDTH attributes of the tag.
+ * If the image is unable to be loaded any text specified via the
+ * <code>ALT</code> attribute will be rendered.
+ * <p>
+ * While this class has been part of swing for a while now, it is public
+ * as of 1.4.
+ *
+ * @author Scott Violet
+ * @see IconView
+ * @since 1.4
+ */
+public class ImageView extends View {
+ /**
+ * If true, when some of the bits are available a repaint is done.
+ * <p>
+ * This is set to false as swing does not offer a repaint that takes a
+ * delay. If this were true, a bunch of immediate repaints would get
+ * generated that end up significantly delaying the loading of the image
+ * (or anything else going on for that matter).
+ */
+ private static boolean sIsInc = false;
+ /**
+ * Repaint delay when some of the bits are available.
+ */
+ private static int sIncRate = 100;
+ /**
+ * Property name for pending image icon
+ */
+ private static final String PENDING_IMAGE = "html.pendingImage";
+ /**
+ * Property name for missing image icon
+ */
+ private static final String MISSING_IMAGE = "html.missingImage";
+
+ /**
+ * Document property for image cache.
+ */
+ private static final String IMAGE_CACHE_PROPERTY = "imageCache";
+
+ // Height/width to use before we know the real size, these should at least
+ // the size of <code>sMissingImageIcon</code> and
+ // <code>sPendingImageIcon</code>
+ private static final int DEFAULT_WIDTH = 38;
+ private static final int DEFAULT_HEIGHT= 38;
+
+ /**
+ * Default border to use if one is not specified.
+ */
+ private static final int DEFAULT_BORDER = 2;
+
+ // Bitmask values
+ private static final int LOADING_FLAG = 1;
+ private static final int LINK_FLAG = 2;
+ private static final int WIDTH_FLAG = 4;
+ private static final int HEIGHT_FLAG = 8;
+ private static final int RELOAD_FLAG = 16;
+ private static final int RELOAD_IMAGE_FLAG = 32;
+ private static final int SYNC_LOAD_FLAG = 64;
+
+ private AttributeSet attr;
+ private Image image;
+ private int width;
+ private int height;
+ /** Bitmask containing some of the above bitmask values. Because the
+ * image loading notification can happen on another thread access to
+ * this is synchronized (at least for modifying it). */
+ private int state;
+ private Container container;
+ private Rectangle fBounds;
+ private Color borderColor;
+ // Size of the border, the insets contains this valid. For example, if
+ // the HSPACE attribute was 4 and BORDER 2, leftInset would be 6.
+ private short borderSize;
+ // Insets, obtained from the painter.
+ private short leftInset;
+ private short rightInset;
+ private short topInset;
+ private short bottomInset;
+ /**
+ * We don't directly implement ImageObserver, instead we use an instance
+ * that calls back to us.
+ */
+ private ImageObserver imageObserver;
+ /**
+ * Used for alt text. Will be non-null if the image couldn't be found,
+ * and there is valid alt text.
+ */
+ private View altView;
+ /** Alignment along the vertical (Y) axis. */
+ private float vAlign;
+
+
+
+ /**
+ * Creates a new view that represents an IMG element.
+ *
+ * @param elem the element to create a view for
+ */
+ public ImageView(Element elem) {
+ super(elem);
+ fBounds = new Rectangle();
+ imageObserver = new ImageHandler();
+ state = RELOAD_FLAG | RELOAD_IMAGE_FLAG;
+ }
+
+ /**
+ * Returns the text to display if the image can't be loaded. This is
+ * obtained from the Elements attribute set with the attribute name
+ * <code>HTML.Attribute.ALT</code>.
+ */
+ public String getAltText() {
+ return (String)getElement().getAttributes().getAttribute
+ (HTML.Attribute.ALT);
+ }
+
+ /**
+ * Return a URL for the image source,
+ * or null if it could not be determined.
+ */
+ public URL getImageURL() {
+ String src = (String)getElement().getAttributes().
+ getAttribute(HTML.Attribute.SRC);
+ if (src == null) {
+ return null;
+ }
+
+ URL reference = ((HTMLDocument)getDocument()).getBase();
+ try {
+ URL u = new URL(reference,src);
+ return u;
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the icon to use if the image couldn't be found.
+ */
+ public Icon getNoImageIcon() {
+ return (Icon) UIManager.getLookAndFeelDefaults().get(MISSING_IMAGE);
+ }
+
+ /**
+ * Returns the icon to use while in the process of loading the image.
+ */
+ public Icon getLoadingImageIcon() {
+ return (Icon) UIManager.getLookAndFeelDefaults().get(PENDING_IMAGE);
+ }
+
+ /**
+ * Returns the image to render.
+ */
+ public Image getImage() {
+ sync();
+ return image;
+ }
+
+ /**
+ * Sets how the image is loaded. If <code>newValue</code> is true,
+ * the image we be loaded when first asked for, otherwise it will
+ * be loaded asynchronously. The default is to not load synchronously,
+ * that is to load the image asynchronously.
+ */
+ public void setLoadsSynchronously(boolean newValue) {
+ synchronized(this) {
+ if (newValue) {
+ state |= SYNC_LOAD_FLAG;
+ }
+ else {
+ state = (state | SYNC_LOAD_FLAG) ^ SYNC_LOAD_FLAG;
+ }
+ }
+ }
+
+ /**
+ * Returns true if the image should be loaded when first asked for.
+ */
+ public boolean getLoadsSynchronously() {
+ return ((state & SYNC_LOAD_FLAG) != 0);
+ }
+
+ /**
+ * Convenience method to get the StyleSheet.
+ */
+ protected StyleSheet getStyleSheet() {
+ HTMLDocument doc = (HTMLDocument) getDocument();
+ return doc.getStyleSheet();
+ }
+
+ /**
+ * Fetches the attributes to use when rendering. This is
+ * implemented to multiplex the attributes specified in the
+ * model with a StyleSheet.
+ */
+ public AttributeSet getAttributes() {
+ sync();
+ return attr;
+ }
+
+ /**
+ * For images the tooltip text comes from text specified with the
+ * <code>ALT</code> attribute. This is overriden to return
+ * <code>getAltText</code>.
+ *
+ * @see JTextComponent#getToolTipText
+ */
+ public String getToolTipText(float x, float y, Shape allocation) {
+ return getAltText();
+ }
+
+ /**
+ * Update any cached values that come from attributes.
+ */
+ protected void setPropertiesFromAttributes() {
+ StyleSheet sheet = getStyleSheet();
+ this.attr = sheet.getViewAttributes(this);
+
+ // Gutters
+ borderSize = (short)getIntAttr(HTML.Attribute.BORDER, isLink() ?
+ DEFAULT_BORDER : 0);
+
+ leftInset = rightInset = (short)(getIntAttr(HTML.Attribute.HSPACE,
+ 0) + borderSize);
+ topInset = bottomInset = (short)(getIntAttr(HTML.Attribute.VSPACE,
+ 0) + borderSize);
+
+ borderColor = ((StyledDocument)getDocument()).getForeground
+ (getAttributes());
+
+ AttributeSet attr = getElement().getAttributes();
+
+ // Alignment.
+ // PENDING: This needs to be changed to support the CSS versions
+ // when conversion from ALIGN to VERTICAL_ALIGN is complete.
+ Object alignment = attr.getAttribute(HTML.Attribute.ALIGN);
+
+ vAlign = 1.0f;
+ if (alignment != null) {
+ alignment = alignment.toString();
+ if ("top".equals(alignment)) {
+ vAlign = 0f;
+ }
+ else if ("middle".equals(alignment)) {
+ vAlign = .5f;
+ }
+ }
+
+ AttributeSet anchorAttr = (AttributeSet)attr.getAttribute(HTML.Tag.A);
+ if (anchorAttr != null && anchorAttr.isDefined
+ (HTML.Attribute.HREF)) {
+ synchronized(this) {
+ state |= LINK_FLAG;
+ }
+ }
+ else {
+ synchronized(this) {
+ state = (state | LINK_FLAG) ^ LINK_FLAG;
+ }
+ }
+ }
+
+ /**
+ * Establishes the parent view for this view.
+ * Seize this moment to cache the AWT Container I'm in.
+ */
+ public void setParent(View parent) {
+ View oldParent = getParent();
+ super.setParent(parent);
+ container = (parent != null) ? getContainer() : null;
+ if (oldParent != parent) {
+ synchronized(this) {
+ state |= RELOAD_FLAG;
+ }
+ }
+ }
+
+ /**
+ * Invoked when the Elements attributes have changed. Recreates the image.
+ */
+ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+ super.changedUpdate(e,a,f);
+
+ synchronized(this) {
+ state |= RELOAD_FLAG | RELOAD_IMAGE_FLAG;
+ }
+
+ // Assume the worst.
+ preferenceChanged(null, true, true);
+ }
+
+ /**
+ * Paints the View.
+ *
+ * @param g the rendering surface to use
+ * @param a the allocated region to render into
+ * @see View#paint
+ */
+ public void paint(Graphics g, Shape a) {
+ sync();
+
+ Rectangle rect = (a instanceof Rectangle) ? (Rectangle)a :
+ a.getBounds();
+
+ Image image = getImage();
+ Rectangle clip = g.getClipBounds();
+
+ fBounds.setBounds(rect);
+ paintHighlights(g, a);
+ paintBorder(g, rect);
+ if (clip != null) {
+ g.clipRect(rect.x + leftInset, rect.y + topInset,
+ rect.width - leftInset - rightInset,
+ rect.height - topInset - bottomInset);
+ }
+ if (image != null) {
+ if (!hasPixels(image)) {
+ // No pixels yet, use the default
+ Icon icon = (image == null) ? getNoImageIcon() :
+ getLoadingImageIcon();
+
+ if (icon != null) {
+ icon.paintIcon(getContainer(), g, rect.x + leftInset,
+ rect.y + topInset);
+ }
+ }
+ else {
+ // Draw the image
+ g.drawImage(image, rect.x + leftInset, rect.y + topInset,
+ width, height, imageObserver);
+ }
+ }
+ else {
+ Icon icon = getNoImageIcon();
+
+ if (icon != null) {
+ icon.paintIcon(getContainer(), g, rect.x + leftInset,
+ rect.y + topInset);
+ }
+ View view = getAltView();
+ // Paint the view representing the alt text, if its non-null
+ if (view != null && ((state & WIDTH_FLAG) == 0 ||
+ width > DEFAULT_WIDTH)) {
+ // Assume layout along the y direction
+ Rectangle altRect = new Rectangle
+ (rect.x + leftInset + DEFAULT_WIDTH, rect.y + topInset,
+ rect.width - leftInset - rightInset - DEFAULT_WIDTH,
+ rect.height - topInset - bottomInset);
+
+ view.paint(g, altRect);
+ }
+ }
+ if (clip != null) {
+ // Reset clip.
+ g.setClip(clip.x, clip.y, clip.width, clip.height);
+ }
+ }
+
+ private void paintHighlights(Graphics g, Shape shape) {
+ if (container instanceof JTextComponent) {
+ JTextComponent tc = (JTextComponent)container;
+ Highlighter h = tc.getHighlighter();
+ if (h instanceof LayeredHighlighter) {
+ ((LayeredHighlighter)h).paintLayeredHighlights
+ (g, getStartOffset(), getEndOffset(), shape, tc, this);
+ }
+ }
+ }
+
+ private void paintBorder(Graphics g, Rectangle rect) {
+ Color color = borderColor;
+
+ if ((borderSize > 0 || image == null) && color != null) {
+ int xOffset = leftInset - borderSize;
+ int yOffset = topInset - borderSize;
+ g.setColor(color);
+ int n = (image == null) ? 1 : borderSize;
+ for (int counter = 0; counter < n; counter++) {
+ g.drawRect(rect.x + xOffset + counter,
+ rect.y + yOffset + counter,
+ rect.width - counter - counter - xOffset -xOffset-1,
+ rect.height - counter - counter -yOffset-yOffset-1);
+ }
+ }
+ }
+
+ /**
+ * Determines the preferred span for this view along an
+ * axis.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the span the view would like to be rendered into;
+ * typically the view is told to render into the span
+ * that is returned, although there is no guarantee;
+ * the parent may choose to resize or break the view
+ */
+ public float getPreferredSpan(int axis) {
+ sync();
+
+ // If the attributes specified a width/height, always use it!
+ if (axis == View.X_AXIS && (state & WIDTH_FLAG) == WIDTH_FLAG) {
+ getPreferredSpanFromAltView(axis);
+ return width + leftInset + rightInset;
+ }
+ if (axis == View.Y_AXIS && (state & HEIGHT_FLAG) == HEIGHT_FLAG) {
+ getPreferredSpanFromAltView(axis);
+ return height + topInset + bottomInset;
+ }
+
+ Image image = getImage();
+
+ if (image != null) {
+ switch (axis) {
+ case View.X_AXIS:
+ return width + leftInset + rightInset;
+ case View.Y_AXIS:
+ return height + topInset + bottomInset;
+ default:
+ throw new IllegalArgumentException("Invalid axis: " + axis);
+ }
+ }
+ else {
+ View view = getAltView();
+ float retValue = 0f;
+
+ if (view != null) {
+ retValue = view.getPreferredSpan(axis);
+ }
+ switch (axis) {
+ case View.X_AXIS:
+ return retValue + (float)(width + leftInset + rightInset);
+ case View.Y_AXIS:
+ return retValue + (float)(height + topInset + bottomInset);
+ default:
+ throw new IllegalArgumentException("Invalid axis: " + axis);
+ }
+ }
+ }
+
+ /**
+ * Determines the desired alignment for this view along an
+ * axis. This is implemented to give the alignment to the
+ * bottom of the icon along the y axis, and the default
+ * along the x axis.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the desired alignment; this should be a value
+ * between 0.0 and 1.0 where 0 indicates alignment at the
+ * origin and 1.0 indicates alignment to the full span
+ * away from the origin; an alignment of 0.5 would be the
+ * center of the view
+ */
+ public float getAlignment(int axis) {
+ switch (axis) {
+ case View.Y_AXIS:
+ return vAlign;
+ default:
+ return super.getAlignment(axis);
+ }
+ }
+
+ /**
+ * Provides a mapping from the document model coordinate space
+ * to the coordinate space of the view mapped to it.
+ *
+ * @param pos the position to convert
+ * @param a the allocated region to render into
+ * @return the bounding box of the given position
+ * @exception BadLocationException if the given position does not represent a
+ * valid location in the associated document
+ * @see View#modelToView
+ */
+ public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
+ int p0 = getStartOffset();
+ int p1 = getEndOffset();
+ if ((pos >= p0) && (pos <= p1)) {
+ Rectangle r = a.getBounds();
+ if (pos == p1) {
+ r.x += r.width;
+ }
+ r.width = 0;
+ return r;
+ }
+ return null;
+ }
+
+ /**
+ * Provides a mapping from the view coordinate space to the logical
+ * coordinate space of the model.
+ *
+ * @param x the X coordinate
+ * @param y the Y coordinate
+ * @param a the allocated region to render into
+ * @return the location within the model that best represents the
+ * given point of view
+ * @see View#viewToModel
+ */
+ public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
+ Rectangle alloc = (Rectangle) a;
+ if (x < alloc.x + alloc.width) {
+ bias[0] = Position.Bias.Forward;
+ return getStartOffset();
+ }
+ bias[0] = Position.Bias.Backward;
+ return getEndOffset();
+ }
+
+ /**
+ * Sets the size of the view. This should cause
+ * layout of the view if it has any layout duties.
+ *
+ * @param width the width >= 0
+ * @param height the height >= 0
+ */
+ public void setSize(float width, float height) {
+ sync();
+
+ if (getImage() == null) {
+ View view = getAltView();
+
+ if (view != null) {
+ view.setSize(Math.max(0f, width - (float)(DEFAULT_WIDTH + leftInset + rightInset)),
+ Math.max(0f, height - (float)(topInset + bottomInset)));
+ }
+ }
+ }
+
+ /**
+ * Returns true if this image within a link?
+ */
+ private boolean isLink() {
+ return ((state & LINK_FLAG) == LINK_FLAG);
+ }
+
+ /**
+ * Returns true if the passed in image has a non-zero width and height.
+ */
+ private boolean hasPixels(Image image) {
+ return image != null &&
+ (image.getHeight(imageObserver) > 0) &&
+ (image.getWidth(imageObserver) > 0);
+ }
+
+ /**
+ * Returns the preferred span of the View used to display the alt text,
+ * or 0 if the view does not exist.
+ */
+ private float getPreferredSpanFromAltView(int axis) {
+ if (getImage() == null) {
+ View view = getAltView();
+
+ if (view != null) {
+ return view.getPreferredSpan(axis);
+ }
+ }
+ return 0f;
+ }
+
+ /**
+ * Request that this view be repainted.
+ * Assumes the view is still at its last-drawn location.
+ */
+ private void repaint(long delay) {
+ if (container != null && fBounds != null) {
+ container.repaint(delay, fBounds.x, fBounds.y, fBounds.width,
+ fBounds.height);
+ }
+ }
+
+ /**
+ * Convenience method for getting an integer attribute from the elements
+ * AttributeSet.
+ */
+ private int getIntAttr(HTML.Attribute name, int deflt) {
+ AttributeSet attr = getElement().getAttributes();
+ if (attr.isDefined(name)) { // does not check parents!
+ int i;
+ String val = (String)attr.getAttribute(name);
+ if (val == null) {
+ i = deflt;
+ }
+ else {
+ try{
+ i = Math.max(0, Integer.parseInt(val));
+ }catch( NumberFormatException x ) {
+ i = deflt;
+ }
+ }
+ return i;
+ } else
+ return deflt;
+ }
+
+ /**
+ * Makes sure the necessary properties and image is loaded.
+ */
+ private void sync() {
+ int s = state;
+ if ((s & RELOAD_IMAGE_FLAG) != 0) {
+ refreshImage();
+ }
+ s = state;
+ if ((s & RELOAD_FLAG) != 0) {
+ synchronized(this) {
+ state = (state | RELOAD_FLAG) ^ RELOAD_FLAG;
+ }
+ setPropertiesFromAttributes();
+ }
+ }
+
+ /**
+ * Loads the image and updates the size accordingly. This should be
+ * invoked instead of invoking <code>loadImage</code> or
+ * <code>updateImageSize</code> directly.
+ */
+ private void refreshImage() {
+ synchronized(this) {
+ // clear out width/height/realoadimage flag and set loading flag
+ state = (state | LOADING_FLAG | RELOAD_IMAGE_FLAG | WIDTH_FLAG |
+ HEIGHT_FLAG) ^ (WIDTH_FLAG | HEIGHT_FLAG |
+ RELOAD_IMAGE_FLAG);
+ image = null;
+ width = height = 0;
+ }
+
+ try {
+ // Load the image
+ loadImage();
+
+ // And update the size params
+ updateImageSize();
+ }
+ finally {
+ synchronized(this) {
+ // Clear out state in case someone threw an exception.
+ state = (state | LOADING_FLAG) ^ LOADING_FLAG;
+ }
+ }
+ }
+
+ /**
+ * Loads the image from the URL <code>getImageURL</code>. This should
+ * only be invoked from <code>refreshImage</code>.
+ */
+ private void loadImage() {
+ URL src = getImageURL();
+ Image newImage = null;
+ if (src != null) {
+ Dictionary cache = (Dictionary)getDocument().
+ getProperty(IMAGE_CACHE_PROPERTY);
+ if (cache != null) {
+ newImage = (Image)cache.get(src);
+ }
+ else {
+ newImage = Toolkit.getDefaultToolkit().createImage(src);
+ if (newImage != null && getLoadsSynchronously()) {
+ // Force the image to be loaded by using an ImageIcon.
+ ImageIcon ii = new ImageIcon();
+ ii.setImage(newImage);
+ }
+ }
+ }
+ image = newImage;
+ }
+
+ /**
+ * Recreates and reloads the image. This should
+ * only be invoked from <code>refreshImage</code>.
+ */
+ private void updateImageSize() {
+ int newWidth = 0;
+ int newHeight = 0;
+ int newState = 0;
+ Image newImage = getImage();
+
+ if (newImage != null) {
+ Element elem = getElement();
+ AttributeSet attr = elem.getAttributes();
+
+ // Get the width/height and set the state ivar before calling
+ // anything that might cause the image to be loaded, and thus the
+ // ImageHandler to be called.
+ newWidth = getIntAttr(HTML.Attribute.WIDTH, -1);
+ if (newWidth > 0) {
+ newState |= WIDTH_FLAG;
+ }
+ newHeight = getIntAttr(HTML.Attribute.HEIGHT, -1);
+ if (newHeight > 0) {
+ newState |= HEIGHT_FLAG;
+ }
+
+ if (newWidth <= 0) {
+ newWidth = newImage.getWidth(imageObserver);
+ if (newWidth <= 0) {
+ newWidth = DEFAULT_WIDTH;
+ }
+ }
+
+ if (newHeight <= 0) {
+ newHeight = newImage.getHeight(imageObserver);
+ if (newHeight <= 0) {
+ newHeight = DEFAULT_HEIGHT;
+ }
+ }
+
+ // Make sure the image starts loading:
+ if ((newState & (WIDTH_FLAG | HEIGHT_FLAG)) != 0) {
+ Toolkit.getDefaultToolkit().prepareImage(newImage, newWidth,
+ newHeight,
+ imageObserver);
+ }
+ else {
+ Toolkit.getDefaultToolkit().prepareImage(newImage, -1, -1,
+ imageObserver);
+ }
+
+ boolean createText = false;
+ synchronized(this) {
+ // If imageloading failed, other thread may have called
+ // ImageLoader which will null out image, hence we check
+ // for it.
+ if (image != null) {
+ if ((newState & WIDTH_FLAG) == WIDTH_FLAG || width == 0) {
+ width = newWidth;
+ }
+ if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG ||
+ height == 0) {
+ height = newHeight;
+ }
+ }
+ else {
+ createText = true;
+ if ((newState & WIDTH_FLAG) == WIDTH_FLAG) {
+ width = newWidth;
+ }
+ if ((newState & HEIGHT_FLAG) == HEIGHT_FLAG) {
+ height = newHeight;
+ }
+ }
+ state = state | newState;
+ state = (state | LOADING_FLAG) ^ LOADING_FLAG;
+ }
+ if (createText) {
+ // Only reset if this thread determined image is null
+ updateAltTextView();
+ }
+ }
+ else {
+ width = height = DEFAULT_HEIGHT;
+ updateAltTextView();
+ }
+ }
+
+ /**
+ * Updates the view representing the alt text.
+ */
+ private void updateAltTextView() {
+ String text = getAltText();
+
+ if (text != null) {
+ ImageLabelView newView;
+
+ newView = new ImageLabelView(getElement(), text);
+ synchronized(this) {
+ altView = newView;
+ }
+ }
+ }
+
+ /**
+ * Returns the view to use for alternate text. This may be null.
+ */
+ private View getAltView() {
+ View view;
+
+ synchronized(this) {
+ view = altView;
+ }
+ if (view != null && view.getParent() == null) {
+ view.setParent(getParent());
+ }
+ return view;
+ }
+
+ /**
+ * Invokes <code>preferenceChanged</code> on the event displatching
+ * thread.
+ */
+ private void safePreferenceChanged() {
+ if (SwingUtilities.isEventDispatchThread()) {
+ Document doc = getDocument();
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readLock();
+ }
+ preferenceChanged(null, true, true);
+ if (doc instanceof AbstractDocument) {
+ ((AbstractDocument)doc).readUnlock();
+ }
+ }
+ else {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ safePreferenceChanged();
+ }
+ });
+ }
+ }
+
+ /**
+ * ImageHandler implements the ImageObserver to correctly update the
+ * display as new parts of the image become available.
+ */
+ private class ImageHandler implements ImageObserver {
+ // This can come on any thread. If we are in the process of reloading
+ // the image and determining our state (loading == true) we don't fire
+ // preference changed, or repaint, we just reset the fWidth/fHeight as
+ // necessary and return. This is ok as we know when loading finishes
+ // it will pick up the new height/width, if necessary.
+ public boolean imageUpdate(Image img, int flags, int x, int y,
+ int newWidth, int newHeight ) {
+ if (image == null || image != img || getParent() == null) {
+ return false;
+ }
+
+ // Bail out if there was an error:
+ if ((flags & (ABORT|ERROR)) != 0) {
+ repaint(0);
+ synchronized(ImageView.this) {
+ if (image == img) {
+ // Be sure image hasn't changed since we don't
+ // initialy synchronize
+ image = null;
+ if ((state & WIDTH_FLAG) != WIDTH_FLAG) {
+ width = DEFAULT_WIDTH;
+ }
+ if ((state & HEIGHT_FLAG) != HEIGHT_FLAG) {
+ height = DEFAULT_HEIGHT;
+ }
+ }
+ if ((state & LOADING_FLAG) == LOADING_FLAG) {
+ // No need to resize or repaint, still in the process
+ // of loading.
+ return false;
+ }
+ }
+ updateAltTextView();
+ safePreferenceChanged();
+ return false;
+ }
+
+ // Resize image if necessary:
+ short changed = 0;
+ if ((flags & ImageObserver.HEIGHT) != 0 && !getElement().
+ getAttributes().isDefined(HTML.Attribute.HEIGHT)) {
+ changed |= 1;
+ }
+ if ((flags & ImageObserver.WIDTH) != 0 && !getElement().
+ getAttributes().isDefined(HTML.Attribute.WIDTH)) {
+ changed |= 2;
+ }
+
+ synchronized(ImageView.this) {
+ if (image != img) {
+ return false;
+ }
+ if ((changed & 1) == 1 && (state & WIDTH_FLAG) == 0) {
+ width = newWidth;
+ }
+ if ((changed & 2) == 2 && (state & HEIGHT_FLAG) == 0) {
+ height = newHeight;
+ }
+ if ((state & LOADING_FLAG) == LOADING_FLAG) {
+ // No need to resize or repaint, still in the process of
+ // loading.
+ return true;
+ }
+ }
+ if (changed != 0) {
+ // May need to resize myself, asynchronously:
+ safePreferenceChanged();
+ return true;
+ }
+
+ // Repaint when done or when new pixels arrive:
+ if ((flags & (FRAMEBITS|ALLBITS)) != 0) {
+ repaint(0);
+ }
+ else if ((flags & SOMEBITS) != 0 && sIsInc) {
+ repaint(sIncRate);
+ }
+ return ((flags & ALLBITS) == 0);
+ }
+ }
+
+
+ /**
+ * ImageLabelView is used if the image can't be loaded, and
+ * the attribute specified an alt attribute. It overriden a handle of
+ * methods as the text is hardcoded and does not come from the document.
+ */
+ private class ImageLabelView extends InlineView {
+ private Segment segment;
+ private Color fg;
+
+ ImageLabelView(Element e, String text) {
+ super(e);
+ reset(text);
+ }
+
+ public void reset(String text) {
+ segment = new Segment(text.toCharArray(), 0, text.length());
+ }
+
+ public void paint(Graphics g, Shape a) {
+ // Don't use supers paint, otherwise selection will be wrong
+ // as our start/end offsets are fake.
+ GlyphPainter painter = getGlyphPainter();
+
+ if (painter != null) {
+ g.setColor(getForeground());
+ painter.paint(this, g, a, getStartOffset(), getEndOffset());
+ }
+ }
+
+ public Segment getText(int p0, int p1) {
+ if (p0 < 0 || p1 > segment.array.length) {
+ throw new RuntimeException("ImageLabelView: Stale view");
+ }
+ segment.offset = p0;
+ segment.count = p1 - p0;
+ return segment;
+ }
+
+ public int getStartOffset() {
+ return 0;
+ }
+
+ public int getEndOffset() {
+ return segment.array.length;
+ }
+
+ public View breakView(int axis, int p0, float pos, float len) {
+ // Don't allow a break
+ return this;
+ }
+
+ public Color getForeground() {
+ View parent;
+ if (fg == null && (parent = getParent()) != null) {
+ Document doc = getDocument();
+ AttributeSet attr = parent.getAttributes();
+
+ if (attr != null && (doc instanceof StyledDocument)) {
+ fg = ((StyledDocument)doc).getForeground(attr);
+ }
+ }
+ return fg;
+ }
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/InlineView.java b/src/share/classes/javax/swing/text/html/InlineView.java
new file mode 100644
index 000000000..4a9f0be9b
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/InlineView.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.*;
+import java.text.BreakIterator;
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.*;
+
+/**
+ * Displays the <dfn>inline element</dfn> styles
+ * based upon css attributes.
+ *
+ * @author Timothy Prinzing
+ */
+public class InlineView extends LabelView {
+
+ /**
+ * Constructs a new view wrapped on an element.
+ *
+ * @param elem the element
+ */
+ public InlineView(Element elem) {
+ super(elem);
+ StyleSheet sheet = getStyleSheet();
+ attr = sheet.getViewAttributes(this);
+ }
+
+ /**
+ * Gives notification that something was inserted into
+ * the document in a location that this view is responsible for.
+ * If either parameter is <code>null</code>, behavior of this method is
+ * implementation dependent.
+ *
+ * @param e the change information from the associated document
+ * @param a the current allocation of the view
+ * @param f the factory to use to rebuild if the view has children
+ * @since 1.5
+ * @see View#insertUpdate
+ */
+ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+ super.insertUpdate(e, a, f);
+ }
+
+ /**
+ * Gives notification that something was removed from the document
+ * in a location that this view is responsible for.
+ * If either parameter is <code>null</code>, behavior of this method is
+ * implementation dependent.
+ *
+ * @param e the change information from the associated document
+ * @param a the current allocation of the view
+ * @param f the factory to use to rebuild if the view has children
+ * @since 1.5
+ * @see View#removeUpdate
+ */
+ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+ super.removeUpdate(e, a, f);
+ }
+
+ /**
+ * Gives notification from the document that attributes were changed
+ * in a location that this view is responsible for.
+ *
+ * @param e the change information from the associated document
+ * @param a the current allocation of the view
+ * @param f the factory to use to rebuild if the view has children
+ * @see View#changedUpdate
+ */
+ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+ super.changedUpdate(e, a, f);
+ StyleSheet sheet = getStyleSheet();
+ attr = sheet.getViewAttributes(this);
+ preferenceChanged(null, true, true);
+ }
+
+ /**
+ * Fetches the attributes to use when rendering. This is
+ * implemented to multiplex the attributes specified in the
+ * model with a StyleSheet.
+ */
+ public AttributeSet getAttributes() {
+ return attr;
+ }
+
+ /**
+ * Determines how attractive a break opportunity in
+ * this view is. This can be used for determining which
+ * view is the most attractive to call <code>breakView</code>
+ * on in the process of formatting. A view that represents
+ * text that has whitespace in it might be more attractive
+ * than a view that has no whitespace, for example. The
+ * higher the weight, the more attractive the break. A
+ * value equal to or lower than <code>BadBreakWeight</code>
+ * should not be considered for a break. A value greater
+ * than or equal to <code>ForcedBreakWeight</code> should
+ * be broken.
+ * <p>
+ * This is implemented to provide the default behavior
+ * of returning <code>BadBreakWeight</code> unless the length
+ * is greater than the length of the view in which case the
+ * entire view represents the fragment. Unless a view has
+ * been written to support breaking behavior, it is not
+ * attractive to try and break the view. An example of
+ * a view that does support breaking is <code>LabelView</code>.
+ * An example of a view that uses break weight is
+ * <code>ParagraphView</code>.
+ *
+ * @param axis may be either View.X_AXIS or View.Y_AXIS
+ * @param pos the potential location of the start of the
+ * broken view >= 0. This may be useful for calculating tab
+ * positions.
+ * @param len specifies the relative length from <em>pos</em>
+ * where a potential break is desired >= 0.
+ * @return the weight, which should be a value between
+ * ForcedBreakWeight and BadBreakWeight.
+ * @see LabelView
+ * @see ParagraphView
+ * @see javax.swing.text.View#BadBreakWeight
+ * @see javax.swing.text.View#GoodBreakWeight
+ * @see javax.swing.text.View#ExcellentBreakWeight
+ * @see javax.swing.text.View#ForcedBreakWeight
+ */
+ public int getBreakWeight(int axis, float pos, float len) {
+ if (nowrap) {
+ return BadBreakWeight;
+ }
+ return super.getBreakWeight(axis, pos, len);
+ }
+
+ /**
+ * Tries to break this view on the given axis. Refer to
+ * {@link javax.swing.text.View#breakView} for a complete
+ * description of this method.
+ * <p>Behavior of this method is unspecified in case <code>axis</code>
+ * is neither <code>View.X_AXIS</code> nor <code>View.Y_AXIS</code>, and
+ * in case <code>offset</code>, <code>pos</code>, or <code>len</code>
+ * is null.
+ *
+ * @param axis may be either <code>View.X_AXIS</code> or
+ * <code>View.Y_AXIS</code>
+ * @param offset the location in the document model
+ * that a broken fragment would occupy >= 0. This
+ * would be the starting offset of the fragment
+ * returned
+ * @param pos the position along the axis that the
+ * broken view would occupy >= 0. This may be useful for
+ * things like tab calculations
+ * @param len specifies the distance along the axis
+ * where a potential break is desired >= 0
+ * @return the fragment of the view that represents the
+ * given span.
+ * @since 1.5
+ * @see javax.swing.text.View#breakView
+ */
+ public View breakView(int axis, int offset, float pos, float len) {
+ return super.breakView(axis, offset, pos, len);
+ }
+
+
+ /**
+ * Set the cached properties from the attributes.
+ */
+ protected void setPropertiesFromAttributes() {
+ super.setPropertiesFromAttributes();
+ AttributeSet a = getAttributes();
+ Object decor = a.getAttribute(CSS.Attribute.TEXT_DECORATION);
+ boolean u = (decor != null) ?
+ (decor.toString().indexOf("underline") >= 0) : false;
+ setUnderline(u);
+ boolean s = (decor != null) ?
+ (decor.toString().indexOf("line-through") >= 0) : false;
+ setStrikeThrough(s);
+ Object vAlign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
+ s = (vAlign != null) ? (vAlign.toString().indexOf("sup") >= 0) : false;
+ setSuperscript(s);
+ s = (vAlign != null) ? (vAlign.toString().indexOf("sub") >= 0) : false;
+ setSubscript(s);
+
+ Object whitespace = a.getAttribute(CSS.Attribute.WHITE_SPACE);
+ if ((whitespace != null) && whitespace.equals("nowrap")) {
+ nowrap = true;
+ } else {
+ nowrap = false;
+ }
+
+ HTMLDocument doc = (HTMLDocument)getDocument();
+ // fetches background color from stylesheet if specified
+ Color bg = doc.getBackground(a);
+ if (bg != null) {
+ setBackground(bg);
+ }
+ }
+
+
+ protected StyleSheet getStyleSheet() {
+ HTMLDocument doc = (HTMLDocument) getDocument();
+ return doc.getStyleSheet();
+ }
+
+ private boolean nowrap;
+ private AttributeSet attr;
+}
diff --git a/src/share/classes/javax/swing/text/html/IsindexView.java b/src/share/classes/javax/swing/text/html/IsindexView.java
new file mode 100644
index 000000000..b75a8312c
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/IsindexView.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.net.URLEncoder;
+import java.net.MalformedURLException;
+import java.io.IOException;
+import java.net.URL;
+import javax.swing.text.*;
+import javax.swing.*;
+
+
+/**
+ * A view that supports the &lt;ISINDEX&lt; tag. This is implemented
+ * as a JPanel that contains
+ *
+ * @author Sunita Mani
+ */
+
+class IsindexView extends ComponentView implements ActionListener {
+
+ JTextField textField;
+
+ /**
+ * Creates an IsindexView
+ */
+ public IsindexView(Element elem) {
+ super(elem);
+ }
+
+ /**
+ * Creates the components necessary to to implement
+ * this view. The component returned is a <code>JPanel</code>,
+ * that contains the PROMPT to the left and <code>JTextField</code>
+ * to the right.
+ */
+ public Component createComponent() {
+ AttributeSet attr = getElement().getAttributes();
+
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setBackground(null);
+
+ String prompt = (String)attr.getAttribute(HTML.Attribute.PROMPT);
+ if (prompt == null) {
+ prompt = UIManager.getString("IsindexView.prompt");
+ }
+ JLabel label = new JLabel(prompt);
+
+ textField = new JTextField();
+ textField.addActionListener(this);
+ panel.add(label, BorderLayout.WEST);
+ panel.add(textField, BorderLayout.CENTER);
+ panel.setAlignmentY(1.0f);
+ panel.setOpaque(false);
+ return panel;
+ }
+
+ /**
+ * Responsible for processing the ActionEvent.
+ * In this case this is hitting enter/return
+ * in the text field. This will construct the
+ * URL from the base URL of the document.
+ * To the URL is appended a '?' followed by the
+ * contents of the JTextField. The search
+ * contents are URLEncoded.
+ */
+ public void actionPerformed(ActionEvent evt) {
+
+ String data = textField.getText();
+ if (data != null) {
+ data = URLEncoder.encode(data);
+ }
+
+
+ AttributeSet attr = getElement().getAttributes();
+ HTMLDocument hdoc = (HTMLDocument)getElement().getDocument();
+
+ String action = (String) attr.getAttribute(HTML.Attribute.ACTION);
+ if (action == null) {
+ action = hdoc.getBase().toString();
+ }
+ try {
+ URL url = new URL(action+"?"+data);
+ JEditorPane pane = (JEditorPane)getContainer();
+ pane.setPage(url);
+ } catch (MalformedURLException e1) {
+ } catch (IOException e2) {
+ }
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/LineView.java b/src/share/classes/javax/swing/text/html/LineView.java
new file mode 100644
index 000000000..b982cd342
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/LineView.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 1997-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.util.Enumeration;
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.event.*;
+import javax.swing.text.*;
+
+/**
+ * A view implementation to display an unwrapped
+ * preformatted line.<p>
+ * This subclasses ParagraphView, but this really only contains one
+ * Row of text.
+ *
+ * @author Timothy Prinzing
+ */
+class LineView extends ParagraphView {
+ /** Last place painted at. */
+ int tabBase;
+
+ /**
+ * Creates a LineView object.
+ *
+ * @param elem the element to wrap in a view
+ */
+ public LineView(Element elem) {
+ super(elem);
+ }
+
+ /**
+ * Preformatted lines are not suppressed if they
+ * have only whitespace, so they are always visible.
+ */
+ public boolean isVisible() {
+ return true;
+ }
+
+ /**
+ * Determines the minimum span for this view along an
+ * axis. The preformatted line should refuse to be
+ * sized less than the preferred size.
+ *
+ * @param axis may be either <code>View.X_AXIS</code> or
+ * <code>View.Y_AXIS</code>
+ * @return the minimum span the view can be rendered into
+ * @see View#getPreferredSpan
+ */
+ public float getMinimumSpan(int axis) {
+ return getPreferredSpan(axis);
+ }
+
+ /**
+ * Gets the resize weight for the specified axis.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the weight
+ */
+ public int getResizeWeight(int axis) {
+ switch (axis) {
+ case View.X_AXIS:
+ return 1;
+ case View.Y_AXIS:
+ return 0;
+ default:
+ throw new IllegalArgumentException("Invalid axis: " + axis);
+ }
+ }
+
+ /**
+ * Gets the alignment for an axis.
+ *
+ * @param axis may be either X_AXIS or Y_AXIS
+ * @return the alignment
+ */
+ public float getAlignment(int axis) {
+ if (axis == View.X_AXIS) {
+ return 0;
+ }
+ return super.getAlignment(axis);
+ }
+
+ /**
+ * Lays out the children. If the layout span has changed,
+ * the rows are rebuilt. The superclass functionality
+ * is called after checking and possibly rebuilding the
+ * rows. If the height has changed, the
+ * <code>preferenceChanged</code> method is called
+ * on the parent since the vertical preference is
+ * rigid.
+ *
+ * @param width the width to lay out against >= 0. This is
+ * the width inside of the inset area.
+ * @param height the height to lay out against >= 0 (not used
+ * by paragraph, but used by the superclass). This
+ * is the height inside of the inset area.
+ */
+ protected void layout(int width, int height) {
+ super.layout(Integer.MAX_VALUE - 1, height);
+ }
+
+ /**
+ * Returns the next tab stop position given a reference position.
+ * This view implements the tab coordinate system, and calls
+ * <code>getTabbedSpan</code> on the logical children in the process
+ * of layout to determine the desired span of the children. The
+ * logical children can delegate their tab expansion upward to
+ * the paragraph which knows how to expand tabs.
+ * <code>LabelView</code> is an example of a view that delegates
+ * its tab expansion needs upward to the paragraph.
+ * <p>
+ * This is implemented to try and locate a <code>TabSet</code>
+ * in the paragraph element's attribute set. If one can be
+ * found, its settings will be used, otherwise a default expansion
+ * will be provided. The base location for for tab expansion
+ * is the left inset from the paragraphs most recent allocation
+ * (which is what the layout of the children is based upon).
+ *
+ * @param x the X reference position
+ * @param tabOffset the position within the text stream
+ * that the tab occurred at >= 0.
+ * @return the trailing end of the tab expansion >= 0
+ * @see TabSet
+ * @see TabStop
+ * @see LabelView
+ */
+ public float nextTabStop(float x, int tabOffset) {
+ // If the text isn't left justified, offset by 10 pixels!
+ if (getTabSet() == null &&
+ StyleConstants.getAlignment(getAttributes()) ==
+ StyleConstants.ALIGN_LEFT) {
+ return getPreTab(x, tabOffset);
+ }
+ return super.nextTabStop(x, tabOffset);
+ }
+
+ /**
+ * Returns the location for the tab.
+ */
+ protected float getPreTab(float x, int tabOffset) {
+ Document d = getDocument();
+ View v = getViewAtPosition(tabOffset, null);
+ if ((d instanceof StyledDocument) && v != null) {
+ // Assume f is fixed point.
+ Font f = ((StyledDocument)d).getFont(v.getAttributes());
+ Container c = getContainer();
+ FontMetrics fm = (c != null) ? c.getFontMetrics(f) :
+ Toolkit.getDefaultToolkit().getFontMetrics(f);
+ int width = getCharactersPerTab() * fm.charWidth('W');
+ int tb = (int)getTabBase();
+ return (float)((((int)x - tb) / width + 1) * width + tb);
+ }
+ return 10.0f + x;
+ }
+
+ /**
+ * @return number of characters per tab, 8.
+ */
+ protected int getCharactersPerTab() {
+ return 8;
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/ListView.java b/src/share/classes/javax/swing/text/html/ListView.java
new file mode 100644
index 000000000..775f2935c
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/ListView.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 1997-1999 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.util.Enumeration;
+import java.awt.*;
+import javax.swing.text.*;
+
+/**
+ * A view implementation to display an html list
+ *
+ * @author Timothy Prinzing
+ */
+public class ListView extends BlockView {
+
+ /**
+ * Creates a new view that represents a list element.
+ *
+ * @param elem the element to create a view for
+ */
+ public ListView(Element elem) {
+ super(elem, View.Y_AXIS);
+ }
+
+ /**
+ * Calculates the desired shape of the list.
+ *
+ * @return the desired span
+ * @see View#getPreferredSpan
+ */
+ public float getAlignment(int axis) {
+ switch (axis) {
+ case View.X_AXIS:
+ return 0.5f;
+ case View.Y_AXIS:
+ return 0.5f;
+ default:
+ throw new IllegalArgumentException("Invalid axis: " + axis);
+ }
+ }
+
+ /**
+ * Renders using the given rendering surface and area on that
+ * surface.
+ *
+ * @param g the rendering surface to use
+ * @param allocation the allocated region to render into
+ * @see View#paint
+ */
+ public void paint(Graphics g, Shape allocation) {
+ super.paint(g, allocation);
+ Rectangle alloc = allocation.getBounds();
+ Rectangle clip = g.getClipBounds();
+ // Since listPainter paints in the insets we have to check for the
+ // case where the child is not painted because the paint region is
+ // to the left of the child. This assumes the ListPainter paints in
+ // the left margin.
+ if ((clip.x + clip.width) < (alloc.x + getLeftInset())) {
+ Rectangle childRect = alloc;
+ alloc = getInsideAllocation(allocation);
+ int n = getViewCount();
+ int endY = clip.y + clip.height;
+ for (int i = 0; i < n; i++) {
+ childRect.setBounds(alloc);
+ childAllocation(i, childRect);
+ if (childRect.y < endY) {
+ if ((childRect.y + childRect.height) >= clip.y) {
+ listPainter.paint(g, childRect.x, childRect.y,
+ childRect.width, childRect.height,
+ this, i);
+ }
+ }
+ else {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Paints one of the children; called by paint(). By default
+ * that is all it does, but a subclass can use this to paint
+ * things relative to the child.
+ *
+ * @param g the graphics context
+ * @param alloc the allocated region to render the child into
+ * @param index the index of the child
+ */
+ protected void paintChild(Graphics g, Rectangle alloc, int index) {
+ listPainter.paint(g, alloc.x, alloc.y, alloc.width, alloc.height, this, index);
+ super.paintChild(g, alloc, index);
+ }
+
+ protected void setPropertiesFromAttributes() {
+ super.setPropertiesFromAttributes();
+ listPainter = getStyleSheet().getListPainter(getAttributes());
+ }
+
+ private StyleSheet.ListPainter listPainter;
+}
diff --git a/src/share/classes/javax/swing/text/html/Map.java b/src/share/classes/javax/swing/text/html/Map.java
new file mode 100644
index 000000000..0180df14f
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/Map.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.Polygon;
+import java.io.Serializable;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import javax.swing.text.AttributeSet;
+
+/**
+ * Map is used to represent a map element that is part of an HTML document.
+ * Once a Map has been created, and any number of areas have been added,
+ * you can test if a point falls inside the map via the contains method.
+ *
+ * @author Scott Violet
+ */
+class Map implements Serializable {
+ /** Name of the Map. */
+ private String name;
+ /** An array of AttributeSets. */
+ private Vector areaAttributes;
+ /** An array of RegionContainments, will slowly grow to match the
+ * length of areaAttributes as needed. */
+ private Vector areas;
+
+ public Map() {
+ }
+
+ public Map(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the name of the Map.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Defines a region of the Map, based on the passed in AttributeSet.
+ */
+ public void addArea(AttributeSet as) {
+ if (as == null) {
+ return;
+ }
+ if (areaAttributes == null) {
+ areaAttributes = new Vector(2);
+ }
+ areaAttributes.addElement(as.copyAttributes());
+ }
+
+ /**
+ * Removes the previously created area.
+ */
+ public void removeArea(AttributeSet as) {
+ if (as != null && areaAttributes != null) {
+ int numAreas = (areas != null) ? areas.size() : 0;
+ for (int counter = areaAttributes.size() - 1; counter >= 0;
+ counter--) {
+ if (((AttributeSet)areaAttributes.elementAt(counter)).
+ isEqual(as)){
+ areaAttributes.removeElementAt(counter);
+ if (counter < numAreas) {
+ areas.removeElementAt(counter);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the AttributeSets representing the differet areas of the Map.
+ */
+ public AttributeSet[] getAreas() {
+ int numAttributes = (areaAttributes != null) ? areaAttributes.size() :
+ 0;
+ if (numAttributes != 0) {
+ AttributeSet[] retValue = new AttributeSet[numAttributes];
+
+ areaAttributes.copyInto(retValue);
+ return retValue;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the AttributeSet that contains the passed in location,
+ * <code>x</code>, <code>y</code>. <code>width</code>, <code>height</code>
+ * gives the size of the region the map is defined over. If a matching
+ * area is found, the AttribueSet for it is returned.
+ */
+ public AttributeSet getArea(int x, int y, int width, int height) {
+ int numAttributes = (areaAttributes != null) ?
+ areaAttributes.size() : 0;
+
+ if (numAttributes > 0) {
+ int numAreas = (areas != null) ? areas.size() : 0;
+
+ if (areas == null) {
+ areas = new Vector(numAttributes);
+ }
+ for (int counter = 0; counter < numAttributes; counter++) {
+ if (counter >= numAreas) {
+ areas.addElement(createRegionContainment
+ ((AttributeSet)areaAttributes.elementAt(counter)));
+ }
+ RegionContainment rc = (RegionContainment)areas.
+ elementAt(counter);
+ if (rc != null && rc.contains(x, y, width, height)) {
+ return (AttributeSet)areaAttributes.elementAt(counter);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Creates and returns an instance of RegionContainment that can be
+ * used to test if a particular point lies inside a region.
+ */
+ protected RegionContainment createRegionContainment
+ (AttributeSet attributes) {
+ Object shape = attributes.getAttribute(HTML.Attribute.SHAPE);
+
+ if (shape == null) {
+ shape = "rect";
+ }
+ if (shape instanceof String) {
+ String shapeString = ((String)shape).toLowerCase();
+ RegionContainment rc = null;
+
+ try {
+ if (shapeString.equals("rect")) {
+ rc = new RectangleRegionContainment(attributes);
+ }
+ else if (shapeString.equals("circle")) {
+ rc = new CircleRegionContainment(attributes);
+ }
+ else if (shapeString.equals("poly")) {
+ rc = new PolygonRegionContainment(attributes);
+ }
+ else if (shapeString.equals("default")) {
+ rc = DefaultRegionContainment.sharedInstance();
+ }
+ } catch (RuntimeException re) {
+ // Something wrong with attributes.
+ rc = null;
+ }
+ return rc;
+ }
+ return null;
+ }
+
+ /**
+ * Creates and returns an array of integers from the String
+ * <code>stringCoords</code>. If one of the values represents a
+ * % the returned value with be negative. If a parse error results
+ * from trying to parse one of the numbers null is returned.
+ */
+ static protected int[] extractCoords(Object stringCoords) {
+ if (stringCoords == null || !(stringCoords instanceof String)) {
+ return null;
+ }
+
+ StringTokenizer st = new StringTokenizer((String)stringCoords,
+ ", \t\n\r");
+ int[] retValue = null;
+ int numCoords = 0;
+
+ while(st.hasMoreElements()) {
+ String token = st.nextToken();
+ int scale;
+
+ if (token.endsWith("%")) {
+ scale = -1;
+ token = token.substring(0, token.length() - 1);
+ }
+ else {
+ scale = 1;
+ }
+ try {
+ int intValue = Integer.parseInt(token);
+
+ if (retValue == null) {
+ retValue = new int[4];
+ }
+ else if(numCoords == retValue.length) {
+ int[] temp = new int[retValue.length * 2];
+
+ System.arraycopy(retValue, 0, temp, 0, retValue.length);
+ retValue = temp;
+ }
+ retValue[numCoords++] = intValue * scale;
+ } catch (NumberFormatException nfe) {
+ return null;
+ }
+ }
+ if (numCoords > 0 && numCoords != retValue.length) {
+ int[] temp = new int[numCoords];
+
+ System.arraycopy(retValue, 0, temp, 0, numCoords);
+ retValue = temp;
+ }
+ return retValue;
+ }
+
+
+ /**
+ * Defines the interface used for to check if a point is inside a
+ * region.
+ */
+ interface RegionContainment {
+ /**
+ * Returns true if the location <code>x</code>, <code>y</code>
+ * falls inside the region defined in the receiver.
+ * <code>width</code>, <code>height</code> is the size of
+ * the enclosing region.
+ */
+ public boolean contains(int x, int y, int width, int height);
+ }
+
+
+ /**
+ * Used to test for containment in a rectangular region.
+ */
+ static class RectangleRegionContainment implements RegionContainment {
+ /** Will be non-null if one of the values is a percent, and any value
+ * that is non null indicates it is a percent
+ * (order is x, y, width, height). */
+ float[] percents;
+ /** Last value of width passed in. */
+ int lastWidth;
+ /** Last value of height passed in. */
+ int lastHeight;
+ /** Top left. */
+ int x0;
+ int y0;
+ /** Bottom right. */
+ int x1;
+ int y1;
+
+ public RectangleRegionContainment(AttributeSet as) {
+ int[] coords = Map.extractCoords(as.getAttribute(HTML.
+ Attribute.COORDS));
+
+ percents = null;
+ if (coords == null || coords.length != 4) {
+ throw new RuntimeException("Unable to parse rectangular area");
+ }
+ else {
+ x0 = coords[0];
+ y0 = coords[1];
+ x1 = coords[2];
+ y1 = coords[3];
+ if (x0 < 0 || y0 < 0 || x1 < 0 || y1 < 0) {
+ percents = new float[4];
+ lastWidth = lastHeight = -1;
+ for (int counter = 0; counter < 4; counter++) {
+ if (coords[counter] < 0) {
+ percents[counter] = Math.abs
+ (coords[counter]) / 100.0f;
+ }
+ else {
+ percents[counter] = -1.0f;
+ }
+ }
+ }
+ }
+ }
+
+ public boolean contains(int x, int y, int width, int height) {
+ if (percents == null) {
+ return contains(x, y);
+ }
+ if (lastWidth != width || lastHeight != height) {
+ lastWidth = width;
+ lastHeight = height;
+ if (percents[0] != -1.0f) {
+ x0 = (int)(percents[0] * width);
+ }
+ if (percents[1] != -1.0f) {
+ y0 = (int)(percents[1] * height);
+ }
+ if (percents[2] != -1.0f) {
+ x1 = (int)(percents[2] * width);
+ }
+ if (percents[3] != -1.0f) {
+ y1 = (int)(percents[3] * height);
+ }
+ }
+ return contains(x, y);
+ }
+
+ public boolean contains(int x, int y) {
+ return ((x >= x0 && x <= x1) &&
+ (y >= y0 && y <= y1));
+ }
+ }
+
+
+ /**
+ * Used to test for containment in a polygon region.
+ */
+ static class PolygonRegionContainment extends Polygon implements
+ RegionContainment {
+ /** If any value is a percent there will be an entry here for the
+ * percent value. Use percentIndex to find out the index for it. */
+ float[] percentValues;
+ int[] percentIndexs;
+ /** Last value of width passed in. */
+ int lastWidth;
+ /** Last value of height passed in. */
+ int lastHeight;
+
+ public PolygonRegionContainment(AttributeSet as) {
+ int[] coords = Map.extractCoords(as.getAttribute(HTML.Attribute.
+ COORDS));
+
+ if (coords == null || coords.length == 0 ||
+ coords.length % 2 != 0) {
+ throw new RuntimeException("Unable to parse polygon area");
+ }
+ else {
+ int numPercents = 0;
+
+ lastWidth = lastHeight = -1;
+ for (int counter = coords.length - 1; counter >= 0;
+ counter--) {
+ if (coords[counter] < 0) {
+ numPercents++;
+ }
+ }
+
+ if (numPercents > 0) {
+ percentIndexs = new int[numPercents];
+ percentValues = new float[numPercents];
+ for (int counter = coords.length - 1, pCounter = 0;
+ counter >= 0; counter--) {
+ if (coords[counter] < 0) {
+ percentValues[pCounter] = coords[counter] /
+ -100.0f;
+ percentIndexs[pCounter] = counter;
+ pCounter++;
+ }
+ }
+ }
+ else {
+ percentIndexs = null;
+ percentValues = null;
+ }
+ npoints = coords.length / 2;
+ xpoints = new int[npoints];
+ ypoints = new int[npoints];
+
+ for (int counter = 0; counter < npoints; counter++) {
+ xpoints[counter] = coords[counter + counter];
+ ypoints[counter] = coords[counter + counter + 1];
+ }
+ }
+ }
+
+ public boolean contains(int x, int y, int width, int height) {
+ if (percentValues == null || (lastWidth == width &&
+ lastHeight == height)) {
+ return contains(x, y);
+ }
+ // Force the bounding box to be recalced.
+ bounds = null;
+ lastWidth = width;
+ lastHeight = height;
+ float fWidth = (float)width;
+ float fHeight = (float)height;
+ for (int counter = percentValues.length - 1; counter >= 0;
+ counter--) {
+ if (percentIndexs[counter] % 2 == 0) {
+ // x
+ xpoints[percentIndexs[counter] / 2] =
+ (int)(percentValues[counter] * fWidth);
+ }
+ else {
+ // y
+ ypoints[percentIndexs[counter] / 2] =
+ (int)(percentValues[counter] * fHeight);
+ }
+ }
+ return contains(x, y);
+ }
+ }
+
+
+ /**
+ * Used to test for containment in a circular region.
+ */
+ static class CircleRegionContainment implements RegionContainment {
+ /** X origin of the circle. */
+ int x;
+ /** Y origin of the circle. */
+ int y;
+ /** Radius of the circle. */
+ int radiusSquared;
+ /** Non-null indicates one of the values represents a percent. */
+ float[] percentValues;
+ /** Last value of width passed in. */
+ int lastWidth;
+ /** Last value of height passed in. */
+ int lastHeight;
+
+ public CircleRegionContainment(AttributeSet as) {
+ int[] coords = Map.extractCoords(as.getAttribute(HTML.Attribute.
+ COORDS));
+
+ if (coords == null || coords.length != 3) {
+ throw new RuntimeException("Unable to parse circular area");
+ }
+ x = coords[0];
+ y = coords[1];
+ radiusSquared = coords[2] * coords[2];
+ if (coords[0] < 0 || coords[1] < 0 || coords[2] < 0) {
+ lastWidth = lastHeight = -1;
+ percentValues = new float[3];
+ for (int counter = 0; counter < 3; counter++) {
+ if (coords[counter] < 0) {
+ percentValues[counter] = coords[counter] /
+ -100.0f;
+ }
+ else {
+ percentValues[counter] = -1.0f;
+ }
+ }
+ }
+ else {
+ percentValues = null;
+ }
+ }
+
+ public boolean contains(int x, int y, int width, int height) {
+ if (percentValues != null && (lastWidth != width ||
+ lastHeight != height)) {
+ int newRad = Math.min(width, height) / 2;
+
+ lastWidth = width;
+ lastHeight = height;
+ if (percentValues[0] != -1.0f) {
+ this.x = (int)(percentValues[0] * width);
+ }
+ if (percentValues[1] != -1.0f) {
+ this.y = (int)(percentValues[1] * height);
+ }
+ if (percentValues[2] != -1.0f) {
+ radiusSquared = (int)(percentValues[2] *
+ Math.min(width, height));
+ radiusSquared *= radiusSquared;
+ }
+ }
+ return (((x - this.x) * (x - this.x) +
+ (y - this.y) * (y - this.y)) <= radiusSquared);
+ }
+ }
+
+
+ /**
+ * An implementation that will return true if the x, y location is
+ * inside a rectangle defined by origin 0, 0, and width equal to
+ * width passed in, and height equal to height passed in.
+ */
+ static class DefaultRegionContainment implements RegionContainment {
+ /** A global shared instance. */
+ static DefaultRegionContainment si = null;
+
+ public static DefaultRegionContainment sharedInstance() {
+ if (si == null) {
+ si = new DefaultRegionContainment();
+ }
+ return si;
+ }
+
+ public boolean contains(int x, int y, int width, int height) {
+ return (x <= width && x >= 0 && y >= 0 && y <= width);
+ }
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/MinimalHTMLWriter.java b/src/share/classes/javax/swing/text/html/MinimalHTMLWriter.java
new file mode 100644
index 000000000..d7637ae79
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/MinimalHTMLWriter.java
@@ -0,0 +1,726 @@
+/*
+ * Copyright 1998-1999 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html;
+
+import java.io.Writer;
+import java.io.IOException;
+import java.util.*;
+import java.awt.Color;
+import javax.swing.text.*;
+
+/**
+ * MinimalHTMLWriter is a fallback writer used by the
+ * HTMLEditorKit to write out HTML for a document that
+ * is a not produced by the EditorKit.
+ *
+ * The format for the document is:
+ * <pre>
+ * &lt;html&gt;
+ * &lt;head&gt;
+ * &lt;style&gt;
+ * &lt;!-- list of named styles
+ * p.normal {
+ * font-family: SansSerif;
+ * margin-height: 0;
+ * font-size: 14
+ * }
+ * --&gt;
+ * &lt;/style&gt;
+ * &lt;/head&gt;
+ * &lt;body&gt;
+ * &lt;p style=normal&gt;
+ * <b>Bold, italic, and underline attributes
+ * of the run are emitted as HTML tags.
+ * The remaining attributes are emitted as
+ * part of the style attribute of a &lt;span&gt; tag.
+ * The syntax is similar to inline styles.</b>
+ * &lt;/p&gt;
+ * &lt;/body&gt;
+ * &lt;/html&gt;
+ * </pre>
+ *
+ * @author Sunita Mani
+ */
+
+public class MinimalHTMLWriter extends AbstractWriter {
+
+ /**
+ * These static finals are used to
+ * tweak and query the fontMask about which
+ * of these tags need to be generated or
+ * terminated.
+ */
+ private static final int BOLD = 0x01;
+ private static final int ITALIC = 0x02;
+ private static final int UNDERLINE = 0x04;
+
+ // Used to map StyleConstants to CSS.
+ private static final CSS css = new CSS();
+
+ private int fontMask = 0;
+
+ int startOffset = 0;
+ int endOffset = 0;
+
+ /**
+ * Stores the attributes of the previous run.
+ * Used to compare with the current run's
+ * attributeset. If identical, then a
+ * &lt;span&gt; tag is not emitted.
+ */
+ private AttributeSet fontAttributes;
+
+ /**
+ * Maps from style name as held by the Document, to the archived
+ * style name (style name written out). These may differ.
+ */
+ private Hashtable styleNameMapping;
+
+ /**
+ * Creates a new MinimalHTMLWriter.
+ *
+ * @param w Writer
+ * @param doc StyledDocument
+ *
+ */
+ public MinimalHTMLWriter(Writer w, StyledDocument doc) {
+ super(w, doc);
+ }
+
+ /**
+ * Creates a new MinimalHTMLWriter.
+ *
+ * @param w Writer
+ * @param doc StyledDocument
+ * @param pos The location in the document to fetch the
+ * content.
+ * @param len The amount to write out.
+ *
+ */
+ public MinimalHTMLWriter(Writer w, StyledDocument doc, int pos, int len) {
+ super(w, doc, pos, len);
+ }
+
+ /**
+ * Generates HTML output
+ * from a StyledDocument.
+ *
+ * @exception IOException on any I/O error
+ * @exception BadLocationException if pos represents an invalid
+ * location within the document.
+ *
+ */
+ public void write() throws IOException, BadLocationException {
+ styleNameMapping = new Hashtable();
+ writeStartTag("<html>");
+ writeHeader();
+ writeBody();
+ writeEndTag("</html>");
+ }
+
+
+ /**
+ * Writes out all the attributes for the
+ * following types:
+ * StyleConstants.ParagraphConstants,
+ * StyleConstants.CharacterConstants,
+ * StyleConstants.FontConstants,
+ * StyleConstants.ColorConstants.
+ * The attribute name and value are separated by a colon.
+ * Each pair is separated by a semicolon.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void writeAttributes(AttributeSet attr) throws IOException {
+ Enumeration attributeNames = attr.getAttributeNames();
+ while (attributeNames.hasMoreElements()) {
+ Object name = attributeNames.nextElement();
+ if ((name instanceof StyleConstants.ParagraphConstants) ||
+ (name instanceof StyleConstants.CharacterConstants) ||
+ (name instanceof StyleConstants.FontConstants) ||
+ (name instanceof StyleConstants.ColorConstants)) {
+ indent();
+ write(name.toString());
+ write(':');
+ write(css.styleConstantsValueToCSSValue
+ ((StyleConstants)name, attr.getAttribute(name)).
+ toString());
+ write(';');
+ write(NEWLINE);
+ }
+ }
+ }
+
+
+ /**
+ * Writes out text.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void text(Element elem) throws IOException, BadLocationException {
+ String contentStr = getText(elem);
+ if ((contentStr.length() > 0) &&
+ (contentStr.charAt(contentStr.length()-1) == NEWLINE)) {
+ contentStr = contentStr.substring(0, contentStr.length()-1);
+ }
+ if (contentStr.length() > 0) {
+ write(contentStr);
+ }
+ }
+
+ /**
+ * Writes out a start tag appropriately
+ * indented. Also increments the indent level.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void writeStartTag(String tag) throws IOException {
+ indent();
+ write(tag);
+ write(NEWLINE);
+ incrIndent();
+ }
+
+
+ /**
+ * Writes out an end tag appropriately
+ * indented. Also decrements the indent level.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void writeEndTag(String endTag) throws IOException {
+ decrIndent();
+ indent();
+ write(endTag);
+ write(NEWLINE);
+ }
+
+
+ /**
+ * Writes out the &lt;head&gt; and &lt;style&gt;
+ * tags, and then invokes writeStyles() to write
+ * out all the named styles as the content of the
+ * &lt;style&gt; tag. The content is surrounded by
+ * valid HTML comment markers to ensure that the
+ * document is viewable in applications/browsers
+ * that do not support the tag.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void writeHeader() throws IOException {
+ writeStartTag("<head>");
+ writeStartTag("<style>");
+ writeStartTag("<!--");
+ writeStyles();
+ writeEndTag("-->");
+ writeEndTag("</style>");
+ writeEndTag("</head>");
+ }
+
+
+
+ /**
+ * Writes out all the named styles as the
+ * content of the &lt;style&gt; tag.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void writeStyles() throws IOException {
+ /*
+ * Access to DefaultStyledDocument done to workaround
+ * a missing API in styled document to access the
+ * stylenames.
+ */
+ DefaultStyledDocument styledDoc = ((DefaultStyledDocument)getDocument());
+ Enumeration styleNames = styledDoc.getStyleNames();
+
+ while (styleNames.hasMoreElements()) {
+ Style s = styledDoc.getStyle((String)styleNames.nextElement());
+
+ /** PENDING: Once the name attribute is removed
+ from the list we check check for 0. **/
+ if (s.getAttributeCount() == 1 &&
+ s.isDefined(StyleConstants.NameAttribute)) {
+ continue;
+ }
+ indent();
+ write("p." + addStyleName(s.getName()));
+ write(" {\n");
+ incrIndent();
+ writeAttributes(s);
+ decrIndent();
+ indent();
+ write("}\n");
+ }
+ }
+
+
+ /**
+ * Iterates over the elements in the document
+ * and processes elements based on whether they are
+ * branch elements or leaf elements. This method specially handles
+ * leaf elements that are text.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void writeBody() throws IOException, BadLocationException {
+ ElementIterator it = getElementIterator();
+
+ /*
+ This will be a section element for a styled document.
+ We represent this element in HTML as the body tags.
+ Therefore we ignore it.
+ */
+ it.current();
+
+ Element next = null;
+
+ writeStartTag("<body>");
+
+ boolean inContent = false;
+
+ while((next = it.next()) != null) {
+ if (!inRange(next)) {
+ continue;
+ }
+ if (next instanceof AbstractDocument.BranchElement) {
+ if (inContent) {
+ writeEndParagraph();
+ inContent = false;
+ fontMask = 0;
+ }
+ writeStartParagraph(next);
+ } else if (isText(next)) {
+ writeContent(next, !inContent);
+ inContent = true;
+ } else {
+ writeLeaf(next);
+ inContent = true;
+ }
+ }
+ if (inContent) {
+ writeEndParagraph();
+ }
+ writeEndTag("</body>");
+ }
+
+
+ /**
+ * Emits an end tag for a &lt;p&gt;
+ * tag. Before writing out the tag, this method ensures
+ * that all other tags that have been opened are
+ * appropriately closed off.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void writeEndParagraph() throws IOException {
+ writeEndMask(fontMask);
+ if (inFontTag()) {
+ endSpanTag();
+ } else {
+ write(NEWLINE);
+ }
+ writeEndTag("</p>");
+ }
+
+
+ /**
+ * Emits the start tag for a paragraph. If
+ * the paragraph has a named style associated with it,
+ * then this method also generates a class attribute for the
+ * &lt;p&gt; tag and sets its value to be the name of the
+ * style.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void writeStartParagraph(Element elem) throws IOException {
+ AttributeSet attr = elem.getAttributes();
+ Object resolveAttr = attr.getAttribute(StyleConstants.ResolveAttribute);
+ if (resolveAttr instanceof StyleContext.NamedStyle) {
+ writeStartTag("<p class=" + mapStyleName(((StyleContext.NamedStyle)resolveAttr).getName()) + ">");
+ } else {
+ writeStartTag("<p>");
+ }
+ }
+
+
+ /**
+ * Responsible for writing out other non-text leaf
+ * elements.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void writeLeaf(Element elem) throws IOException {
+ indent();
+ if (elem.getName() == StyleConstants.IconElementName) {
+ writeImage(elem);
+ } else if (elem.getName() == StyleConstants.ComponentElementName) {
+ writeComponent(elem);
+ }
+ }
+
+
+ /**
+ * Responsible for handling Icon Elements;
+ * deliberately unimplemented. How to implement this method is
+ * an issue of policy. For example, if you're generating
+ * an &lt;img&gt; tag, how should you
+ * represent the src attribute (the location of the image)?
+ * In certain cases it could be a URL, in others it could
+ * be read from a stream.
+ *
+ * @param elem element of type StyleConstants.IconElementName
+ */
+ protected void writeImage(Element elem) throws IOException {
+ }
+
+
+ /**
+ * Responsible for handling Component Elements;
+ * deliberately unimplemented.
+ * How this method is implemented is a matter of policy.
+ */
+ protected void writeComponent(Element elem) throws IOException {
+ }
+
+
+ /**
+ * Returns true if the element is a text element.
+ *
+ */
+ protected boolean isText(Element elem) {
+ return (elem.getName() == AbstractDocument.ContentElementName);
+ }
+
+
+ /**
+ * Writes out the attribute set
+ * in an HTML-compliant manner.
+ *
+ * @exception IOException on any I/O error
+ * @exception BadLocationException if pos represents an invalid
+ * location within the document.
+ */
+ protected void writeContent(Element elem, boolean needsIndenting)
+ throws IOException, BadLocationException {
+
+ AttributeSet attr = elem.getAttributes();
+ writeNonHTMLAttributes(attr);
+ if (needsIndenting) {
+ indent();
+ }
+ writeHTMLTags(attr);
+ text(elem);
+ }
+
+
+ /**
+ * Generates
+ * bold &lt;b&gt;, italic &lt;i&gt;, and &lt;u&gt; tags for the
+ * text based on its attribute settings.
+ *
+ * @exception IOException on any I/O error
+ */
+
+ protected void writeHTMLTags(AttributeSet attr) throws IOException {
+
+ int oldMask = fontMask;
+ setFontMask(attr);
+
+ int endMask = 0;
+ int startMask = 0;
+ if ((oldMask & BOLD) != 0) {
+ if ((fontMask & BOLD) == 0) {
+ endMask |= BOLD;
+ }
+ } else if ((fontMask & BOLD) != 0) {
+ startMask |= BOLD;
+ }
+
+ if ((oldMask & ITALIC) != 0) {
+ if ((fontMask & ITALIC) == 0) {
+ endMask |= ITALIC;
+ }
+ } else if ((fontMask & ITALIC) != 0) {
+ startMask |= ITALIC;
+ }
+
+ if ((oldMask & UNDERLINE) != 0) {
+ if ((fontMask & UNDERLINE) == 0) {
+ endMask |= UNDERLINE;
+ }
+ } else if ((fontMask & UNDERLINE) != 0) {
+ startMask |= UNDERLINE;
+ }
+ writeEndMask(endMask);
+ writeStartMask(startMask);
+ }
+
+
+ /**
+ * Tweaks the appropriate bits of fontMask
+ * to reflect whether the text is to be displayed in
+ * bold, italic, and/or with an underline.
+ *
+ */
+ private void setFontMask(AttributeSet attr) {
+ if (StyleConstants.isBold(attr)) {
+ fontMask |= BOLD;
+ }
+
+ if (StyleConstants.isItalic(attr)) {
+ fontMask |= ITALIC;
+ }
+
+ if (StyleConstants.isUnderline(attr)) {
+ fontMask |= UNDERLINE;
+ }
+ }
+
+
+
+
+ /**
+ * Writes out start tags &lt;u&gt;, &lt;i&gt;, and &lt;b&gt; based on
+ * the mask settings.
+ *
+ * @exception IOException on any I/O error
+ */
+ private void writeStartMask(int mask) throws IOException {
+ if (mask != 0) {
+ if ((mask & UNDERLINE) != 0) {
+ write("<u>");
+ }
+ if ((mask & ITALIC) != 0) {
+ write("<i>");
+ }
+ if ((mask & BOLD) != 0) {
+ write("<b>");
+ }
+ }
+ }
+
+ /**
+ * Writes out end tags for &lt;u&gt;, &lt;i&gt;, and &lt;b&gt; based on
+ * the mask settings.
+ *
+ * @exception IOException on any I/O error
+ */
+ private void writeEndMask(int mask) throws IOException {
+ if (mask != 0) {
+ if ((mask & BOLD) != 0) {
+ write("</b>");
+ }
+ if ((mask & ITALIC) != 0) {
+ write("</i>");
+ }
+ if ((mask & UNDERLINE) != 0) {
+ write("</u>");
+ }
+ }
+ }
+
+
+ /**
+ * Writes out the remaining
+ * character-level attributes (attributes other than bold,
+ * italic, and underline) in an HTML-compliant way. Given that
+ * attributes such as font family and font size have no direct
+ * mapping to HTML tags, a &lt;span&gt; tag is generated and its
+ * style attribute is set to contain the list of remaining
+ * attributes just like inline styles.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void writeNonHTMLAttributes(AttributeSet attr) throws IOException {
+
+ String style = "";
+ String separator = "; ";
+
+ if (inFontTag() && fontAttributes.isEqual(attr)) {
+ return;
+ }
+
+ boolean first = true;
+ Color color = (Color)attr.getAttribute(StyleConstants.Foreground);
+ if (color != null) {
+ style += "color: " + css.styleConstantsValueToCSSValue
+ ((StyleConstants)StyleConstants.Foreground,
+ color);
+ first = false;
+ }
+ Integer size = (Integer)attr.getAttribute(StyleConstants.FontSize);
+ if (size != null) {
+ if (!first) {
+ style += separator;
+ }
+ style += "font-size: " + size.intValue() + "pt";
+ first = false;
+ }
+
+ String family = (String)attr.getAttribute(StyleConstants.FontFamily);
+ if (family != null) {
+ if (!first) {
+ style += separator;
+ }
+ style += "font-family: " + family;
+ first = false;
+ }
+
+ if (style.length() > 0) {
+ if (fontMask != 0) {
+ writeEndMask(fontMask);
+ fontMask = 0;
+ }
+ startSpanTag(style);
+ fontAttributes = attr;
+ }
+ else if (fontAttributes != null) {
+ writeEndMask(fontMask);
+ fontMask = 0;
+ endSpanTag();
+ }
+ }
+
+
+ /**
+ * Returns true if we are currently in a &lt;font&gt; tag.
+ */
+ protected boolean inFontTag() {
+ return (fontAttributes != null);
+ }
+
+ /**
+ * This is no longer used, instead &lt;span&gt; will be written out.
+ * <p>
+ * Writes out an end tag for the &lt;font&gt; tag.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void endFontTag() throws IOException {
+ write(NEWLINE);
+ writeEndTag("</font>");
+ fontAttributes = null;
+ }
+
+
+ /**
+ * This is no longer used, instead &lt;span&gt; will be written out.
+ * <p>
+ * Writes out a start tag for the &lt;font&gt; tag.
+ * Because font tags cannot be nested,
+ * this method closes out
+ * any enclosing font tag before writing out a
+ * new start tag.
+ *
+ * @exception IOException on any I/O error
+ */
+ protected void startFontTag(String style) throws IOException {
+ boolean callIndent = false;
+ if (inFontTag()) {
+ endFontTag();
+ callIndent = true;
+ }
+ writeStartTag("<font style=\"" + style + "\">");
+ if (callIndent) {
+ indent();
+ }
+ }
+
+ /**
+ * Writes out a start tag for the &lt;font&gt; tag.
+ * Because font tags cannot be nested,
+ * this method closes out
+ * any enclosing font tag before writing out a
+ * new start tag.
+ *
+ * @exception IOException on any I/O error
+ */
+ private void startSpanTag(String style) throws IOException {
+ boolean callIndent = false;
+ if (inFontTag()) {
+ endSpanTag();
+ callIndent = true;
+ }
+ writeStartTag("<span style=\"" + style + "\">");
+ if (callIndent) {
+ indent();
+ }
+ }
+
+ /**
+ * Writes out an end tag for the &lt;span&gt; tag.
+ *
+ * @exception IOException on any I/O error
+ */
+ private void endSpanTag() throws IOException {
+ write(NEWLINE);
+ writeEndTag("</span>");
+ fontAttributes = null;
+ }
+
+ /**
+ * Adds the style named <code>style</code> to the style mapping. This
+ * returns the name that should be used when outputting. CSS does not
+ * allow the full Unicode set to be used as a style name.
+ */
+ private String addStyleName(String style) {
+ if (styleNameMapping == null) {
+ return style;
+ }
+ StringBuffer sb = null;
+ for (int counter = style.length() - 1; counter >= 0; counter--) {
+ if (!isValidCharacter(style.charAt(counter))) {
+ if (sb == null) {
+ sb = new StringBuffer(style);
+ }
+ sb.setCharAt(counter, 'a');
+ }
+ }
+ String mappedName = (sb != null) ? sb.toString() : style;
+ while (styleNameMapping.get(mappedName) != null) {
+ mappedName = mappedName + 'x';
+ }
+ styleNameMapping.put(style, mappedName);
+ return mappedName;
+ }
+
+ /**
+ * Returns the mapped style name corresponding to <code>style</code>.
+ */
+ private String mapStyleName(String style) {
+ if (styleNameMapping == null) {
+ return style;
+ }
+ String retValue = (String)styleNameMapping.get(style);
+ return (retValue == null) ? style : retValue;
+ }
+
+ private boolean isValidCharacter(char character) {
+ return ((character >= 'a' && character <= 'z') ||
+ (character >= 'A' && character <= 'Z'));
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/MuxingAttributeSet.java b/src/share/classes/javax/swing/text/html/MuxingAttributeSet.java
new file mode 100644
index 000000000..4247e15f0
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/MuxingAttributeSet.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2001 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import javax.swing.text.*;
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * An implementation of <code>AttributeSet</code> that can multiplex
+ * across a set of <code>AttributeSet</code>s.
+ *
+ */
+class MuxingAttributeSet implements AttributeSet, Serializable {
+ /**
+ * Creates a <code>MuxingAttributeSet</code> with the passed in
+ * attributes.
+ */
+ public MuxingAttributeSet(AttributeSet[] attrs) {
+ this.attrs = attrs;
+ }
+
+ /**
+ * Creates an empty <code>MuxingAttributeSet</code>. This is intended for
+ * use by subclasses only, and it is also intended that subclasses will
+ * set the constituent <code>AttributeSet</code>s before invoking any
+ * of the <code>AttributeSet</code> methods.
+ */
+ protected MuxingAttributeSet() {
+ }
+
+ /**
+ * Directly sets the <code>AttributeSet</code>s that comprise this
+ * <code>MuxingAttributeSet</code>.
+ */
+ protected synchronized void setAttributes(AttributeSet[] attrs) {
+ this.attrs = attrs;
+ }
+
+ /**
+ * Returns the <code>AttributeSet</code>s multiplexing too. When the
+ * <code>AttributeSet</code>s need to be referenced, this should be called.
+ */
+ protected synchronized AttributeSet[] getAttributes() {
+ return attrs;
+ }
+
+ /**
+ * Inserts <code>as</code> at <code>index</code>. This assumes
+ * the value of <code>index</code> is between 0 and attrs.length,
+ * inclusive.
+ */
+ protected synchronized void insertAttributeSetAt(AttributeSet as,
+ int index) {
+ int numAttrs = attrs.length;
+ AttributeSet newAttrs[] = new AttributeSet[numAttrs + 1];
+ if (index < numAttrs) {
+ if (index > 0) {
+ System.arraycopy(attrs, 0, newAttrs, 0, index);
+ System.arraycopy(attrs, index, newAttrs, index + 1,
+ numAttrs - index);
+ }
+ else {
+ System.arraycopy(attrs, 0, newAttrs, 1, numAttrs);
+ }
+ }
+ else {
+ System.arraycopy(attrs, 0, newAttrs, 0, numAttrs);
+ }
+ newAttrs[index] = as;
+ attrs = newAttrs;
+ }
+
+ /**
+ * Removes the AttributeSet at <code>index</code>. This assumes
+ * the value of <code>index</code> is greater than or equal to 0,
+ * and less than attrs.length.
+ */
+ protected synchronized void removeAttributeSetAt(int index) {
+ int numAttrs = attrs.length;
+ AttributeSet[] newAttrs = new AttributeSet[numAttrs - 1];
+ if (numAttrs > 0) {
+ if (index == 0) {
+ // FIRST
+ System.arraycopy(attrs, 1, newAttrs, 0, numAttrs - 1);
+ }
+ else if (index < (numAttrs - 1)) {
+ // MIDDLE
+ System.arraycopy(attrs, 0, newAttrs, 0, index);
+ System.arraycopy(attrs, index + 1, newAttrs, index,
+ numAttrs - index - 1);
+ }
+ else {
+ // END
+ System.arraycopy(attrs, 0, newAttrs, 0, numAttrs - 1);
+ }
+ }
+ attrs = newAttrs;
+ }
+
+ // --- AttributeSet methods ----------------------------
+
+ /**
+ * Gets the number of attributes that are defined.
+ *
+ * @return the number of attributes
+ * @see AttributeSet#getAttributeCount
+ */
+ public int getAttributeCount() {
+ AttributeSet[] as = getAttributes();
+ int n = 0;
+ for (int i = 0; i < as.length; i++) {
+ n += as[i].getAttributeCount();
+ }
+ return n;
+ }
+
+ /**
+ * Checks whether a given attribute is defined.
+ * This will convert the key over to CSS if the
+ * key is a StyleConstants key that has a CSS
+ * mapping.
+ *
+ * @param key the attribute key
+ * @return true if the attribute is defined
+ * @see AttributeSet#isDefined
+ */
+ public boolean isDefined(Object key) {
+ AttributeSet[] as = getAttributes();
+ for (int i = 0; i < as.length; i++) {
+ if (as[i].isDefined(key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether two attribute sets are equal.
+ *
+ * @param attr the attribute set to check against
+ * @return true if the same
+ * @see AttributeSet#isEqual
+ */
+ public boolean isEqual(AttributeSet attr) {
+ return ((getAttributeCount() == attr.getAttributeCount()) &&
+ containsAttributes(attr));
+ }
+
+ /**
+ * Copies a set of attributes.
+ *
+ * @return the copy
+ * @see AttributeSet#copyAttributes
+ */
+ public AttributeSet copyAttributes() {
+ AttributeSet[] as = getAttributes();
+ MutableAttributeSet a = new SimpleAttributeSet();
+ int n = 0;
+ for (int i = as.length - 1; i >= 0; i--) {
+ a.addAttributes(as[i]);
+ }
+ return a;
+ }
+
+ /**
+ * Gets the value of an attribute. If the requested
+ * attribute is a StyleConstants attribute that has
+ * a CSS mapping, the request will be converted.
+ *
+ * @param key the attribute name
+ * @return the attribute value
+ * @see AttributeSet#getAttribute
+ */
+ public Object getAttribute(Object key) {
+ AttributeSet[] as = getAttributes();
+ int n = as.length;
+ for (int i = 0; i < n; i++) {
+ Object o = as[i].getAttribute(key);
+ if (o != null) {
+ return o;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the names of all attributes.
+ *
+ * @return the attribute names
+ * @see AttributeSet#getAttributeNames
+ */
+ public Enumeration getAttributeNames() {
+ return new MuxingAttributeNameEnumeration();
+ }
+
+ /**
+ * Checks whether a given attribute name/value is defined.
+ *
+ * @param name the attribute name
+ * @param value the attribute value
+ * @return true if the name/value is defined
+ * @see AttributeSet#containsAttribute
+ */
+ public boolean containsAttribute(Object name, Object value) {
+ return value.equals(getAttribute(name));
+ }
+
+ /**
+ * Checks whether the attribute set contains all of
+ * the given attributes.
+ *
+ * @param attrs the attributes to check
+ * @return true if the element contains all the attributes
+ * @see AttributeSet#containsAttributes
+ */
+ public boolean containsAttributes(AttributeSet attrs) {
+ boolean result = true;
+
+ Enumeration names = attrs.getAttributeNames();
+ while (result && names.hasMoreElements()) {
+ Object name = names.nextElement();
+ result = attrs.getAttribute(name).equals(getAttribute(name));
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns null, subclasses may wish to do something more
+ * intelligent with this.
+ */
+ public AttributeSet getResolveParent() {
+ return null;
+ }
+
+ /**
+ * The <code>AttributeSet</code>s that make up the resulting
+ * <code>AttributeSet</code>.
+ */
+ private AttributeSet[] attrs;
+
+
+ /**
+ * An Enumeration of the Attribute names in a MuxingAttributeSet.
+ * This may return the same name more than once.
+ */
+ private class MuxingAttributeNameEnumeration implements Enumeration {
+
+ MuxingAttributeNameEnumeration() {
+ updateEnum();
+ }
+
+ public boolean hasMoreElements() {
+ if (currentEnum == null) {
+ return false;
+ }
+ return currentEnum.hasMoreElements();
+ }
+
+ public Object nextElement() {
+ if (currentEnum == null) {
+ throw new NoSuchElementException("No more names");
+ }
+ Object retObject = currentEnum.nextElement();
+ if (!currentEnum.hasMoreElements()) {
+ updateEnum();
+ }
+ return retObject;
+ }
+
+ void updateEnum() {
+ AttributeSet[] as = getAttributes();
+ currentEnum = null;
+ while (currentEnum == null && attrIndex < as.length) {
+ currentEnum = as[attrIndex++].getAttributeNames();
+ if (!currentEnum.hasMoreElements()) {
+ currentEnum = null;
+ }
+ }
+ }
+
+
+ /** Index into attrs the current Enumeration came from. */
+ private int attrIndex;
+ /** Enumeration from attrs. */
+ private Enumeration currentEnum;
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/NoFramesView.java b/src/share/classes/javax/swing/text/html/NoFramesView.java
new file mode 100644
index 000000000..083a1a467
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/NoFramesView.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import javax.swing.text.*;
+import java.awt.*;
+
+/**
+ * This is the view associated with the html tag NOFRAMES.
+ * This view has been written to ignore the contents of the
+ * NOFRAMES tag. The contents of the tag will only be visible
+ * when the JTextComponent the view is contained in is editable.
+ *
+ * @author Sunita Mani
+ */
+class NoFramesView extends BlockView {
+
+ /**
+ * Creates a new view that represents an
+ * html box. This can be used for a number
+ * of elements. By default this view is not
+ * visible.
+ *
+ * @param elem the element to create a view for
+ * @param axis either View.X_AXIS or View.Y_AXIS
+ */
+ public NoFramesView(Element elem, int axis) {
+ super(elem, axis);
+ visible = false;
+ }
+
+
+ /**
+ * If this view is not visible, then it returns.
+ * Otherwise it invokes the superclass.
+ *
+ * @param g the rendering surface to use
+ * @param allocation the allocated region to render into
+ * @see #isVisible
+ * @see text.ParagraphView#paint
+ */
+ public void paint(Graphics g, Shape allocation) {
+ Container host = getContainer();
+ if (host != null &&
+ visible != ((JTextComponent)host).isEditable()) {
+ visible = ((JTextComponent)host).isEditable();
+ }
+
+ if (!isVisible()) {
+ return;
+ }
+ super.paint(g, allocation);
+ }
+
+
+ /**
+ * Determines if the JTextComponent that the view
+ * is contained in is editable. If so, then this
+ * view and all its child views are visible.
+ * Once this has been determined, the superclass
+ * is invoked to continue processing.
+ *
+ * @param p the parent View.
+ * @see BlockView#setParent
+ */
+ public void setParent(View p) {
+ if (p != null) {
+ Container host = p.getContainer();
+ if (host != null) {
+ visible = ((JTextComponent)host).isEditable();
+ }
+ }
+ super.setParent(p);
+ }
+
+ /**
+ * Returns a true/false value that represents
+ * whether the view is visible or not.
+ */
+ public boolean isVisible() {
+ return visible;
+ }
+
+
+ /**
+ * Do nothing if the view is not visible, otherwise
+ * invoke the superclass to perform layout.
+ */
+ protected void layout(int width, int height) {
+ if (!isVisible()) {
+ return;
+ }
+ super.layout(width, height);
+ }
+
+ /**
+ * Determines the preferred span for this view. Returns
+ * 0 if the view is not visible, otherwise it calls the
+ * superclass method to get the preferred span.
+ * axis.
+ *
+ * @param axis may be either View.X_AXIS or View.Y_AXIS
+ * @return the span the view would like to be rendered into;
+ * typically the view is told to render into the span
+ * that is returned, although there is no guarantee;
+ * the parent may choose to resize or break the view
+ * @see text.ParagraphView#getPreferredSpan
+ */
+ public float getPreferredSpan(int axis) {
+ if (!visible) {
+ return 0;
+ }
+ return super.getPreferredSpan(axis);
+ }
+
+ /**
+ * Determines the minimum span for this view along an
+ * axis. Returns 0 if the view is not visible, otherwise
+ * it calls the superclass method to get the minimum span.
+ *
+ * @param axis may be either <code>View.X_AXIS</code> or
+ * <code>View.Y_AXIS</code>
+ * @return the minimum span the view can be rendered into
+ * @see text.ParagraphView#getMinimumSpan
+ */
+ public float getMinimumSpan(int axis) {
+ if (!visible) {
+ return 0;
+ }
+ return super.getMinimumSpan(axis);
+ }
+
+ /**
+ * Determines the maximum span for this view along an
+ * axis. Returns 0 if the view is not visible, otherwise
+ * it calls the superclass method ot get the maximum span.
+ *
+ * @param axis may be either <code>View.X_AXIS</code> or
+ * <code>View.Y_AXIS</code>
+ * @return the maximum span the view can be rendered into
+ * @see text.ParagraphView#getMaximumSpan
+ */
+ public float getMaximumSpan(int axis) {
+ if (!visible) {
+ return 0;
+ }
+ return super.getMaximumSpan(axis);
+ }
+
+ boolean visible;
+}
diff --git a/src/share/classes/javax/swing/text/html/ObjectView.java b/src/share/classes/javax/swing/text/html/ObjectView.java
new file mode 100644
index 000000000..63aa66e7f
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/ObjectView.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 1998-2004 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.util.Enumeration;
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.text.*;
+import java.beans.*;
+import java.lang.reflect.*;
+
+/**
+ * Component decorator that implements the view interface
+ * for &lt;object&gt; elements.
+ * <p>
+ * This view will try to load the class specified by the
+ * <code>classid</code> attribute. If possible, the Classloader
+ * used to load the associated Document is used.
+ * This would typically be the same as the ClassLoader
+ * used to load the EditorKit. If the document's
+ * ClassLoader is null, <code>Class.forName</code> is used.
+ * <p>
+ * If the class can successfully be loaded, an attempt will
+ * be made to create an instance of it by calling
+ * <code>Class.newInstance</code>. An attempt will be made
+ * to narrow the instance to type <code>java.awt.Component</code>
+ * to display the object.
+ * <p>
+ * This view can also manage a set of parameters with limitations.
+ * The parameters to the &lt;object&gt; element are expected to
+ * be present on the associated elements attribute set as simple
+ * strings. Each bean property will be queried as a key on
+ * the AttributeSet, with the expectation that a non-null value
+ * (of type String) will be present if there was a parameter
+ * specification for the property. Reflection is used to
+ * set the parameter. Currently, this is limited to a very
+ * simple single parameter of type String.
+ * <p>
+ * A simple example HTML invocation is:
+ * <pre>
+ * &lt;object classid="javax.swing.JLabel"&gt;
+ * &lt;param name="text" value="sample text"&gt;
+ * &lt;/object&gt;
+ * </pre>
+ *
+ * @author Timothy Prinzing
+ */
+public class ObjectView extends ComponentView {
+
+ /**
+ * Creates a new ObjectView object.
+ *
+ * @param elem the element to decorate
+ */
+ public ObjectView(Element elem) {
+ super(elem);
+ }
+
+ /**
+ * Create the component. The classid is used
+ * as a specification of the classname, which
+ * we try to load.
+ */
+ protected Component createComponent() {
+ AttributeSet attr = getElement().getAttributes();
+ String classname = (String) attr.getAttribute(HTML.Attribute.CLASSID);
+ try {
+ Class c = Class.forName(classname, true,Thread.currentThread().
+ getContextClassLoader());
+ Object o = c.newInstance();
+ if (o instanceof Component) {
+ Component comp = (Component) o;
+ setParameters(comp, attr);
+ return comp;
+ }
+ } catch (Throwable e) {
+ // couldn't create a component... fall through to the
+ // couldn't load representation.
+ }
+
+ return getUnloadableRepresentation();
+ }
+
+ /**
+ * Fetch a component that can be used to represent the
+ * object if it can't be created.
+ */
+ Component getUnloadableRepresentation() {
+ // PENDING(prinz) get some artwork and return something
+ // interesting here.
+ Component comp = new JLabel("??");
+ comp.setForeground(Color.red);
+ return comp;
+ }
+
+ /**
+ * Get a Class object to use for loading the
+ * classid. If possible, the Classloader
+ * used to load the associated Document is used.
+ * This would typically be the same as the ClassLoader
+ * used to load the EditorKit. If the documents
+ * ClassLoader is null,
+ * <code>Class.forName</code> is used.
+ */
+ private Class getClass(String classname) throws ClassNotFoundException {
+ Class klass;
+
+ Class docClass = getDocument().getClass();
+ ClassLoader loader = docClass.getClassLoader();
+ if (loader != null) {
+ klass = loader.loadClass(classname);
+ } else {
+ klass = Class.forName(classname);
+ }
+ return klass;
+ }
+
+ /**
+ * Initialize this component according the KEY/VALUEs passed in
+ * via the &lt;param&gt; elements in the corresponding
+ * &lt;object&gt; element.
+ */
+ private void setParameters(Component comp, AttributeSet attr) {
+ Class k = comp.getClass();
+ BeanInfo bi;
+ try {
+ bi = Introspector.getBeanInfo(k);
+ } catch (IntrospectionException ex) {
+ System.err.println("introspector failed, ex: "+ex);
+ return; // quit for now
+ }
+ PropertyDescriptor props[] = bi.getPropertyDescriptors();
+ for (int i=0; i < props.length; i++) {
+ // System.err.println("checking on props[i]: "+props[i].getName());
+ Object v = attr.getAttribute(props[i].getName());
+ if (v instanceof String) {
+ // found a property parameter
+ String value = (String) v;
+ Method writer = props[i].getWriteMethod();
+ if (writer == null) {
+ // read-only property. ignore
+ return; // for now
+ }
+ Class[] params = writer.getParameterTypes();
+ if (params.length != 1) {
+ // zero or more than one argument, ignore
+ return; // for now
+ }
+ Object [] args = { value };
+ try {
+ writer.invoke(comp, args);
+ } catch (Exception ex) {
+ System.err.println("Invocation failed");
+ // invocation code
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/share/classes/javax/swing/text/html/Option.java b/src/share/classes/javax/swing/text/html/Option.java
new file mode 100644
index 000000000..1cafbdff5
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/Option.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.io.Serializable;
+import javax.swing.text.*;
+
+/**
+ * Value for the ListModel used to represent
+ * &lt;option&gt; elements. This is the object
+ * installed as items of the DefaultComboBoxModel
+ * used to represent the &lt;select&gt; element.
+ * <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<sup><font size="-2">TM</font></sup>
+ * has been added to the <code>java.beans</code> package.
+ * Please see {@link java.beans.XMLEncoder}.
+ *
+ * @author Timothy Prinzing
+ */
+public class Option implements Serializable {
+
+ /**
+ * Creates a new Option object.
+ *
+ * @param attr the attributes associated with the
+ * option element. The attributes are copied to
+ * ensure they won't change.
+ */
+ public Option(AttributeSet attr) {
+ this.attr = attr.copyAttributes();
+ selected = (attr.getAttribute(HTML.Attribute.SELECTED) != null);
+ }
+
+ /**
+ * Sets the label to be used for the option.
+ */
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ /**
+ * Fetch the label associated with the option.
+ */
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * Fetch the attributes associated with this option.
+ */
+ public AttributeSet getAttributes() {
+ return attr;
+ }
+
+ /**
+ * String representation is the label.
+ */
+ public String toString() {
+ return label;
+ }
+
+ /**
+ * Sets the selected state.
+ */
+ protected void setSelection(boolean state) {
+ selected = state;
+ }
+
+ /**
+ * Fetches the selection state associated with this option.
+ */
+ public boolean isSelected() {
+ return selected;
+ }
+
+ /**
+ * Convenience method to return the string associated
+ * with the <code>value</code> attribute. If the
+ * value has not been specified, the label will be
+ * returned.
+ */
+ public String getValue() {
+ String value = (String) attr.getAttribute(HTML.Attribute.VALUE);
+ if (value == null) {
+ value = label;
+ }
+ return value;
+ }
+
+ private boolean selected;
+ private String label;
+ private AttributeSet attr;
+}
diff --git a/src/share/classes/javax/swing/text/html/OptionComboBoxModel.java b/src/share/classes/javax/swing/text/html/OptionComboBoxModel.java
new file mode 100644
index 000000000..088a9cd04
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/OptionComboBoxModel.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import java.io.Serializable;
+
+
+/**
+ * OptionComboBoxModel extends the capabilities of the DefaultComboBoxModel,
+ * to store the Option that is initially marked as selected.
+ * This is stored, in order to enable an accurate reset of the
+ * ComboBox that represents the SELECT form element when the
+ * user requests a clear/reset. Given that a combobox only allow
+ * for one item to be selected, the last OPTION that has the
+ * attribute set wins.
+ *
+ @author Sunita Mani
+ */
+
+class OptionComboBoxModel extends DefaultComboBoxModel implements Serializable {
+
+ private Option selectedOption = null;
+
+ /**
+ * Stores the Option that has been marked its
+ * selected attribute set.
+ */
+ public void setInitialSelection(Option option) {
+ selectedOption = option;
+ }
+
+ /**
+ * Fetches the Option item that represents that was
+ * initially set to a selected state.
+ */
+ public Option getInitialSelection() {
+ return selectedOption;
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/OptionListModel.java b/src/share/classes/javax/swing/text/html/OptionListModel.java
new file mode 100644
index 000000000..da8ea51cc
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/OptionListModel.java
@@ -0,0 +1,571 @@
+/*
+ * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import java.util.EventListener;
+import java.util.BitSet;
+import java.io.Serializable;
+
+
+/**
+ * This class extends DefaultListModel, and also implements
+ * the ListSelectionModel interface, allowing for it to store state
+ * relevant to a SELECT form element which is implemented as a List.
+ * If SELECT has a size attribute whose value is greater than 1,
+ * or if allows multiple selection then a JList is used to
+ * represent it and the OptionListModel is used as its model.
+ * It also stores the initial state of the JList, to ensure an
+ * accurate reset, if the user requests a reset of the form.
+ *
+ @author Sunita Mani
+ */
+
+class OptionListModel extends DefaultListModel implements ListSelectionModel, Serializable {
+
+
+ private static final int MIN = -1;
+ private static final int MAX = Integer.MAX_VALUE;
+ private int selectionMode = SINGLE_SELECTION;
+ private int minIndex = MAX;
+ private int maxIndex = MIN;
+ private int anchorIndex = -1;
+ private int leadIndex = -1;
+ private int firstChangedIndex = MAX;
+ private int lastChangedIndex = MIN;
+ private boolean isAdjusting = false;
+ private BitSet value = new BitSet(32);
+ private BitSet initialValue = new BitSet(32);
+ protected EventListenerList listenerList = new EventListenerList();
+
+ protected boolean leadAnchorNotificationEnabled = true;
+
+ public int getMinSelectionIndex() { return isSelectionEmpty() ? -1 : minIndex; }
+
+ public int getMaxSelectionIndex() { return maxIndex; }
+
+ public boolean getValueIsAdjusting() { return isAdjusting; }
+
+ public int getSelectionMode() { return selectionMode; }
+
+ public void setSelectionMode(int selectionMode) {
+ switch (selectionMode) {
+ case SINGLE_SELECTION:
+ case SINGLE_INTERVAL_SELECTION:
+ case MULTIPLE_INTERVAL_SELECTION:
+ this.selectionMode = selectionMode;
+ break;
+ default:
+ throw new IllegalArgumentException("invalid selectionMode");
+ }
+ }
+
+ public boolean isSelectedIndex(int index) {
+ return ((index < minIndex) || (index > maxIndex)) ? false : value.get(index);
+ }
+
+ public boolean isSelectionEmpty() {
+ return (minIndex > maxIndex);
+ }
+
+ public void addListSelectionListener(ListSelectionListener l) {
+ listenerList.add(ListSelectionListener.class, l);
+ }
+
+ public void removeListSelectionListener(ListSelectionListener l) {
+ listenerList.remove(ListSelectionListener.class, l);
+ }
+
+ /**
+ * Returns an array of all the <code>ListSelectionListener</code>s added
+ * to this OptionListModel with addListSelectionListener().
+ *
+ * @return all of the <code>ListSelectionListener</code>s added or an empty
+ * array if no listeners have been added
+ * @since 1.4
+ */
+ public ListSelectionListener[] getListSelectionListeners() {
+ return (ListSelectionListener[])listenerList.getListeners(
+ ListSelectionListener.class);
+ }
+
+ /**
+ * Notify listeners that we are beginning or ending a
+ * series of value changes
+ */
+ protected void fireValueChanged(boolean isAdjusting) {
+ fireValueChanged(getMinSelectionIndex(), getMaxSelectionIndex(), isAdjusting);
+ }
+
+
+ /**
+ * Notify ListSelectionListeners that the value of the selection,
+ * in the closed interval firstIndex,lastIndex, has changed.
+ */
+ protected void fireValueChanged(int firstIndex, int lastIndex) {
+ fireValueChanged(firstIndex, lastIndex, getValueIsAdjusting());
+ }
+
+ /**
+ * @param firstIndex The first index in the interval.
+ * @param index1 The last index in the interval.
+ * @param isAdjusting True if this is the final change in a series of them.
+ * @see EventListenerList
+ */
+ protected void fireValueChanged(int firstIndex, int lastIndex, boolean isAdjusting)
+ {
+ Object[] listeners = listenerList.getListenerList();
+ ListSelectionEvent e = null;
+
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ListSelectionListener.class) {
+ if (e == null) {
+ e = new ListSelectionEvent(this, firstIndex, lastIndex, isAdjusting);
+ }
+ ((ListSelectionListener)listeners[i+1]).valueChanged(e);
+ }
+ }
+ }
+
+ private void fireValueChanged() {
+ if (lastChangedIndex == MIN) {
+ return;
+ }
+ /* Change the values before sending the event to the
+ * listeners in case the event causes a listener to make
+ * another change to the selection.
+ */
+ int oldFirstChangedIndex = firstChangedIndex;
+ int oldLastChangedIndex = lastChangedIndex;
+ firstChangedIndex = MAX;
+ lastChangedIndex = MIN;
+ fireValueChanged(oldFirstChangedIndex, oldLastChangedIndex);
+ }
+
+
+ // Update first and last change indices
+ private void markAsDirty(int r) {
+ firstChangedIndex = Math.min(firstChangedIndex, r);
+ lastChangedIndex = Math.max(lastChangedIndex, r);
+ }
+
+ // Set the state at this index and update all relevant state.
+ private void set(int r) {
+ if (value.get(r)) {
+ return;
+ }
+ value.set(r);
+ Option option = (Option)get(r);
+ option.setSelection(true);
+ markAsDirty(r);
+
+ // Update minimum and maximum indices
+ minIndex = Math.min(minIndex, r);
+ maxIndex = Math.max(maxIndex, r);
+ }
+
+ // Clear the state at this index and update all relevant state.
+ private void clear(int r) {
+ if (!value.get(r)) {
+ return;
+ }
+ value.clear(r);
+ Option option = (Option)get(r);
+ option.setSelection(false);
+ markAsDirty(r);
+
+ // Update minimum and maximum indices
+ /*
+ If (r > minIndex) the minimum has not changed.
+ The case (r < minIndex) is not possible because r'th value was set.
+ We only need to check for the case when lowest entry has been cleared,
+ and in this case we need to search for the first value set above it.
+ */
+ if (r == minIndex) {
+ for(minIndex = minIndex + 1; minIndex <= maxIndex; minIndex++) {
+ if (value.get(minIndex)) {
+ break;
+ }
+ }
+ }
+ /*
+ If (r < maxIndex) the maximum has not changed.
+ The case (r > maxIndex) is not possible because r'th value was set.
+ We only need to check for the case when highest entry has been cleared,
+ and in this case we need to search for the first value set below it.
+ */
+ if (r == maxIndex) {
+ for(maxIndex = maxIndex - 1; minIndex <= maxIndex; maxIndex--) {
+ if (value.get(maxIndex)) {
+ break;
+ }
+ }
+ }
+ /* Performance note: This method is called from inside a loop in
+ changeSelection() but we will only iterate in the loops
+ above on the basis of one iteration per deselected cell - in total.
+ Ie. the next time this method is called the work of the previous
+ deselection will not be repeated.
+
+ We also don't need to worry about the case when the min and max
+ values are in their unassigned states. This cannot happen because
+ this method's initial check ensures that the selection was not empty
+ and therefore that the minIndex and maxIndex had 'real' values.
+
+ If we have cleared the whole selection, set the minIndex and maxIndex
+ to their cannonical values so that the next set command always works
+ just by using Math.min and Math.max.
+ */
+ if (isSelectionEmpty()) {
+ minIndex = MAX;
+ maxIndex = MIN;
+ }
+ }
+
+ /**
+ * Sets the value of the leadAnchorNotificationEnabled flag.
+ * @see #isLeadAnchorNotificationEnabled()
+ */
+ public void setLeadAnchorNotificationEnabled(boolean flag) {
+ leadAnchorNotificationEnabled = flag;
+ }
+
+ /**
+ * Returns the value of the leadAnchorNotificationEnabled flag.
+ * When leadAnchorNotificationEnabled is true the model
+ * generates notification events with bounds that cover all the changes to
+ * the selection plus the changes to the lead and anchor indices.
+ * Setting the flag to false causes a norrowing of the event's bounds to
+ * include only the elements that have been selected or deselected since
+ * the last change. Either way, the model continues to maintain the lead
+ * and anchor variables internally. The default is true.
+ * @return the value of the leadAnchorNotificationEnabled flag
+ * @see #setLeadAnchorNotificationEnabled(boolean)
+ */
+ public boolean isLeadAnchorNotificationEnabled() {
+ return leadAnchorNotificationEnabled;
+ }
+
+ private void updateLeadAnchorIndices(int anchorIndex, int leadIndex) {
+ if (leadAnchorNotificationEnabled) {
+ if (this.anchorIndex != anchorIndex) {
+ if (this.anchorIndex != -1) { // The unassigned state.
+ markAsDirty(this.anchorIndex);
+ }
+ markAsDirty(anchorIndex);
+ }
+
+ if (this.leadIndex != leadIndex) {
+ if (this.leadIndex != -1) { // The unassigned state.
+ markAsDirty(this.leadIndex);
+ }
+ markAsDirty(leadIndex);
+ }
+ }
+ this.anchorIndex = anchorIndex;
+ this.leadIndex = leadIndex;
+ }
+
+ private boolean contains(int a, int b, int i) {
+ return (i >= a) && (i <= b);
+ }
+
+ private void changeSelection(int clearMin, int clearMax,
+ int setMin, int setMax, boolean clearFirst) {
+ for(int i = Math.min(setMin, clearMin); i <= Math.max(setMax, clearMax); i++) {
+
+ boolean shouldClear = contains(clearMin, clearMax, i);
+ boolean shouldSet = contains(setMin, setMax, i);
+
+ if (shouldSet && shouldClear) {
+ if (clearFirst) {
+ shouldClear = false;
+ }
+ else {
+ shouldSet = false;
+ }
+ }
+
+ if (shouldSet) {
+ set(i);
+ }
+ if (shouldClear) {
+ clear(i);
+ }
+ }
+ fireValueChanged();
+ }
+
+ /* Change the selection with the effect of first clearing the values
+ * in the inclusive range [clearMin, clearMax] then setting the values
+ * in the inclusive range [setMin, setMax]. Do this in one pass so
+ * that no values are cleared if they would later be set.
+ */
+ private void changeSelection(int clearMin, int clearMax, int setMin, int setMax) {
+ changeSelection(clearMin, clearMax, setMin, setMax, true);
+ }
+
+ public void clearSelection() {
+ removeSelectionInterval(minIndex, maxIndex);
+ }
+
+ public void setSelectionInterval(int index0, int index1) {
+ if (index0 == -1 || index1 == -1) {
+ return;
+ }
+
+ if (getSelectionMode() == SINGLE_SELECTION) {
+ index0 = index1;
+ }
+
+ updateLeadAnchorIndices(index0, index1);
+
+ int clearMin = minIndex;
+ int clearMax = maxIndex;
+ int setMin = Math.min(index0, index1);
+ int setMax = Math.max(index0, index1);
+ changeSelection(clearMin, clearMax, setMin, setMax);
+ }
+
+ public void addSelectionInterval(int index0, int index1)
+ {
+ if (index0 == -1 || index1 == -1) {
+ return;
+ }
+
+ if (getSelectionMode() != MULTIPLE_INTERVAL_SELECTION) {
+ setSelectionInterval(index0, index1);
+ return;
+ }
+
+ updateLeadAnchorIndices(index0, index1);
+
+ int clearMin = MAX;
+ int clearMax = MIN;
+ int setMin = Math.min(index0, index1);
+ int setMax = Math.max(index0, index1);
+ changeSelection(clearMin, clearMax, setMin, setMax);
+ }
+
+
+ public void removeSelectionInterval(int index0, int index1)
+ {
+ if (index0 == -1 || index1 == -1) {
+ return;
+ }
+
+ updateLeadAnchorIndices(index0, index1);
+
+ int clearMin = Math.min(index0, index1);
+ int clearMax = Math.max(index0, index1);
+ int setMin = MAX;
+ int setMax = MIN;
+ changeSelection(clearMin, clearMax, setMin, setMax);
+ }
+
+ private void setState(int index, boolean state) {
+ if (state) {
+ set(index);
+ }
+ else {
+ clear(index);
+ }
+ }
+
+ /**
+ * Insert length indices beginning before/after index. If the value
+ * at index is itself selected, set all of the newly inserted
+ * items, otherwise leave them unselected. This method is typically
+ * called to sync the selection model with a corresponding change
+ * in the data model.
+ */
+ public void insertIndexInterval(int index, int length, boolean before)
+ {
+ /* The first new index will appear at insMinIndex and the last
+ * one will appear at insMaxIndex
+ */
+ int insMinIndex = (before) ? index : index + 1;
+ int insMaxIndex = (insMinIndex + length) - 1;
+
+ /* Right shift the entire bitset by length, beginning with
+ * index-1 if before is true, index+1 if it's false (i.e. with
+ * insMinIndex).
+ */
+ for(int i = maxIndex; i >= insMinIndex; i--) {
+ setState(i + length, value.get(i));
+ }
+
+ /* Initialize the newly inserted indices.
+ */
+ boolean setInsertedValues = value.get(index);
+ for(int i = insMinIndex; i <= insMaxIndex; i++) {
+ setState(i, setInsertedValues);
+ }
+ }
+
+
+ /**
+ * Remove the indices in the interval index0,index1 (inclusive) from
+ * the selection model. This is typically called to sync the selection
+ * model width a corresponding change in the data model. Note
+ * that (as always) index0 can be greater than index1.
+ */
+ public void removeIndexInterval(int index0, int index1)
+ {
+ int rmMinIndex = Math.min(index0, index1);
+ int rmMaxIndex = Math.max(index0, index1);
+ int gapLength = (rmMaxIndex - rmMinIndex) + 1;
+
+ /* Shift the entire bitset to the left to close the index0, index1
+ * gap.
+ */
+ for(int i = rmMinIndex; i <= maxIndex; i++) {
+ setState(i, value.get(i + gapLength));
+ }
+ }
+
+
+ public void setValueIsAdjusting(boolean isAdjusting) {
+ if (isAdjusting != this.isAdjusting) {
+ this.isAdjusting = isAdjusting;
+ this.fireValueChanged(isAdjusting);
+ }
+ }
+
+
+ public String toString() {
+ String s = ((getValueIsAdjusting()) ? "~" : "=") + value.toString();
+ return getClass().getName() + " " + Integer.toString(hashCode()) + " " + s;
+ }
+
+ /**
+ * Returns a clone of the receiver with the same selection.
+ * <code>listenerLists</code> are not duplicated.
+ *
+ * @return a clone of the receiver
+ * @exception CloneNotSupportedException if the receiver does not
+ * both (a) implement the <code>Cloneable</code> interface
+ * and (b) define a <code>clone</code> method
+ */
+ public Object clone() throws CloneNotSupportedException {
+ OptionListModel clone = (OptionListModel)super.clone();
+ clone.value = (BitSet)value.clone();
+ clone.listenerList = new EventListenerList();
+ return clone;
+ }
+
+ public int getAnchorSelectionIndex() {
+ return anchorIndex;
+ }
+
+ public int getLeadSelectionIndex() {
+ return leadIndex;
+ }
+
+ /**
+ * Set the anchor selection index, leaving all selection values unchanged.
+ *
+ * @see #getAnchorSelectionIndex
+ * @see #setLeadSelectionIndex
+ */
+ public void setAnchorSelectionIndex(int anchorIndex) {
+ this.anchorIndex = anchorIndex;
+ }
+
+ /**
+ * Set the lead selection index, ensuring that values between the
+ * anchor and the new lead are either all selected or all deselected.
+ * If the value at the anchor index is selected, first clear all the
+ * values in the range [anchor, oldLeadIndex], then select all the values
+ * values in the range [anchor, newLeadIndex], where oldLeadIndex is the old
+ * leadIndex and newLeadIndex is the new one.
+ * <p>
+ * If the value at the anchor index is not selected, do the same thing in reverse,
+ * selecting values in the old range and deslecting values in the new one.
+ * <p>
+ * Generate a single event for this change and notify all listeners.
+ * For the purposes of generating minimal bounds in this event, do the
+ * operation in a single pass; that way the first and last index inside the
+ * ListSelectionEvent that is broadcast will refer to cells that actually
+ * changed value because of this method. If, instead, this operation were
+ * done in two steps the effect on the selection state would be the same
+ * but two events would be generated and the bounds around the changed values
+ * would be wider, including cells that had been first cleared and only
+ * to later be set.
+ * <p>
+ * This method can be used in the mouseDragged() method of a UI class
+ * to extend a selection.
+ *
+ * @see #getLeadSelectionIndex
+ * @see #setAnchorSelectionIndex
+ */
+ public void setLeadSelectionIndex(int leadIndex) {
+ int anchorIndex = this.anchorIndex;
+ if (getSelectionMode() == SINGLE_SELECTION) {
+ anchorIndex = leadIndex;
+ }
+
+ int oldMin = Math.min(this.anchorIndex, this.leadIndex);;
+ int oldMax = Math.max(this.anchorIndex, this.leadIndex);;
+ int newMin = Math.min(anchorIndex, leadIndex);
+ int newMax = Math.max(anchorIndex, leadIndex);
+ if (value.get(this.anchorIndex)) {
+ changeSelection(oldMin, oldMax, newMin, newMax);
+ }
+ else {
+ changeSelection(newMin, newMax, oldMin, oldMax, false);
+ }
+ this.anchorIndex = anchorIndex;
+ this.leadIndex = leadIndex;
+ }
+
+
+ /**
+ * This method is responsible for storing the state
+ * of the initial selection. If the selectionMode
+ * is the default, i.e allowing only for SINGLE_SELECTION,
+ * then the very last OPTION that has the selected
+ * attribute set wins.
+ */
+ public void setInitialSelection(int i) {
+ if (initialValue.get(i)) {
+ return;
+ }
+ if (selectionMode == SINGLE_SELECTION) {
+ // reset to empty
+ initialValue.and(new BitSet());
+ }
+ initialValue.set(i);
+ }
+
+ /**
+ * Fetches the BitSet that represents the initial
+ * set of selected items in the list.
+ */
+ public BitSet getInitialSelection() {
+ return initialValue;
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/ParagraphView.java b/src/share/classes/javax/swing/text/html/ParagraphView.java
new file mode 100644
index 000000000..6d6006b0a
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/ParagraphView.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 1998-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.*;
+import javax.swing.SizeRequirements;
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.View;
+import javax.swing.text.ViewFactory;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.JTextComponent;
+
+/**
+ * Displays the a paragraph, and uses css attributes for its
+ * configuration.
+ *
+ * @author Timothy Prinzing
+ */
+
+public class ParagraphView extends javax.swing.text.ParagraphView {
+
+ /**
+ * Constructs a ParagraphView for the given element.
+ *
+ * @param elem the element that this view is responsible for
+ */
+ public ParagraphView(Element elem) {
+ super(elem);
+ }
+
+ /**
+ * Establishes the parent view for this view. This is
+ * guaranteed to be called before any other methods if the
+ * parent view is functioning properly.
+ * <p>
+ * This is implemented
+ * to forward to the superclass as well as call the
+ * <a href="#setPropertiesFromAttributes">setPropertiesFromAttributes</a>
+ * method to set the paragraph properties from the css
+ * attributes. The call is made at this time to ensure
+ * the ability to resolve upward through the parents
+ * view attributes.
+ *
+ * @param parent the new parent, or null if the view is
+ * being removed from a parent it was previously added
+ * to
+ */
+ public void setParent(View parent) {
+ super.setParent(parent);
+ if (parent != null) {
+ setPropertiesFromAttributes();
+ }
+ }
+
+ /**
+ * Fetches the attributes to use when rendering. This is
+ * implemented to multiplex the attributes specified in the
+ * model with a StyleSheet.
+ */
+ public AttributeSet getAttributes() {
+ if (attr == null) {
+ StyleSheet sheet = getStyleSheet();
+ attr = sheet.getViewAttributes(this);
+ }
+ return attr;
+ }
+
+ /**
+ * Sets up the paragraph from css attributes instead of
+ * the values found in StyleConstants (i.e. which are used
+ * by the superclass). Since
+ */
+ protected void setPropertiesFromAttributes() {
+ StyleSheet sheet = getStyleSheet();
+ attr = sheet.getViewAttributes(this);
+ painter = sheet.getBoxPainter(attr);
+ if (attr != null) {
+ super.setPropertiesFromAttributes();
+ setInsets((short) painter.getInset(TOP, this),
+ (short) painter.getInset(LEFT, this),
+ (short) painter.getInset(BOTTOM, this),
+ (short) painter.getInset(RIGHT, this));
+ Object o = attr.getAttribute(CSS.Attribute.TEXT_ALIGN);
+ if (o != null) {
+ // set horizontal alignment
+ String ta = o.toString();
+ if (ta.equals("left")) {
+ setJustification(StyleConstants.ALIGN_LEFT);
+ } else if (ta.equals("center")) {
+ setJustification(StyleConstants.ALIGN_CENTER);
+ } else if (ta.equals("right")) {
+ setJustification(StyleConstants.ALIGN_RIGHT);
+ } else if (ta.equals("justify")) {
+ setJustification(StyleConstants.ALIGN_JUSTIFIED);
+ }
+ }
+ // Get the width/height
+ cssWidth = (CSS.LengthValue)attr.getAttribute(
+ CSS.Attribute.WIDTH);
+ cssHeight = (CSS.LengthValue)attr.getAttribute(
+ CSS.Attribute.HEIGHT);
+ }
+ }
+
+ protected StyleSheet getStyleSheet() {
+ HTMLDocument doc = (HTMLDocument) getDocument();
+ return doc.getStyleSheet();
+ }
+
+
+ /**
+ * Calculate the needs for the paragraph along the minor axis.
+ *
+ * <p>If size requirements are explicitly specified for the paragraph,
+ * use that requirements. Otherwise, use the requirements of the
+ * superclass {@link javax.swing.text.ParagraphView}.</p>
+ *
+ * <p>If the {@code axis} parameter is neither {@code View.X_AXIS} nor
+ * {@code View.Y_AXIS}, {@link IllegalArgumentException} is thrown. If the
+ * {@code r} parameter is {@code null,} a new {@code SizeRequirements}
+ * object is created, otherwise the supplied {@code SizeRequirements}
+ * object is returned.</p>
+ *
+ * @param axis the minor axis
+ * @param r the input {@code SizeRequirements} object
+ * @return the new or adjusted {@code SizeRequirements} object
+ * @throw IllegalArgumentException if the {@code axis} parameter is invalid
+ */
+ protected SizeRequirements calculateMinorAxisRequirements(
+ int axis, SizeRequirements r) {
+ r = super.calculateMinorAxisRequirements(axis, r);
+
+ if (BlockView.spanSetFromAttributes(axis, r, cssWidth, cssHeight)) {
+ // Offset by the margins so that pref/min/max return the
+ // right value.
+ int margin = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
+ getTopInset() + getBottomInset();
+ r.minimum -= margin;
+ r.preferred -= margin;
+ r.maximum -= margin;
+ }
+ return r;
+ }
+
+
+ /**
+ * Indicates whether or not this view should be
+ * displayed. If none of the children wish to be
+ * displayed and the only visible child is the
+ * break that ends the paragraph, the paragraph
+ * will not be considered visible. Otherwise,
+ * it will be considered visible and return true.
+ *
+ * @return true if the paragraph should be displayed
+ */
+ public boolean isVisible() {
+
+ int n = getLayoutViewCount() - 1;
+ for (int i = 0; i < n; i++) {
+ View v = getLayoutView(i);
+ if (v.isVisible()) {
+ return true;
+ }
+ }
+ if (n > 0) {
+ View v = getLayoutView(n);
+ if ((v.getEndOffset() - v.getStartOffset()) == 1) {
+ return false;
+ }
+ }
+ // If it's the last paragraph and not editable, it shouldn't
+ // be visible.
+ if (getStartOffset() == getDocument().getLength()) {
+ boolean editable = false;
+ Component c = getContainer();
+ if (c instanceof JTextComponent) {
+ editable = ((JTextComponent)c).isEditable();
+ }
+ if (!editable) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Renders using the given rendering surface and area on that
+ * surface. This is implemented to delgate to the superclass
+ * after stashing the base coordinate for tab calculations.
+ *
+ * @param g the rendering surface to use
+ * @param a the allocated region to render into
+ * @see View#paint
+ */
+ public void paint(Graphics g, Shape a) {
+ if (a == null) {
+ return;
+ }
+
+ Rectangle r;
+ if (a instanceof Rectangle) {
+ r = (Rectangle) a;
+ } else {
+ r = a.getBounds();
+ }
+ painter.paint(g, r.x, r.y, r.width, r.height, this);
+ super.paint(g, a);
+ }
+
+ /**
+ * Determines the preferred span for this view. Returns
+ * 0 if the view is not visible, otherwise it calls the
+ * superclass method to get the preferred span.
+ * axis.
+ *
+ * @param axis may be either View.X_AXIS or View.Y_AXIS
+ * @return the span the view would like to be rendered into;
+ * typically the view is told to render into the span
+ * that is returned, although there is no guarantee;
+ * the parent may choose to resize or break the view
+ * @see javax.swing.text.ParagraphView#getPreferredSpan
+ */
+ public float getPreferredSpan(int axis) {
+ if (!isVisible()) {
+ return 0;
+ }
+ return super.getPreferredSpan(axis);
+ }
+
+ /**
+ * Determines the minimum span for this view along an
+ * axis. Returns 0 if the view is not visible, otherwise
+ * it calls the superclass method to get the minimum span.
+ *
+ * @param axis may be either <code>View.X_AXIS</code> or
+ * <code>View.Y_AXIS</code>
+ * @return the minimum span the view can be rendered into
+ * @see javax.swing.text.ParagraphView#getMinimumSpan
+ */
+ public float getMinimumSpan(int axis) {
+ if (!isVisible()) {
+ return 0;
+ }
+ return super.getMinimumSpan(axis);
+ }
+
+ /**
+ * Determines the maximum span for this view along an
+ * axis. Returns 0 if the view is not visible, otherwise
+ * it calls the superclass method ot get the maximum span.
+ *
+ * @param axis may be either <code>View.X_AXIS</code> or
+ * <code>View.Y_AXIS</code>
+ * @return the maximum span the view can be rendered into
+ * @see javax.swing.text.ParagraphView#getMaximumSpan
+ */
+ public float getMaximumSpan(int axis) {
+ if (!isVisible()) {
+ return 0;
+ }
+ return super.getMaximumSpan(axis);
+ }
+
+ private AttributeSet attr;
+ private StyleSheet.BoxPainter painter;
+ private CSS.LengthValue cssWidth;
+ private CSS.LengthValue cssHeight;
+}
diff --git a/src/share/classes/javax/swing/text/html/ResourceLoader.java b/src/share/classes/javax/swing/text/html/ResourceLoader.java
new file mode 100644
index 000000000..a099cc041
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/ResourceLoader.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 1999 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html;
+
+import java.io.InputStream;
+
+/**
+ * Simple class to load resources using the 1.2
+ * security model. Since the html support is loaded
+ * lazily, it's resources are potentially fetched with
+ * applet code in the call stack. By providing this
+ * functionality in a class that is only built on 1.2,
+ * reflection can be used from the code that is also
+ * built on 1.1 to call this functionality (and avoid
+ * the evils of preprocessing). This functionality
+ * is called from HTMLEditorKit.getResourceAsStream.
+ *
+ * @author Timothy Prinzing
+ */
+class ResourceLoader implements java.security.PrivilegedAction {
+
+ ResourceLoader(String name) {
+ this.name = name;
+ }
+
+ public Object run() {
+ Object o = HTMLEditorKit.class.getResourceAsStream(name);
+ return o;
+ }
+
+ public static InputStream getResourceAsStream(String name) {
+ java.security.PrivilegedAction a = new ResourceLoader(name);
+ return (InputStream) java.security.AccessController.doPrivileged(a);
+ }
+
+ private String name;
+}
diff --git a/src/share/classes/javax/swing/text/html/StyleSheet.java b/src/share/classes/javax/swing/text/html/StyleSheet.java
new file mode 100644
index 000000000..bc5546d71
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/StyleSheet.java
@@ -0,0 +1,3340 @@
+/*
+ * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import sun.swing.SwingUtilities2;
+import java.util.*;
+import java.awt.*;
+import java.io.*;
+import java.net.*;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.border.*;
+import javax.swing.event.ChangeListener;
+import javax.swing.text.*;
+
+/**
+ * Support for defining the visual characteristics of
+ * HTML views being rendered. The StyleSheet is used to
+ * translate the HTML model into visual characteristics.
+ * This enables views to be customized by a look-and-feel,
+ * multiple views over the same model can be rendered
+ * differently, etc. This can be thought of as a CSS
+ * rule repository. The key for CSS attributes is an
+ * object of type CSS.Attribute. The type of the value
+ * is up to the StyleSheet implementation, but the
+ * <code>toString</code> method is required
+ * to return a string representation of CSS value.
+ * <p>
+ * The primary entry point for HTML View implementations
+ * to get their attributes is the
+ * <a href="#getViewAttributes">getViewAttributes</a>
+ * method. This should be implemented to establish the
+ * desired policy used to associate attributes with the view.
+ * Each HTMLEditorKit (i.e. and therefore each associated
+ * JEditorPane) can have its own StyleSheet, but by default one
+ * sheet will be shared by all of the HTMLEditorKit instances.
+ * HTMLDocument instance can also have a StyleSheet, which
+ * holds the document-specific CSS specifications.
+ * <p>
+ * In order for Views to store less state and therefore be
+ * more lightweight, the StyleSheet can act as a factory for
+ * painters that handle some of the rendering tasks. This allows
+ * implementations to determine what they want to cache
+ * and have the sharing potentially at the level that a
+ * selector is common to multiple views. Since the StyleSheet
+ * may be used by views over multiple documents and typically
+ * the HTML attributes don't effect the selector being used,
+ * the potential for sharing is significant.
+ * <p>
+ * The rules are stored as named styles, and other information
+ * is stored to translate the context of an element to a
+ * rule quickly. The following code fragment will display
+ * the named styles, and therefore the CSS rules contained.
+ * <code><pre>
+ * &nbsp;
+ * &nbsp; import java.util.*;
+ * &nbsp; import javax.swing.text.*;
+ * &nbsp; import javax.swing.text.html.*;
+ * &nbsp;
+ * &nbsp; public class ShowStyles {
+ * &nbsp;
+ * &nbsp; public static void main(String[] args) {
+ * &nbsp; HTMLEditorKit kit = new HTMLEditorKit();
+ * &nbsp; HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
+ * &nbsp; StyleSheet styles = doc.getStyleSheet();
+ * &nbsp;
+ * &nbsp; Enumeration rules = styles.getStyleNames();
+ * &nbsp; while (rules.hasMoreElements()) {
+ * &nbsp; String name = (String) rules.nextElement();
+ * &nbsp; Style rule = styles.getStyle(name);
+ * &nbsp; System.out.println(rule.toString());
+ * &nbsp; }
+ * &nbsp; System.exit(0);
+ * &nbsp; }
+ * &nbsp; }
+ * &nbsp;
+ * </pre></code>
+ * <p>
+ * The semantics for when a CSS style should overide visual attributes
+ * defined by an element are not well defined. For example, the html
+ * <code>&lt;body bgcolor=red&gt;</code> makes the body have a red
+ * background. But if the html file also contains the CSS rule
+ * <code>body { background: blue }</code> it becomes less clear as to
+ * what color the background of the body should be. The current
+ * implemention gives visual attributes defined in the element the
+ * highest precedence, that is they are always checked before any styles.
+ * Therefore, in the previous example the background would have a
+ * red color as the body element defines the background color to be red.
+ * <p>
+ * As already mentioned this supports CSS. We don't support the full CSS
+ * spec. Refer to the javadoc of the CSS class to see what properties
+ * we support. The two major CSS parsing related
+ * concepts we do not currently
+ * support are pseudo selectors, such as <code>A:link { color: red }</code>,
+ * and the <code>important</code> modifier.
+ * <p>
+ * <font color="red">Note: This implementation is currently
+ * incomplete. It can be replaced with alternative implementations
+ * that are complete. Future versions of this class will provide
+ * better CSS support.</font>
+ *
+ * @author Timothy Prinzing
+ * @author Sunita Mani
+ * @author Sara Swanson
+ * @author Jill Nakata
+ */
+public class StyleSheet extends StyleContext {
+ // As the javadoc states, this class maintains a mapping between
+ // a CSS selector (such as p.bar) and a Style.
+ // This consists of a number of parts:
+ // . Each selector is broken down into its constituent simple selectors,
+ // and stored in an inverted graph, for example:
+ // p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt }
+ // results in the graph:
+ // root
+ // |
+ // p
+ // / \
+ // ol ul
+ // each node (an instance of SelectorMapping) has an associated
+ // specificity and potentially a Style.
+ // . Every rule that is asked for (either by way of getRule(String) or
+ // getRule(HTML.Tag, Element)) results in a unique instance of
+ // ResolvedStyle. ResolvedStyles contain the AttributeSets from the
+ // SelectorMapping.
+ // . When a new rule is created it is inserted into the graph, and
+ // the AttributeSets of each ResolvedStyles are updated appropriately.
+ // . This class creates special AttributeSets, LargeConversionSet and
+ // SmallConversionSet, that maintain a mapping between StyleConstants
+ // and CSS so that developers that wish to use the StyleConstants
+ // methods can do so.
+ // . When one of the AttributeSets is mutated by way of a
+ // StyleConstants key, all the associated CSS keys are removed. This is
+ // done so that the two representations don't get out of sync. For
+ // example, if the developer adds StyleConsants.BOLD, FALSE to an
+ // AttributeSet that contains HTML.Tag.B, the HTML.Tag.B entry will
+ // be removed.
+
+ /**
+ * Construct a StyleSheet
+ */
+ public StyleSheet() {
+ super();
+ selectorMapping = new SelectorMapping(0);
+ resolvedStyles = new Hashtable();
+ if (css == null) {
+ css = new CSS();
+ }
+ }
+
+ /**
+ * Fetches the style to use to render the given type
+ * of HTML tag. The element given is representing
+ * the tag and can be used to determine the nesting
+ * for situations where the attributes will differ
+ * if nesting inside of elements.
+ *
+ * @param t the type to translate to visual attributes
+ * @param e the element representing the tag; the element
+ * can be used to determine the nesting for situations where
+ * the attributes will differ if nested inside of other
+ * elements
+ * @return the set of CSS attributes to use to render
+ * the tag
+ */
+ public Style getRule(HTML.Tag t, Element e) {
+ SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
+
+ try {
+ // Build an array of all the parent elements.
+ Vector searchContext = sb.getVector();
+
+ for (Element p = e; p != null; p = p.getParentElement()) {
+ searchContext.addElement(p);
+ }
+
+ // Build a fully qualified selector.
+ int n = searchContext.size();
+ StringBuffer cacheLookup = sb.getStringBuffer();
+ AttributeSet attr;
+ String eName;
+ Object name;
+
+ // >= 1 as the HTML.Tag for the 0th element is passed in.
+ for (int counter = n - 1; counter >= 1; counter--) {
+ e = (Element)searchContext.elementAt(counter);
+ attr = e.getAttributes();
+ name = attr.getAttribute(StyleConstants.NameAttribute);
+ eName = name.toString();
+ cacheLookup.append(eName);
+ if (attr != null) {
+ if (attr.isDefined(HTML.Attribute.ID)) {
+ cacheLookup.append('#');
+ cacheLookup.append(attr.getAttribute
+ (HTML.Attribute.ID));
+ }
+ else if (attr.isDefined(HTML.Attribute.CLASS)) {
+ cacheLookup.append('.');
+ cacheLookup.append(attr.getAttribute
+ (HTML.Attribute.CLASS));
+ }
+ }
+ cacheLookup.append(' ');
+ }
+ cacheLookup.append(t.toString());
+ e = (Element)searchContext.elementAt(0);
+ attr = e.getAttributes();
+ if (e.isLeaf()) {
+ // For leafs, we use the second tier attributes.
+ Object testAttr = attr.getAttribute(t);
+ if (testAttr instanceof AttributeSet) {
+ attr = (AttributeSet)testAttr;
+ }
+ else {
+ attr = null;
+ }
+ }
+ if (attr != null) {
+ if (attr.isDefined(HTML.Attribute.ID)) {
+ cacheLookup.append('#');
+ cacheLookup.append(attr.getAttribute(HTML.Attribute.ID));
+ }
+ else if (attr.isDefined(HTML.Attribute.CLASS)) {
+ cacheLookup.append('.');
+ cacheLookup.append(attr.getAttribute
+ (HTML.Attribute.CLASS));
+ }
+ }
+
+ Style style = getResolvedStyle(cacheLookup.toString(),
+ searchContext, t);
+ return style;
+ }
+ finally {
+ SearchBuffer.releaseSearchBuffer(sb);
+ }
+ }
+
+ /**
+ * Fetches the rule that best matches the selector given
+ * in string form. Where <code>selector</code> is a space separated
+ * String of the element names. For example, <code>selector</code>
+ * might be 'html body tr td''<p>
+ * The attributes of the returned Style will change
+ * as rules are added and removed. That is if you to ask for a rule
+ * with a selector "table p" and a new rule was added with a selector
+ * of "p" the returned Style would include the new attributes from
+ * the rule "p".
+ */
+ public Style getRule(String selector) {
+ selector = cleanSelectorString(selector);
+ if (selector != null) {
+ Style style = getResolvedStyle(selector);
+ return style;
+ }
+ return null;
+ }
+
+ /**
+ * Adds a set of rules to the sheet. The rules are expected to
+ * be in valid CSS format. Typically this would be called as
+ * a result of parsing a &lt;style&gt; tag.
+ */
+ public void addRule(String rule) {
+ if (rule != null) {
+ //tweaks to control display properties
+ //see BasicEditorPaneUI
+ final String baseUnitsDisable = "BASE_SIZE_DISABLE";
+ final String baseUnits = "BASE_SIZE ";
+ final String w3cLengthUnitsEnable = "W3C_LENGTH_UNITS_ENABLE";
+ final String w3cLengthUnitsDisable = "W3C_LENGTH_UNITS_DISABLE";
+ if (rule == baseUnitsDisable) {
+ sizeMap = sizeMapDefault;
+ } else if (rule.startsWith(baseUnits)) {
+ rebaseSizeMap(Integer.
+ parseInt(rule.substring(baseUnits.length())));
+ } else if (rule == w3cLengthUnitsEnable) {
+ w3cLengthUnits = true;
+ } else if (rule == w3cLengthUnitsDisable) {
+ w3cLengthUnits = false;
+ } else {
+ CssParser parser = new CssParser();
+ try {
+ parser.parse(getBase(), new StringReader(rule), false, false);
+ } catch (IOException ioe) { }
+ }
+ }
+ }
+
+ /**
+ * Translates a CSS declaration to an AttributeSet that represents
+ * the CSS declaration. Typically this would be called as a
+ * result of encountering an HTML style attribute.
+ */
+ public AttributeSet getDeclaration(String decl) {
+ if (decl == null) {
+ return SimpleAttributeSet.EMPTY;
+ }
+ CssParser parser = new CssParser();
+ return parser.parseDeclaration(decl);
+ }
+
+ /**
+ * Loads a set of rules that have been specified in terms of
+ * CSS1 grammar. If there are collisions with existing rules,
+ * the newly specified rule will win.
+ *
+ * @param in the stream to read the CSS grammar from
+ * @param ref the reference URL. This value represents the
+ * location of the stream and may be null. All relative
+ * URLs specified in the stream will be based upon this
+ * parameter.
+ */
+ public void loadRules(Reader in, URL ref) throws IOException {
+ CssParser parser = new CssParser();
+ parser.parse(ref, in, false, false);
+ }
+
+ /**
+ * Fetches a set of attributes to use in the view for
+ * displaying. This is basically a set of attributes that
+ * can be used for View.getAttributes.
+ */
+ public AttributeSet getViewAttributes(View v) {
+ return new ViewAttributeSet(v);
+ }
+
+ /**
+ * Removes a named style previously added to the document.
+ *
+ * @param nm the name of the style to remove
+ */
+ public void removeStyle(String nm) {
+ Style aStyle = getStyle(nm);
+
+ if (aStyle != null) {
+ String selector = cleanSelectorString(nm);
+ String[] selectors = getSimpleSelectors(selector);
+ synchronized(this) {
+ SelectorMapping mapping = getRootSelectorMapping();
+ for (int i = selectors.length - 1; i >= 0; i--) {
+ mapping = mapping.getChildSelectorMapping(selectors[i],
+ true);
+ }
+ Style rule = mapping.getStyle();
+ if (rule != null) {
+ mapping.setStyle(null);
+ if (resolvedStyles.size() > 0) {
+ Enumeration values = resolvedStyles.elements();
+ while (values.hasMoreElements()) {
+ ResolvedStyle style = (ResolvedStyle)values.
+ nextElement();
+ style.removeStyle(rule);
+ }
+ }
+ }
+ }
+ }
+ super.removeStyle(nm);
+ }
+
+ /**
+ * Adds the rules from the StyleSheet <code>ss</code> to those of
+ * the receiver. <code>ss's</code> rules will override the rules of
+ * any previously added style sheets. An added StyleSheet will never
+ * override the rules of the receiving style sheet.
+ *
+ * @since 1.3
+ */
+ public void addStyleSheet(StyleSheet ss) {
+ synchronized(this) {
+ if (linkedStyleSheets == null) {
+ linkedStyleSheets = new Vector();
+ }
+ if (!linkedStyleSheets.contains(ss)) {
+ int index = 0;
+ if (ss instanceof javax.swing.plaf.UIResource
+ && linkedStyleSheets.size() > 1) {
+ index = linkedStyleSheets.size() - 1;
+ }
+ linkedStyleSheets.insertElementAt(ss, index);
+ linkStyleSheetAt(ss, index);
+ }
+ }
+ }
+
+ /**
+ * Removes the StyleSheet <code>ss</code> from those of the receiver.
+ *
+ * @since 1.3
+ */
+ public void removeStyleSheet(StyleSheet ss) {
+ synchronized(this) {
+ if (linkedStyleSheets != null) {
+ int index = linkedStyleSheets.indexOf(ss);
+ if (index != -1) {
+ linkedStyleSheets.removeElementAt(index);
+ unlinkStyleSheet(ss, index);
+ if (index == 0 && linkedStyleSheets.size() == 0) {
+ linkedStyleSheets = null;
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // The following is used to import style sheets.
+ //
+
+ /**
+ * Returns an array of the linked StyleSheets. Will return null
+ * if there are no linked StyleSheets.
+ *
+ * @since 1.3
+ */
+ public StyleSheet[] getStyleSheets() {
+ StyleSheet[] retValue;
+
+ synchronized(this) {
+ if (linkedStyleSheets != null) {
+ retValue = new StyleSheet[linkedStyleSheets.size()];
+ linkedStyleSheets.copyInto(retValue);
+ }
+ else {
+ retValue = null;
+ }
+ }
+ return retValue;
+ }
+
+ /**
+ * Imports a style sheet from <code>url</code>. The resulting rules
+ * are directly added to the receiver. If you do not want the rules
+ * to become part of the receiver, create a new StyleSheet and use
+ * addStyleSheet to link it in.
+ *
+ * @since 1.3
+ */
+ public void importStyleSheet(URL url) {
+ try {
+ InputStream is;
+
+ is = url.openStream();
+ Reader r = new BufferedReader(new InputStreamReader(is));
+ CssParser parser = new CssParser();
+ parser.parse(url, r, false, true);
+ r.close();
+ is.close();
+ } catch (Throwable e) {
+ // on error we simply have no styles... the html
+ // will look mighty wrong but still function.
+ }
+ }
+
+ /**
+ * Sets the base. All import statements that are relative, will be
+ * relative to <code>base</code>.
+ *
+ * @since 1.3
+ */
+ public void setBase(URL base) {
+ this.base = base;
+ }
+
+ /**
+ * Returns the base.
+ *
+ * @since 1.3
+ */
+ public URL getBase() {
+ return base;
+ }
+
+ /**
+ * Adds a CSS attribute to the given set.
+ *
+ * @since 1.3
+ */
+ public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key,
+ String value) {
+ css.addInternalCSSValue(attr, key, value);
+ }
+
+ /**
+ * Adds a CSS attribute to the given set.
+ *
+ * @since 1.3
+ */
+ public boolean addCSSAttributeFromHTML(MutableAttributeSet attr,
+ CSS.Attribute key, String value) {
+ Object iValue = css.getCssValue(key, value);
+ if (iValue != null) {
+ attr.addAttribute(key, iValue);
+ return true;
+ }
+ return false;
+ }
+
+ // ---- Conversion functionality ---------------------------------
+
+ /**
+ * Converts a set of HTML attributes to an equivalent
+ * set of CSS attributes.
+ *
+ * @param htmlAttrSet AttributeSet containing the HTML attributes.
+ */
+ public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
+ AttributeSet cssAttrSet = css.translateHTMLToCSS(htmlAttrSet);
+
+ MutableAttributeSet cssStyleSet = addStyle(null, null);
+ cssStyleSet.addAttributes(cssAttrSet);
+
+ return cssStyleSet;
+ }
+
+ /**
+ * Adds an attribute to the given set, and returns
+ * the new representative set. This is reimplemented to
+ * convert StyleConstant attributes to CSS prior to forwarding
+ * to the superclass behavior. The StyleConstants attribute
+ * has no corresponding CSS entry, the StyleConstants attribute
+ * is stored (but will likely be unused).
+ *
+ * @param old the old attribute set
+ * @param key the non-null attribute key
+ * @param value the attribute value
+ * @return the updated attribute set
+ * @see MutableAttributeSet#addAttribute
+ */
+ public AttributeSet addAttribute(AttributeSet old, Object key,
+ Object value) {
+ if (css == null) {
+ // supers constructor will call this before returning,
+ // and we need to make sure CSS is non null.
+ css = new CSS();
+ }
+ if (key instanceof StyleConstants) {
+ HTML.Tag tag = HTML.getTagForStyleConstantsKey(
+ (StyleConstants)key);
+
+ if (tag != null && old.isDefined(tag)) {
+ old = removeAttribute(old, tag);
+ }
+
+ Object cssValue = css.styleConstantsValueToCSSValue
+ ((StyleConstants)key, value);
+ if (cssValue != null) {
+ Object cssKey = css.styleConstantsKeyToCSSKey
+ ((StyleConstants)key);
+ if (cssKey != null) {
+ return super.addAttribute(old, cssKey, cssValue);
+ }
+ }
+ }
+ return super.addAttribute(old, key, value);
+ }
+
+ /**
+ * Adds a set of attributes to the element. If any of these attributes
+ * are StyleConstants attributes, they will be converted to CSS prior
+ * to forwarding to the superclass behavior.
+ *
+ * @param old the old attribute set
+ * @param attr the attributes to add
+ * @return the updated attribute set
+ * @see MutableAttributeSet#addAttribute
+ */
+ public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) {
+ if (!(attr instanceof HTMLDocument.TaggedAttributeSet)) {
+ old = removeHTMLTags(old, attr);
+ }
+ return super.addAttributes(old, convertAttributeSet(attr));
+ }
+
+ /**
+ * Removes an attribute from the set. If the attribute is a StyleConstants
+ * attribute, the request will be converted to a CSS attribute prior to
+ * forwarding to the superclass behavior.
+ *
+ * @param old the old set of attributes
+ * @param key the non-null attribute name
+ * @return the updated attribute set
+ * @see MutableAttributeSet#removeAttribute
+ */
+ public AttributeSet removeAttribute(AttributeSet old, Object key) {
+ if (key instanceof StyleConstants) {
+ HTML.Tag tag = HTML.getTagForStyleConstantsKey(
+ (StyleConstants)key);
+ if (tag != null) {
+ old = super.removeAttribute(old, tag);
+ }
+
+ Object cssKey = css.styleConstantsKeyToCSSKey((StyleConstants)key);
+ if (cssKey != null) {
+ return super.removeAttribute(old, cssKey);
+ }
+ }
+ return super.removeAttribute(old, key);
+ }
+
+ /**
+ * Removes a set of attributes for the element. If any of the attributes
+ * is a StyleConstants attribute, the request will be converted to a CSS
+ * attribute prior to forwarding to the superclass behavior.
+ *
+ * @param old the old attribute set
+ * @param names the attribute names
+ * @return the updated attribute set
+ * @see MutableAttributeSet#removeAttributes
+ */
+ public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) {
+ // PENDING: Should really be doing something similar to
+ // removeHTMLTags here, but it is rather expensive to have to
+ // clone names
+ return super.removeAttributes(old, names);
+ }
+
+ /**
+ * Removes a set of attributes. If any of the attributes
+ * is a StyleConstants attribute, the request will be converted to a CSS
+ * attribute prior to forwarding to the superclass behavior.
+ *
+ * @param old the old attribute set
+ * @param attrs the attributes
+ * @return the updated attribute set
+ * @see MutableAttributeSet#removeAttributes
+ */
+ public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) {
+ if (old != attrs) {
+ old = removeHTMLTags(old, attrs);
+ }
+ return super.removeAttributes(old, convertAttributeSet(attrs));
+ }
+
+ /**
+ * Creates a compact set of attributes that might be shared.
+ * This is a hook for subclasses that want to alter the
+ * behavior of SmallAttributeSet. This can be reimplemented
+ * to return an AttributeSet that provides some sort of
+ * attribute conversion.
+ *
+ * @param a The set of attributes to be represented in the
+ * the compact form.
+ */
+ protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) {
+ return new SmallConversionSet(a);
+ }
+
+ /**
+ * Creates a large set of attributes that should trade off
+ * space for time. This set will not be shared. This is
+ * a hook for subclasses that want to alter the behavior
+ * of the larger attribute storage format (which is
+ * SimpleAttributeSet by default). This can be reimplemented
+ * to return a MutableAttributeSet that provides some sort of
+ * attribute conversion.
+ *
+ * @param a The set of attributes to be represented in the
+ * the larger form.
+ */
+ protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) {
+ return new LargeConversionSet(a);
+ }
+
+ /**
+ * For any StyleConstants key in attr that has an associated HTML.Tag,
+ * it is removed from old. The resulting AttributeSet is then returned.
+ */
+ private AttributeSet removeHTMLTags(AttributeSet old, AttributeSet attr) {
+ if (!(attr instanceof LargeConversionSet) &&
+ !(attr instanceof SmallConversionSet)) {
+ Enumeration names = attr.getAttributeNames();
+
+ while (names.hasMoreElements()) {
+ Object key = names.nextElement();
+
+ if (key instanceof StyleConstants) {
+ HTML.Tag tag = HTML.getTagForStyleConstantsKey(
+ (StyleConstants)key);
+
+ if (tag != null && old.isDefined(tag)) {
+ old = super.removeAttribute(old, tag);
+ }
+ }
+ }
+ }
+ return old;
+ }
+
+ /**
+ * Converts a set of attributes (if necessary) so that
+ * any attributes that were specified as StyleConstants
+ * attributes and have a CSS mapping, will be converted
+ * to CSS attributes.
+ */
+ AttributeSet convertAttributeSet(AttributeSet a) {
+ if ((a instanceof LargeConversionSet) ||
+ (a instanceof SmallConversionSet)) {
+ // known to be converted.
+ return a;
+ }
+ // in most cases, there are no StyleConstants attributes
+ // so we iterate the collection of keys to avoid creating
+ // a new set.
+ Enumeration names = a.getAttributeNames();
+ while (names.hasMoreElements()) {
+ Object name = names.nextElement();
+ if (name instanceof StyleConstants) {
+ // we really need to do a conversion, iterate again
+ // building a new set.
+ MutableAttributeSet converted = new LargeConversionSet();
+ Enumeration keys = a.getAttributeNames();
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ Object cssValue = null;
+ if (key instanceof StyleConstants) {
+ // convert the StyleConstants attribute if possible
+ Object cssKey = css.styleConstantsKeyToCSSKey
+ ((StyleConstants)key);
+ if (cssKey != null) {
+ Object value = a.getAttribute(key);
+ cssValue = css.styleConstantsValueToCSSValue
+ ((StyleConstants)key, value);
+ if (cssValue != null) {
+ converted.addAttribute(cssKey, cssValue);
+ }
+ }
+ }
+ if (cssValue == null) {
+ converted.addAttribute(key, a.getAttribute(key));
+ }
+ }
+ return converted;
+ }
+ }
+ return a;
+ }
+
+ /**
+ * Large set of attributes that does conversion of requests
+ * for attributes of type StyleConstants.
+ */
+ class LargeConversionSet extends SimpleAttributeSet {
+
+ /**
+ * Creates a new attribute set based on a supplied set of attributes.
+ *
+ * @param source the set of attributes
+ */
+ public LargeConversionSet(AttributeSet source) {
+ super(source);
+ }
+
+ public LargeConversionSet() {
+ super();
+ }
+
+ /**
+ * Checks whether a given attribute is defined.
+ *
+ * @param key the attribute key
+ * @return true if the attribute is defined
+ * @see AttributeSet#isDefined
+ */
+ public boolean isDefined(Object key) {
+ if (key instanceof StyleConstants) {
+ Object cssKey = css.styleConstantsKeyToCSSKey
+ ((StyleConstants)key);
+ if (cssKey != null) {
+ return super.isDefined(cssKey);
+ }
+ }
+ return super.isDefined(key);
+ }
+
+ /**
+ * Gets the value of an attribute.
+ *
+ * @param key the attribute name
+ * @return the attribute value
+ * @see AttributeSet#getAttribute
+ */
+ public Object getAttribute(Object key) {
+ if (key instanceof StyleConstants) {
+ Object cssKey = css.styleConstantsKeyToCSSKey
+ ((StyleConstants)key);
+ if (cssKey != null) {
+ Object value = super.getAttribute(cssKey);
+ if (value != null) {
+ return css.cssValueToStyleConstantsValue
+ ((StyleConstants)key, value);
+ }
+ }
+ }
+ return super.getAttribute(key);
+ }
+ }
+
+ /**
+ * Small set of attributes that does conversion of requests
+ * for attributes of type StyleConstants.
+ */
+ class SmallConversionSet extends SmallAttributeSet {
+
+ /**
+ * Creates a new attribute set based on a supplied set of attributes.
+ *
+ * @param source the set of attributes
+ */
+ public SmallConversionSet(AttributeSet attrs) {
+ super(attrs);
+ }
+
+ /**
+ * Checks whether a given attribute is defined.
+ *
+ * @param key the attribute key
+ * @return true if the attribute is defined
+ * @see AttributeSet#isDefined
+ */
+ public boolean isDefined(Object key) {
+ if (key instanceof StyleConstants) {
+ Object cssKey = css.styleConstantsKeyToCSSKey
+ ((StyleConstants)key);
+ if (cssKey != null) {
+ return super.isDefined(cssKey);
+ }
+ }
+ return super.isDefined(key);
+ }
+
+ /**
+ * Gets the value of an attribute.
+ *
+ * @param key the attribute name
+ * @return the attribute value
+ * @see AttributeSet#getAttribute
+ */
+ public Object getAttribute(Object key) {
+ if (key instanceof StyleConstants) {
+ Object cssKey = css.styleConstantsKeyToCSSKey
+ ((StyleConstants)key);
+ if (cssKey != null) {
+ Object value = super.getAttribute(cssKey);
+ if (value != null) {
+ return css.cssValueToStyleConstantsValue
+ ((StyleConstants)key, value);
+ }
+ }
+ }
+ return super.getAttribute(key);
+ }
+ }
+
+ // ---- Resource handling ----------------------------------------
+
+ /**
+ * Fetches the font to use for the given set of attributes.
+ */
+ public Font getFont(AttributeSet a) {
+ return css.getFont(this, a, 12, this);
+ }
+
+ /**
+ * Takes a set of attributes and turn it into a foreground color
+ * specification. This might be used to specify things
+ * like brighter, more hue, etc.
+ *
+ * @param a the set of attributes
+ * @return the color
+ */
+ public Color getForeground(AttributeSet a) {
+ Color c = css.getColor(a, CSS.Attribute.COLOR);
+ if (c == null) {
+ return Color.black;
+ }
+ return c;
+ }
+
+ /**
+ * Takes a set of attributes and turn it into a background color
+ * specification. This might be used to specify things
+ * like brighter, more hue, etc.
+ *
+ * @param a the set of attributes
+ * @return the color
+ */
+ public Color getBackground(AttributeSet a) {
+ return css.getColor(a, CSS.Attribute.BACKGROUND_COLOR);
+ }
+
+ /**
+ * Fetches the box formatter to use for the given set
+ * of CSS attributes.
+ */
+ public BoxPainter getBoxPainter(AttributeSet a) {
+ return new BoxPainter(a, css, this);
+ }
+
+ /**
+ * Fetches the list formatter to use for the given set
+ * of CSS attributes.
+ */
+ public ListPainter getListPainter(AttributeSet a) {
+ return new ListPainter(a, this);
+ }
+
+ /**
+ * Sets the base font size, with valid values between 1 and 7.
+ */
+ public void setBaseFontSize(int sz) {
+ css.setBaseFontSize(sz);
+ }
+
+ /**
+ * Sets the base font size from the passed in String. The string
+ * can either identify a specific font size, with legal values between
+ * 1 and 7, or identifiy a relative font size such as +1 or -2.
+ */
+ public void setBaseFontSize(String size) {
+ css.setBaseFontSize(size);
+ }
+
+ public static int getIndexOfSize(float pt) {
+ return CSS.getIndexOfSize(pt, sizeMapDefault);
+ }
+
+ /**
+ * Returns the point size, given a size index.
+ */
+ public float getPointSize(int index) {
+ return css.getPointSize(index, this);
+ }
+
+ /**
+ * Given a string such as "+2", "-2", or "2",
+ * returns a point size value.
+ */
+ public float getPointSize(String size) {
+ return css.getPointSize(size, this);
+ }
+
+ /**
+ * Converts a color string such as "RED" or "#NNNNNN" to a Color.
+ * Note: This will only convert the HTML3.2 color strings
+ * or a string of length 7;
+ * otherwise, it will return null.
+ */
+ public Color stringToColor(String string) {
+ return CSS.stringToColor(string);
+ }
+
+ /**
+ * Returns the ImageIcon to draw in the background for
+ * <code>attr</code>.
+ */
+ ImageIcon getBackgroundImage(AttributeSet attr) {
+ Object value = attr.getAttribute(CSS.Attribute.BACKGROUND_IMAGE);
+
+ if (value != null) {
+ return ((CSS.BackgroundImage)value).getImage(getBase());
+ }
+ return null;
+ }
+
+ /**
+ * Adds a rule into the StyleSheet.
+ *
+ * @param selector the selector to use for the rule.
+ * This will be a set of simple selectors, and must
+ * be a length of 1 or greater.
+ * @param declaration the set of CSS attributes that
+ * make up the rule.
+ */
+ void addRule(String[] selector, AttributeSet declaration,
+ boolean isLinked) {
+ int n = selector.length;
+ StringBuffer sb = new StringBuffer();
+ sb.append(selector[0]);
+ for (int counter = 1; counter < n; counter++) {
+ sb.append(' ');
+ sb.append(selector[counter]);
+ }
+ String selectorName = sb.toString();
+ Style rule = getStyle(selectorName);
+ if (rule == null) {
+ // Notice how the rule is first created, and it not part of
+ // the synchronized block. It is done like this as creating
+ // a new rule will fire a ChangeEvent. We do not want to be
+ // holding the lock when calling to other objects, it can
+ // result in deadlock.
+ Style altRule = addStyle(selectorName, null);
+ synchronized(this) {
+ SelectorMapping mapping = getRootSelectorMapping();
+ for (int i = n - 1; i >= 0; i--) {
+ mapping = mapping.getChildSelectorMapping
+ (selector[i], true);
+ }
+ rule = mapping.getStyle();
+ if (rule == null) {
+ rule = altRule;
+ mapping.setStyle(rule);
+ refreshResolvedRules(selectorName, selector, rule,
+ mapping.getSpecificity());
+ }
+ }
+ }
+ if (isLinked) {
+ rule = getLinkedStyle(rule);
+ }
+ rule.addAttributes(declaration);
+ }
+
+ //
+ // The following gaggle of methods is used in maintaing the rules from
+ // the sheet.
+ //
+
+ /**
+ * Updates the attributes of the rules to reference any related
+ * rules in <code>ss</code>.
+ */
+ private synchronized void linkStyleSheetAt(StyleSheet ss, int index) {
+ if (resolvedStyles.size() > 0) {
+ Enumeration values = resolvedStyles.elements();
+ while (values.hasMoreElements()) {
+ ResolvedStyle rule = (ResolvedStyle)values.nextElement();
+ rule.insertExtendedStyleAt(ss.getRule(rule.getName()),
+ index);
+ }
+ }
+ }
+
+ /**
+ * Removes references to the rules in <code>ss</code>.
+ * <code>index</code> gives the index the StyleSheet was at, that is
+ * how many StyleSheets had been added before it.
+ */
+ private synchronized void unlinkStyleSheet(StyleSheet ss, int index) {
+ if (resolvedStyles.size() > 0) {
+ Enumeration values = resolvedStyles.elements();
+ while (values.hasMoreElements()) {
+ ResolvedStyle rule = (ResolvedStyle)values.nextElement();
+ rule.removeExtendedStyleAt(index);
+ }
+ }
+ }
+
+ /**
+ * Returns the simple selectors that comprise selector.
+ */
+ /* protected */
+ String[] getSimpleSelectors(String selector) {
+ selector = cleanSelectorString(selector);
+ SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
+ Vector selectors = sb.getVector();
+ int lastIndex = 0;
+ int length = selector.length();
+ while (lastIndex != -1) {
+ int newIndex = selector.indexOf(' ', lastIndex);
+ if (newIndex != -1) {
+ selectors.addElement(selector.substring(lastIndex, newIndex));
+ if (++newIndex == length) {
+ lastIndex = -1;
+ }
+ else {
+ lastIndex = newIndex;
+ }
+ }
+ else {
+ selectors.addElement(selector.substring(lastIndex));
+ lastIndex = -1;
+ }
+ }
+ String[] retValue = new String[selectors.size()];
+ selectors.copyInto(retValue);
+ SearchBuffer.releaseSearchBuffer(sb);
+ return retValue;
+ }
+
+ /**
+ * Returns a string that only has one space between simple selectors,
+ * which may be the passed in String.
+ */
+ /*protected*/ String cleanSelectorString(String selector) {
+ boolean lastWasSpace = true;
+ for (int counter = 0, maxCounter = selector.length();
+ counter < maxCounter; counter++) {
+ switch(selector.charAt(counter)) {
+ case ' ':
+ if (lastWasSpace) {
+ return _cleanSelectorString(selector);
+ }
+ lastWasSpace = true;
+ break;
+ case '\n':
+ case '\r':
+ case '\t':
+ return _cleanSelectorString(selector);
+ default:
+ lastWasSpace = false;
+ }
+ }
+ if (lastWasSpace) {
+ return _cleanSelectorString(selector);
+ }
+ // It was fine.
+ return selector;
+ }
+
+ /**
+ * Returns a new String that contains only one space between non
+ * white space characters.
+ */
+ private String _cleanSelectorString(String selector) {
+ SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
+ StringBuffer buff = sb.getStringBuffer();
+ boolean lastWasSpace = true;
+ int lastIndex = 0;
+ char[] chars = selector.toCharArray();
+ int numChars = chars.length;
+ String retValue = null;
+ try {
+ for (int counter = 0; counter < numChars; counter++) {
+ switch(chars[counter]) {
+ case ' ':
+ if (!lastWasSpace) {
+ lastWasSpace = true;
+ if (lastIndex < counter) {
+ buff.append(chars, lastIndex,
+ 1 + counter - lastIndex);
+ }
+ }
+ lastIndex = counter + 1;
+ break;
+ case '\n':
+ case '\r':
+ case '\t':
+ if (!lastWasSpace) {
+ lastWasSpace = true;
+ if (lastIndex < counter) {
+ buff.append(chars, lastIndex,
+ counter - lastIndex);
+ buff.append(' ');
+ }
+ }
+ lastIndex = counter + 1;
+ break;
+ default:
+ lastWasSpace = false;
+ break;
+ }
+ }
+ if (lastWasSpace && buff.length() > 0) {
+ // Remove last space.
+ buff.setLength(buff.length() - 1);
+ }
+ else if (lastIndex < numChars) {
+ buff.append(chars, lastIndex, numChars - lastIndex);
+ }
+ retValue = buff.toString();
+ }
+ finally {
+ SearchBuffer.releaseSearchBuffer(sb);
+ }
+ return retValue;
+ }
+
+ /**
+ * Returns the root selector mapping that all selectors are relative
+ * to. This is an inverted graph of the selectors.
+ */
+ private SelectorMapping getRootSelectorMapping() {
+ return selectorMapping;
+ }
+
+ /**
+ * Returns the specificity of the passed in String. It assumes the
+ * passed in string doesn't contain junk, that is each selector is
+ * separated by a space and each selector at most contains one . or one
+ * #. A simple selector has a weight of 1, an id selector has a weight
+ * of 100, and a class selector has a weight of 10000.
+ */
+ /*protected*/ static int getSpecificity(String selector) {
+ int specificity = 0;
+ boolean lastWasSpace = true;
+
+ for (int counter = 0, maxCounter = selector.length();
+ counter < maxCounter; counter++) {
+ switch(selector.charAt(counter)) {
+ case '.':
+ specificity += 100;
+ break;
+ case '#':
+ specificity += 10000;
+ break;
+ case ' ':
+ lastWasSpace = true;
+ break;
+ default:
+ if (lastWasSpace) {
+ lastWasSpace = false;
+ specificity += 1;
+ }
+ }
+ }
+ return specificity;
+ }
+
+ /**
+ * Returns the style that linked attributes should be added to. This
+ * will create the style if necessary.
+ */
+ private Style getLinkedStyle(Style localStyle) {
+ // NOTE: This is not synchronized, and the caller of this does
+ // not synchronize. There is the chance for one of the callers to
+ // overwrite the existing resolved parent, but it is quite rare.
+ // The reason this is left like this is because setResolveParent
+ // will fire a ChangeEvent. It is really, REALLY bad for us to
+ // hold a lock when calling outside of us, it may cause a deadlock.
+ Style retStyle = (Style)localStyle.getResolveParent();
+ if (retStyle == null) {
+ retStyle = addStyle(null, null);
+ localStyle.setResolveParent(retStyle);
+ }
+ return retStyle;
+ }
+
+ /**
+ * Returns the resolved style for <code>selector</code>. This will
+ * create the resolved style, if necessary.
+ */
+ private synchronized Style getResolvedStyle(String selector,
+ Vector elements,
+ HTML.Tag t) {
+ Style retStyle = (Style)resolvedStyles.get(selector);
+ if (retStyle == null) {
+ retStyle = createResolvedStyle(selector, elements, t);
+ }
+ return retStyle;
+ }
+
+ /**
+ * Returns the resolved style for <code>selector</code>. This will
+ * create the resolved style, if necessary.
+ */
+ private synchronized Style getResolvedStyle(String selector) {
+ Style retStyle = (Style)resolvedStyles.get(selector);
+ if (retStyle == null) {
+ retStyle = createResolvedStyle(selector);
+ }
+ return retStyle;
+ }
+
+ /**
+ * Adds <code>mapping</code> to <code>elements</code>. It is added
+ * such that <code>elements</code> will remain ordered by
+ * specificity.
+ */
+ private void addSortedStyle(SelectorMapping mapping, Vector elements) {
+ int size = elements.size();
+
+ if (size > 0) {
+ int specificity = mapping.getSpecificity();
+
+ for (int counter = 0; counter < size; counter++) {
+ if (specificity >= ((SelectorMapping)elements.elementAt
+ (counter)).getSpecificity()) {
+ elements.insertElementAt(mapping, counter);
+ return;
+ }
+ }
+ }
+ elements.addElement(mapping);
+ }
+
+ /**
+ * Adds <code>parentMapping</code> to <code>styles</code>, and
+ * recursively calls this method if <code>parentMapping</code> has
+ * any child mappings for any of the Elements in <code>elements</code>.
+ */
+ private synchronized void getStyles(SelectorMapping parentMapping,
+ Vector styles,
+ String[] tags, String[] ids, String[] classes,
+ int index, int numElements,
+ Hashtable alreadyChecked) {
+ // Avoid desending the same mapping twice.
+ if (alreadyChecked.contains(parentMapping)) {
+ return;
+ }
+ alreadyChecked.put(parentMapping, parentMapping);
+ Style style = parentMapping.getStyle();
+ if (style != null) {
+ addSortedStyle(parentMapping, styles);
+ }
+ for (int counter = index; counter < numElements; counter++) {
+ String tagString = tags[counter];
+ if (tagString != null) {
+ SelectorMapping childMapping = parentMapping.
+ getChildSelectorMapping(tagString, false);
+ if (childMapping != null) {
+ getStyles(childMapping, styles, tags, ids, classes,
+ counter + 1, numElements, alreadyChecked);
+ }
+ if (classes[counter] != null) {
+ String className = classes[counter];
+ childMapping = parentMapping.getChildSelectorMapping(
+ tagString + "." + className, false);
+ if (childMapping != null) {
+ getStyles(childMapping, styles, tags, ids, classes,
+ counter + 1, numElements, alreadyChecked);
+ }
+ childMapping = parentMapping.getChildSelectorMapping(
+ "." + className, false);
+ if (childMapping != null) {
+ getStyles(childMapping, styles, tags, ids, classes,
+ counter + 1, numElements, alreadyChecked);
+ }
+ }
+ if (ids[counter] != null) {
+ String idName = ids[counter];
+ childMapping = parentMapping.getChildSelectorMapping(
+ tagString + "#" + idName, false);
+ if (childMapping != null) {
+ getStyles(childMapping, styles, tags, ids, classes,
+ counter + 1, numElements, alreadyChecked);
+ }
+ childMapping = parentMapping.getChildSelectorMapping(
+ "#" + idName, false);
+ if (childMapping != null) {
+ getStyles(childMapping, styles, tags, ids, classes,
+ counter + 1, numElements, alreadyChecked);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates and returns a Style containing all the rules that match
+ * <code>selector</code>.
+ */
+ private synchronized Style createResolvedStyle(String selector,
+ String[] tags,
+ String[] ids, String[] classes) {
+ SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
+ Vector tempVector = sb.getVector();
+ Hashtable tempHashtable = sb.getHashtable();
+ // Determine all the Styles that are appropriate, placing them
+ // in tempVector
+ try {
+ SelectorMapping mapping = getRootSelectorMapping();
+ int numElements = tags.length;
+ String tagString = tags[0];
+ SelectorMapping childMapping = mapping.getChildSelectorMapping(
+ tagString, false);
+ if (childMapping != null) {
+ getStyles(childMapping, tempVector, tags, ids, classes, 1,
+ numElements, tempHashtable);
+ }
+ if (classes[0] != null) {
+ String className = classes[0];
+ childMapping = mapping.getChildSelectorMapping(
+ tagString + "." + className, false);
+ if (childMapping != null) {
+ getStyles(childMapping, tempVector, tags, ids, classes, 1,
+ numElements, tempHashtable);
+ }
+ childMapping = mapping.getChildSelectorMapping(
+ "." + className, false);
+ if (childMapping != null) {
+ getStyles(childMapping, tempVector, tags, ids, classes,
+ 1, numElements, tempHashtable);
+ }
+ }
+ if (ids[0] != null) {
+ String idName = ids[0];
+ childMapping = mapping.getChildSelectorMapping(
+ tagString + "#" + idName, false);
+ if (childMapping != null) {
+ getStyles(childMapping, tempVector, tags, ids, classes,
+ 1, numElements, tempHashtable);
+ }
+ childMapping = mapping.getChildSelectorMapping(
+ "#" + idName, false);
+ if (childMapping != null) {
+ getStyles(childMapping, tempVector, tags, ids, classes,
+ 1, numElements, tempHashtable);
+ }
+ }
+ // Create a new Style that will delegate to all the matching
+ // Styles.
+ int numLinkedSS = (linkedStyleSheets != null) ?
+ linkedStyleSheets.size() : 0;
+ int numStyles = tempVector.size();
+ AttributeSet[] attrs = new AttributeSet[numStyles + numLinkedSS];
+ for (int counter = 0; counter < numStyles; counter++) {
+ attrs[counter] = ((SelectorMapping)tempVector.
+ elementAt(counter)).getStyle();
+ }
+ // Get the AttributeSet from linked style sheets.
+ for (int counter = 0; counter < numLinkedSS; counter++) {
+ AttributeSet attr = ((StyleSheet)linkedStyleSheets.
+ elementAt(counter)).getRule(selector);
+ if (attr == null) {
+ attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
+ }
+ else {
+ attrs[counter + numStyles] = attr;
+ }
+ }
+ ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
+ numStyles);
+ resolvedStyles.put(selector, retStyle);
+ return retStyle;
+ }
+ finally {
+ SearchBuffer.releaseSearchBuffer(sb);
+ }
+ }
+
+ /**
+ * Creates and returns a Style containing all the rules that
+ * matches <code>selector</code>.
+ *
+ * @param elements a Vector of all the Elements
+ * the style is being asked for. The
+ * first Element is the deepest Element, with the last Element
+ * representing the root.
+ * @param t the Tag to use for
+ * the first Element in <code>elements</code>
+ */
+ private Style createResolvedStyle(String selector, Vector elements,
+ HTML.Tag t) {
+ int numElements = elements.size();
+ // Build three arrays, one for tags, one for class's, and one for
+ // id's
+ String tags[] = new String[numElements];
+ String ids[] = new String[numElements];
+ String classes[] = new String[numElements];
+ for (int counter = 0; counter < numElements; counter++) {
+ Element e = (Element)elements.elementAt(counter);
+ AttributeSet attr = e.getAttributes();
+ if (counter == 0 && e.isLeaf()) {
+ // For leafs, we use the second tier attributes.
+ Object testAttr = attr.getAttribute(t);
+ if (testAttr instanceof AttributeSet) {
+ attr = (AttributeSet)testAttr;
+ }
+ else {
+ attr = null;
+ }
+ }
+ if (attr != null) {
+ HTML.Tag tag = (HTML.Tag)attr.getAttribute(StyleConstants.
+ NameAttribute);
+ if (tag != null) {
+ tags[counter] = tag.toString();
+ }
+ else {
+ tags[counter] = null;
+ }
+ if (attr.isDefined(HTML.Attribute.CLASS)) {
+ classes[counter] = attr.getAttribute
+ (HTML.Attribute.CLASS).toString();
+ }
+ else {
+ classes[counter] = null;
+ }
+ if (attr.isDefined(HTML.Attribute.ID)) {
+ ids[counter] = attr.getAttribute(HTML.Attribute.ID).
+ toString();
+ }
+ else {
+ ids[counter] = null;
+ }
+ }
+ else {
+ tags[counter] = ids[counter] = classes[counter] = null;
+ }
+ }
+ tags[0] = t.toString();
+ return createResolvedStyle(selector, tags, ids, classes);
+ }
+
+ /**
+ * Creates and returns a Style containing all the rules that match
+ * <code>selector</code>. It is assumed that each simple selector
+ * in <code>selector</code> is separated by a space.
+ */
+ private Style createResolvedStyle(String selector) {
+ SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
+ // Will contain the tags, ids, and classes, in that order.
+ Vector elements = sb.getVector();
+ try {
+ boolean done;
+ int dotIndex = 0;
+ int spaceIndex = 0;
+ int poundIndex = 0;
+ int lastIndex = 0;
+ int length = selector.length();
+ while (lastIndex < length) {
+ if (dotIndex == lastIndex) {
+ dotIndex = selector.indexOf('.', lastIndex);
+ }
+ if (poundIndex == lastIndex) {
+ poundIndex = selector.indexOf('#', lastIndex);
+ }
+ spaceIndex = selector.indexOf(' ', lastIndex);
+ if (spaceIndex == -1) {
+ spaceIndex = length;
+ }
+ if (dotIndex != -1 && poundIndex != -1 &&
+ dotIndex < spaceIndex && poundIndex < spaceIndex) {
+ if (poundIndex < dotIndex) {
+ // #.
+ if (lastIndex == poundIndex) {
+ elements.addElement("");
+ }
+ else {
+ elements.addElement(selector.substring(lastIndex,
+ poundIndex));
+ }
+ if ((dotIndex + 1) < spaceIndex) {
+ elements.addElement(selector.substring
+ (dotIndex + 1, spaceIndex));
+ }
+ else {
+ elements.addElement(null);
+ }
+ if ((poundIndex + 1) == dotIndex) {
+ elements.addElement(null);
+ }
+ else {
+ elements.addElement(selector.substring
+ (poundIndex + 1, dotIndex));
+ }
+ }
+ else if(poundIndex < spaceIndex) {
+ // .#
+ if (lastIndex == dotIndex) {
+ elements.addElement("");
+ }
+ else {
+ elements.addElement(selector.substring(lastIndex,
+ dotIndex));
+ }
+ if ((dotIndex + 1) < poundIndex) {
+ elements.addElement(selector.substring
+ (dotIndex + 1, poundIndex));
+ }
+ else {
+ elements.addElement(null);
+ }
+ if ((poundIndex + 1) == spaceIndex) {
+ elements.addElement(null);
+ }
+ else {
+ elements.addElement(selector.substring
+ (poundIndex + 1, spaceIndex));
+ }
+ }
+ dotIndex = poundIndex = spaceIndex + 1;
+ }
+ else if (dotIndex != -1 && dotIndex < spaceIndex) {
+ // .
+ if (dotIndex == lastIndex) {
+ elements.addElement("");
+ }
+ else {
+ elements.addElement(selector.substring(lastIndex,
+ dotIndex));
+ }
+ if ((dotIndex + 1) == spaceIndex) {
+ elements.addElement(null);
+ }
+ else {
+ elements.addElement(selector.substring(dotIndex + 1,
+ spaceIndex));
+ }
+ elements.addElement(null);
+ dotIndex = spaceIndex + 1;
+ }
+ else if (poundIndex != -1 && poundIndex < spaceIndex) {
+ // #
+ if (poundIndex == lastIndex) {
+ elements.addElement("");
+ }
+ else {
+ elements.addElement(selector.substring(lastIndex,
+ poundIndex));
+ }
+ elements.addElement(null);
+ if ((poundIndex + 1) == spaceIndex) {
+ elements.addElement(null);
+ }
+ else {
+ elements.addElement(selector.substring(poundIndex + 1,
+ spaceIndex));
+ }
+ poundIndex = spaceIndex + 1;
+ }
+ else {
+ // id
+ elements.addElement(selector.substring(lastIndex,
+ spaceIndex));
+ elements.addElement(null);
+ elements.addElement(null);
+ }
+ lastIndex = spaceIndex + 1;
+ }
+ // Create the tag, id, and class arrays.
+ int total = elements.size();
+ int numTags = total / 3;
+ String[] tags = new String[numTags];
+ String[] ids = new String[numTags];
+ String[] classes = new String[numTags];
+ for (int index = 0, eIndex = total - 3; index < numTags;
+ index++, eIndex -= 3) {
+ tags[index] = (String)elements.elementAt(eIndex);
+ classes[index] = (String)elements.elementAt(eIndex + 1);
+ ids[index] = (String)elements.elementAt(eIndex + 2);
+ }
+ return createResolvedStyle(selector, tags, ids, classes);
+ }
+ finally {
+ SearchBuffer.releaseSearchBuffer(sb);
+ }
+ }
+
+ /**
+ * Should be invoked when a new rule is added that did not previously
+ * exist. Goes through and refreshes the necessary resolved
+ * rules.
+ */
+ private synchronized void refreshResolvedRules(String selectorName,
+ String[] selector,
+ Style newStyle,
+ int specificity) {
+ if (resolvedStyles.size() > 0) {
+ Enumeration values = resolvedStyles.elements();
+ while (values.hasMoreElements()) {
+ ResolvedStyle style = (ResolvedStyle)values.nextElement();
+ if (style.matches(selectorName)) {
+ style.insertStyle(newStyle, specificity);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * A temporary class used to hold a Vector, a StringBuffer and a
+ * Hashtable. This is used to avoid allocing a lot of garbage when
+ * searching for rules. Use the static method obtainSearchBuffer and
+ * releaseSearchBuffer to get a SearchBuffer, and release it when
+ * done.
+ */
+ private static class SearchBuffer {
+ /** A stack containing instances of SearchBuffer. Used in getting
+ * rules. */
+ static Stack searchBuffers = new Stack();
+ // A set of temporary variables that can be used in whatever way.
+ Vector vector = null;
+ StringBuffer stringBuffer = null;
+ Hashtable hashtable = null;
+
+ /**
+ * Returns an instance of SearchBuffer. Be sure and issue
+ * a releaseSearchBuffer when done with it.
+ */
+ static SearchBuffer obtainSearchBuffer() {
+ SearchBuffer sb;
+ try {
+ if(!searchBuffers.empty()) {
+ sb = (SearchBuffer)searchBuffers.pop();
+ } else {
+ sb = new SearchBuffer();
+ }
+ } catch (EmptyStackException ese) {
+ sb = new SearchBuffer();
+ }
+ return sb;
+ }
+
+ /**
+ * Adds <code>sb</code> to the stack of SearchBuffers that can
+ * be used.
+ */
+ static void releaseSearchBuffer(SearchBuffer sb) {
+ sb.empty();
+ searchBuffers.push(sb);
+ }
+
+ StringBuffer getStringBuffer() {
+ if (stringBuffer == null) {
+ stringBuffer = new StringBuffer();
+ }
+ return stringBuffer;
+ }
+
+ Vector getVector() {
+ if (vector == null) {
+ vector = new Vector();
+ }
+ return vector;
+ }
+
+ Hashtable getHashtable() {
+ if (hashtable == null) {
+ hashtable = new Hashtable();
+ }
+ return hashtable;
+ }
+
+ void empty() {
+ if (stringBuffer != null) {
+ stringBuffer.setLength(0);
+ }
+ if (vector != null) {
+ vector.removeAllElements();
+ }
+ if (hashtable != null) {
+ hashtable.clear();
+ }
+ }
+ }
+
+
+ static final Border noBorder = new EmptyBorder(0,0,0,0);
+
+ /**
+ * Class to carry out some of the duties of
+ * CSS formatting. Implementations of this
+ * class enable views to present the CSS formatting
+ * while not knowing anything about how the CSS values
+ * are being cached.
+ * <p>
+ * As a delegate of Views, this object is responsible for
+ * the insets of a View and making sure the background
+ * is maintained according to the CSS attributes.
+ */
+ public static class BoxPainter implements Serializable {
+
+ BoxPainter(AttributeSet a, CSS css, StyleSheet ss) {
+ this.ss = ss;
+ this.css = css;
+ border = getBorder(a);
+ binsets = border.getBorderInsets(null);
+ topMargin = getLength(CSS.Attribute.MARGIN_TOP, a);
+ bottomMargin = getLength(CSS.Attribute.MARGIN_BOTTOM, a);
+ leftMargin = getLength(CSS.Attribute.MARGIN_LEFT, a);
+ rightMargin = getLength(CSS.Attribute.MARGIN_RIGHT, a);
+ bg = ss.getBackground(a);
+ if (ss.getBackgroundImage(a) != null) {
+ bgPainter = new BackgroundImagePainter(a, css, ss);
+ }
+ }
+
+ /**
+ * Fetches a border to render for the given attributes.
+ * PENDING(prinz) This is pretty badly hacked at the
+ * moment.
+ */
+ Border getBorder(AttributeSet a) {
+ return new CSSBorder(a);
+ }
+
+ /**
+ * Fetches the color to use for borders. This will either be
+ * the value specified by the border-color attribute (which
+ * is not inherited), or it will default to the color attribute
+ * (which is inherited).
+ */
+ Color getBorderColor(AttributeSet a) {
+ Color color = css.getColor(a, CSS.Attribute.BORDER_COLOR);
+ if (color == null) {
+ color = css.getColor(a, CSS.Attribute.COLOR);
+ if (color == null) {
+ return Color.black;
+ }
+ }
+ return color;
+ }
+
+ /**
+ * Fetches the inset needed on a given side to
+ * account for the margin, border, and padding.
+ *
+ * @param side The size of the box to fetch the
+ * inset for. This can be View.TOP,
+ * View.LEFT, View.BOTTOM, or View.RIGHT.
+ * @param v the view making the request. This is
+ * used to get the AttributeSet, and may be used to
+ * resolve percentage arguments.
+ * @exception IllegalArgumentException for an invalid direction
+ */
+ public float getInset(int side, View v) {
+ AttributeSet a = v.getAttributes();
+ float inset = 0;
+ switch(side) {
+ case View.LEFT:
+ inset += getOrientationMargin(HorizontalMargin.LEFT,
+ leftMargin, a, isLeftToRight(v));
+ inset += binsets.left;
+ inset += getLength(CSS.Attribute.PADDING_LEFT, a);
+ break;
+ case View.RIGHT:
+ inset += getOrientationMargin(HorizontalMargin.RIGHT,
+ rightMargin, a, isLeftToRight(v));
+ inset += binsets.right;
+ inset += getLength(CSS.Attribute.PADDING_RIGHT, a);
+ break;
+ case View.TOP:
+ inset += topMargin;
+ inset += binsets.top;
+ inset += getLength(CSS.Attribute.PADDING_TOP, a);
+ break;
+ case View.BOTTOM:
+ inset += bottomMargin;
+ inset += binsets.bottom;
+ inset += getLength(CSS.Attribute.PADDING_BOTTOM, a);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid side: " + side);
+ }
+ return inset;
+ }
+
+ /**
+ * Paints the CSS box according to the attributes
+ * given. This should paint the border, padding,
+ * and background.
+ *
+ * @param g the rendering surface.
+ * @param x the x coordinate of the allocated area to
+ * render into.
+ * @param y the y coordinate of the allocated area to
+ * render into.
+ * @param w the width of the allocated area to render into.
+ * @param h the height of the allocated area to render into.
+ * @param v the view making the request. This is
+ * used to get the AttributeSet, and may be used to
+ * resolve percentage arguments.
+ */
+ public void paint(Graphics g, float x, float y, float w, float h, View v) {
+ // PENDING(prinz) implement real rendering... which would
+ // do full set of border and background capabilities.
+ // remove margin
+
+ float dx = 0;
+ float dy = 0;
+ float dw = 0;
+ float dh = 0;
+ AttributeSet a = v.getAttributes();
+ boolean isLeftToRight = isLeftToRight(v);
+ float localLeftMargin = getOrientationMargin(HorizontalMargin.LEFT,
+ leftMargin,
+ a, isLeftToRight);
+ float localRightMargin = getOrientationMargin(HorizontalMargin.RIGHT,
+ rightMargin,
+ a, isLeftToRight);
+ if (!(v instanceof HTMLEditorKit.HTMLFactory.BodyBlockView)) {
+ dx = localLeftMargin;
+ dy = topMargin;
+ dw = -(localLeftMargin + localRightMargin);
+ dh = -(topMargin + bottomMargin);
+ }
+ if (bg != null) {
+ g.setColor(bg);
+ g.fillRect((int) (x + dx),
+ (int) (y + dy),
+ (int) (w + dw),
+ (int) (h + dh));
+ }
+ if (bgPainter != null) {
+ bgPainter.paint(g, x + dx, y + dy, w + dw, h + dh, v);
+ }
+ x += localLeftMargin;
+ y += topMargin;
+ w -= localLeftMargin + localRightMargin;
+ h -= topMargin + bottomMargin;
+ if (border instanceof BevelBorder) {
+ //BevelBorder does not support border width
+ int bw = (int) getLength(CSS.Attribute.BORDER_TOP_WIDTH, a);
+ for (int i = bw - 1; i >= 0; i--) {
+ border.paintBorder(null, g, (int) x + i, (int) y + i,
+ (int) w - 2 * i, (int) h - 2 * i);
+ }
+ } else {
+ border.paintBorder(null, g, (int) x, (int) y, (int) w, (int) h);
+ }
+ }
+
+ float getLength(CSS.Attribute key, AttributeSet a) {
+ return css.getLength(a, key, ss);
+ }
+
+ static boolean isLeftToRight(View v) {
+ boolean ret = true;
+ if (isOrientationAware(v)) {
+ Container container = null;
+ if (v != null && (container = v.getContainer()) != null) {
+ ret = container.getComponentOrientation().isLeftToRight();
+ }
+ }
+ return ret;
+ }
+
+ /*
+ * only certain tags are concerned about orientation
+ * <dir>, <menu>, <ul>, <ol>
+ * for all others we return true. It is implemented this way
+ * for performance purposes
+ */
+ static boolean isOrientationAware(View v) {
+ boolean ret = false;
+ AttributeSet attr = null;
+ Object obj = null;
+ if (v != null
+ && (attr = v.getElement().getAttributes()) != null
+ && (obj = attr.getAttribute(StyleConstants.NameAttribute)) instanceof HTML.Tag
+ && (obj == HTML.Tag.DIR
+ || obj == HTML.Tag.MENU
+ || obj == HTML.Tag.UL
+ || obj == HTML.Tag.OL)) {
+ ret = true;
+ }
+
+ return ret;
+ }
+
+ static enum HorizontalMargin { LEFT, RIGHT };
+
+ /**
+ * for <dir>, <menu>, <ul> etc.
+ * margins are Left-To-Right/Right-To-Left depended.
+ * see 5088268 for more details
+ * margin-(left|right)-(ltr|rtl) were introduced to describe it
+ * if margin-(left|right) is present we are to use it.
+ *
+ * @param side The horizontal side to fetch margin for
+ * This can be HorizontalMargin.LEFT or HorizontalMargin.RIGHT
+ * @param cssMargin margin from css
+ * @param a AttributeSet for the View we getting margin for
+ * @param isLeftToRight
+ * @return orientation depended margin
+ */
+ float getOrientationMargin(HorizontalMargin side, float cssMargin,
+ AttributeSet a, boolean isLeftToRight) {
+ float margin = cssMargin;
+ float orientationMargin = cssMargin;
+ Object cssMarginValue = null;
+ switch (side) {
+ case RIGHT:
+ {
+ orientationMargin = (isLeftToRight) ?
+ getLength(CSS.Attribute.MARGIN_RIGHT_LTR, a) :
+ getLength(CSS.Attribute.MARGIN_RIGHT_RTL, a);
+ cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_RIGHT);
+ }
+ break;
+ case LEFT :
+ {
+ orientationMargin = (isLeftToRight) ?
+ getLength(CSS.Attribute.MARGIN_LEFT_LTR, a) :
+ getLength(CSS.Attribute.MARGIN_LEFT_RTL, a);
+ cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_LEFT);
+ }
+ break;
+ }
+
+ if (cssMarginValue == null
+ && orientationMargin != Integer.MIN_VALUE) {
+ margin = orientationMargin;
+ }
+ return margin;
+ }
+
+ float topMargin;
+ float bottomMargin;
+ float leftMargin;
+ float rightMargin;
+ // Bitmask, used to indicate what margins are relative:
+ // bit 0 for top, 1 for bottom, 2 for left and 3 for right.
+ short marginFlags;
+ Border border;
+ Insets binsets;
+ CSS css;
+ StyleSheet ss;
+ Color bg;
+ BackgroundImagePainter bgPainter;
+ }
+
+ /**
+ * Class to carry out some of the duties of CSS list
+ * formatting. Implementations of this
+ * class enable views to present the CSS formatting
+ * while not knowing anything about how the CSS values
+ * are being cached.
+ */
+ public static class ListPainter implements Serializable {
+
+ ListPainter(AttributeSet attr, StyleSheet ss) {
+ this.ss = ss;
+ /* Get the image to use as a list bullet */
+ String imgstr = (String)attr.getAttribute(CSS.Attribute.
+ LIST_STYLE_IMAGE);
+ type = null;
+ if (imgstr != null && !imgstr.equals("none")) {
+ String tmpstr = null;
+ try {
+ StringTokenizer st = new StringTokenizer(imgstr, "()");
+ if (st.hasMoreTokens())
+ tmpstr = st.nextToken();
+ if (st.hasMoreTokens())
+ tmpstr = st.nextToken();
+ URL u = new URL(tmpstr);
+ img = new ImageIcon(u);
+ } catch (MalformedURLException e) {
+ if (tmpstr != null && ss != null && ss.getBase() != null) {
+ try {
+ URL u = new URL(ss.getBase(), tmpstr);
+ img = new ImageIcon(u);
+ } catch (MalformedURLException murle) {
+ img = null;
+ }
+ }
+ else {
+ img = null;
+ }
+ }
+ }
+
+ /* Get the type of bullet to use in the list */
+ if (img == null) {
+ type = (CSS.Value)attr.getAttribute(CSS.Attribute.
+ LIST_STYLE_TYPE);
+ }
+ start = 1;
+
+ paintRect = new Rectangle();
+ }
+
+ /**
+ * Returns a string that represents the value
+ * of the HTML.Attribute.TYPE attribute.
+ * If this attributes is not defined, then
+ * then the type defaults to "disc" unless
+ * the tag is on Ordered list. In the case
+ * of the latter, the default type is "decimal".
+ */
+ private CSS.Value getChildType(View childView) {
+ CSS.Value childtype = (CSS.Value)childView.getAttributes().
+ getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
+
+ if (childtype == null) {
+ if (type == null) {
+ // Parent view.
+ View v = childView.getParent();
+ HTMLDocument doc = (HTMLDocument)v.getDocument();
+ if (doc.matchNameAttribute(v.getElement().getAttributes(),
+ HTML.Tag.OL)) {
+ childtype = CSS.Value.DECIMAL;
+ } else {
+ childtype = CSS.Value.DISC;
+ }
+ } else {
+ childtype = type;
+ }
+ }
+ return childtype;
+ }
+
+ /**
+ * Obtains the starting index from <code>parent</code>.
+ */
+ private void getStart(View parent) {
+ checkedForStart = true;
+ Element element = parent.getElement();
+ if (element != null) {
+ AttributeSet attr = element.getAttributes();
+ Object startValue;
+ if (attr != null && attr.isDefined(HTML.Attribute.START) &&
+ (startValue = attr.getAttribute
+ (HTML.Attribute.START)) != null &&
+ (startValue instanceof String)) {
+
+ try {
+ start = Integer.parseInt((String)startValue);
+ }
+ catch (NumberFormatException nfe) {}
+ }
+ }
+ }
+
+ /**
+ * Returns an integer that should be used to render the child at
+ * <code>childIndex</code> with. The retValue will usually be
+ * <code>childIndex</code> + 1, unless <code>parentView</code>
+ * has some Views that do not represent LI's, or one of the views
+ * has a HTML.Attribute.START specified.
+ */
+ private int getRenderIndex(View parentView, int childIndex) {
+ if (!checkedForStart) {
+ getStart(parentView);
+ }
+ int retIndex = childIndex;
+ for (int counter = childIndex; counter >= 0; counter--) {
+ AttributeSet as = parentView.getElement().getElement(counter).
+ getAttributes();
+ if (as.getAttribute(StyleConstants.NameAttribute) !=
+ HTML.Tag.LI) {
+ retIndex--;
+ } else if (as.isDefined(HTML.Attribute.VALUE)) {
+ Object value = as.getAttribute(HTML.Attribute.VALUE);
+ if (value != null &&
+ (value instanceof String)) {
+ try {
+ int iValue = Integer.parseInt((String)value);
+ return retIndex - counter + iValue;
+ }
+ catch (NumberFormatException nfe) {}
+ }
+ }
+ }
+ return retIndex + start;
+ }
+
+ /**
+ * Paints the CSS list decoration according to the
+ * attributes given.
+ *
+ * @param g the rendering surface.
+ * @param x the x coordinate of the list item allocation
+ * @param y the y coordinate of the list item allocation
+ * @param w the width of the list item allocation
+ * @param h the height of the list item allocation
+ * @param v the allocated area to paint into.
+ * @param item which list item is being painted. This
+ * is a number greater than or equal to 0.
+ */
+ public void paint(Graphics g, float x, float y, float w, float h, View v, int item) {
+ View cv = v.getView(item);
+ Object name = cv.getElement().getAttributes().getAttribute
+ (StyleConstants.NameAttribute);
+ // Only draw something if the View is a list item. This won't
+ // be the case for comments.
+ if (!(name instanceof HTML.Tag) ||
+ name != HTML.Tag.LI) {
+ return;
+ }
+ // deside on what side draw bullets, etc.
+ isLeftToRight =
+ cv.getContainer().getComponentOrientation().isLeftToRight();
+
+ // How the list indicator is aligned is not specified, it is
+ // left up to the UA. IE and NS differ on this behavior.
+ // This is closer to NS where we align to the first line of text.
+ // If the child is not text we draw the indicator at the
+ // origin (0).
+ float align = 0;
+ if (cv.getViewCount() > 0) {
+ View pView = cv.getView(0);
+ Object cName = pView.getElement().getAttributes().
+ getAttribute(StyleConstants.NameAttribute);
+ if ((cName == HTML.Tag.P || cName == HTML.Tag.IMPLIED) &&
+ pView.getViewCount() > 0) {
+ paintRect.setBounds((int)x, (int)y, (int)w, (int)h);
+ Shape shape = cv.getChildAllocation(0, paintRect);
+ if (shape != null && (shape = pView.getView(0).
+ getChildAllocation(0, shape)) != null) {
+ Rectangle rect = (shape instanceof Rectangle) ?
+ (Rectangle)shape : shape.getBounds();
+
+ align = pView.getView(0).getAlignment(View.Y_AXIS);
+ y = rect.y;
+ h = rect.height;
+ }
+ }
+ }
+
+ // set the color of a decoration
+ if (ss != null) {
+ g.setColor(ss.getForeground(cv.getAttributes()));
+ } else {
+ g.setColor(Color.black);
+ }
+
+ if (img != null) {
+ drawIcon(g, (int) x, (int) y, (int) w, (int) h, align,
+ v.getContainer());
+ return;
+ }
+ CSS.Value childtype = getChildType(cv);
+ Font font = ((StyledDocument)cv.getDocument()).
+ getFont(cv.getAttributes());
+ if (font != null) {
+ g.setFont(font);
+ }
+ if (childtype == CSS.Value.SQUARE || childtype == CSS.Value.CIRCLE
+ || childtype == CSS.Value.DISC) {
+ drawShape(g, childtype, (int) x, (int) y,
+ (int) w, (int) h, align);
+ } else if (childtype == CSS.Value.DECIMAL) {
+ drawLetter(g, '1', (int) x, (int) y, (int) w, (int) h, align,
+ getRenderIndex(v, item));
+ } else if (childtype == CSS.Value.LOWER_ALPHA) {
+ drawLetter(g, 'a', (int) x, (int) y, (int) w, (int) h, align,
+ getRenderIndex(v, item));
+ } else if (childtype == CSS.Value.UPPER_ALPHA) {
+ drawLetter(g, 'A', (int) x, (int) y, (int) w, (int) h, align,
+ getRenderIndex(v, item));
+ } else if (childtype == CSS.Value.LOWER_ROMAN) {
+ drawLetter(g, 'i', (int) x, (int) y, (int) w, (int) h, align,
+ getRenderIndex(v, item));
+ } else if (childtype == CSS.Value.UPPER_ROMAN) {
+ drawLetter(g, 'I', (int) x, (int) y, (int) w, (int) h, align,
+ getRenderIndex(v, item));
+ }
+ }
+
+ /**
+ * Draws the bullet icon specified by the list-style-image argument.
+ *
+ * @param g the graphics context
+ * @param ax x coordinate to place the bullet
+ * @param ay y coordinate to place the bullet
+ * @param aw width of the container the bullet is placed in
+ * @param ah height of the container the bullet is placed in
+ * @param align preferred alignment factor for the child view
+ */
+ void drawIcon(Graphics g, int ax, int ay, int aw, int ah,
+ float align, Component c) {
+ // Align to bottom of icon.
+ int gap = isLeftToRight ? - (img.getIconWidth() + bulletgap) :
+ (aw + bulletgap);
+ int x = ax + gap;
+ int y = Math.max(ay, ay + (int)(align * ah) -img.getIconHeight());
+
+ img.paintIcon(c, g, x, y);
+ }
+
+ /**
+ * Draws the graphical bullet item specified by the type argument.
+ *
+ * @param g the graphics context
+ * @param type type of bullet to draw (circle, square, disc)
+ * @param ax x coordinate to place the bullet
+ * @param ay y coordinate to place the bullet
+ * @param aw width of the container the bullet is placed in
+ * @param ah height of the container the bullet is placed in
+ * @param align preferred alignment factor for the child view
+ */
+ void drawShape(Graphics g, CSS.Value type, int ax, int ay, int aw,
+ int ah, float align) {
+ // Align to bottom of shape.
+ int gap = isLeftToRight ? - (bulletgap + 8) : (aw + bulletgap);
+ int x = ax + gap;
+ int y = Math.max(ay, ay + (int)(align * ah) - 8);
+
+ if (type == CSS.Value.SQUARE) {
+ g.drawRect(x, y, 8, 8);
+ } else if (type == CSS.Value.CIRCLE) {
+ g.drawOval(x, y, 8, 8);
+ } else {
+ g.fillOval(x, y, 8, 8);
+ }
+ }
+
+ /**
+ * Draws the letter or number for an ordered list.
+ *
+ * @param g the graphics context
+ * @param letter type of ordered list to draw
+ * @param ax x coordinate to place the bullet
+ * @param ay y coordinate to place the bullet
+ * @param aw width of the container the bullet is placed in
+ * @param ah height of the container the bullet is placed in
+ * @param index position of the list item in the list
+ */
+ void drawLetter(Graphics g, char letter, int ax, int ay, int aw,
+ int ah, float align, int index) {
+ String str = formatItemNum(index, letter);
+ str = isLeftToRight ? str + "." : "." + str;
+ FontMetrics fm = SwingUtilities2.getFontMetrics(null, g);
+ int stringwidth = SwingUtilities2.stringWidth(null, fm, str);
+ int gap = isLeftToRight ? - (stringwidth + bulletgap) :
+ (aw + bulletgap);
+ int x = ax + gap;
+ int y = Math.max(ay + fm.getAscent(), ay + (int)(ah * align));
+ SwingUtilities2.drawString(null, g, str, x, y);
+ }
+
+ /**
+ * Converts the item number into the ordered list number
+ * (i.e. 1 2 3, i ii iii, a b c, etc.
+ *
+ * @param itemNum number to format
+ * @param type type of ordered list
+ */
+ String formatItemNum(int itemNum, char type) {
+ String numStyle = "1";
+
+ boolean uppercase = false;
+
+ String formattedNum;
+
+ switch (type) {
+ case '1':
+ default:
+ formattedNum = String.valueOf(itemNum);
+ break;
+
+ case 'A':
+ uppercase = true;
+ // fall through
+ case 'a':
+ formattedNum = formatAlphaNumerals(itemNum);
+ break;
+
+ case 'I':
+ uppercase = true;
+ // fall through
+ case 'i':
+ formattedNum = formatRomanNumerals(itemNum);
+ }
+
+ if (uppercase) {
+ formattedNum = formattedNum.toUpperCase();
+ }
+
+ return formattedNum;
+ }
+
+ /**
+ * Converts the item number into an alphabetic character
+ *
+ * @param itemNum number to format
+ */
+ String formatAlphaNumerals(int itemNum) {
+ String result = "";
+
+ if (itemNum > 26) {
+ result = formatAlphaNumerals(itemNum / 26) +
+ formatAlphaNumerals(itemNum % 26);
+ } else {
+ // -1 because item is 1 based.
+ result = String.valueOf((char)('a' + itemNum - 1));
+ }
+
+ return result;
+ }
+
+ /* list of roman numerals */
+ static final char romanChars[][] = {
+ {'i', 'v'},
+ {'x', 'l' },
+ {'c', 'd' },
+ {'m', '?' },
+ };
+
+ /**
+ * Converts the item number into a roman numeral
+ *
+ * @param num number to format
+ */
+ String formatRomanNumerals(int num) {
+ return formatRomanNumerals(0, num);
+ }
+
+ /**
+ * Converts the item number into a roman numeral
+ *
+ * @param num number to format
+ */
+ String formatRomanNumerals(int level, int num) {
+ if (num < 10) {
+ return formatRomanDigit(level, num);
+ } else {
+ return formatRomanNumerals(level + 1, num / 10) +
+ formatRomanDigit(level, num % 10);
+ }
+ }
+
+
+ /**
+ * Converts the item number into a roman numeral
+ *
+ * @param level position
+ * @param num digit to format
+ */
+ String formatRomanDigit(int level, int digit) {
+ String result = "";
+ if (digit == 9) {
+ result = result + romanChars[level][0];
+ result = result + romanChars[level + 1][0];
+ return result;
+ } else if (digit == 4) {
+ result = result + romanChars[level][0];
+ result = result + romanChars[level][1];
+ return result;
+ } else if (digit >= 5) {
+ result = result + romanChars[level][1];
+ digit -= 5;
+ }
+
+ for (int i = 0; i < digit; i++) {
+ result = result + romanChars[level][0];
+ }
+
+ return result;
+ }
+
+ private Rectangle paintRect;
+ private boolean checkedForStart;
+ private int start;
+ private CSS.Value type;
+ URL imageurl;
+ private StyleSheet ss = null;
+ Icon img = null;
+ private int bulletgap = 5;
+ private boolean isLeftToRight;
+ }
+
+
+ /**
+ * Paints the background image.
+ */
+ static class BackgroundImagePainter implements Serializable {
+ ImageIcon backgroundImage;
+ float hPosition;
+ float vPosition;
+ // bit mask: 0 for repeat x, 1 for repeat y, 2 for horiz relative,
+ // 3 for vert relative
+ short flags;
+ // These are used when painting, updatePaintCoordinates updates them.
+ private int paintX;
+ private int paintY;
+ private int paintMaxX;
+ private int paintMaxY;
+
+ BackgroundImagePainter(AttributeSet a, CSS css, StyleSheet ss) {
+ backgroundImage = ss.getBackgroundImage(a);
+ // Determine the position.
+ CSS.BackgroundPosition pos = (CSS.BackgroundPosition)a.getAttribute
+ (CSS.Attribute.BACKGROUND_POSITION);
+ if (pos != null) {
+ hPosition = pos.getHorizontalPosition();
+ vPosition = pos.getVerticalPosition();
+ if (pos.isHorizontalPositionRelativeToSize()) {
+ flags |= 4;
+ }
+ else if (pos.isHorizontalPositionRelativeToSize()) {
+ hPosition *= css.getFontSize(a, 12, ss);
+ }
+ if (pos.isVerticalPositionRelativeToSize()) {
+ flags |= 8;
+ }
+ else if (pos.isVerticalPositionRelativeToFontSize()) {
+ vPosition *= css.getFontSize(a, 12, ss);
+ }
+ }
+ // Determine any repeating values.
+ CSS.Value repeats = (CSS.Value)a.getAttribute(CSS.Attribute.
+ BACKGROUND_REPEAT);
+ if (repeats == null || repeats == CSS.Value.BACKGROUND_REPEAT) {
+ flags |= 3;
+ }
+ else if (repeats == CSS.Value.BACKGROUND_REPEAT_X) {
+ flags |= 1;
+ }
+ else if (repeats == CSS.Value.BACKGROUND_REPEAT_Y) {
+ flags |= 2;
+ }
+ }
+
+ void paint(Graphics g, float x, float y, float w, float h, View v) {
+ Rectangle clip = g.getClipRect();
+ if (clip != null) {
+ // Constrain the clip so that images don't draw outside the
+ // legal bounds.
+ g.clipRect((int)x, (int)y, (int)w, (int)h);
+ }
+ if ((flags & 3) == 0) {
+ // no repeating
+ int width = backgroundImage.getIconWidth();
+ int height = backgroundImage.getIconWidth();
+ if ((flags & 4) == 4) {
+ paintX = (int)(x + w * hPosition -
+ (float)width * hPosition);
+ }
+ else {
+ paintX = (int)x + (int)hPosition;
+ }
+ if ((flags & 8) == 8) {
+ paintY = (int)(y + h * vPosition -
+ (float)height * vPosition);
+ }
+ else {
+ paintY = (int)y + (int)vPosition;
+ }
+ if (clip == null ||
+ !((paintX + width <= clip.x) ||
+ (paintY + height <= clip.y) ||
+ (paintX >= clip.x + clip.width) ||
+ (paintY >= clip.y + clip.height))) {
+ backgroundImage.paintIcon(null, g, paintX, paintY);
+ }
+ }
+ else {
+ int width = backgroundImage.getIconWidth();
+ int height = backgroundImage.getIconHeight();
+ if (width > 0 && height > 0) {
+ paintX = (int)x;
+ paintY = (int)y;
+ paintMaxX = (int)(x + w);
+ paintMaxY = (int)(y + h);
+ if (updatePaintCoordinates(clip, width, height)) {
+ while (paintX < paintMaxX) {
+ int ySpot = paintY;
+ while (ySpot < paintMaxY) {
+ backgroundImage.paintIcon(null, g, paintX,
+ ySpot);
+ ySpot += height;
+ }
+ paintX += width;
+ }
+ }
+ }
+ }
+ if (clip != null) {
+ // Reset clip.
+ g.setClip(clip.x, clip.y, clip.width, clip.height);
+ }
+ }
+
+ private boolean updatePaintCoordinates
+ (Rectangle clip, int width, int height){
+ if ((flags & 3) == 1) {
+ paintMaxY = paintY + 1;
+ }
+ else if ((flags & 3) == 2) {
+ paintMaxX = paintX + 1;
+ }
+ if (clip != null) {
+ if ((flags & 3) == 1 && ((paintY + height <= clip.y) ||
+ (paintY > clip.y + clip.height))) {
+ // not visible.
+ return false;
+ }
+ if ((flags & 3) == 2 && ((paintX + width <= clip.x) ||
+ (paintX > clip.x + clip.width))) {
+ // not visible.
+ return false;
+ }
+ if ((flags & 1) == 1) {
+ if ((clip.x + clip.width) < paintMaxX) {
+ if ((clip.x + clip.width - paintX) % width == 0) {
+ paintMaxX = clip.x + clip.width;
+ }
+ else {
+ paintMaxX = ((clip.x + clip.width - paintX) /
+ width + 1) * width + paintX;
+ }
+ }
+ if (clip.x > paintX) {
+ paintX = (clip.x - paintX) / width * width + paintX;
+ }
+ }
+ if ((flags & 2) == 2) {
+ if ((clip.y + clip.height) < paintMaxY) {
+ if ((clip.y + clip.height - paintY) % height == 0) {
+ paintMaxY = clip.y + clip.height;
+ }
+ else {
+ paintMaxY = ((clip.y + clip.height - paintY) /
+ height + 1) * height + paintY;
+ }
+ }
+ if (clip.y > paintY) {
+ paintY = (clip.y - paintY) / height * height + paintY;
+ }
+ }
+ }
+ // Valid
+ return true;
+ }
+ }
+
+
+ /**
+ * A subclass of MuxingAttributeSet that translates between
+ * CSS and HTML and StyleConstants. The AttributeSets used are
+ * the CSS rules that match the Views Elements.
+ */
+ class ViewAttributeSet extends MuxingAttributeSet {
+ ViewAttributeSet(View v) {
+ host = v;
+
+ // PENDING(prinz) fix this up to be a more realistic
+ // implementation.
+ Document doc = v.getDocument();
+ SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
+ Vector muxList = sb.getVector();
+ try {
+ if (doc instanceof HTMLDocument) {
+ StyleSheet styles = StyleSheet.this;
+ Element elem = v.getElement();
+ AttributeSet a = elem.getAttributes();
+ AttributeSet htmlAttr = styles.translateHTMLToCSS(a);
+
+ if (htmlAttr.getAttributeCount() != 0) {
+ muxList.addElement(htmlAttr);
+ }
+ if (elem.isLeaf()) {
+ Enumeration keys = a.getAttributeNames();
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ if (key instanceof HTML.Tag) {
+ if ((HTML.Tag)key == HTML.Tag.A) {
+ Object o = a.getAttribute((HTML.Tag)key);
+ /**
+ In the case of an A tag, the css rules
+ apply only for tags that have their
+ href attribute defined and not for
+ anchors that only have their name attributes
+ defined, i.e anchors that function as
+ destinations. Hence we do not add the
+ attributes for that latter kind of
+ anchors. When CSS2 support is added,
+ it will be possible to specificity this
+ kind of conditional behaviour in the
+ stylesheet.
+ **/
+ if (o != null && o instanceof AttributeSet) {
+ AttributeSet attr = (AttributeSet)o;
+ if (attr.getAttribute(HTML.Attribute.HREF) == null) {
+ continue;
+ }
+ }
+ }
+ AttributeSet cssRule = styles.getRule((HTML.Tag) key, elem);
+ if (cssRule != null) {
+ muxList.addElement(cssRule);
+ }
+ }
+ }
+ } else {
+ HTML.Tag t = (HTML.Tag) a.getAttribute
+ (StyleConstants.NameAttribute);
+ AttributeSet cssRule = styles.getRule(t, elem);
+ if (cssRule != null) {
+ muxList.addElement(cssRule);
+ }
+ }
+ }
+ AttributeSet[] attrs = new AttributeSet[muxList.size()];
+ muxList.copyInto(attrs);
+ setAttributes(attrs);
+ }
+ finally {
+ SearchBuffer.releaseSearchBuffer(sb);
+ }
+ }
+
+ // --- AttributeSet methods ----------------------------
+
+ /**
+ * Checks whether a given attribute is defined.
+ * This will convert the key over to CSS if the
+ * key is a StyleConstants key that has a CSS
+ * mapping.
+ *
+ * @param key the attribute key
+ * @return true if the attribute is defined
+ * @see AttributeSet#isDefined
+ */
+ public boolean isDefined(Object key) {
+ if (key instanceof StyleConstants) {
+ Object cssKey = css.styleConstantsKeyToCSSKey
+ ((StyleConstants)key);
+ if (cssKey != null) {
+ key = cssKey;
+ }
+ }
+ return super.isDefined(key);
+ }
+
+ /**
+ * Gets the value of an attribute. If the requested
+ * attribute is a StyleConstants attribute that has
+ * a CSS mapping, the request will be converted.
+ *
+ * @param key the attribute name
+ * @return the attribute value
+ * @see AttributeSet#getAttribute
+ */
+ public Object getAttribute(Object key) {
+ if (key instanceof StyleConstants) {
+ Object cssKey = css.styleConstantsKeyToCSSKey
+ ((StyleConstants)key);
+ if (cssKey != null) {
+ Object value = doGetAttribute(cssKey);
+ if (value instanceof CSS.CssValue) {
+ return ((CSS.CssValue)value).toStyleConstants
+ ((StyleConstants)key, host);
+ }
+ }
+ }
+ return doGetAttribute(key);
+ }
+
+ Object doGetAttribute(Object key) {
+ Object retValue = super.getAttribute(key);
+ if (retValue != null) {
+ return retValue;
+ }
+ // didn't find it... try parent if it's a css attribute
+ // that is inherited.
+ if (key instanceof CSS.Attribute) {
+ CSS.Attribute css = (CSS.Attribute) key;
+ if (css.isInherited()) {
+ AttributeSet parent = getResolveParent();
+ if (parent != null)
+ return parent.getAttribute(key);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * If not overriden, the resolving parent defaults to
+ * the parent element.
+ *
+ * @return the attributes from the parent
+ * @see AttributeSet#getResolveParent
+ */
+ public AttributeSet getResolveParent() {
+ if (host == null) {
+ return null;
+ }
+ View parent = host.getParent();
+ return (parent != null) ? parent.getAttributes() : null;
+ }
+
+ /** View created for. */
+ View host;
+ }
+
+
+ /**
+ * A subclass of MuxingAttributeSet that implements Style. Currently
+ * the MutableAttributeSet methods are unimplemented, that is they
+ * do nothing.
+ */
+ // PENDING(sky): Decide what to do with this. Either make it
+ // contain a SimpleAttributeSet that modify methods are delegated to,
+ // or change getRule to return an AttributeSet and then don't make this
+ // implement Style.
+ static class ResolvedStyle extends MuxingAttributeSet implements
+ Serializable, Style {
+ ResolvedStyle(String name, AttributeSet[] attrs, int extendedIndex) {
+ super(attrs);
+ this.name = name;
+ this.extendedIndex = extendedIndex;
+ }
+
+ /**
+ * Inserts a Style into the receiver so that the styles the
+ * receiver represents are still ordered by specificity.
+ * <code>style</code> will be added before any extended styles, that
+ * is before extendedIndex.
+ */
+ synchronized void insertStyle(Style style, int specificity) {
+ AttributeSet[] attrs = getAttributes();
+ int maxCounter = attrs.length;
+ int counter = 0;
+ for (;counter < extendedIndex; counter++) {
+ if (specificity > getSpecificity(((Style)attrs[counter]).
+ getName())) {
+ break;
+ }
+ }
+ insertAttributeSetAt(style, counter);
+ extendedIndex++;
+ }
+
+ /**
+ * Removes a previously added style. This will do nothing if
+ * <code>style</code> is not referenced by the receiver.
+ */
+ synchronized void removeStyle(Style style) {
+ AttributeSet[] attrs = getAttributes();
+
+ for (int counter = attrs.length - 1; counter >= 0; counter--) {
+ if (attrs[counter] == style) {
+ removeAttributeSetAt(counter);
+ if (counter < extendedIndex) {
+ extendedIndex--;
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Adds <code>s</code> as one of the Attributesets to look up
+ * attributes in.
+ */
+ synchronized void insertExtendedStyleAt(Style attr, int index) {
+ insertAttributeSetAt(attr, extendedIndex + index);
+ }
+
+ /**
+ * Adds <code>s</code> as one of the AttributeSets to look up
+ * attributes in. It will be the AttributeSet last checked.
+ */
+ synchronized void addExtendedStyle(Style attr) {
+ insertAttributeSetAt(attr, getAttributes().length);
+ }
+
+ /**
+ * Removes the style at <code>index</code> +
+ * <code>extendedIndex</code>.
+ */
+ synchronized void removeExtendedStyleAt(int index) {
+ removeAttributeSetAt(extendedIndex + index);
+ }
+
+ /**
+ * Returns true if the receiver matches <code>selector</code>, where
+ * a match is defined by the CSS rule matching.
+ * Each simple selector must be separated by a single space.
+ */
+ protected boolean matches(String selector) {
+ int sLast = selector.length();
+
+ if (sLast == 0) {
+ return false;
+ }
+ int thisLast = name.length();
+ int sCurrent = selector.lastIndexOf(' ');
+ int thisCurrent = name.lastIndexOf(' ');
+ if (sCurrent >= 0) {
+ sCurrent++;
+ }
+ if (thisCurrent >= 0) {
+ thisCurrent++;
+ }
+ if (!matches(selector, sCurrent, sLast, thisCurrent, thisLast)) {
+ return false;
+ }
+ while (sCurrent != -1) {
+ sLast = sCurrent - 1;
+ sCurrent = selector.lastIndexOf(' ', sLast - 1);
+ if (sCurrent >= 0) {
+ sCurrent++;
+ }
+ boolean match = false;
+ while (!match && thisCurrent != -1) {
+ thisLast = thisCurrent - 1;
+ thisCurrent = name.lastIndexOf(' ', thisLast - 1);
+ if (thisCurrent >= 0) {
+ thisCurrent++;
+ }
+ match = matches(selector, sCurrent, sLast, thisCurrent,
+ thisLast);
+ }
+ if (!match) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if the substring of the receiver, in the range
+ * thisCurrent, thisLast matches the substring of selector in
+ * the ranme sCurrent to sLast based on CSS selector matching.
+ */
+ boolean matches(String selector, int sCurrent, int sLast,
+ int thisCurrent, int thisLast) {
+ sCurrent = Math.max(sCurrent, 0);
+ thisCurrent = Math.max(thisCurrent, 0);
+ int thisDotIndex = boundedIndexOf(name, '.', thisCurrent,
+ thisLast);
+ int thisPoundIndex = boundedIndexOf(name, '#', thisCurrent,
+ thisLast);
+ int sDotIndex = boundedIndexOf(selector, '.', sCurrent, sLast);
+ int sPoundIndex = boundedIndexOf(selector, '#', sCurrent, sLast);
+ if (sDotIndex != -1) {
+ // Selector has a '.', which indicates name must match it,
+ // or if the '.' starts the selector than name must have
+ // the same class (doesn't matter what element name).
+ if (thisDotIndex == -1) {
+ return false;
+ }
+ if (sCurrent == sDotIndex) {
+ if ((thisLast - thisDotIndex) != (sLast - sDotIndex) ||
+ !selector.regionMatches(sCurrent, name, thisDotIndex,
+ (thisLast - thisDotIndex))) {
+ return false;
+ }
+ }
+ else {
+ // Has to fully match.
+ if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
+ !selector.regionMatches(sCurrent, name, thisCurrent,
+ (thisLast - thisCurrent))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ if (sPoundIndex != -1) {
+ // Selector has a '#', which indicates name must match it,
+ // or if the '#' starts the selector than name must have
+ // the same id (doesn't matter what element name).
+ if (thisPoundIndex == -1) {
+ return false;
+ }
+ if (sCurrent == sPoundIndex) {
+ if ((thisLast - thisPoundIndex) !=(sLast - sPoundIndex) ||
+ !selector.regionMatches(sCurrent, name, thisPoundIndex,
+ (thisLast - thisPoundIndex))) {
+ return false;
+ }
+ }
+ else {
+ // Has to fully match.
+ if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
+ !selector.regionMatches(sCurrent, name, thisCurrent,
+ (thisLast - thisCurrent))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ if (thisDotIndex != -1) {
+ // Reciever references a class, just check element name.
+ return (((thisDotIndex - thisCurrent) == (sLast - sCurrent)) &&
+ selector.regionMatches(sCurrent, name, thisCurrent,
+ thisDotIndex - thisCurrent));
+ }
+ if (thisPoundIndex != -1) {
+ // Reciever references an id, just check element name.
+ return (((thisPoundIndex - thisCurrent) ==(sLast - sCurrent))&&
+ selector.regionMatches(sCurrent, name, thisCurrent,
+ thisPoundIndex - thisCurrent));
+ }
+ // Fail through, no classes or ides, just check string.
+ return (((thisLast - thisCurrent) == (sLast - sCurrent)) &&
+ selector.regionMatches(sCurrent, name, thisCurrent,
+ thisLast - thisCurrent));
+ }
+
+ /**
+ * Similiar to String.indexOf, but allows an upper bound
+ * (this is slower in that it will still check string starting at
+ * start.
+ */
+ int boundedIndexOf(String string, char search, int start,
+ int end) {
+ int retValue = string.indexOf(search, start);
+ if (retValue >= end) {
+ return -1;
+ }
+ return retValue;
+ }
+
+ public void addAttribute(Object name, Object value) {}
+ public void addAttributes(AttributeSet attributes) {}
+ public void removeAttribute(Object name) {}
+ public void removeAttributes(Enumeration<?> names) {}
+ public void removeAttributes(AttributeSet attributes) {}
+ public void setResolveParent(AttributeSet parent) {}
+ public String getName() {return name;}
+ public void addChangeListener(ChangeListener l) {}
+ public void removeChangeListener(ChangeListener l) {}
+ public ChangeListener[] getChangeListeners() {
+ return new ChangeListener[0];
+ }
+
+ /** The name of the Style, which is the selector.
+ * This will NEVER change!
+ */
+ String name;
+ /** Start index of styles coming from other StyleSheets. */
+ private int extendedIndex;
+ }
+
+
+ /**
+ * SelectorMapping contains a specifitiy, as an integer, and an associated
+ * Style. It can also reference children <code>SelectorMapping</code>s,
+ * so that it behaves like a tree.
+ * <p>
+ * This is not thread safe, it is assumed the caller will take the
+ * necessary precations if this is to be used in a threaded environment.
+ */
+ static class SelectorMapping implements Serializable {
+ public SelectorMapping(int specificity) {
+ this.specificity = specificity;
+ }
+
+ /**
+ * Returns the specificity this mapping represents.
+ */
+ public int getSpecificity() {
+ return specificity;
+ }
+
+ /**
+ * Sets the Style associated with this mapping.
+ */
+ public void setStyle(Style style) {
+ this.style = style;
+ }
+
+ /**
+ * Returns the Style associated with this mapping.
+ */
+ public Style getStyle() {
+ return style;
+ }
+
+ /**
+ * Returns the child mapping identified by the simple selector
+ * <code>selector</code>. If a child mapping does not exist for
+ *<code>selector</code>, and <code>create</code> is true, a new
+ * one will be created.
+ */
+ public SelectorMapping getChildSelectorMapping(String selector,
+ boolean create) {
+ SelectorMapping retValue = null;
+
+ if (children != null) {
+ retValue = (SelectorMapping)children.get(selector);
+ }
+ else if (create) {
+ children = new HashMap(7);
+ }
+ if (retValue == null && create) {
+ int specificity = getChildSpecificity(selector);
+
+ retValue = createChildSelectorMapping(specificity);
+ children.put(selector, retValue);
+ }
+ return retValue;
+ }
+
+ /**
+ * Creates a child <code>SelectorMapping</code> with the specified
+ * <code>specificity</code>.
+ */
+ protected SelectorMapping createChildSelectorMapping(int specificity) {
+ return new SelectorMapping(specificity);
+ }
+
+ /**
+ * Returns the specificity for the child selector
+ * <code>selector</code>.
+ */
+ protected int getChildSpecificity(String selector) {
+ // class (.) 100
+ // id (#) 10000
+ char firstChar = selector.charAt(0);
+ int specificity = getSpecificity();
+
+ if (firstChar == '.') {
+ specificity += 100;
+ }
+ else if (firstChar == '#') {
+ specificity += 10000;
+ }
+ else {
+ specificity += 1;
+ if (selector.indexOf('.') != -1) {
+ specificity += 100;
+ }
+ if (selector.indexOf('#') != -1) {
+ specificity += 10000;
+ }
+ }
+ return specificity;
+ }
+
+ /**
+ * The specificity for this selector.
+ */
+ private int specificity;
+ /**
+ * Style for this selector.
+ */
+ private Style style;
+ /**
+ * Any sub selectors. Key will be String, and value will be
+ * another SelectorMapping.
+ */
+ private HashMap children;
+ }
+
+
+ // ---- Variables ---------------------------------------------
+
+ final static int DEFAULT_FONT_SIZE = 3;
+
+ private CSS css;
+
+ /**
+ * An inverted graph of the selectors.
+ */
+ private SelectorMapping selectorMapping;
+
+ /** Maps from selector (as a string) to Style that includes all
+ * relevant styles. */
+ private Hashtable resolvedStyles;
+
+ /** Vector of StyleSheets that the rules are to reference.
+ */
+ private Vector linkedStyleSheets;
+
+ /** Where the style sheet was found. Used for relative imports. */
+ private URL base;
+
+
+ /**
+ * Default parser for CSS specifications that get loaded into
+ * the StyleSheet.<p>
+ * This class is NOT thread safe, do not ask it to parse while it is
+ * in the middle of parsing.
+ */
+ class CssParser implements CSSParser.CSSParserCallback {
+
+ /**
+ * Parses the passed in CSS declaration into an AttributeSet.
+ */
+ public AttributeSet parseDeclaration(String string) {
+ try {
+ return parseDeclaration(new StringReader(string));
+ } catch (IOException ioe) {}
+ return null;
+ }
+
+ /**
+ * Parses the passed in CSS declaration into an AttributeSet.
+ */
+ public AttributeSet parseDeclaration(Reader r) throws IOException {
+ parse(base, r, true, false);
+ return declaration.copyAttributes();
+ }
+
+ /**
+ * Parse the given CSS stream
+ */
+ public void parse(URL base, Reader r, boolean parseDeclaration,
+ boolean isLink) throws IOException {
+ this.base = base;
+ this.isLink = isLink;
+ this.parsingDeclaration = parseDeclaration;
+ declaration.removeAttributes(declaration);
+ selectorTokens.removeAllElements();
+ selectors.removeAllElements();
+ propertyName = null;
+ parser.parse(r, this, parseDeclaration);
+ }
+
+ //
+ // CSSParserCallback methods, public to implement the interface.
+ //
+
+ /**
+ * Invoked when a valid @import is encountered, will call
+ * <code>importStyleSheet</code> if a
+ * <code>MalformedURLException</code> is not thrown in creating
+ * the URL.
+ */
+ public void handleImport(String importString) {
+ URL url = CSS.getURL(base, importString);
+ if (url != null) {
+ importStyleSheet(url);
+ }
+ }
+
+ /**
+ * A selector has been encountered.
+ */
+ public void handleSelector(String selector) {
+ //class and index selectors are case sensitive
+ if (!(selector.startsWith(".")
+ || selector.startsWith("#"))) {
+ selector = selector.toLowerCase();
+ }
+ int length = selector.length();
+
+ if (selector.endsWith(",")) {
+ if (length > 1) {
+ selector = selector.substring(0, length - 1);
+ selectorTokens.addElement(selector);
+ }
+ addSelector();
+ }
+ else if (length > 0) {
+ selectorTokens.addElement(selector);
+ }
+ }
+
+ /**
+ * Invoked when the start of a rule is encountered.
+ */
+ public void startRule() {
+ if (selectorTokens.size() > 0) {
+ addSelector();
+ }
+ propertyName = null;
+ }
+
+ /**
+ * Invoked when a property name is encountered.
+ */
+ public void handleProperty(String property) {
+ propertyName = property;
+ }
+
+ /**
+ * Invoked when a property value is encountered.
+ */
+ public void handleValue(String value) {
+ if (propertyName != null && value != null && value.length() > 0) {
+ CSS.Attribute cssKey = CSS.getAttribute(propertyName);
+ if (cssKey != null) {
+ // There is currently no mechanism to determine real
+ // base that style sheet was loaded from. For the time
+ // being, this maps for LIST_STYLE_IMAGE, which appear
+ // to be the only one that currently matters. A more
+ // general mechanism is definately needed.
+ if (cssKey == CSS.Attribute.LIST_STYLE_IMAGE) {
+ if (value != null && !value.equals("none")) {
+ URL url = CSS.getURL(base, value);
+
+ if (url != null) {
+ value = url.toString();
+ }
+ }
+ }
+ addCSSAttribute(declaration, cssKey, value);
+ }
+ propertyName = null;
+ }
+ }
+
+ /**
+ * Invoked when the end of a rule is encountered.
+ */
+ public void endRule() {
+ int n = selectors.size();
+ for (int i = 0; i < n; i++) {
+ String[] selector = (String[]) selectors.elementAt(i);
+ if (selector.length > 0) {
+ StyleSheet.this.addRule(selector, declaration, isLink);
+ }
+ }
+ declaration.removeAttributes(declaration);
+ selectors.removeAllElements();
+ }
+
+ private void addSelector() {
+ String[] selector = new String[selectorTokens.size()];
+ selectorTokens.copyInto(selector);
+ selectors.addElement(selector);
+ selectorTokens.removeAllElements();
+ }
+
+
+ Vector selectors = new Vector();
+ Vector selectorTokens = new Vector();
+ /** Name of the current property. */
+ String propertyName;
+ MutableAttributeSet declaration = new SimpleAttributeSet();
+ /** True if parsing a declaration, that is the Reader will not
+ * contain a selector. */
+ boolean parsingDeclaration;
+ /** True if the attributes are coming from a linked/imported style. */
+ boolean isLink;
+ /** Where the CSS stylesheet lives. */
+ URL base;
+ CSSParser parser = new CSSParser();
+ }
+
+ void rebaseSizeMap(int base) {
+ final int minimalFontSize = 4;
+ sizeMap = new int[sizeMapDefault.length];
+ for (int i = 0; i < sizeMapDefault.length; i++) {
+ sizeMap[i] = Math.max(base * sizeMapDefault[i] /
+ sizeMapDefault[CSS.baseFontSizeIndex],
+ minimalFontSize);
+ }
+
+ }
+
+ int[] getSizeMap() {
+ return sizeMap;
+ }
+ boolean isW3CLengthUnits() {
+ return w3cLengthUnits;
+ }
+
+ /**
+ * The HTML/CSS size model has seven slots
+ * that one can assign sizes to.
+ */
+ static final int sizeMapDefault[] = { 8, 10, 12, 14, 18, 24, 36 };
+
+ private int sizeMap[] = sizeMapDefault;
+ private boolean w3cLengthUnits = false;
+}
diff --git a/src/share/classes/javax/swing/text/html/TableView.java b/src/share/classes/javax/swing/text/html/TableView.java
new file mode 100644
index 000000000..db0dc790f
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/TableView.java
@@ -0,0 +1,1801 @@
+/*
+ * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import java.awt.*;
+import java.util.BitSet;
+import java.util.Vector;
+import java.util.Arrays;
+import javax.swing.SizeRequirements;
+import javax.swing.event.DocumentEvent;
+
+import javax.swing.text.*;
+
+/**
+ * HTML table view.
+ *
+ * @author Timothy Prinzing
+ * @see View
+ */
+/*public*/ class TableView extends BoxView implements ViewFactory {
+
+ /**
+ * Constructs a TableView for the given element.
+ *
+ * @param elem the element that this view is responsible for
+ */
+ public TableView(Element elem) {
+ super(elem, View.Y_AXIS);
+ rows = new Vector();
+ gridValid = false;
+ captionIndex = -1;
+ totalColumnRequirements = new SizeRequirements();
+ }
+
+ /**
+ * Creates a new table row.
+ *
+ * @param elem an element
+ * @return the row
+ */
+ protected RowView createTableRow(Element elem) {
+ // PENDING(prinz) need to add support for some of the other
+ // elements, but for now just ignore anything that is not
+ // a TR.
+ Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
+ if (o == HTML.Tag.TR) {
+ return new RowView(elem);
+ }
+ return null;
+ }
+
+ /**
+ * The number of columns in the table.
+ */
+ public int getColumnCount() {
+ return columnSpans.length;
+ }
+
+ /**
+ * Fetches the span (width) of the given column.
+ * This is used by the nested cells to query the
+ * sizes of grid locations outside of themselves.
+ */
+ public int getColumnSpan(int col) {
+ if (col < columnSpans.length) {
+ return columnSpans[col];
+ }
+ return 0;
+ }
+
+ /**
+ * The number of rows in the table.
+ */
+ public int getRowCount() {
+ return rows.size();
+ }
+
+ /**
+ * Fetch the span of multiple rows. This includes
+ * the border area.
+ */
+ public int getMultiRowSpan(int row0, int row1) {
+ RowView rv0 = getRow(row0);
+ RowView rv1 = getRow(row1);
+ if ((rv0 != null) && (rv1 != null)) {
+ int index0 = rv0.viewIndex;
+ int index1 = rv1.viewIndex;
+ int span = getOffset(Y_AXIS, index1) - getOffset(Y_AXIS, index0) +
+ getSpan(Y_AXIS, index1);
+ return span;
+ }
+ return 0;
+ }
+
+ /**
+ * Fetches the span (height) of the given row.
+ */
+ public int getRowSpan(int row) {
+ RowView rv = getRow(row);
+ if (rv != null) {
+ return getSpan(Y_AXIS, rv.viewIndex);
+ }
+ return 0;
+ }
+
+ RowView getRow(int row) {
+ if (row < rows.size()) {
+ return (RowView) rows.elementAt(row);
+ }
+ return null;
+ }
+
+ protected View getViewAtPoint(int x, int y, Rectangle alloc) {
+ int n = getViewCount();
+ View v = null;
+ Rectangle allocation = new Rectangle();
+ for (int i = 0; i < n; i++) {
+ allocation.setBounds(alloc);
+ childAllocation(i, allocation);
+ v = getView(i);
+ if (v instanceof RowView) {
+ v = ((RowView)v).findViewAtPoint(x, y, allocation);
+ if (v != null) {
+ alloc.setBounds(allocation);
+ return v;
+ }
+ }
+ }
+ return super.getViewAtPoint(x, y, alloc);
+ }
+
+ /**
+ * Determines the number of columns occupied by
+ * the table cell represented by given element.
+ */
+ protected int getColumnsOccupied(View v) {
+ AttributeSet a = v.getElement().getAttributes();
+
+ if (a.isDefined(HTML.Attribute.COLSPAN)) {
+ String s = (String) a.getAttribute(HTML.Attribute.COLSPAN);
+ if (s != null) {
+ try {
+ return Integer.parseInt(s);
+ } catch (NumberFormatException nfe) {
+ // fall through to one column
+ }
+ }
+ }
+
+ return 1;
+ }
+
+ /**
+ * Determines the number of rows occupied by
+ * the table cell represented by given element.
+ */
+ protected int getRowsOccupied(View v) {
+ AttributeSet a = v.getElement().getAttributes();
+
+ if (a.isDefined(HTML.Attribute.ROWSPAN)) {
+ String s = (String) a.getAttribute(HTML.Attribute.ROWSPAN);
+ if (s != null) {
+ try {
+ return Integer.parseInt(s);
+ } catch (NumberFormatException nfe) {
+ // fall through to one row
+ }
+ }
+ }
+
+ return 1;
+ }
+
+ protected void invalidateGrid() {
+ gridValid = false;
+ }
+
+ protected StyleSheet getStyleSheet() {
+ HTMLDocument doc = (HTMLDocument) getDocument();
+ return doc.getStyleSheet();
+ }
+
+ /**
+ * Update the insets, which contain the caption if there
+ * is a caption.
+ */
+ void updateInsets() {
+ short top = (short) painter.getInset(TOP, this);
+ short bottom = (short) painter.getInset(BOTTOM, this);
+ if (captionIndex != -1) {
+ View caption = getView(captionIndex);
+ short h = (short) caption.getPreferredSpan(Y_AXIS);
+ AttributeSet a = caption.getAttributes();
+ Object align = a.getAttribute(CSS.Attribute.CAPTION_SIDE);
+ if ((align != null) && (align.equals("bottom"))) {
+ bottom += h;
+ } else {
+ top += h;
+ }
+ }
+ setInsets(top, (short) painter.getInset(LEFT, this),
+ bottom, (short) painter.getInset(RIGHT, this));
+ }
+
+ /**
+ * Update any cached values that come from attributes.
+ */
+ protected void setPropertiesFromAttributes() {
+ StyleSheet sheet = getStyleSheet();
+ attr = sheet.getViewAttributes(this);
+ painter = sheet.getBoxPainter(attr);
+ if (attr != null) {
+ setInsets((short) painter.getInset(TOP, this),
+ (short) painter.getInset(LEFT, this),
+ (short) painter.getInset(BOTTOM, this),
+ (short) painter.getInset(RIGHT, this));
+
+ CSS.LengthValue lv = (CSS.LengthValue)
+ attr.getAttribute(CSS.Attribute.BORDER_SPACING);
+ if (lv != null) {
+ cellSpacing = (int) lv.getValue();
+ } else {
+ cellSpacing = 0;
+ }
+ lv = (CSS.LengthValue)
+ attr.getAttribute(CSS.Attribute.BORDER_TOP_WIDTH);
+ if (lv != null) {
+ borderWidth = (int) lv.getValue();
+ } else {
+ borderWidth = 0;
+ }
+
+ }
+ }
+
+ /**
+ * Fill in the grid locations that are placeholders
+ * for multi-column, multi-row, and missing grid
+ * locations.
+ */
+ void updateGrid() {
+ if (! gridValid) {
+ relativeCells = false;
+ multiRowCells = false;
+
+ // determine which views are table rows and clear out
+ // grid points marked filled.
+ captionIndex = -1;
+ rows.removeAllElements();
+ int n = getViewCount();
+ for (int i = 0; i < n; i++) {
+ View v = getView(i);
+ if (v instanceof RowView) {
+ rows.addElement(v);
+ RowView rv = (RowView) v;
+ rv.clearFilledColumns();
+ rv.rowIndex = rows.size() - 1;
+ rv.viewIndex = i;
+ } else {
+ Object o = v.getElement().getAttributes().getAttribute(StyleConstants.NameAttribute);
+ if (o instanceof HTML.Tag) {
+ HTML.Tag kind = (HTML.Tag) o;
+ if (kind == HTML.Tag.CAPTION) {
+ captionIndex = i;
+ }
+ }
+ }
+ }
+
+ int maxColumns = 0;
+ int nrows = rows.size();
+ for (int row = 0; row < nrows; row++) {
+ RowView rv = getRow(row);
+ int col = 0;
+ for (int cell = 0; cell < rv.getViewCount(); cell++, col++) {
+ View cv = rv.getView(cell);
+ if (! relativeCells) {
+ AttributeSet a = cv.getAttributes();
+ CSS.LengthValue lv = (CSS.LengthValue)
+ a.getAttribute(CSS.Attribute.WIDTH);
+ if ((lv != null) && (lv.isPercentage())) {
+ relativeCells = true;
+ }
+ }
+ // advance to a free column
+ for (; rv.isFilled(col); col++);
+ int rowSpan = getRowsOccupied(cv);
+ if (rowSpan > 1) {
+ multiRowCells = true;
+ }
+ int colSpan = getColumnsOccupied(cv);
+ if ((colSpan > 1) || (rowSpan > 1)) {
+ // fill in the overflow entries for this cell
+ int rowLimit = row + rowSpan;
+ int colLimit = col + colSpan;
+ for (int i = row; i < rowLimit; i++) {
+ for (int j = col; j < colLimit; j++) {
+ if (i != row || j != col) {
+ addFill(i, j);
+ }
+ }
+ }
+ if (colSpan > 1) {
+ col += colSpan - 1;
+ }
+ }
+ }
+ maxColumns = Math.max(maxColumns, col);
+ }
+
+ // setup the column layout/requirements
+ columnSpans = new int[maxColumns];
+ columnOffsets = new int[maxColumns];
+ columnRequirements = new SizeRequirements[maxColumns];
+ for (int i = 0; i < maxColumns; i++) {
+ columnRequirements[i] = new SizeRequirements();
+ columnRequirements[i].maximum = Integer.MAX_VALUE;
+ }
+ gridValid = true;
+ }
+ }
+
+ /**
+ * Mark a grid location as filled in for a cells overflow.
+ */
+ void addFill(int row, int col) {
+ RowView rv = getRow(row);
+ if (rv != null) {
+ rv.fillColumn(col);
+ }
+ }
+
+ /**
+ * Layout the columns to fit within the given target span.
+ *
+ * @param targetSpan the given span for total of all the table
+ * columns
+ * @param reqs the requirements desired for each column. This
+ * is the column maximum of the cells minimum, preferred, and
+ * maximum requested span
+ * @param spans the return value of how much to allocated to
+ * each column
+ * @param offsets the return value of the offset from the
+ * origin for each column
+ * @return the offset from the origin and the span for each column
+ * in the offsets and spans parameters
+ */
+ protected void layoutColumns(int targetSpan, int[] offsets, int[] spans,
+ SizeRequirements[] reqs) {
+ //clean offsets and spans
+ Arrays.fill(offsets, 0);
+ Arrays.fill(spans, 0);
+ colIterator.setLayoutArrays(offsets, spans, targetSpan);
+ CSS.calculateTiledLayout(colIterator, targetSpan);
+ }
+
+ /**
+ * Calculate the requirements for each column. The calculation
+ * is done as two passes over the table. The table cells that
+ * occupy a single column are scanned first to determine the
+ * maximum of minimum, preferred, and maximum spans along the
+ * give axis. Table cells that span multiple columns are excluded
+ * from the first pass. A second pass is made to determine if
+ * the cells that span multiple columns are satisfied. If the
+ * column requirements are not satisified, the needs of the
+ * multi-column cell is mixed into the existing column requirements.
+ * The calculation of the multi-column distribution is based upon
+ * the proportions of the existing column requirements and taking
+ * into consideration any constraining maximums.
+ */
+ void calculateColumnRequirements(int axis) {
+ // clean columnRequirements
+ for (SizeRequirements req : columnRequirements) {
+ req.minimum = 0;
+ req.preferred = 0;
+ req.maximum = Integer.MAX_VALUE;
+ }
+ Container host = getContainer();
+ if (host != null) {
+ if (host instanceof JTextComponent) {
+ skipComments = !((JTextComponent)host).isEditable();
+ } else {
+ skipComments = true;
+ }
+ }
+ // pass 1 - single column cells
+ boolean hasMultiColumn = false;
+ int nrows = getRowCount();
+ for (int i = 0; i < nrows; i++) {
+ RowView row = getRow(i);
+ int col = 0;
+ int ncells = row.getViewCount();
+ for (int cell = 0; cell < ncells; cell++) {
+ View cv = row.getView(cell);
+ if (skipComments && !(cv instanceof CellView)) {
+ continue;
+ }
+ for (; row.isFilled(col); col++); // advance to a free column
+ int rowSpan = getRowsOccupied(cv);
+ int colSpan = getColumnsOccupied(cv);
+ if (colSpan == 1) {
+ checkSingleColumnCell(axis, col, cv);
+ } else {
+ hasMultiColumn = true;
+ col += colSpan - 1;
+ }
+ col++;
+ }
+ }
+
+ // pass 2 - multi-column cells
+ if (hasMultiColumn) {
+ for (int i = 0; i < nrows; i++) {
+ RowView row = getRow(i);
+ int col = 0;
+ int ncells = row.getViewCount();
+ for (int cell = 0; cell < ncells; cell++) {
+ View cv = row.getView(cell);
+ if (skipComments && !(cv instanceof CellView)) {
+ continue;
+ }
+ for (; row.isFilled(col); col++); // advance to a free column
+ int colSpan = getColumnsOccupied(cv);
+ if (colSpan > 1) {
+ checkMultiColumnCell(axis, col, colSpan, cv);
+ col += colSpan - 1;
+ }
+ col++;
+ }
+ }
+ }
+ }
+
+ /**
+ * check the requirements of a table cell that spans a single column.
+ */
+ void checkSingleColumnCell(int axis, int col, View v) {
+ SizeRequirements req = columnRequirements[col];
+ req.minimum = Math.max((int) v.getMinimumSpan(axis), req.minimum);
+ req.preferred = Math.max((int) v.getPreferredSpan(axis), req.preferred);
+ }
+
+ /**
+ * check the requirements of a table cell that spans multiple
+ * columns.
+ */
+ void checkMultiColumnCell(int axis, int col, int ncols, View v) {
+ // calculate the totals
+ long min = 0;
+ long pref = 0;
+ long max = 0;
+ for (int i = 0; i < ncols; i++) {
+ SizeRequirements req = columnRequirements[col + i];
+ min += req.minimum;
+ pref += req.preferred;
+ max += req.maximum;
+ }
+
+ // check if the minimum size needs adjustment.
+ int cmin = (int) v.getMinimumSpan(axis);
+ if (cmin > min) {
+ /*
+ * the columns that this cell spans need adjustment to fit
+ * this table cell.... calculate the adjustments.
+ */
+ SizeRequirements[] reqs = new SizeRequirements[ncols];
+ for (int i = 0; i < ncols; i++) {
+ reqs[i] = columnRequirements[col + i];
+ }
+ int[] spans = new int[ncols];
+ int[] offsets = new int[ncols];
+ SizeRequirements.calculateTiledPositions(cmin, null, reqs,
+ offsets, spans);
+ // apply the adjustments
+ for (int i = 0; i < ncols; i++) {
+ SizeRequirements req = reqs[i];
+ req.minimum = Math.max(spans[i], req.minimum);
+ req.preferred = Math.max(req.minimum, req.preferred);
+ req.maximum = Math.max(req.preferred, req.maximum);
+ }
+ }
+
+ // check if the preferred size needs adjustment.
+ int cpref = (int) v.getPreferredSpan(axis);
+ if (cpref > pref) {
+ /*
+ * the columns that this cell spans need adjustment to fit
+ * this table cell.... calculate the adjustments.
+ */
+ SizeRequirements[] reqs = new SizeRequirements[ncols];
+ for (int i = 0; i < ncols; i++) {
+ reqs[i] = columnRequirements[col + i];
+ }
+ int[] spans = new int[ncols];
+ int[] offsets = new int[ncols];
+ SizeRequirements.calculateTiledPositions(cpref, null, reqs,
+ offsets, spans);
+ // apply the adjustments
+ for (int i = 0; i < ncols; i++) {
+ SizeRequirements req = reqs[i];
+ req.preferred = Math.max(spans[i], req.preferred);
+ req.maximum = Math.max(req.preferred, req.maximum);
+ }
+ }
+
+ }
+
+ // --- BoxView methods -----------------------------------------
+
+ /**
+ * Calculate the requirements for the minor axis. This is called by
+ * the superclass whenever the requirements need to be updated (i.e.
+ * a preferenceChanged was messaged through this view).
+ * <p>
+ * This is implemented to calculate the requirements as the sum of the
+ * requirements of the columns and then adjust it if the
+ * CSS width or height attribute is specified and applicable to
+ * the axis.
+ */
+ protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
+ updateGrid();
+
+ // calculate column requirements for each column
+ calculateColumnRequirements(axis);
+
+
+ // the requirements are the sum of the columns.
+ if (r == null) {
+ r = new SizeRequirements();
+ }
+ long min = 0;
+ long pref = 0;
+ int n = columnRequirements.length;
+ for (int i = 0; i < n; i++) {
+ SizeRequirements req = columnRequirements[i];
+ min += req.minimum;
+ pref += req.preferred;
+ }
+ int adjust = (n + 1) * cellSpacing + 2 * borderWidth;
+ min += adjust;
+ pref += adjust;
+ r.minimum = (int) min;
+ r.preferred = (int) pref;
+ r.maximum = (int) pref;
+
+
+ AttributeSet attr = getAttributes();
+ CSS.LengthValue cssWidth = (CSS.LengthValue)attr.getAttribute(
+ CSS.Attribute.WIDTH);
+
+ if (BlockView.spanSetFromAttributes(axis, r, cssWidth, null)) {
+ if (r.minimum < (int)min) {
+ // The user has requested a smaller size than is needed to
+ // show the table, override it.
+ r.maximum = r.minimum = r.preferred = (int) min;
+ }
+ }
+ totalColumnRequirements.minimum = r.minimum;
+ totalColumnRequirements.preferred = r.preferred;
+ totalColumnRequirements.maximum = r.maximum;
+
+ // set the alignment
+ Object o = attr.getAttribute(CSS.Attribute.TEXT_ALIGN);
+ if (o != null) {
+ // set horizontal alignment
+ String ta = o.toString();
+ if (ta.equals("left")) {
+ r.alignment = 0;
+ } else if (ta.equals("center")) {
+ r.alignment = 0.5f;
+ } else if (ta.equals("right")) {
+ r.alignment = 1;
+ } else {
+ r.alignment = 0;
+ }
+ } else {
+ r.alignment = 0;
+ }
+
+ return r;
+ }
+
+ /**
+ * Calculate the requirements for the major axis. This is called by
+ * the superclass whenever the requirements need to be updated (i.e.
+ * a preferenceChanged was messaged through this view).
+ * <p>
+ * This is implemented to provide the superclass behavior adjusted for
+ * multi-row table cells.
+ */
+ protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
+ updateInsets();
+ rowIterator.updateAdjustments();
+ r = CSS.calculateTiledRequirements(rowIterator, r);
+ r.maximum = r.preferred;
+ return r;
+ }
+
+ /**
+ * Perform layout for the minor axis of the box (i.e. the
+ * axis orthoginal to the axis that it represents). The results
+ * of the layout should be placed in the given arrays which represent
+ * the allocations to the children along the minor axis. This
+ * is called by the superclass whenever the layout needs to be
+ * updated along the minor axis.
+ * <p>
+ * This is implemented to call the
+ * <a href="#layoutColumns">layoutColumns</a> method, and then
+ * forward to the superclass to actually carry out the layout
+ * of the tables rows.
+ *
+ * @param targetSpan the total span given to the view, which
+ * whould be used to layout the children
+ * @param axis the axis being layed out
+ * @param offsets the offsets from the origin of the view for
+ * each of the child views. This is a return value and is
+ * filled in by the implementation of this method
+ * @param spans the span of each child view; this is a return
+ * value and is filled in by the implementation of this method
+ * @return the offset and span for each child view in the
+ * offsets and spans parameters
+ */
+ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
+ // make grid is properly represented
+ updateGrid();
+
+ // all of the row layouts are invalid, so mark them that way
+ int n = getRowCount();
+ for (int i = 0; i < n; i++) {
+ RowView row = getRow(i);
+ row.layoutChanged(axis);
+ }
+
+ // calculate column spans
+ layoutColumns(targetSpan, columnOffsets, columnSpans, columnRequirements);
+
+ // continue normal layout
+ super.layoutMinorAxis(targetSpan, axis, offsets, spans);
+ }
+
+
+ /**
+ * Perform layout for the major axis of the box (i.e. the
+ * axis that it represents). The results
+ * of the layout should be placed in the given arrays which represent
+ * the allocations to the children along the minor axis. This
+ * is called by the superclass whenever the layout needs to be
+ * updated along the minor axis.
+ * <p>
+ * This method is where the layout of the table rows within the
+ * table takes place. This method is implemented to call the use
+ * the RowIterator and the CSS collapsing tile to layout
+ * with border spacing and border collapsing capabilities.
+ *
+ * @param targetSpan the total span given to the view, which
+ * whould be used to layout the children
+ * @param axis the axis being layed out
+ * @param offsets the offsets from the origin of the view for
+ * each of the child views; this is a return value and is
+ * filled in by the implementation of this method
+ * @param spans the span of each child view; this is a return
+ * value and is filled in by the implementation of this method
+ * @return the offset and span for each child view in the
+ * offsets and spans parameters
+ */
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
+ rowIterator.setLayoutArrays(offsets, spans);
+ CSS.calculateTiledLayout(rowIterator, targetSpan);
+
+ if (captionIndex != -1) {
+ // place the caption
+ View caption = getView(captionIndex);
+ int h = (int) caption.getPreferredSpan(Y_AXIS);
+ spans[captionIndex] = h;
+ short boxBottom = (short) painter.getInset(BOTTOM, this);
+ if (boxBottom != getBottomInset()) {
+ offsets[captionIndex] = targetSpan + boxBottom;
+ } else {
+ offsets[captionIndex] = - getTopInset();
+ }
+ }
+ }
+
+ /**
+ * Fetches the child view that represents the given position in
+ * the model. This is implemented to walk through the children
+ * looking for a range that contains the given position. In this
+ * view the children do not necessarily have a one to one mapping
+ * with the child elements.
+ *
+ * @param pos the search position >= 0
+ * @param a the allocation to the table on entry, and the
+ * allocation of the view containing the position on exit
+ * @return the view representing the given position, or
+ * null if there isn't one
+ */
+ protected View getViewAtPosition(int pos, Rectangle a) {
+ int n = getViewCount();
+ for (int i = 0; i < n; i++) {
+ View v = getView(i);
+ int p0 = v.getStartOffset();
+ int p1 = v.getEndOffset();
+ if ((pos >= p0) && (pos < p1)) {
+ // it's in this view.
+ if (a != null) {
+ childAllocation(i, a);
+ }
+ return v;
+ }
+ }
+ if (pos == getEndOffset()) {
+ View v = getView(n - 1);
+ if (a != null) {
+ this.childAllocation(n - 1, a);
+ }
+ return v;
+ }
+ return null;
+ }
+
+ // --- View methods ---------------------------------------------
+
+ /**
+ * Fetches the attributes to use when rendering. This is
+ * implemented to multiplex the attributes specified in the
+ * model with a StyleSheet.
+ */
+ public AttributeSet getAttributes() {
+ if (attr == null) {
+ StyleSheet sheet = getStyleSheet();
+ attr = sheet.getViewAttributes(this);
+ }
+ return attr;
+ }
+
+ /**
+ * Renders using the given rendering surface and area on that
+ * surface. This is implemented to delegate to the css box
+ * painter to paint the border and background prior to the
+ * interior. The superclass culls rendering the children
+ * that don't directly intersect the clip and the row may
+ * have cells hanging from a row above in it. The table
+ * does not use the superclass rendering behavior and instead
+ * paints all of the rows and lets the rows cull those
+ * cells not intersecting the clip region.
+ *
+ * @param g the rendering surface to use
+ * @param allocation the allocated region to render into
+ * @see View#paint
+ */
+ public void paint(Graphics g, Shape allocation) {
+ // paint the border
+ Rectangle a = allocation.getBounds();
+ setSize(a.width, a.height);
+ if (captionIndex != -1) {
+ // adjust the border for the caption
+ short top = (short) painter.getInset(TOP, this);
+ short bottom = (short) painter.getInset(BOTTOM, this);
+ if (top != getTopInset()) {
+ int h = getTopInset() - top;
+ a.y += h;
+ a.height -= h;
+ } else {
+ a.height -= getBottomInset() - bottom;
+ }
+ }
+ painter.paint(g, a.x, a.y, a.width, a.height, this);
+ // paint interior
+ int n = getViewCount();
+ for (int i = 0; i < n; i++) {
+ View v = getView(i);
+ v.paint(g, getChildAllocation(i, allocation));
+ }
+ //super.paint(g, a);
+ }
+
+ /**
+ * Establishes the parent view for this view. This is
+ * guaranteed to be called before any other methods if the
+ * parent view is functioning properly.
+ * <p>
+ * This is implemented
+ * to forward to the superclass as well as call the
+ * <a href="#setPropertiesFromAttributes">setPropertiesFromAttributes</a>
+ * method to set the paragraph properties from the css
+ * attributes. The call is made at this time to ensure
+ * the ability to resolve upward through the parents
+ * view attributes.
+ *
+ * @param parent the new parent, or null if the view is
+ * being removed from a parent it was previously added
+ * to
+ */
+ public void setParent(View parent) {
+ super.setParent(parent);
+ if (parent != null) {
+ setPropertiesFromAttributes();
+ }
+ }
+
+ /**
+ * Fetches the ViewFactory implementation that is feeding
+ * the view hierarchy.
+ * This replaces the ViewFactory with an implementation that
+ * calls through to the createTableRow and createTableCell
+ * methods. If the element given to the factory isn't a
+ * table row or cell, the request is delegated to the factory
+ * produced by the superclass behavior.
+ *
+ * @return the factory, null if none
+ */
+ public ViewFactory getViewFactory() {
+ return this;
+ }
+
+ /**
+ * Gives notification that something was inserted into
+ * the document in a location that this view is responsible for.
+ * This replaces the ViewFactory with an implementation that
+ * calls through to the createTableRow and createTableCell
+ * methods. If the element given to the factory isn't a
+ * table row or cell, the request is delegated to the factory
+ * passed as an argument.
+ *
+ * @param e the change information from the associated document
+ * @param a the current allocation of the view
+ * @param f the factory to use to rebuild if the view has children
+ * @see View#insertUpdate
+ */
+ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+ super.insertUpdate(e, a, this);
+ }
+
+ /**
+ * Gives notification that something was removed from the document
+ * in a location that this view is responsible for.
+ * This replaces the ViewFactory with an implementation that
+ * calls through to the createTableRow and createTableCell
+ * methods. If the element given to the factory isn't a
+ * table row or cell, the request is delegated to the factory
+ * passed as an argument.
+ *
+ * @param e the change information from the associated document
+ * @param a the current allocation of the view
+ * @param f the factory to use to rebuild if the view has children
+ * @see View#removeUpdate
+ */
+ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+ super.removeUpdate(e, a, this);
+ }
+
+ /**
+ * Gives notification from the document that attributes were changed
+ * in a location that this view is responsible for.
+ * This replaces the ViewFactory with an implementation that
+ * calls through to the createTableRow and createTableCell
+ * methods. If the element given to the factory isn't a
+ * table row or cell, the request is delegated to the factory
+ * passed as an argument.
+ *
+ * @param e the change information from the associated document
+ * @param a the current allocation of the view
+ * @param f the factory to use to rebuild if the view has children
+ * @see View#changedUpdate
+ */
+ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+ super.changedUpdate(e, a, this);
+ }
+
+ protected void forwardUpdate(DocumentEvent.ElementChange ec,
+ DocumentEvent e, Shape a, ViewFactory f) {
+ super.forwardUpdate(ec, e, a, f);
+ // A change in any of the table cells usually effects the whole table,
+ // so redraw it all!
+ if (a != null) {
+ Component c = getContainer();
+ if (c != null) {
+ Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a :
+ a.getBounds();
+ c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
+ }
+ }
+ }
+
+ /**
+ * Change the child views. This is implemented to
+ * provide the superclass behavior and invalidate the
+ * grid so that rows and columns will be recalculated.
+ */
+ public void replace(int offset, int length, View[] views) {
+ super.replace(offset, length, views);
+ invalidateGrid();
+ }
+
+ // --- ViewFactory methods ------------------------------------------
+
+ /**
+ * The table itself acts as a factory for the various
+ * views that actually represent pieces of the table.
+ * All other factory activity is delegated to the factory
+ * returned by the parent of the table.
+ */
+ public View create(Element elem) {
+ Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
+ if (o instanceof HTML.Tag) {
+ HTML.Tag kind = (HTML.Tag) o;
+ if (kind == HTML.Tag.TR) {
+ return createTableRow(elem);
+ } else if ((kind == HTML.Tag.TD) || (kind == HTML.Tag.TH)) {
+ return new CellView(elem);
+ } else if (kind == HTML.Tag.CAPTION) {
+ return new javax.swing.text.html.ParagraphView(elem);
+ }
+ }
+ // default is to delegate to the normal factory
+ View p = getParent();
+ if (p != null) {
+ ViewFactory f = p.getViewFactory();
+ if (f != null) {
+ return f.create(elem);
+ }
+ }
+ return null;
+ }
+
+ // ---- variables ----------------------------------------------------
+
+ private AttributeSet attr;
+ private StyleSheet.BoxPainter painter;
+
+ private int cellSpacing;
+ private int borderWidth;
+
+ /**
+ * The index of the caption view if there is a caption.
+ * This has a value of -1 if there is no caption. The
+ * caption lives in the inset area of the table, and is
+ * updated with each time the grid is recalculated.
+ */
+ private int captionIndex;
+
+ /**
+ * Do any of the table cells contain a relative size
+ * specification? This is updated with each call to
+ * updateGrid(). If this is true, the ColumnIterator
+ * will do extra work to calculate relative cell
+ * specifications.
+ */
+ private boolean relativeCells;
+
+ /**
+ * Do any of the table cells span multiple rows? If
+ * true, the RowRequirementIterator will do additional
+ * work to adjust the requirements of rows spanned by
+ * a single table cell. This is updated with each call to
+ * updateGrid().
+ */
+ private boolean multiRowCells;
+
+ int[] columnSpans;
+ int[] columnOffsets;
+ /**
+ * SizeRequirements for all the columns.
+ */
+ SizeRequirements totalColumnRequirements;
+ SizeRequirements[] columnRequirements;
+
+ RowIterator rowIterator = new RowIterator();
+ ColumnIterator colIterator = new ColumnIterator();
+
+ Vector rows;
+
+ // whether to display comments inside table or not.
+ boolean skipComments = false;
+
+ boolean gridValid;
+ static final private BitSet EMPTY = new BitSet();
+
+ class ColumnIterator implements CSS.LayoutIterator {
+
+ /**
+ * Disable percentage adjustments which should only apply
+ * when calculating layout, not requirements.
+ */
+ void disablePercentages() {
+ percentages = null;
+ }
+
+ /**
+ * Update percentage adjustments if they are needed.
+ */
+ private void updatePercentagesAndAdjustmentWeights(int span) {
+ adjustmentWeights = new int[columnRequirements.length];
+ for (int i = 0; i < columnRequirements.length; i++) {
+ adjustmentWeights[i] = 0;
+ }
+ if (relativeCells) {
+ percentages = new int[columnRequirements.length];
+ } else {
+ percentages = null;
+ }
+ int nrows = getRowCount();
+ for (int rowIndex = 0; rowIndex < nrows; rowIndex++) {
+ RowView row = getRow(rowIndex);
+ int col = 0;
+ int ncells = row.getViewCount();
+ for (int cell = 0; cell < ncells; cell++, col++) {
+ View cv = row.getView(cell);
+ for (; row.isFilled(col); col++); // advance to a free column
+ int rowSpan = getRowsOccupied(cv);
+ int colSpan = getColumnsOccupied(cv);
+ AttributeSet a = cv.getAttributes();
+ CSS.LengthValue lv = (CSS.LengthValue)
+ a.getAttribute(CSS.Attribute.WIDTH);
+ if ( lv != null ) {
+ int len = (int) (lv.getValue(span) / colSpan + 0.5f);
+ for (int i = 0; i < colSpan; i++) {
+ if (lv.isPercentage()) {
+ // add a percentage requirement
+ percentages[col+i] = Math.max(percentages[col+i], len);
+ adjustmentWeights[col + i] = Math.max(adjustmentWeights[col + i], WorstAdjustmentWeight);
+ } else {
+ adjustmentWeights[col + i] = Math.max(adjustmentWeights[col + i], WorstAdjustmentWeight - 1);
+ }
+ }
+ }
+ col += colSpan - 1;
+ }
+ }
+ }
+
+ /**
+ * Set the layout arrays to use for holding layout results
+ */
+ public void setLayoutArrays(int offsets[], int spans[], int targetSpan) {
+ this.offsets = offsets;
+ this.spans = spans;
+ updatePercentagesAndAdjustmentWeights(targetSpan);
+ }
+
+ // --- RequirementIterator methods -------------------
+
+ public int getCount() {
+ return columnRequirements.length;
+ }
+
+ public void setIndex(int i) {
+ col = i;
+ }
+
+ public void setOffset(int offs) {
+ offsets[col] = offs;
+ }
+
+ public int getOffset() {
+ return offsets[col];
+ }
+
+ public void setSpan(int span) {
+ spans[col] = span;
+ }
+
+ public int getSpan() {
+ return spans[col];
+ }
+
+ public float getMinimumSpan(float parentSpan) {
+ // do not care for percentages, since min span can't
+ // be less than columnRequirements[col].minimum,
+ // but can be less than percentage value.
+ return columnRequirements[col].minimum;
+ }
+
+ public float getPreferredSpan(float parentSpan) {
+ if ((percentages != null) && (percentages[col] != 0)) {
+ return Math.max(percentages[col], columnRequirements[col].minimum);
+ }
+ return columnRequirements[col].preferred;
+ }
+
+ public float getMaximumSpan(float parentSpan) {
+ return columnRequirements[col].maximum;
+ }
+
+ public float getBorderWidth() {
+ return borderWidth;
+ }
+
+
+ public float getLeadingCollapseSpan() {
+ return cellSpacing;
+ }
+
+ public float getTrailingCollapseSpan() {
+ return cellSpacing;
+ }
+
+ public int getAdjustmentWeight() {
+ return adjustmentWeights[col];
+ }
+
+ /**
+ * Current column index
+ */
+ private int col;
+
+ /**
+ * percentage values (may be null since there
+ * might not be any).
+ */
+ private int[] percentages;
+
+ private int[] adjustmentWeights;
+
+ private int[] offsets;
+ private int[] spans;
+ }
+
+ class RowIterator implements CSS.LayoutIterator {
+
+ RowIterator() {
+ }
+
+ void updateAdjustments() {
+ int axis = Y_AXIS;
+ if (multiRowCells) {
+ // adjust requirements of multi-row cells
+ int n = getRowCount();
+ adjustments = new int[n];
+ for (int i = 0; i < n; i++) {
+ RowView rv = getRow(i);
+ if (rv.multiRowCells == true) {
+ int ncells = rv.getViewCount();
+ for (int j = 0; j < ncells; j++) {
+ View v = rv.getView(j);
+ int nrows = getRowsOccupied(v);
+ if (nrows > 1) {
+ int spanNeeded = (int) v.getPreferredSpan(axis);
+ adjustMultiRowSpan(spanNeeded, nrows, i);
+ }
+ }
+ }
+ }
+ } else {
+ adjustments = null;
+ }
+ }
+
+ /**
+ * Fixup preferences to accomodate a multi-row table cell
+ * if not already covered by existing preferences. This is
+ * a no-op if not all of the rows needed (to do this check/fixup)
+ * have arrived yet.
+ */
+ void adjustMultiRowSpan(int spanNeeded, int nrows, int rowIndex) {
+ if ((rowIndex + nrows) > getCount()) {
+ // rows are missing (could be a bad rowspan specification)
+ // or not all the rows have arrived. Do the best we can with
+ // the current set of rows.
+ nrows = getCount() - rowIndex;
+ if (nrows < 1) {
+ return;
+ }
+ }
+ int span = 0;
+ for (int i = 0; i < nrows; i++) {
+ RowView rv = getRow(rowIndex + i);
+ span += rv.getPreferredSpan(Y_AXIS);
+ }
+ if (spanNeeded > span) {
+ int adjust = (spanNeeded - span);
+ int rowAdjust = adjust / nrows;
+ int firstAdjust = rowAdjust + (adjust - (rowAdjust * nrows));
+ RowView rv = getRow(rowIndex);
+ adjustments[rowIndex] = Math.max(adjustments[rowIndex],
+ firstAdjust);
+ for (int i = 1; i < nrows; i++) {
+ adjustments[rowIndex + i] = Math.max(
+ adjustments[rowIndex + i], rowAdjust);
+ }
+ }
+ }
+
+ void setLayoutArrays(int[] offsets, int[] spans) {
+ this.offsets = offsets;
+ this.spans = spans;
+ }
+
+ // --- RequirementIterator methods -------------------
+
+ public void setOffset(int offs) {
+ RowView rv = getRow(row);
+ if (rv != null) {
+ offsets[rv.viewIndex] = offs;
+ }
+ }
+
+ public int getOffset() {
+ RowView rv = getRow(row);
+ if (rv != null) {
+ return offsets[rv.viewIndex];
+ }
+ return 0;
+ }
+
+ public void setSpan(int span) {
+ RowView rv = getRow(row);
+ if (rv != null) {
+ spans[rv.viewIndex] = span;
+ }
+ }
+
+ public int getSpan() {
+ RowView rv = getRow(row);
+ if (rv != null) {
+ return spans[rv.viewIndex];
+ }
+ return 0;
+ }
+
+ public int getCount() {
+ return rows.size();
+ }
+
+ public void setIndex(int i) {
+ row = i;
+ }
+
+ public float getMinimumSpan(float parentSpan) {
+ return getPreferredSpan(parentSpan);
+ }
+
+ public float getPreferredSpan(float parentSpan) {
+ RowView rv = getRow(row);
+ if (rv != null) {
+ int adjust = (adjustments != null) ? adjustments[row] : 0;
+ return rv.getPreferredSpan(TableView.this.getAxis()) + adjust;
+ }
+ return 0;
+ }
+
+ public float getMaximumSpan(float parentSpan) {
+ return getPreferredSpan(parentSpan);
+ }
+
+ public float getBorderWidth() {
+ return borderWidth;
+ }
+
+ public float getLeadingCollapseSpan() {
+ return cellSpacing;
+ }
+
+ public float getTrailingCollapseSpan() {
+ return cellSpacing;
+ }
+
+ public int getAdjustmentWeight() {
+ return 0;
+ }
+
+ /**
+ * Current row index
+ */
+ private int row;
+
+ /**
+ * Adjustments to the row requirements to handle multi-row
+ * table cells.
+ */
+ private int[] adjustments;
+
+ private int[] offsets;
+ private int[] spans;
+ }
+
+ /**
+ * View of a row in a row-centric table.
+ */
+ public class RowView extends BoxView {
+
+ /**
+ * Constructs a TableView for the given element.
+ *
+ * @param elem the element that this view is responsible for
+ */
+ public RowView(Element elem) {
+ super(elem, View.X_AXIS);
+ fillColumns = new BitSet();
+ RowView.this.setPropertiesFromAttributes();
+ }
+
+ void clearFilledColumns() {
+ fillColumns.and(EMPTY);
+ }
+
+ void fillColumn(int col) {
+ fillColumns.set(col);
+ }
+
+ boolean isFilled(int col) {
+ return fillColumns.get(col);
+ }
+
+ /**
+ * The number of columns present in this row.
+ */
+ int getColumnCount() {
+ int nfill = 0;
+ int n = fillColumns.size();
+ for (int i = 0; i < n; i++) {
+ if (fillColumns.get(i)) {
+ nfill ++;
+ }
+ }
+ return getViewCount() + nfill;
+ }
+
+ /**
+ * Fetches the attributes to use when rendering. This is
+ * implemented to multiplex the attributes specified in the
+ * model with a StyleSheet.
+ */
+ public AttributeSet getAttributes() {
+ return attr;
+ }
+
+ View findViewAtPoint(int x, int y, Rectangle alloc) {
+ int n = getViewCount();
+ for (int i = 0; i < n; i++) {
+ if (getChildAllocation(i, alloc).contains(x, y)) {
+ childAllocation(i, alloc);
+ return getView(i);
+ }
+ }
+ return null;
+ }
+
+ protected StyleSheet getStyleSheet() {
+ HTMLDocument doc = (HTMLDocument) getDocument();
+ return doc.getStyleSheet();
+ }
+
+ /**
+ * This is called by a child to indicate its
+ * preferred span has changed. This is implemented to
+ * execute the superclass behavior and well as try to
+ * determine if a row with a multi-row cell hangs across
+ * this row. If a multi-row cell covers this row it also
+ * needs to propagate a preferenceChanged so that it will
+ * recalculate the multi-row cell.
+ *
+ * @param child the child view
+ * @param width true if the width preference should change
+ * @param height true if the height preference should change
+ */
+ public void preferenceChanged(View child, boolean width, boolean height) {
+ super.preferenceChanged(child, width, height);
+ if (TableView.this.multiRowCells && height) {
+ for (int i = rowIndex - 1; i >= 0; i--) {
+ RowView rv = TableView.this.getRow(i);
+ if (rv.multiRowCells) {
+ rv.preferenceChanged(null, false, true);
+ break;
+ }
+ }
+ }
+ }
+
+ // The major axis requirements for a row are dictated by the column
+ // requirements. These methods use the value calculated by
+ // TableView.
+ protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
+ SizeRequirements req = new SizeRequirements();
+ req.minimum = totalColumnRequirements.minimum;
+ req.maximum = totalColumnRequirements.maximum;
+ req.preferred = totalColumnRequirements.preferred;
+ req.alignment = 0f;
+ return req;
+ }
+
+ public float getMinimumSpan(int axis) {
+ float value;
+
+ if (axis == View.X_AXIS) {
+ value = totalColumnRequirements.minimum + getLeftInset() +
+ getRightInset();
+ }
+ else {
+ value = super.getMinimumSpan(axis);
+ }
+ return value;
+ }
+
+ public float getMaximumSpan(int axis) {
+ float value;
+
+ if (axis == View.X_AXIS) {
+ // We're flexible.
+ value = (float)Integer.MAX_VALUE;
+ }
+ else {
+ value = super.getMaximumSpan(axis);
+ }
+ return value;
+ }
+
+ public float getPreferredSpan(int axis) {
+ float value;
+
+ if (axis == View.X_AXIS) {
+ value = totalColumnRequirements.preferred + getLeftInset() +
+ getRightInset();
+ }
+ else {
+ value = super.getPreferredSpan(axis);
+ }
+ return value;
+ }
+
+ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
+ super.changedUpdate(e, a, f);
+ int pos = e.getOffset();
+ if (pos <= getStartOffset() && (pos + e.getLength()) >=
+ getEndOffset()) {
+ RowView.this.setPropertiesFromAttributes();
+ }
+ }
+
+ /**
+ * Renders using the given rendering surface and area on that
+ * surface. This is implemented to delegate to the css box
+ * painter to paint the border and background prior to the
+ * interior.
+ *
+ * @param g the rendering surface to use
+ * @param allocation the allocated region to render into
+ * @see View#paint
+ */
+ public void paint(Graphics g, Shape allocation) {
+ Rectangle a = (Rectangle) allocation;
+ painter.paint(g, a.x, a.y, a.width, a.height, this);
+ super.paint(g, a);
+ }
+
+ /**
+ * Change the child views. This is implemented to
+ * provide the superclass behavior and invalidate the
+ * grid so that rows and columns will be recalculated.
+ */
+ public void replace(int offset, int length, View[] views) {
+ super.replace(offset, length, views);
+ invalidateGrid();
+ }
+
+ /**
+ * Calculate the height requirements of the table row. The
+ * requirements of multi-row cells are not considered for this
+ * calculation. The table itself will check and adjust the row
+ * requirements for all the rows that have multi-row cells spanning
+ * them. This method updates the multi-row flag that indicates that
+ * this row and rows below need additional consideration.
+ */
+ protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
+// return super.calculateMinorAxisRequirements(axis, r);
+ long min = 0;
+ long pref = 0;
+ long max = 0;
+ multiRowCells = false;
+ int n = getViewCount();
+ for (int i = 0; i < n; i++) {
+ View v = getView(i);
+ if (getRowsOccupied(v) > 1) {
+ multiRowCells = true;
+ max = Math.max((int) v.getMaximumSpan(axis), max);
+ } else {
+ min = Math.max((int) v.getMinimumSpan(axis), min);
+ pref = Math.max((int) v.getPreferredSpan(axis), pref);
+ max = Math.max((int) v.getMaximumSpan(axis), max);
+ }
+ }
+
+ if (r == null) {
+ r = new SizeRequirements();
+ r.alignment = 0.5f;
+ }
+ r.preferred = (int) pref;
+ r.minimum = (int) min;
+ r.maximum = (int) max;
+ return r;
+ }
+
+ /**
+ * Perform layout for the major axis of the box (i.e. the
+ * axis that it represents). The results of the layout should
+ * be placed in the given arrays which represent the allocations
+ * to the children along the major axis.
+ * <p>
+ * This is re-implemented to give each child the span of the column
+ * width for the table, and to give cells that span multiple columns
+ * the multi-column span.
+ *
+ * @param targetSpan the total span given to the view, which
+ * whould be used to layout the children
+ * @param axis the axis being layed out
+ * @param offsets the offsets from the origin of the view for
+ * each of the child views; this is a return value and is
+ * filled in by the implementation of this method
+ * @param spans the span of each child view; this is a return
+ * value and is filled in by the implementation of this method
+ * @return the offset and span for each child view in the
+ * offsets and spans parameters
+ */
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
+ int col = 0;
+ int ncells = getViewCount();
+ for (int cell = 0; cell < ncells; cell++) {
+ View cv = getView(cell);
+ if (skipComments && !(cv instanceof CellView)) {
+ continue;
+ }
+ for (; isFilled(col); col++); // advance to a free column
+ int colSpan = getColumnsOccupied(cv);
+ spans[cell] = columnSpans[col];
+ offsets[cell] = columnOffsets[col];
+ if (colSpan > 1) {
+ int n = columnSpans.length;
+ for (int j = 1; j < colSpan; j++) {
+ // Because the table may be only partially formed, some
+ // of the columns may not yet exist. Therefore we check
+ // the bounds.
+ if ((col+j) < n) {
+ spans[cell] += columnSpans[col+j];
+ spans[cell] += cellSpacing;
+ }
+ }
+ col += colSpan - 1;
+ }
+ col++;
+ }
+ }
+
+ /**
+ * Perform layout for the minor axis of the box (i.e. the
+ * axis orthoginal to the axis that it represents). The results
+ * of the layout should be placed in the given arrays which represent
+ * the allocations to the children along the minor axis. This
+ * is called by the superclass whenever the layout needs to be
+ * updated along the minor axis.
+ * <p>
+ * This is implemented to delegate to the superclass, then adjust
+ * the span for any cell that spans multiple rows.
+ *
+ * @param targetSpan the total span given to the view, which
+ * whould be used to layout the children
+ * @param axis the axis being layed out
+ * @param offsets the offsets from the origin of the view for
+ * each of the child views; this is a return value and is
+ * filled in by the implementation of this method
+ * @param spans the span of each child view; this is a return
+ * value and is filled in by the implementation of this method
+ * @return the offset and span for each child view in the
+ * offsets and spans parameters
+ */
+ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
+ super.layoutMinorAxis(targetSpan, axis, offsets, spans);
+ int col = 0;
+ int ncells = getViewCount();
+ for (int cell = 0; cell < ncells; cell++, col++) {
+ View cv = getView(cell);
+ for (; isFilled(col); col++); // advance to a free column
+ int colSpan = getColumnsOccupied(cv);
+ int rowSpan = getRowsOccupied(cv);
+ if (rowSpan > 1) {
+
+ int row0 = rowIndex;
+ int row1 = Math.min(rowIndex + rowSpan - 1, getRowCount()-1);
+ spans[cell] = getMultiRowSpan(row0, row1);
+ }
+ if (colSpan > 1) {
+ col += colSpan - 1;
+ }
+ }
+ }
+
+ /**
+ * Determines the resizability of the view along the
+ * given axis. A value of 0 or less is not resizable.
+ *
+ * @param axis may be either View.X_AXIS or View.Y_AXIS
+ * @return the resize weight
+ * @exception IllegalArgumentException for an invalid axis
+ */
+ public int getResizeWeight(int axis) {
+ return 1;
+ }
+
+ /**
+ * Fetches the child view that represents the given position in
+ * the model. This is implemented to walk through the children
+ * looking for a range that contains the given position. In this
+ * view the children do not necessarily have a one to one mapping
+ * with the child elements.
+ *
+ * @param pos the search position >= 0
+ * @param a the allocation to the table on entry, and the
+ * allocation of the view containing the position on exit
+ * @return the view representing the given position, or
+ * null if there isn't one
+ */
+ protected View getViewAtPosition(int pos, Rectangle a) {
+ int n = getViewCount();
+ for (int i = 0; i < n; i++) {
+ View v = getView(i);
+ int p0 = v.getStartOffset();
+ int p1 = v.getEndOffset();
+ if ((pos >= p0) && (pos < p1)) {
+ // it's in this view.
+ if (a != null) {
+ childAllocation(i, a);
+ }
+ return v;
+ }
+ }
+ if (pos == getEndOffset()) {
+ View v = getView(n - 1);
+ if (a != null) {
+ this.childAllocation(n - 1, a);
+ }
+ return v;
+ }
+ return null;
+ }
+
+ /**
+ * Update any cached values that come from attributes.
+ */
+ void setPropertiesFromAttributes() {
+ StyleSheet sheet = getStyleSheet();
+ attr = sheet.getViewAttributes(this);
+ painter = sheet.getBoxPainter(attr);
+ }
+
+ private StyleSheet.BoxPainter painter;
+ private AttributeSet attr;
+
+ /** columns filled by multi-column or multi-row cells */
+ BitSet fillColumns;
+
+ /**
+ * The row index within the overall grid
+ */
+ int rowIndex;
+
+ /**
+ * The view index (for row index to view index conversion).
+ * This is set by the updateGrid method.
+ */
+ int viewIndex;
+
+ /**
+ * Does this table row have cells that span multiple rows?
+ */
+ boolean multiRowCells;
+
+ }
+
+ /**
+ * Default view of an html table cell. This needs to be moved
+ * somewhere else.
+ */
+ class CellView extends BlockView {
+
+ /**
+ * Constructs a TableCell for the given element.
+ *
+ * @param elem the element that this view is responsible for
+ */
+ public CellView(Element elem) {
+ super(elem, Y_AXIS);
+ }
+
+ /**
+ * Perform layout for the major axis of the box (i.e. the
+ * axis that it represents). The results of the layout should
+ * be placed in the given arrays which represent the allocations
+ * to the children along the major axis. This is called by the
+ * superclass to recalculate the positions of the child views
+ * when the layout might have changed.
+ * <p>
+ * This is implemented to delegate to the superclass to
+ * tile the children. If the target span is greater than
+ * was needed, the offsets are adjusted to align the children
+ * (i.e. position according to the html valign attribute).
+ *
+ * @param targetSpan the total span given to the view, which
+ * whould be used to layout the children
+ * @param axis the axis being layed out
+ * @param offsets the offsets from the origin of the view for
+ * each of the child views; this is a return value and is
+ * filled in by the implementation of this method
+ * @param spans the span of each child view; this is a return
+ * value and is filled in by the implementation of this method
+ * @return the offset and span for each child view in the
+ * offsets and spans parameters
+ */
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
+ super.layoutMajorAxis(targetSpan, axis, offsets, spans);
+ // calculate usage
+ int used = 0;
+ int n = spans.length;
+ for (int i = 0; i < n; i++) {
+ used += spans[i];
+ }
+
+ // calculate adjustments
+ int adjust = 0;
+ if (used < targetSpan) {
+ // PENDING(prinz) change to use the css alignment.
+ String valign = (String) getElement().getAttributes().getAttribute(
+ HTML.Attribute.VALIGN);
+ if (valign == null) {
+ AttributeSet rowAttr = getElement().getParentElement().getAttributes();
+ valign = (String) rowAttr.getAttribute(HTML.Attribute.VALIGN);
+ }
+ if ((valign == null) || valign.equals("middle")) {
+ adjust = (targetSpan - used) / 2;
+ } else if (valign.equals("bottom")) {
+ adjust = targetSpan - used;
+ }
+ }
+
+ // make adjustments.
+ if (adjust != 0) {
+ for (int i = 0; i < n; i++) {
+ offsets[i] += adjust;
+ }
+ }
+ }
+
+ /**
+ * Calculate the requirements needed along the major axis.
+ * This is called by the superclass whenever the requirements
+ * need to be updated (i.e. a preferenceChanged was messaged
+ * through this view).
+ * <p>
+ * This is implemented to delegate to the superclass, but
+ * indicate the maximum size is very large (i.e. the cell
+ * is willing to expend to occupy the full height of the row).
+ *
+ * @param axis the axis being layed out.
+ * @param r the requirements to fill in. If null, a new one
+ * should be allocated.
+ */
+ protected SizeRequirements calculateMajorAxisRequirements(int axis,
+ SizeRequirements r) {
+ SizeRequirements req = super.calculateMajorAxisRequirements(axis, r);
+ req.maximum = Integer.MAX_VALUE;
+ return req;
+ }
+
+ @Override
+ protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
+ SizeRequirements rv = super.calculateMinorAxisRequirements(axis, r);
+ //for the cell the minimum should be derived from the child views
+ //the parent behaviour is to use CSS for that
+ int n = getViewCount();
+ int min = 0;
+ for (int i = 0; i < n; i++) {
+ View v = getView(i);
+ min = Math.max((int) v.getMinimumSpan(axis), min);
+ }
+ rv.minimum = Math.min(rv.minimum, min);
+ return rv;
+ }
+ }
+
+
+}
diff --git a/src/share/classes/javax/swing/text/html/TextAreaDocument.java b/src/share/classes/javax/swing/text/html/TextAreaDocument.java
new file mode 100644
index 000000000..7f9648da6
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/TextAreaDocument.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.swing.text.html;
+
+import javax.swing.text.*;
+
+
+/**
+ * TextAreaDocument extends the capabilities of the PlainDocument
+ * to store the data that is initially set in the Document.
+ * This is stored in order to enable an accurate reset of the
+ * state when a reset is requested.
+ *
+ * @author Sunita Mani
+ */
+
+class TextAreaDocument extends PlainDocument {
+
+ String initialText;
+
+
+ /**
+ * Resets the model by removing all the data,
+ * and restoring it to its initial state.
+ */
+ void reset() {
+ try {
+ remove(0, getLength());
+ if (initialText != null) {
+ insertString(0, initialText, null);
+ }
+ } catch (BadLocationException e) {
+ }
+ }
+
+ /**
+ * Stores the data that the model is initially
+ * loaded with.
+ */
+ void storeInitialText() {
+ try {
+ initialText = getText(0, getLength());
+ } catch (BadLocationException e) {
+ }
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/default.css b/src/share/classes/javax/swing/text/html/default.css
new file mode 100644
index 000000000..9edc66354
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/default.css
@@ -0,0 +1,267 @@
+/*
+ * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ */
+
+body {font-size: 14pt;
+ font-family: Serif;
+ font-weight: normal;
+ margin-left: 0;
+ margin-right: 0;
+ color: black}
+
+p {margin-top: 15}
+
+h1 {font-size: x-large;
+ font-weight: bold;
+ margin-top: 10;
+ margin-bottom: 10}
+
+h2 {font-size: large;
+ font-weight: bold;
+ margin-top: 10;
+ margin-bottom: 10}
+
+h3 {font-size: medium;
+ font-weight: bold;
+ margin-top: 10;
+ margin-bottom: 10}
+
+h4 {font-size: small;
+ font-weight: bold;
+ margin-top: 10;
+ margin-bottom: 10}
+
+h5 {font-size: x-small;
+ font-weight: bold;
+ margin-top: 10;
+ margin-bottom: 10}
+
+h6 {font-size: xx-small;
+ font-weight: bold;
+ margin-top: 10;
+ margin-bottom: 10}
+
+li p {margin-top: 0;
+ margin-bottom: 0}
+
+td p {margin-top: 0}
+
+menu li p {margin-top: 0;
+ margin-bottom: 0}
+
+menu li {margin-left: 0;
+ margin-right: 0;
+ margin-top: 0;
+ margin-bottom: 0}
+
+menu {margin-left-ltr: 40;
+ margin-right-rtl: 40;
+ margin-top: 10;
+ margin-bottom: 10}
+
+dir li p {margin-top: 0;
+ margin-bottom: 0}
+
+dir li {margin-left: 0;
+ margin-right: 0;
+ margin-top: 0;
+ margin-bottom: 0}
+
+dir {margin-left-ltr: 40;
+ margin-right-rtl: 40;
+ margin-top: 10;
+ margin-bottom: 10}
+
+dd {margin-left-ltr: 40;
+ margin-right-rtl: 40;
+ margin-top: 0;
+ margin-bottom: 0}
+
+dd p {margin-left: 0;
+ margin-rigth: 0;
+ margin-top: 0;
+ margin-bottom: 0}
+
+dt {margin-top: 0;
+ margin-bottom: 0}
+
+dl {margin-left: 0;
+ margin-top: 10;
+ margin-bottom: 10}
+
+ol li {margin-left: 0;
+ margin-right: 0;
+ margin-top: 0;
+ margin-bottom: 0}
+
+ol {
+ margin-top: 10;
+ margin-bottom: 10;
+ margin-left-ltr: 50;
+ margin-right-rtl: 50;
+ list-style-type: decimal
+}
+
+ol li p {margin-top: 0;
+ margin-bottom:0}
+
+ul li {margin-left: 0;
+ margin-right: 0;
+ margin-top: 0;
+ margin-bottom: 0}
+
+ul {margin-top: 10;
+ margin-bottom: 10;
+ margin-left-ltr: 50;
+ margin-right-rtl: 50;
+ list-style-type: disc;
+ -bullet-gap: 10}
+
+ul li ul li {margin-left: 0;
+ margin-right: 0;
+ margin-top: 0;
+ margin-bottom: 0}
+
+ul li ul {list-style-type: circle;
+ margin-left-ltr: 25;
+ margin-right-rtl: 25;}
+
+ul li ul li ul li {margin-left: 0;
+ margin-right: 0;
+ margin-top: 0;
+ margin-bottom: 0}
+
+ul li ul li ul {list-style-type: square;
+ margin-left-ltr: 25;
+ margin-right-rtl: 25}
+
+ul li menu {list-style-type: circle;
+ margin-left-ltr: 25;
+ margin-right-rtl: 25;}
+
+ul li p {margin-top: 0;
+ margin-bottom:0}
+
+a {color: blue;
+ text-decoration: underline}
+
+big {font-size: x-large}
+
+small {font-size: x-small}
+
+samp {font-size: small;
+ font-family: Monospaced}
+
+cite {font-style: italic}
+
+code {font-size: small;
+ font-family: Monospaced}
+
+dfn {font-style: italic}
+
+em {font-style: italic}
+
+i {font-style: italic}
+
+b {font-weight: bold}
+
+kbd {font-size: small;
+ font-family: Monospaced}
+
+s {text-decoration: line-through}
+
+strike {text-decoration: line-through}
+
+strong {font-weight: bold}
+
+sub {vertical-align: sub}
+
+sup {vertical-align: sup}
+
+tt {font-family: Monospaced}
+
+u {text-decoration: underline}
+
+var {font-weight: bold;
+ font-style: italic}
+
+table {
+ border-style: outset;
+ border-width: 0;
+}
+
+tr {
+ text-align: left
+}
+
+td {
+ border-width: 0;
+ border-style: inset;
+ padding-left: 3;
+ padding-right: 3;
+ padding-top: 3;
+ padding-bottom: 3
+}
+
+th {
+ text-align: center;
+ font-weight: bold;
+ border-width: 0;
+ border-style: inset;
+ padding-left: 3;
+ padding-right: 3;
+ padding-top: 3;
+ padding-bottom: 3
+}
+
+address {
+ color: blue;
+ font-style: italic
+}
+
+blockquote {
+ margin-top: 5;
+ margin-bottom: 5;
+ margin-left: 35;
+ margin-right: 35
+}
+
+center {text-align: center}
+
+pre {margin-top: 5;
+ margin-bottom: 5;
+ font-family: Monospaced}
+
+pre p {margin-top: 0}
+
+caption {
+ caption-side: top;
+ text-align: center
+}
+
+nobr { white-space: nowrap }
+
diff --git a/src/share/classes/javax/swing/text/html/package.html b/src/share/classes/javax/swing/text/html/package.html
new file mode 100644
index 000000000..cbad9ecb8
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/package.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
+DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+
+This code is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 2 only, as
+published by the Free Software Foundation. Sun designates this
+particular file as subject to the "Classpath" exception as provided
+by Sun in the LICENSE file that accompanied this code.
+
+This code is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+version 2 for more details (a copy is included in the LICENSE file that
+accompanied this code).
+
+You should have received a copy of the GNU General Public License version
+2 along with this work; if not, write to the Free Software Foundation,
+Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+CA 95054 USA or visit www.sun.com if you need additional information or
+have any questions.
+-->
+
+</head>
+<body bgcolor="white">
+
+Provides the class <code>HTMLEditorKit</code> and supporting classes
+for creating HTML text editors.
+
+<p>
+<strong>Note:</strong>
+Most of the Swing API is <em>not</em> thread safe.
+For details, see
+<a
+href="http://java.sun.com/docs/books/tutorial/uiswing/overview/threads.html"
+target="_top">Threads and Swing</a>,
+a section in
+<em><a href="http://java.sun.com/docs/books/tutorial/"
+target="_top">The Java Tutorial</a></em>.
+
+<h2>Package Specification</h2>
+
+
+<ul>
+ <li><a href="http://www.w3.org/TR/REC-html32.html" target="_top">
+ HTML 3.2 Reference Specification</a> -
+ The HTML specification on which HTMLEditorKit is based.
+</ul>
+
+@since 1.2
+@serial exclude
+
+</body>
+</html>
diff --git a/src/share/classes/javax/swing/text/html/parser/AttributeList.java b/src/share/classes/javax/swing/text/html/parser/AttributeList.java
new file mode 100644
index 000000000..a2d48603d
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/AttributeList.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 1998-2004 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html.parser;
+
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.Enumeration;
+import java.io.*;
+
+/**
+ * This class defines the attributes of an SGML element
+ * as described in a DTD using the ATTLIST construct.
+ * An AttributeList can be obtained from the Element
+ * class using the getAttributes() method.
+ * <p>
+ * It is actually an element in a linked list. Use the
+ * getNext() method repeatedly to enumerate all the attributes
+ * of an element.
+ *
+ * @see Element
+ * @author Arthur Van Hoff
+ *
+ */
+public final
+class AttributeList implements DTDConstants, Serializable {
+ public String name;
+ public int type;
+ public Vector<?> values;
+ public int modifier;
+ public String value;
+ public AttributeList next;
+
+ AttributeList() {
+ }
+
+ /**
+ * Create an attribute list element.
+ */
+ public AttributeList(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Create an attribute list element.
+ */
+ public AttributeList(String name, int type, int modifier, String value, Vector<?> values, AttributeList next) {
+ this.name = name;
+ this.type = type;
+ this.modifier = modifier;
+ this.value = value;
+ this.values = values;
+ this.next = next;
+ }
+
+ /**
+ * @return attribute name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return attribute type
+ * @see DTDConstants
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * @return attribute modifier
+ * @see DTDConstants
+ */
+ public int getModifier() {
+ return modifier;
+ }
+
+ /**
+ * @return possible attribute values
+ */
+ public Enumeration<?> getValues() {
+ return (values != null) ? values.elements() : null;
+ }
+
+ /**
+ * @return default attribute value
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * @return the next attribute in the list
+ */
+ public AttributeList getNext() {
+ return next;
+ }
+
+ /**
+ * @return string representation
+ */
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * Create a hashtable of attribute types.
+ */
+ static Hashtable attributeTypes = new Hashtable();
+
+ static void defineAttributeType(String nm, int val) {
+ Integer num = new Integer(val);
+ attributeTypes.put(nm, num);
+ attributeTypes.put(num, nm);
+ }
+
+ static {
+ defineAttributeType("CDATA", CDATA);
+ defineAttributeType("ENTITY", ENTITY);
+ defineAttributeType("ENTITIES", ENTITIES);
+ defineAttributeType("ID", ID);
+ defineAttributeType("IDREF", IDREF);
+ defineAttributeType("IDREFS", IDREFS);
+ defineAttributeType("NAME", NAME);
+ defineAttributeType("NAMES", NAMES);
+ defineAttributeType("NMTOKEN", NMTOKEN);
+ defineAttributeType("NMTOKENS", NMTOKENS);
+ defineAttributeType("NOTATION", NOTATION);
+ defineAttributeType("NUMBER", NUMBER);
+ defineAttributeType("NUMBERS", NUMBERS);
+ defineAttributeType("NUTOKEN", NUTOKEN);
+ defineAttributeType("NUTOKENS", NUTOKENS);
+
+ attributeTypes.put("fixed", new Integer(FIXED));
+ attributeTypes.put("required", new Integer(REQUIRED));
+ attributeTypes.put("current", new Integer(CURRENT));
+ attributeTypes.put("conref", new Integer(CONREF));
+ attributeTypes.put("implied", new Integer(IMPLIED));
+ }
+
+ public static int name2type(String nm) {
+ Integer i = (Integer)attributeTypes.get(nm);
+ return (i == null) ? CDATA : i.intValue();
+ }
+
+ public static String type2name(int tp) {
+ return (String)attributeTypes.get(new Integer(tp));
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/parser/ContentModel.java b/src/share/classes/javax/swing/text/html/parser/ContentModel.java
new file mode 100644
index 000000000..852391f4b
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/ContentModel.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 1998-2004 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html.parser;
+
+import java.util.Vector;
+import java.util.Enumeration;
+import java.io.*;
+
+
+/**
+ * A representation of a content model. A content model is
+ * basically a restricted BNF expression. It is restricted in
+ * the sense that it must be deterministic. This means that you
+ * don't have to represent it as a finite state automata.<p>
+ * See Annex H on page 556 of the SGML handbook for more information.
+ *
+ * @author Arthur van Hoff
+ *
+ */
+public final class ContentModel implements Serializable {
+ /**
+ * Type. Either '*', '?', '+', ',', '|', '&'.
+ */
+ public int type;
+
+ /**
+ * The content. Either an Element or a ContentModel.
+ */
+ public Object content;
+
+ /**
+ * The next content model (in a ',', '|' or '&' expression).
+ */
+ public ContentModel next;
+
+ public ContentModel() {
+ }
+
+ /**
+ * Create a content model for an element.
+ */
+ public ContentModel(Element content) {
+ this(0, content, null);
+ }
+
+ /**
+ * Create a content model of a particular type.
+ */
+ public ContentModel(int type, ContentModel content) {
+ this(type, content, null);
+ }
+
+ /**
+ * Create a content model of a particular type.
+ */
+ public ContentModel(int type, Object content, ContentModel next) {
+ this.type = type;
+ this.content = content;
+ this.next = next;
+ }
+
+ /**
+ * Return true if the content model could
+ * match an empty input stream.
+ */
+ public boolean empty() {
+ switch (type) {
+ case '*':
+ case '?':
+ return true;
+
+ case '+':
+ case '|':
+ for (ContentModel m = (ContentModel)content ; m != null ; m = m.next) {
+ if (m.empty()) {
+ return true;
+ }
+ }
+ return false;
+
+ case ',':
+ case '&':
+ for (ContentModel m = (ContentModel)content ; m != null ; m = m.next) {
+ if (!m.empty()) {
+ return false;
+ }
+ }
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Update elemVec with the list of elements that are
+ * part of the this contentModel.
+ */
+ public void getElements(Vector<Element> elemVec) {
+ switch (type) {
+ case '*':
+ case '?':
+ case '+':
+ ((ContentModel)content).getElements(elemVec);
+ break;
+ case ',':
+ case '|':
+ case '&':
+ for (ContentModel m=(ContentModel)content; m != null; m=m.next){
+ m.getElements(elemVec);
+ }
+ break;
+ default:
+ elemVec.addElement((Element)content);
+ }
+ }
+
+ private boolean valSet[];
+ private boolean val[];
+ // A cache used by first(). This cache was found to speed parsing
+ // by about 10% (based on measurements of the 4-12 code base after
+ // buffering was fixed).
+
+ /**
+ * Return true if the token could potentially be the
+ * first token in the input stream.
+ */
+ public boolean first(Object token) {
+ switch (type) {
+ case '*':
+ case '?':
+ case '+':
+ return ((ContentModel)content).first(token);
+
+ case ',':
+ for (ContentModel m = (ContentModel)content ; m != null ; m = m.next) {
+ if (m.first(token)) {
+ return true;
+ }
+ if (!m.empty()) {
+ return false;
+ }
+ }
+ return false;
+
+ case '|':
+ case '&': {
+ Element e = (Element) token;
+ if (valSet == null) {
+ valSet = new boolean[Element.maxIndex + 1];
+ val = new boolean[Element.maxIndex + 1];
+ // All Element instances are created before this ever executes
+ }
+ if (valSet[e.index]) {
+ return val[e.index];
+ }
+ for (ContentModel m = (ContentModel)content ; m != null ; m = m.next) {
+ if (m.first(token)) {
+ val[e.index] = true;
+ break;
+ }
+ }
+ valSet[e.index] = true;
+ return val[e.index];
+ }
+
+ default:
+ return (content == token);
+ // PENDING: refer to comment in ContentModelState
+/*
+ if (content == token) {
+ return true;
+ }
+ Element e = (Element)content;
+ if (e.omitStart() && e.content != null) {
+ return e.content.first(token);
+ }
+ return false;
+*/
+ }
+ }
+
+ /**
+ * Return the element that must be next.
+ */
+ public Element first() {
+ switch (type) {
+ case '&':
+ case '|':
+ case '*':
+ case '?':
+ return null;
+
+ case '+':
+ case ',':
+ return ((ContentModel)content).first();
+
+ default:
+ return (Element)content;
+ }
+ }
+
+ /**
+ * Convert to a string.
+ */
+ public String toString() {
+ switch (type) {
+ case '*':
+ return content + "*";
+ case '?':
+ return content + "?";
+ case '+':
+ return content + "+";
+
+ case ',':
+ case '|':
+ case '&':
+ char data[] = {' ', (char)type, ' '};
+ String str = "";
+ for (ContentModel m = (ContentModel)content ; m != null ; m = m.next) {
+ str = str + m;
+ if (m.next != null) {
+ str += new String(data);
+ }
+ }
+ return "(" + str + ")";
+
+ default:
+ return content.toString();
+ }
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/parser/ContentModelState.java b/src/share/classes/javax/swing/text/html/parser/ContentModelState.java
new file mode 100644
index 000000000..42444452a
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/ContentModelState.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html.parser;
+
+/**
+ * A content model state. This is basically a list of pointers to
+ * the BNF expression representing the model (the ContentModel).
+ * Each element in a DTD has a content model which describes the
+ * elements that may occur inside, and the order in which they can
+ * occur.
+ * <p>
+ * Each time a token is reduced a new state is created.
+ * <p>
+ * See Annex H on page 556 of the SGML handbook for more information.
+ *
+ * @see Parser
+ * @see DTD
+ * @see Element
+ * @see ContentModel
+ * @author Arthur van Hoff
+ */
+class ContentModelState {
+ ContentModel model;
+ long value;
+ ContentModelState next;
+
+ /**
+ * Create a content model state for a content model.
+ */
+ public ContentModelState(ContentModel model) {
+ this(model, null, 0);
+ }
+
+ /**
+ * Create a content model state for a content model given the
+ * remaining state that needs to be reduce.
+ */
+ ContentModelState(Object content, ContentModelState next) {
+ this(content, next, 0);
+ }
+
+ /**
+ * Create a content model state for a content model given the
+ * remaining state that needs to be reduce.
+ */
+ ContentModelState(Object content, ContentModelState next, long value) {
+ this.model = (ContentModel)content;
+ this.next = next;
+ this.value = value;
+ }
+
+ /**
+ * Return the content model that is relevant to the current state.
+ */
+ public ContentModel getModel() {
+ ContentModel m = model;
+ for (int i = 0; i < value; i++) {
+ if (m.next != null) {
+ m = m.next;
+ } else {
+ return null;
+ }
+ }
+ return m;
+ }
+
+ /**
+ * Check if the state can be terminated. That is there are no more
+ * tokens required in the input stream.
+ * @return true if the model can terminate without further input
+ */
+ public boolean terminate() {
+ switch (model.type) {
+ case '+':
+ if ((value == 0) && !(model).empty()) {
+ return false;
+ }
+ case '*':
+ case '?':
+ return (next == null) || next.terminate();
+
+ case '|':
+ for (ContentModel m = (ContentModel)model.content ; m != null ; m = m.next) {
+ if (m.empty()) {
+ return (next == null) || next.terminate();
+ }
+ }
+ return false;
+
+ case '&': {
+ ContentModel m = (ContentModel)model.content;
+
+ for (int i = 0 ; m != null ; i++, m = m.next) {
+ if ((value & (1L << i)) == 0) {
+ if (!m.empty()) {
+ return false;
+ }
+ }
+ }
+ return (next == null) || next.terminate();
+ }
+
+ case ',': {
+ ContentModel m = (ContentModel)model.content;
+ for (int i = 0 ; i < value ; i++, m = m.next);
+
+ for (; (m != null) && m.empty() ; m = m.next);
+ if (m != null) {
+ return false;
+ }
+ return (next == null) || next.terminate();
+ }
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Check if the state can be terminated. That is there are no more
+ * tokens required in the input stream.
+ * @return the only possible element that can occur next
+ */
+ public Element first() {
+ switch (model.type) {
+ case '*':
+ case '?':
+ case '|':
+ case '&':
+ return null;
+
+ case '+':
+ return model.first();
+
+ case ',': {
+ ContentModel m = (ContentModel)model.content;
+ for (int i = 0 ; i < value ; i++, m = m.next);
+ return m.first();
+ }
+
+ default:
+ return model.first();
+ }
+ }
+
+ /**
+ * Advance this state to a new state. An exception is thrown if the
+ * token is illegal at this point in the content model.
+ * @return next state after reducing a token
+ */
+ public ContentModelState advance(Object token) {
+ switch (model.type) {
+ case '+':
+ if (model.first(token)) {
+ return new ContentModelState(model.content,
+ new ContentModelState(model, next, value + 1)).advance(token);
+ }
+ if (value != 0) {
+ if (next != null) {
+ return next.advance(token);
+ } else {
+ return null;
+ }
+ }
+ break;
+
+ case '*':
+ if (model.first(token)) {
+ return new ContentModelState(model.content, this).advance(token);
+ }
+ if (next != null) {
+ return next.advance(token);
+ } else {
+ return null;
+ }
+
+ case '?':
+ if (model.first(token)) {
+ return new ContentModelState(model.content, next).advance(token);
+ }
+ if (next != null) {
+ return next.advance(token);
+ } else {
+ return null;
+ }
+
+ case '|':
+ for (ContentModel m = (ContentModel)model.content ; m != null ; m = m.next) {
+ if (m.first(token)) {
+ return new ContentModelState(m, next).advance(token);
+ }
+ }
+ break;
+
+ case ',': {
+ ContentModel m = (ContentModel)model.content;
+ for (int i = 0 ; i < value ; i++, m = m.next);
+
+ if (m.first(token) || m.empty()) {
+ if (m.next == null) {
+ return new ContentModelState(m, next).advance(token);
+ } else {
+ return new ContentModelState(m,
+ new ContentModelState(model, next, value + 1)).advance(token);
+ }
+ }
+ break;
+ }
+
+ case '&': {
+ ContentModel m = (ContentModel)model.content;
+ boolean complete = true;
+
+ for (int i = 0 ; m != null ; i++, m = m.next) {
+ if ((value & (1L << i)) == 0) {
+ if (m.first(token)) {
+ return new ContentModelState(m,
+ new ContentModelState(model, next, value | (1L << i))).advance(token);
+ }
+ if (!m.empty()) {
+ complete = false;
+ }
+ }
+ }
+ if (complete) {
+ if (next != null) {
+ return next.advance(token);
+ } else {
+ return null;
+ }
+ }
+ break;
+ }
+
+ default:
+ if (model.content == token) {
+ if (next == null && (token instanceof Element) &&
+ ((Element)token).content != null) {
+ return new ContentModelState(((Element)token).content);
+ }
+ return next;
+ }
+ // PENDING: Currently we don't correctly deal with optional start
+ // tags. This can most notably be seen with the 4.01 spec where
+ // TBODY's start and end tags are optional.
+ // Uncommenting this and the PENDING in ContentModel will
+ // correctly skip the omit tags, but the delegate is not notified.
+ // Some additional API needs to be added to track skipped tags,
+ // and this can then be added back.
+/*
+ if ((model.content instanceof Element)) {
+ Element e = (Element)model.content;
+
+ if (e.omitStart() && e.content != null) {
+ return new ContentModelState(e.content, next).advance(
+ token);
+ }
+ }
+*/
+ }
+
+ // We used to throw this exception at this point. However, it
+ // was determined that throwing this exception was more expensive
+ // than returning null, and we could not justify to ourselves why
+ // it was necessary to throw an exception, rather than simply
+ // returning null. I'm leaving it in a commented out state so
+ // that it can be easily restored if the situation ever arises.
+ //
+ // throw new IllegalArgumentException("invalid token: " + token);
+ return null;
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/parser/DTD.java b/src/share/classes/javax/swing/text/html/parser/DTD.java
new file mode 100644
index 000000000..3fd48f16b
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/DTD.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html.parser;
+
+import java.io.PrintStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.util.Hashtable;
+import java.util.Vector;
+import java.util.BitSet;
+import java.util.StringTokenizer;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.net.URL;
+
+/**
+ * The representation of an SGML DTD. DTD describes a document
+ * syntax and is used in parsing of HTML documents. It contains
+ * a list of elements and their attributes as well as a list of
+ * entities defined in the DTD.
+ *
+ * @see Element
+ * @see AttributeList
+ * @see ContentModel
+ * @see Parser
+ * @author Arthur van Hoff
+ */
+public
+class DTD implements DTDConstants {
+ public String name;
+ public Vector<Element> elements = new Vector<Element>();
+ public Hashtable<String,Element> elementHash
+ = new Hashtable<String,Element>();
+ public Hashtable<Object,Entity> entityHash
+ = new Hashtable<Object,Entity>();
+ public final Element pcdata = getElement("#pcdata");
+ public final Element html = getElement("html");
+ public final Element meta = getElement("meta");
+ public final Element base = getElement("base");
+ public final Element isindex = getElement("isindex");
+ public final Element head = getElement("head");
+ public final Element body = getElement("body");
+ public final Element applet = getElement("applet");
+ public final Element param = getElement("param");
+ public final Element p = getElement("p");
+ public final Element title = getElement("title");
+ final Element style = getElement("style");
+ final Element link = getElement("link");
+ final Element script = getElement("script");
+
+ public static final int FILE_VERSION = 1;
+
+ /**
+ * Creates a new DTD with the specified name.
+ * @param name the name, as a <code>String</code> of the new DTD
+ */
+ protected DTD(String name) {
+ this.name = name;
+ defEntity("#RE", GENERAL, '\r');
+ defEntity("#RS", GENERAL, '\n');
+ defEntity("#SPACE", GENERAL, ' ');
+ defineElement("unknown", EMPTY, false, true, null, null, null, null);
+ }
+
+ /**
+ * Gets the name of the DTD.
+ * @return the name of the DTD
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Gets an entity by name.
+ * @return the <code>Entity</code> corresponding to the
+ * <code>name</code> <code>String</code>
+ */
+ public Entity getEntity(String name) {
+ return (Entity)entityHash.get(name);
+ }
+
+ /**
+ * Gets a character entity.
+ * @return the <code>Entity</code> corresponding to the
+ * <code>ch</code> character
+ */
+ public Entity getEntity(int ch) {
+ return (Entity)entityHash.get(new Integer(ch));
+ }
+
+ /**
+ * Returns <code>true</code> if the element is part of the DTD,
+ * otherwise returns <code>false</code>.
+ *
+ * @param name the requested <code>String</code>
+ * @return <code>true</code> if <code>name</code> exists as
+ * part of the DTD, otherwise returns <code>false</code>
+ */
+ boolean elementExists(String name) {
+ return !"unknown".equals(name) && (elementHash.get(name) != null);
+ }
+
+ /**
+ * Gets an element by name. A new element is
+ * created if the element doesn't exist.
+ *
+ * @param name the requested <code>String</code>
+ * @return the <code>Element</code> corresponding to
+ * <code>name</code>, which may be newly created
+ */
+ public Element getElement(String name) {
+ Element e = (Element)elementHash.get(name);
+ if (e == null) {
+ e = new Element(name, elements.size());
+ elements.addElement(e);
+ elementHash.put(name, e);
+ }
+ return e;
+ }
+
+ /**
+ * Gets an element by index.
+ *
+ * @param index the requested index
+ * @return the <code>Element</code> corresponding to
+ * <code>index</code>
+ */
+ public Element getElement(int index) {
+ return (Element)elements.elementAt(index);
+ }
+
+ /**
+ * Defines an entity. If the <code>Entity</code> specified
+ * by <code>name</code>, <code>type</code>, and <code>data</code>
+ * exists, it is returned; otherwise a new <code>Entity</code>
+ * is created and is returned.
+ *
+ * @param name the name of the <code>Entity</code> as a <code>String</code>
+ * @param type the type of the <code>Entity</code>
+ * @param data the <code>Entity</code>'s data
+ * @return the <code>Entity</code> requested or a new <code>Entity</code>
+ * if not found
+ */
+ public Entity defineEntity(String name, int type, char data[]) {
+ Entity ent = (Entity)entityHash.get(name);
+ if (ent == null) {
+ ent = new Entity(name, type, data);
+ entityHash.put(name, ent);
+ if (((type & GENERAL) != 0) && (data.length == 1)) {
+ switch (type & ~GENERAL) {
+ case CDATA:
+ case SDATA:
+ entityHash.put(new Integer(data[0]), ent);
+ break;
+ }
+ }
+ }
+ return ent;
+ }
+
+ /**
+ * Returns the <code>Element</code> which matches the
+ * specified parameters. If one doesn't exist, a new
+ * one is created and returned.
+ *
+ * @param name the name of the <code>Element</code>
+ * @param type the type of the <code>Element</code>
+ * @param omitStart <code>true</code> if start should be omitted
+ * @param omitEnd <code>true</code> if end should be omitted
+ * @param content the <code>ContentModel</code>
+ * @param atts the <code>AttributeList</code> specifying the
+ * <code>Element</code>
+ * @return the <code>Element</code> specified
+ */
+ public Element defineElement(String name, int type,
+ boolean omitStart, boolean omitEnd, ContentModel content,
+ BitSet exclusions, BitSet inclusions, AttributeList atts) {
+ Element e = getElement(name);
+ e.type = type;
+ e.oStart = omitStart;
+ e.oEnd = omitEnd;
+ e.content = content;
+ e.exclusions = exclusions;
+ e.inclusions = inclusions;
+ e.atts = atts;
+ return e;
+ }
+
+ /**
+ * Defines attributes for an {@code Element}.
+ *
+ * @param name the name of the <code>Element</code>
+ * @param atts the <code>AttributeList</code> specifying the
+ * <code>Element</code>
+ */
+ public void defineAttributes(String name, AttributeList atts) {
+ Element e = getElement(name);
+ e.atts = atts;
+ }
+
+ /**
+ * Creates and returns a character <code>Entity</code>.
+ * @param name the entity's name
+ * @return the new character <code>Entity</code>
+ */
+ public Entity defEntity(String name, int type, int ch) {
+ char data[] = {(char)ch};
+ return defineEntity(name, type, data);
+ }
+
+ /**
+ * Creates and returns an <code>Entity</code>.
+ * @param name the entity's name
+ * @return the new <code>Entity</code>
+ */
+ protected Entity defEntity(String name, int type, String str) {
+ int len = str.length();
+ char data[] = new char[len];
+ str.getChars(0, len, data, 0);
+ return defineEntity(name, type, data);
+ }
+
+ /**
+ * Creates and returns an <code>Element</code>.
+ * @param name the element's name
+ * @return the new <code>Element</code>
+ */
+ protected Element defElement(String name, int type,
+ boolean omitStart, boolean omitEnd, ContentModel content,
+ String[] exclusions, String[] inclusions, AttributeList atts) {
+ BitSet excl = null;
+ if (exclusions != null && exclusions.length > 0) {
+ excl = new BitSet();
+ for (int i = 0; i < exclusions.length; i++) {
+ String str = exclusions[i];
+ if (str.length() > 0) {
+ excl.set(getElement(str).getIndex());
+ }
+ }
+ }
+ BitSet incl = null;
+ if (inclusions != null && inclusions.length > 0) {
+ incl = new BitSet();
+ for (int i = 0; i < inclusions.length; i++) {
+ String str = inclusions[i];
+ if (str.length() > 0) {
+ incl.set(getElement(str).getIndex());
+ }
+ }
+ }
+ return defineElement(name, type, omitStart, omitEnd, content, excl, incl, atts);
+ }
+
+ /**
+ * Creates and returns an <code>AttributeList</code>.
+ * @param name the attribute list's name
+ * @return the new <code>AttributeList</code>
+ */
+ protected AttributeList defAttributeList(String name, int type, int modifier, String value, String values, AttributeList atts) {
+ Vector vals = null;
+ if (values != null) {
+ vals = new Vector();
+ for (StringTokenizer s = new StringTokenizer(values, "|") ; s.hasMoreTokens() ;) {
+ String str = s.nextToken();
+ if (str.length() > 0) {
+ vals.addElement(str);
+ }
+ }
+ }
+ return new AttributeList(name, type, modifier, value, vals, atts);
+ }
+
+ /**
+ * Creates and returns a new content model.
+ * @param type the type of the new content model
+ * @return the new <code>ContentModel</code>
+ */
+ protected ContentModel defContentModel(int type, Object obj, ContentModel next) {
+ return new ContentModel(type, obj, next);
+ }
+
+ /**
+ * Returns a string representation of this DTD.
+ * @return the string representation of this DTD
+ */
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * The hashtable of DTDs.
+ */
+ static Hashtable dtdHash = new Hashtable();
+
+ public static void putDTDHash(String name, DTD dtd) {
+ dtdHash.put(name, dtd);
+ }
+ /**
+ * Returns a DTD with the specified <code>name</code>. If
+ * a DTD with that name doesn't exist, one is created
+ * and returned. Any uppercase characters in the name
+ * are converted to lowercase.
+ *
+ * @param name the name of the DTD
+ * @return the DTD which corresponds to <code>name</code>
+ */
+ public static DTD getDTD(String name) throws IOException {
+ name = name.toLowerCase();
+ DTD dtd = (DTD)dtdHash.get(name);
+ if (dtd == null)
+ dtd = new DTD(name);
+
+ return dtd;
+ }
+
+ /**
+ * Recreates a DTD from an archived format.
+ * @param in the <code>DataInputStream</code> to read from
+ */
+ public void read(DataInputStream in) throws IOException {
+ if (in.readInt() != FILE_VERSION) {
+ }
+
+ //
+ // Read the list of names
+ //
+ String[] names = new String[in.readShort()];
+ for (int i = 0; i < names.length; i++) {
+ names[i] = in.readUTF();
+ }
+
+
+ //
+ // Read the entities
+ //
+ int num = in.readShort();
+ for (int i = 0; i < num; i++) {
+ short nameId = in.readShort();
+ int type = in.readByte();
+ String name = in.readUTF();
+ defEntity(names[nameId], type | GENERAL, name);
+ }
+
+ // Read the elements
+ //
+ num = in.readShort();
+ for (int i = 0; i < num; i++) {
+ short nameId = in.readShort();
+ int type = in.readByte();
+ byte flags = in.readByte();
+ ContentModel m = readContentModel(in, names);
+ String[] exclusions = readNameArray(in, names);
+ String[] inclusions = readNameArray(in, names);
+ AttributeList atts = readAttributeList(in, names);
+ defElement(names[nameId], type,
+ ((flags & 0x01) != 0), ((flags & 0x02) != 0),
+ m, exclusions, inclusions, atts);
+ }
+ }
+
+ private ContentModel readContentModel(DataInputStream in, String[] names)
+ throws IOException {
+ byte flag = in.readByte();
+ switch(flag) {
+ case 0: // null
+ return null;
+ case 1: { // content_c
+ int type = in.readByte();
+ ContentModel m = readContentModel(in, names);
+ ContentModel next = readContentModel(in, names);
+ return defContentModel(type, m, next);
+ }
+ case 2: { // content_e
+ int type = in.readByte();
+ Element el = getElement(names[in.readShort()]);
+ ContentModel next = readContentModel(in, names);
+ return defContentModel(type, el, next);
+ }
+ default:
+ throw new IOException("bad bdtd");
+ }
+ }
+
+ private String[] readNameArray(DataInputStream in, String[] names)
+ throws IOException {
+ int num = in.readShort();
+ if (num == 0) {
+ return null;
+ }
+ String[] result = new String[num];
+ for (int i = 0; i < num; i++) {
+ result[i] = names[in.readShort()];
+ }
+ return result;
+ }
+
+
+ private AttributeList readAttributeList(DataInputStream in, String[] names)
+ throws IOException {
+ AttributeList result = null;
+ for (int num = in.readByte(); num > 0; --num) {
+ short nameId = in.readShort();
+ int type = in.readByte();
+ int modifier = in.readByte();
+ short valueId = in.readShort();
+ String value = (valueId == -1) ? null : names[valueId];
+ Vector values = null;
+ short numValues = in.readShort();
+ if (numValues > 0) {
+ values = new Vector(numValues);
+ for (int i = 0; i < numValues; i++) {
+ values.addElement(names[in.readShort()]);
+ }
+ }
+result = new AttributeList(names[nameId], type, modifier, value,
+ values, result);
+ // We reverse the order of the linked list by doing this, but
+ // that order isn't important.
+ }
+ return result;
+ }
+
+}
diff --git a/src/share/classes/javax/swing/text/html/parser/DTDConstants.java b/src/share/classes/javax/swing/text/html/parser/DTDConstants.java
new file mode 100644
index 000000000..f3fba925e
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/DTDConstants.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 1998-1999 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html.parser;
+
+/**
+ * SGML constants used in a DTD. The names of the
+ * constants correspond the the equivalent SGML constructs
+ * as described in "The SGML Handbook" by Charles F. Goldfarb.
+ *
+ * @see DTD
+ * @see Element
+ * @author Arthur van Hoff
+ */
+public
+interface DTDConstants {
+ // Attribute value types
+ int CDATA = 1;
+ int ENTITY = 2;
+ int ENTITIES = 3;
+ int ID = 4;
+ int IDREF = 5;
+ int IDREFS = 6;
+ int NAME = 7;
+ int NAMES = 8;
+ int NMTOKEN = 9;
+ int NMTOKENS = 10;
+ int NOTATION = 11;
+ int NUMBER = 12;
+ int NUMBERS = 13;
+ int NUTOKEN = 14;
+ int NUTOKENS = 15;
+
+ // Content model types
+ int RCDATA = 16;
+ int EMPTY = 17;
+ int MODEL = 18;
+ int ANY = 19;
+
+ // Attribute value modifiers
+ int FIXED = 1;
+ int REQUIRED = 2;
+ int CURRENT = 3;
+ int CONREF = 4;
+ int IMPLIED = 5;
+
+ // Entity types
+ int PUBLIC = 10;
+ int SDATA = 11;
+ int PI = 12;
+ int STARTTAG = 13;
+ int ENDTAG = 14;
+ int MS = 15;
+ int MD = 16;
+ int SYSTEM = 17;
+
+ int GENERAL = 1<<16;
+ int DEFAULT = 1<<17;
+ int PARAMETER = 1<<18;
+}
diff --git a/src/share/classes/javax/swing/text/html/parser/DocumentParser.java b/src/share/classes/javax/swing/text/html/parser/DocumentParser.java
new file mode 100644
index 000000000..80417b5f7
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/DocumentParser.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 1998-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html.parser;
+
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.HTML;
+import javax.swing.text.ChangedCharSetException;
+
+import java.util.*;
+import java.io.*;
+import java.net.*;
+
+/**
+ * A Parser for HTML Documents (actually, you can specify a DTD, but
+ * you should really only use this class with the html dtd in swing).
+ * Reads an InputStream of HTML and
+ * invokes the appropriate methods in the ParserCallback class. This
+ * is the default parser used by HTMLEditorKit to parse HTML url's.
+ * <p>This will message the callback for all valid tags, as well as
+ * tags that are implied but not explicitly specified. For example, the
+ * html string (&lt;p&gt;blah) only has a p tag defined. The callback
+ * will see the following methods:
+ * <ol><li><i>handleStartTag(html, ...)</i></li>
+ * <li><i>handleStartTag(head, ...)</i></li>
+ * <li><i>handleEndTag(head)</i></li>
+ * <li><i>handleStartTag(body, ...)</i></li>
+ * <li>handleStartTag(p, ...)</i></li>
+ * <li>handleText(...)</li>
+ * <li><i>handleEndTag(p)</i></li>
+ * <li><i>handleEndTag(body)</i></li>
+ * <li><i>handleEndTag(html)</i></li>
+ * </ol>
+ * The items in <i>italic</i> are implied, that is, although they were not
+ * explicitly specified, to be correct html they should have been present
+ * (head isn't necessary, but it is still generated). For tags that
+ * are implied, the AttributeSet argument will have a value of
+ * <code>Boolean.TRUE</code> for the key
+ * <code>HTMLEditorKit.ParserCallback.IMPLIED</code>.
+ * <p>HTML.Attributes defines a type safe enumeration of html attributes.
+ * If an attribute key of a tag is defined in HTML.Attribute, the
+ * HTML.Attribute will be used as the key, otherwise a String will be used.
+ * For example &lt;p foo=bar class=neat&gt; has two attributes. foo is
+ * not defined in HTML.Attribute, where as class is, therefore the
+ * AttributeSet will have two values in it, HTML.Attribute.CLASS with
+ * a String value of 'neat' and the String key 'foo' with a String value of
+ * 'bar'.
+ * <p>The position argument will indicate the start of the tag, comment
+ * or text. Similiar to arrays, the first character in the stream has a
+ * position of 0. For tags that are
+ * implied the position will indicate
+ * the location of the next encountered tag. In the first example,
+ * the implied start body and html tags will have the same position as the
+ * p tag, and the implied end p, html and body tags will all have the same
+ * position.
+ * <p>As html skips whitespace the position for text will be the position
+ * of the first valid character, eg in the string '\n\n\nblah'
+ * the text 'blah' will have a position of 3, the newlines are skipped.
+ * <p>
+ * For attributes that do not have a value, eg in the html
+ * string <code>&lt;foo blah&gt;</code> the attribute <code>blah</code>
+ * does not have a value, there are two possible values that will be
+ * placed in the AttributeSet's value:
+ * <ul>
+ * <li>If the DTD does not contain an definition for the element, or the
+ * definition does not have an explicit value then the value in the
+ * AttributeSet will be <code>HTML.NULL_ATTRIBUTE_VALUE</code>.
+ * <li>If the DTD contains an explicit value, as in:
+ * <code>&lt;!ATTLIST OPTION selected (selected) #IMPLIED&gt;</code>
+ * this value from the dtd (in this case selected) will be used.
+ * </ul>
+ * <p>
+ * Once the stream has been parsed, the callback is notified of the most
+ * likely end of line string. The end of line string will be one of
+ * \n, \r or \r\n, which ever is encountered the most in parsing the
+ * stream.
+ *
+ * @author Sunita Mani
+ */
+public class DocumentParser extends javax.swing.text.html.parser.Parser {
+
+ private int inbody;
+ private int intitle;
+ private int inhead;
+ private int instyle;
+ private int inscript;
+ private boolean seentitle;
+ private HTMLEditorKit.ParserCallback callback = null;
+ private boolean ignoreCharSet = false;
+ private static final boolean debugFlag = false;
+
+ public DocumentParser(DTD dtd) {
+ super(dtd);
+ }
+
+ public void parse(Reader in, HTMLEditorKit.ParserCallback callback, boolean ignoreCharSet) throws IOException {
+ this.ignoreCharSet = ignoreCharSet;
+ this.callback = callback;
+ parse(in);
+ // end of line
+ callback.handleEndOfLineString(getEndOfLineString());
+ }
+
+ /**
+ * Handle Start Tag.
+ */
+ protected void handleStartTag(TagElement tag) {
+
+ Element elem = tag.getElement();
+ if (elem == dtd.body) {
+ inbody++;
+ } else if (elem == dtd.html) {
+ } else if (elem == dtd.head) {
+ inhead++;
+ } else if (elem == dtd.title) {
+ intitle++;
+ } else if (elem == dtd.style) {
+ instyle++;
+ } else if (elem == dtd.script) {
+ inscript++;
+ }
+ if (debugFlag) {
+ if (tag.fictional()) {
+ debug("Start Tag: " + tag.getHTMLTag() + " pos: " + getCurrentPos());
+ } else {
+ debug("Start Tag: " + tag.getHTMLTag() + " attributes: " +
+ getAttributes() + " pos: " + getCurrentPos());
+ }
+ }
+ if (tag.fictional()) {
+ SimpleAttributeSet attrs = new SimpleAttributeSet();
+ attrs.addAttribute(HTMLEditorKit.ParserCallback.IMPLIED,
+ Boolean.TRUE);
+ callback.handleStartTag(tag.getHTMLTag(), attrs,
+ getBlockStartPosition());
+ } else {
+ callback.handleStartTag(tag.getHTMLTag(), getAttributes(),
+ getBlockStartPosition());
+ flushAttributes();
+ }
+ }
+
+
+ protected void handleComment(char text[]) {
+ if (debugFlag) {
+ debug("comment: ->" + new String(text) + "<-"
+ + " pos: " + getCurrentPos());
+ }
+ callback.handleComment(text, getBlockStartPosition());
+ }
+
+ /**
+ * Handle Empty Tag.
+ */
+ protected void handleEmptyTag(TagElement tag) throws ChangedCharSetException {
+
+ Element elem = tag.getElement();
+ if (elem == dtd.meta && !ignoreCharSet) {
+ SimpleAttributeSet atts = getAttributes();
+ if (atts != null) {
+ String content = (String)atts.getAttribute(HTML.Attribute.CONTENT);
+ if (content != null) {
+ if ("content-type".equalsIgnoreCase((String)atts.getAttribute(HTML.Attribute.HTTPEQUIV))) {
+ if (!content.equalsIgnoreCase("text/html") &&
+ !content.equalsIgnoreCase("text/plain")) {
+ throw new ChangedCharSetException(content, false);
+ }
+ } else if ("charset" .equalsIgnoreCase((String)atts.getAttribute(HTML.Attribute.HTTPEQUIV))) {
+ throw new ChangedCharSetException(content, true);
+ }
+ }
+ }
+ }
+ if (inbody != 0 || elem == dtd.meta || elem == dtd.base || elem == dtd.isindex || elem == dtd.style || elem == dtd.link) {
+ if (debugFlag) {
+ if (tag.fictional()) {
+ debug("Empty Tag: " + tag.getHTMLTag() + " pos: " + getCurrentPos());
+ } else {
+ debug("Empty Tag: " + tag.getHTMLTag() + " attributes: "
+ + getAttributes() + " pos: " + getCurrentPos());
+ }
+ }
+ if (tag.fictional()) {
+ SimpleAttributeSet attrs = new SimpleAttributeSet();
+ attrs.addAttribute(HTMLEditorKit.ParserCallback.IMPLIED,
+ Boolean.TRUE);
+ callback.handleSimpleTag(tag.getHTMLTag(), attrs,
+ getBlockStartPosition());
+ } else {
+ callback.handleSimpleTag(tag.getHTMLTag(), getAttributes(),
+ getBlockStartPosition());
+ flushAttributes();
+ }
+ }
+ }
+
+ /**
+ * Handle End Tag.
+ */
+ protected void handleEndTag(TagElement tag) {
+ Element elem = tag.getElement();
+ if (elem == dtd.body) {
+ inbody--;
+ } else if (elem == dtd.title) {
+ intitle--;
+ seentitle = true;
+ } else if (elem == dtd.head) {
+ inhead--;
+ } else if (elem == dtd.style) {
+ instyle--;
+ } else if (elem == dtd.script) {
+ inscript--;
+ }
+ if (debugFlag) {
+ debug("End Tag: " + tag.getHTMLTag() + " pos: " + getCurrentPos());
+ }
+ callback.handleEndTag(tag.getHTMLTag(), getBlockStartPosition());
+
+ }
+
+ /**
+ * Handle Text.
+ */
+ protected void handleText(char data[]) {
+ if (data != null) {
+ if (inscript != 0) {
+ callback.handleComment(data, getBlockStartPosition());
+ return;
+ }
+ if (inbody != 0 || ((instyle != 0) ||
+ ((intitle != 0) && !seentitle))) {
+ if (debugFlag) {
+ debug("text: ->" + new String(data) + "<-" + " pos: " + getCurrentPos());
+ }
+ callback.handleText(data, getBlockStartPosition());
+ }
+ }
+ }
+
+ /*
+ * Error handling.
+ */
+ protected void handleError(int ln, String errorMsg) {
+ if (debugFlag) {
+ debug("Error: ->" + errorMsg + "<-" + " pos: " + getCurrentPos());
+ }
+ /* PENDING: need to improve the error string. */
+ callback.handleError(errorMsg, getCurrentPos());
+ }
+
+
+ /*
+ * debug messages
+ */
+ private void debug(String msg) {
+ System.out.println(msg);
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/parser/Element.java b/src/share/classes/javax/swing/text/html/parser/Element.java
new file mode 100644
index 000000000..54b68618d
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/Element.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html.parser;
+
+import java.util.Hashtable;
+import java.util.BitSet;
+import java.io.*;
+
+/**
+ * An element as described in a DTD using the ELEMENT construct.
+ * This is essentiall the description of a tag. It describes the
+ * type, content model, attributes, attribute types etc. It is used
+ * to correctly parse a document by the Parser.
+ *
+ * @see DTD
+ * @see AttributeList
+ * @author Arthur van Hoff
+ */
+public final
+class Element implements DTDConstants, Serializable {
+ public int index;
+ public String name;
+ public boolean oStart;
+ public boolean oEnd;
+ public BitSet inclusions;
+ public BitSet exclusions;
+ public int type = ANY;
+ public ContentModel content;
+ public AttributeList atts;
+
+ static int maxIndex = 0;
+
+ /**
+ * A field to store user data. Mostly used to store
+ * style sheets.
+ */
+ public Object data;
+
+ Element() {
+ }
+
+ /**
+ * Create a new element.
+ */
+ Element(String name, int index) {
+ this.name = name;
+ this.index = index;
+ maxIndex = Math.max(maxIndex, index);
+ }
+
+ /**
+ * Get the name of the element.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Return true if the start tag can be omitted.
+ */
+ public boolean omitStart() {
+ return oStart;
+ }
+
+ /**
+ * Return true if the end tag can be omitted.
+ */
+ public boolean omitEnd() {
+ return oEnd;
+ }
+
+ /**
+ * Get type.
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * Get content model
+ */
+ public ContentModel getContent() {
+ return content;
+ }
+
+ /**
+ * Get the attributes.
+ */
+ public AttributeList getAttributes() {
+ return atts;
+ }
+
+ /**
+ * Get index.
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Check if empty
+ */
+ public boolean isEmpty() {
+ return type == EMPTY;
+ }
+
+ /**
+ * Convert to a string.
+ */
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * Get an attribute by name.
+ */
+ public AttributeList getAttribute(String name) {
+ for (AttributeList a = atts ; a != null ; a = a.next) {
+ if (a.name.equals(name)) {
+ return a;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get an attribute by value.
+ */
+ public AttributeList getAttributeByValue(String name) {
+ for (AttributeList a = atts ; a != null ; a = a.next) {
+ if ((a.values != null) && a.values.contains(name)) {
+ return a;
+ }
+ }
+ return null;
+ }
+
+
+ static Hashtable contentTypes = new Hashtable();
+
+ static {
+ contentTypes.put("CDATA", new Integer(CDATA));
+ contentTypes.put("RCDATA", new Integer(RCDATA));
+ contentTypes.put("EMPTY", new Integer(EMPTY));
+ contentTypes.put("ANY", new Integer(ANY));
+ }
+
+ public static int name2type(String nm) {
+ Integer val = (Integer)contentTypes.get(nm);
+ return (val != null) ? val.intValue() : 0;
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/parser/Entity.java b/src/share/classes/javax/swing/text/html/parser/Entity.java
new file mode 100644
index 000000000..f4a5cb9a7
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/Entity.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 1998-2001 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html.parser;
+
+import java.util.Hashtable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.CharArrayReader;
+import java.net.URL;
+
+/**
+ * An entity is described in a DTD using the ENTITY construct.
+ * It defines the type and value of the the entity.
+ *
+ * @see DTD
+ * @author Arthur van Hoff
+ */
+public final
+class Entity implements DTDConstants {
+ public String name;
+ public int type;
+ public char data[];
+
+ /**
+ * Creates an entity.
+ * @param name the name of the entity
+ * @param type the type of the entity
+ * @param data the char array of data
+ */
+ public Entity(String name, int type, char data[]) {
+ this.name = name;
+ this.type = type;
+ this.data = data;
+ }
+
+ /**
+ * Gets the name of the entity.
+ * @return the name of the entity, as a <code>String</code>
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Gets the type of the entity.
+ * @return the type of the entity
+ */
+ public int getType() {
+ return type & 0xFFFF;
+ }
+
+ /**
+ * Returns <code>true</code> if it is a parameter entity.
+ * @return <code>true</code> if it is a parameter entity
+ */
+ public boolean isParameter() {
+ return (type & PARAMETER) != 0;
+ }
+
+ /**
+ * Returns <code>true</code> if it is a general entity.
+ * @return <code>true</code> if it is a general entity
+ */
+ public boolean isGeneral() {
+ return (type & GENERAL) != 0;
+ }
+
+ /**
+ * Returns the <code>data</code>.
+ * @return the <code>data</code>
+ */
+ public char getData()[] {
+ return data;
+ }
+
+ /**
+ * Returns the data as a <code>String</code>.
+ * @return the data as a <code>String</code>
+ */
+ public String getString() {
+ return new String(data, 0, data.length);
+ }
+
+
+ static Hashtable entityTypes = new Hashtable();
+
+ static {
+ entityTypes.put("PUBLIC", new Integer(PUBLIC));
+ entityTypes.put("CDATA", new Integer(CDATA));
+ entityTypes.put("SDATA", new Integer(SDATA));
+ entityTypes.put("PI", new Integer(PI));
+ entityTypes.put("STARTTAG", new Integer(STARTTAG));
+ entityTypes.put("ENDTAG", new Integer(ENDTAG));
+ entityTypes.put("MS", new Integer(MS));
+ entityTypes.put("MD", new Integer(MD));
+ entityTypes.put("SYSTEM", new Integer(SYSTEM));
+ }
+
+ /**
+ * Converts <code>nm</code> string to the corresponding
+ * entity type. If the string does not have a corresponding
+ * entity type, returns the type corresponding to "CDATA".
+ * Valid entity types are: "PUBLIC", "CDATA", "SDATA", "PI",
+ * "STARTTAG", "ENDTAG", "MS", "MD", "SYSTEM".
+ *
+ * @param nm the string to be converted
+ * @return the corresponding entity type, or the type corresponding
+ * to "CDATA", if none exists
+ */
+ public static int name2type(String nm) {
+ Integer i = (Integer)entityTypes.get(nm);
+ return (i == null) ? CDATA : i.intValue();
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/parser/Parser.java b/src/share/classes/javax/swing/text/html/parser/Parser.java
new file mode 100644
index 000000000..24ba58fff
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/Parser.java
@@ -0,0 +1,2312 @@
+/*
+ * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html.parser;
+
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.html.HTML;
+import javax.swing.text.ChangedCharSetException;
+import java.io.*;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.net.URL;
+
+import sun.misc.MessageUtils;
+
+/**
+ * A simple DTD-driven HTML parser. The parser reads an
+ * HTML file from an InputStream and calls various methods
+ * (which should be overridden in a subclass) when tags and
+ * data are encountered.
+ * <p>
+ * Unfortunately there are many badly implemented HTML parsers
+ * out there, and as a result there are many badly formatted
+ * HTML files. This parser attempts to parse most HTML files.
+ * This means that the implementation sometimes deviates from
+ * the SGML specification in favor of HTML.
+ * <p>
+ * The parser treats \r and \r\n as \n. Newlines after starttags
+ * and before end tags are ignored just as specified in the SGML/HTML
+ * specification.
+ * <p>
+ * The html spec does not specify how spaces are to be coalesced very well.
+ * Specifically, the following scenarios are not discussed (note that a
+ * space should be used here, but I am using &amp;nbsp to force the space to
+ * be displayed):
+ * <p>
+ * '&lt;b>blah&nbsp;&lt;i>&nbsp;&lt;strike>&nbsp;foo' which can be treated as:
+ * '&lt;b>blah&nbsp;&lt;i>&lt;strike>foo'
+ * <p>as well as:
+ * '&lt;p>&lt;a href="xx">&nbsp;&lt;em>Using&lt;/em>&lt;/a>&lt;/p>'
+ * which appears to be treated as:
+ * '&lt;p>&lt;a href="xx">&lt;em>Using&lt;/em>&lt;/a>&lt;/p>'
+ * <p>
+ * If <code>strict</code> is false, when a tag that breaks flow,
+ * (<code>TagElement.breaksFlows</code>) or trailing whitespace is
+ * encountered, all whitespace will be ignored until a non whitespace
+ * character is encountered. This appears to give behavior closer to
+ * the popular browsers.
+ *
+ * @see DTD
+ * @see TagElement
+ * @see SimpleAttributeSet
+ * @author Arthur van Hoff
+ * @author Sunita Mani
+ */
+public
+class Parser implements DTDConstants {
+
+ private char text[] = new char[1024];
+ private int textpos = 0;
+ private TagElement last;
+ private boolean space;
+
+ private char str[] = new char[128];
+ private int strpos = 0;
+
+ protected DTD dtd = null;
+
+ private int ch;
+ private int ln;
+ private Reader in;
+
+ private Element recent;
+ private TagStack stack;
+ private boolean skipTag = false;
+ private TagElement lastFormSent = null;
+ private SimpleAttributeSet attributes = new SimpleAttributeSet();
+
+ // State for <html>, <head> and <body>. Since people like to slap
+ // together HTML documents without thinking, occasionally they
+ // have multiple instances of these tags. These booleans track
+ // the first sightings of these tags so they can be safely ignored
+ // by the parser if repeated.
+ private boolean seenHtml = false;
+ private boolean seenHead = false;
+ private boolean seenBody = false;
+
+ /**
+ * The html spec does not specify how spaces are coalesced very well.
+ * If strict == false, ignoreSpace is used to try and mimic the behavior
+ * of the popular browsers.
+ * <p>
+ * The problematic scenarios are:
+ * '&lt;b>blah &lt;i> &lt;strike> foo' which can be treated as:
+ * '&lt;b>blah &lt;i>&lt;strike>foo'
+ * as well as:
+ * '&lt;p>&lt;a href="xx"> &lt;em>Using&lt;/em>&lt;/a>&lt;/p>'
+ * which appears to be treated as:
+ * '&lt;p>&lt;a href="xx">&lt;em>Using&lt;/em>&lt;/a>&lt;/p>'
+ * <p>
+ * When a tag that breaks flow, or trailing whitespace is encountered
+ * ignoreSpace is set to true. From then on, all whitespace will be
+ * ignored.
+ * ignoreSpace will be set back to false the first time a
+ * non whitespace character is encountered. This appears to give
+ * behavior closer to the popular browsers.
+ */
+ private boolean ignoreSpace;
+
+ /**
+ * This flag determines whether or not the Parser will be strict
+ * in enforcing SGML compatibility. If false, it will be lenient
+ * with certain common classes of erroneous HTML constructs.
+ * Strict or not, in either case an error will be recorded.
+ *
+ */
+ protected boolean strict = false;
+
+
+ /** Number of \r\n's encountered. */
+ private int crlfCount;
+ /** Number of \r's encountered. A \r\n will not increment this. */
+ private int crCount;
+ /** Number of \n's encountered. A \r\n will not increment this. */
+ private int lfCount;
+
+ //
+ // To correctly identify the start of a tag/comment/text we need two
+ // ivars. Two are needed as handleText isn't invoked until the tag
+ // after the text has been parsed, that is the parser parses the text,
+ // then a tag, then invokes handleText followed by handleStart.
+ //
+ /** The start position of the current block. Block is overloaded here,
+ * it really means the current start position for the current comment,
+ * tag, text. Use getBlockStartPosition to access this. */
+ private int currentBlockStartPos;
+ /** Start position of the last block. */
+ private int lastBlockStartPos;
+
+ /**
+ * array for mapping numeric references in range
+ * 130-159 to displayable Unicode characters.
+ */
+ private static final char[] cp1252Map = {
+ 8218, // &#130;
+ 402, // &#131;
+ 8222, // &#132;
+ 8230, // &#133;
+ 8224, // &#134;
+ 8225, // &#135;
+ 710, // &#136;
+ 8240, // &#137;
+ 352, // &#138;
+ 8249, // &#139;
+ 338, // &#140;
+ 141, // &#141;
+ 142, // &#142;
+ 143, // &#143;
+ 144, // &#144;
+ 8216, // &#145;
+ 8217, // &#146;
+ 8220, // &#147;
+ 8221, // &#148;
+ 8226, // &#149;
+ 8211, // &#150;
+ 8212, // &#151;
+ 732, // &#152;
+ 8482, // &#153;
+ 353, // &#154;
+ 8250, // &#155;
+ 339, // &#156;
+ 157, // &#157;
+ 158, // &#158;
+ 376 // &#159;
+ };
+
+ public Parser(DTD dtd) {
+ this.dtd = dtd;
+ }
+
+
+ /**
+ * @return the line number of the line currently being parsed
+ */
+ protected int getCurrentLine() {
+ return ln;
+ }
+
+ /**
+ * Returns the start position of the current block. Block is
+ * overloaded here, it really means the current start position for
+ * the current comment tag, text, block.... This is provided for
+ * subclassers that wish to know the start of the current block when
+ * called with one of the handleXXX methods.
+ */
+ int getBlockStartPosition() {
+ return Math.max(0, lastBlockStartPos - 1);
+ }
+
+ /**
+ * Makes a TagElement.
+ */
+ protected TagElement makeTag(Element elem, boolean fictional) {
+ return new TagElement(elem, fictional);
+ }
+
+ protected TagElement makeTag(Element elem) {
+ return makeTag(elem, false);
+ }
+
+ protected SimpleAttributeSet getAttributes() {
+ return attributes;
+ }
+
+ protected void flushAttributes() {
+ attributes.removeAttributes(attributes);
+ }
+
+ /**
+ * Called when PCDATA is encountered.
+ */
+ protected void handleText(char text[]) {
+ }
+
+ /**
+ * Called when an HTML title tag is encountered.
+ */
+ protected void handleTitle(char text[]) {
+ // default behavior is to call handleText. Subclasses
+ // can override if necessary.
+ handleText(text);
+ }
+
+ /**
+ * Called when an HTML comment is encountered.
+ */
+ protected void handleComment(char text[]) {
+ }
+
+ protected void handleEOFInComment() {
+ // We've reached EOF. Our recovery strategy is to
+ // see if we have more than one line in the comment;
+ // if so, we pretend that the comment was an unterminated
+ // single line comment, and reparse the lines after the
+ // first line as normal HTML content.
+
+ int commentEndPos = strIndexOf('\n');
+ if (commentEndPos >= 0) {
+ handleComment(getChars(0, commentEndPos));
+ try {
+ in.close();
+ in = new CharArrayReader(getChars(commentEndPos + 1));
+ ch = '>';
+ } catch (IOException e) {
+ error("ioexception");
+ }
+
+ resetStrBuffer();
+ } else {
+ // no newline, so signal an error
+ error("eof.comment");
+ }
+ }
+
+ /**
+ * Called when an empty tag is encountered.
+ */
+ protected void handleEmptyTag(TagElement tag) throws ChangedCharSetException {
+ }
+
+ /**
+ * Called when a start tag is encountered.
+ */
+ protected void handleStartTag(TagElement tag) {
+ }
+
+ /**
+ * Called when an end tag is encountered.
+ */
+ protected void handleEndTag(TagElement tag) {
+ }
+
+ /**
+ * An error has occurred.
+ */
+ protected void handleError(int ln, String msg) {
+ /*
+ Thread.dumpStack();
+ System.out.println("**** " + stack);
+ System.out.println("line " + ln + ": error: " + msg);
+ System.out.println();
+ */
+ }
+
+ /**
+ * Output text.
+ */
+ void handleText(TagElement tag) {
+ if (tag.breaksFlow()) {
+ space = false;
+ if (!strict) {
+ ignoreSpace = true;
+ }
+ }
+ if (textpos == 0) {
+ if ((!space) || (stack == null) || last.breaksFlow() ||
+ !stack.advance(dtd.pcdata)) {
+ last = tag;
+ space = false;
+ lastBlockStartPos = currentBlockStartPos;
+ return;
+ }
+ }
+ if (space) {
+ if (!ignoreSpace) {
+ // enlarge buffer if needed
+ if (textpos + 1 > text.length) {
+ char newtext[] = new char[text.length + 200];
+ System.arraycopy(text, 0, newtext, 0, text.length);
+ text = newtext;
+ }
+
+ // output pending space
+ text[textpos++] = ' ';
+ if (!strict && !tag.getElement().isEmpty()) {
+ ignoreSpace = true;
+ }
+ }
+ space = false;
+ }
+ char newtext[] = new char[textpos];
+ System.arraycopy(text, 0, newtext, 0, textpos);
+ // Handles cases of bad html where the title tag
+ // was getting lost when we did error recovery.
+ if (tag.getElement().getName().equals("title")) {
+ handleTitle(newtext);
+ } else {
+ handleText(newtext);
+ }
+ lastBlockStartPos = currentBlockStartPos;
+ textpos = 0;
+ last = tag;
+ space = false;
+ }
+
+ /**
+ * Invoke the error handler.
+ */
+ protected void error(String err, String arg1, String arg2,
+ String arg3) {
+ handleError(ln, err + " " + arg1 + " " + arg2 + " " + arg3);
+ }
+
+ protected void error(String err, String arg1, String arg2) {
+ error(err, arg1, arg2, "?");
+ }
+ protected void error(String err, String arg1) {
+ error(err, arg1, "?", "?");
+ }
+ protected void error(String err) {
+ error(err, "?", "?", "?");
+ }
+
+
+ /**
+ * Handle a start tag. The new tag is pushed
+ * onto the tag stack. The attribute list is
+ * checked for required attributes.
+ */
+ protected void startTag(TagElement tag) throws ChangedCharSetException {
+ Element elem = tag.getElement();
+
+ // If the tag is an empty tag and texpos != 0
+ // this implies that there is text before the
+ // start tag that needs to be processed before
+ // handling the tag.
+ //
+ if (!elem.isEmpty() ||
+ ((last != null) && !last.breaksFlow()) ||
+ (textpos != 0)) {
+ handleText(tag);
+ } else {
+ // this variable gets updated in handleText().
+ // Since in this case we do not call handleText()
+ // we need to update it here.
+ //
+ last = tag;
+ // Note that we should really check last.breakFlows before
+ // assuming this should be false.
+ space = false;
+ }
+ lastBlockStartPos = currentBlockStartPos;
+
+ // check required attributes
+ for (AttributeList a = elem.atts ; a != null ; a = a.next) {
+ if ((a.modifier == REQUIRED) &&
+ ((attributes.isEmpty()) ||
+ ((!attributes.isDefined(a.name)) &&
+ (!attributes.isDefined(HTML.getAttributeKey(a.name)))))) {
+ error("req.att ", a.getName(), elem.getName());
+ }
+ }
+
+ if (elem.isEmpty()) {
+ handleEmptyTag(tag);
+ /*
+ } else if (elem.getName().equals("form")) {
+ handleStartTag(tag);
+ */
+ } else {
+ recent = elem;
+ stack = new TagStack(tag, stack);
+ handleStartTag(tag);
+ }
+ }
+
+ /**
+ * Handle an end tag. The end tag is popped
+ * from the tag stack.
+ */
+ protected void endTag(boolean omitted) {
+ handleText(stack.tag);
+
+ if (omitted && !stack.elem.omitEnd()) {
+ error("end.missing", stack.elem.getName());
+ } else if (!stack.terminate()) {
+ error("end.unexpected", stack.elem.getName());
+ }
+
+ // handle the tag
+ handleEndTag(stack.tag);
+ stack = stack.next;
+ recent = (stack != null) ? stack.elem : null;
+ }
+
+
+ boolean ignoreElement(Element elem) {
+
+ String stackElement = stack.elem.getName();
+ String elemName = elem.getName();
+ /* We ignore all elements that are not valid in the context of
+ a table except <td>, <th> (these we handle in
+ legalElementContext()) and #pcdata. We also ignore the
+ <font> tag in the context of <ul> and <ol> We additonally
+ ignore the <meta> and the <style> tag if the body tag has
+ been seen. **/
+ if ((elemName.equals("html") && seenHtml) ||
+ (elemName.equals("head") && seenHead) ||
+ (elemName.equals("body") && seenBody)) {
+ return true;
+ }
+ if (elemName.equals("dt") || elemName.equals("dd")) {
+ TagStack s = stack;
+ while (s != null && !s.elem.getName().equals("dl")) {
+ s = s.next;
+ }
+ if (s == null) {
+ return true;
+ }
+ }
+
+ if (((stackElement.equals("table")) &&
+ (!elemName.equals("#pcdata")) && (!elemName.equals("input"))) ||
+ ((elemName.equals("font")) &&
+ (stackElement.equals("ul") || stackElement.equals("ol"))) ||
+ (elemName.equals("meta") && stack != null) ||
+ (elemName.equals("style") && seenBody) ||
+ (stackElement.equals("table") && elemName.equals("a"))) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Marks the first time a tag has been seen in a document
+ */
+
+ protected void markFirstTime(Element elem) {
+ String elemName = elem.getName();
+ if (elemName.equals("html")) {
+ seenHtml = true;
+ } else if (elemName.equals("head")) {
+ seenHead = true;
+ } else if (elemName.equals("body")) {
+ if (buf.length == 1) {
+ // Refer to note in definition of buf for details on this.
+ char[] newBuf = new char[256];
+
+ newBuf[0] = buf[0];
+ buf = newBuf;
+ }
+ seenBody = true;
+ }
+ }
+
+ /**
+ * Create a legal content for an element.
+ */
+ boolean legalElementContext(Element elem) throws ChangedCharSetException {
+
+ // System.out.println("-- legalContext -- " + elem);
+
+ // Deal with the empty stack
+ if (stack == null) {
+ // System.out.println("-- stack is empty");
+ if (elem != dtd.html) {
+ // System.out.println("-- pushing html");
+ startTag(makeTag(dtd.html, true));
+ return legalElementContext(elem);
+ }
+ return true;
+ }
+
+ // Is it allowed in the current context
+ if (stack.advance(elem)) {
+ // System.out.println("-- legal context");
+ markFirstTime(elem);
+ return true;
+ }
+ boolean insertTag = false;
+
+ // The use of all error recovery strategies are contingent
+ // on the value of the strict property.
+ //
+ // These are commonly occuring errors. if insertTag is true,
+ // then we want to adopt an error recovery strategy that
+ // involves attempting to insert an additional tag to
+ // legalize the context. The two errors addressed here
+ // are:
+ // 1) when a <td> or <th> is seen soon after a <table> tag.
+ // In this case we insert a <tr>.
+ // 2) when any other tag apart from a <tr> is seen
+ // in the context of a <tr>. In this case we would
+ // like to add a <td>. If a <tr> is seen within a
+ // <tr> context, then we will close out the current
+ // <tr>.
+ //
+ // This insertion strategy is handled later in the method.
+ // The reason for checking this now, is that in other cases
+ // we would like to apply other error recovery strategies for example
+ // ignoring tags.
+ //
+ // In certain cases it is better to ignore a tag than try to
+ // fix the situation. So the first test is to see if this
+ // is what we need to do.
+ //
+ String stackElemName = stack.elem.getName();
+ String elemName = elem.getName();
+
+
+ if (!strict &&
+ ((stackElemName.equals("table") && elemName.equals("td")) ||
+ (stackElemName.equals("table") && elemName.equals("th")) ||
+ (stackElemName.equals("tr") && !elemName.equals("tr")))){
+ insertTag = true;
+ }
+
+
+ if (!strict && !insertTag && (stack.elem.getName() != elem.getName() ||
+ elem.getName().equals("body"))) {
+ if (skipTag = ignoreElement(elem)) {
+ error("tag.ignore", elem.getName());
+ return skipTag;
+ }
+ }
+
+ // Check for anything after the start of the table besides tr, td, th
+ // or caption, and if those aren't there, insert the <tr> and call
+ // legalElementContext again.
+ if (!strict && stackElemName.equals("table") &&
+ !elemName.equals("tr") && !elemName.equals("td") &&
+ !elemName.equals("th") && !elemName.equals("caption")) {
+ Element e = dtd.getElement("tr");
+ TagElement t = makeTag(e, true);
+ legalTagContext(t);
+ startTag(t);
+ error("start.missing", elem.getName());
+ return legalElementContext(elem);
+ }
+
+ // They try to find a legal context by checking if the current
+ // tag is valid in an enclosing context. If so
+ // close out the tags by outputing end tags and then
+ // insert the curent tag. If the tags that are
+ // being closed out do not have an optional end tag
+ // specification in the DTD then an html error is
+ // reported.
+ //
+ if (!insertTag && stack.terminate() && (!strict || stack.elem.omitEnd())) {
+ for (TagStack s = stack.next ; s != null ; s = s.next) {
+ if (s.advance(elem)) {
+ while (stack != s) {
+ endTag(true);
+ }
+ return true;
+ }
+ if (!s.terminate() || (strict && !s.elem.omitEnd())) {
+ break;
+ }
+ }
+ }
+
+ // Check if we know what tag is expected next.
+ // If so insert the tag. Report an error if the
+ // tag does not have its start tag spec in the DTD as optional.
+ //
+ Element next = stack.first();
+ if (next != null && (!strict || next.omitStart()) &&
+ !(next==dtd.head && elem==dtd.pcdata) ) {
+ // System.out.println("-- omitting start tag: " + next);
+ TagElement t = makeTag(next, true);
+ legalTagContext(t);
+ startTag(t);
+ if (!next.omitStart()) {
+ error("start.missing", elem.getName());
+ }
+ return legalElementContext(elem);
+ }
+
+
+ // Traverse the list of expected elements and determine if adding
+ // any of these elements would make for a legal context.
+ //
+
+ if (!strict) {
+ ContentModel content = stack.contentModel();
+ Vector elemVec = new Vector();
+ if (content != null) {
+ content.getElements(elemVec);
+ for (Enumeration v = elemVec.elements(); v.hasMoreElements();) {
+ Element e = (Element)v.nextElement();
+
+ // Ensure that this element has not been included as
+ // part of the exclusions in the DTD.
+ //
+ if (stack.excluded(e.getIndex())) {
+ continue;
+ }
+
+ boolean reqAtts = false;
+
+ for (AttributeList a = e.getAttributes(); a != null ; a = a.next) {
+ if (a.modifier == REQUIRED) {
+ reqAtts = true;
+ break;
+ }
+ }
+ // Ensure that no tag that has required attributes
+ // gets inserted.
+ //
+ if (reqAtts) {
+ continue;
+ }
+
+ ContentModel m = e.getContent();
+ if (m != null && m.first(elem)) {
+ // System.out.println("-- adding a legal tag: " + e);
+ TagElement t = makeTag(e, true);
+ legalTagContext(t);
+ startTag(t);
+ error("start.missing", e.getName());
+ return legalElementContext(elem);
+ }
+ }
+ }
+ }
+
+ // Check if the stack can be terminated. If so add the appropriate
+ // end tag. Report an error if the tag being ended does not have its
+ // end tag spec in the DTD as optional.
+ //
+ if (stack.terminate() && (stack.elem != dtd.body) && (!strict || stack.elem.omitEnd())) {
+ // System.out.println("-- omitting end tag: " + stack.elem);
+ if (!stack.elem.omitEnd()) {
+ error("end.missing", elem.getName());
+ }
+
+ endTag(true);
+ return legalElementContext(elem);
+ }
+
+ // At this point we know that something is screwed up.
+ return false;
+ }
+
+ /**
+ * Create a legal context for a tag.
+ */
+ void legalTagContext(TagElement tag) throws ChangedCharSetException {
+ if (legalElementContext(tag.getElement())) {
+ markFirstTime(tag.getElement());
+ return;
+ }
+
+ // Avoid putting a block tag in a flow tag.
+ if (tag.breaksFlow() && (stack != null) && !stack.tag.breaksFlow()) {
+ endTag(true);
+ legalTagContext(tag);
+ return;
+ }
+
+ // Avoid putting something wierd in the head of the document.
+ for (TagStack s = stack ; s != null ; s = s.next) {
+ if (s.tag.getElement() == dtd.head) {
+ while (stack != s) {
+ endTag(true);
+ }
+ endTag(true);
+ legalTagContext(tag);
+ return;
+ }
+ }
+
+ // Everything failed
+ error("tag.unexpected", tag.getElement().getName());
+ }
+
+ /**
+ * Error context. Something went wrong, make sure we are in
+ * the document's body context
+ */
+ void errorContext() throws ChangedCharSetException {
+ for (; (stack != null) && (stack.tag.getElement() != dtd.body) ; stack = stack.next) {
+ handleEndTag(stack.tag);
+ }
+ if (stack == null) {
+ legalElementContext(dtd.body);
+ startTag(makeTag(dtd.body, true));
+ }
+ }
+
+ /**
+ * Add a char to the string buffer.
+ */
+ void addString(int c) {
+ if (strpos == str.length) {
+ char newstr[] = new char[str.length + 128];
+ System.arraycopy(str, 0, newstr, 0, str.length);
+ str = newstr;
+ }
+ str[strpos++] = (char)c;
+ }
+
+ /**
+ * Get the string that's been accumulated.
+ */
+ String getString(int pos) {
+ char newStr[] = new char[strpos - pos];
+ System.arraycopy(str, pos, newStr, 0, strpos - pos);
+ strpos = pos;
+ return new String(newStr);
+ }
+
+ char[] getChars(int pos) {
+ char newStr[] = new char[strpos - pos];
+ System.arraycopy(str, pos, newStr, 0, strpos - pos);
+ strpos = pos;
+ return newStr;
+ }
+
+ char[] getChars(int pos, int endPos) {
+ char newStr[] = new char[endPos - pos];
+ System.arraycopy(str, pos, newStr, 0, endPos - pos);
+ // REMIND: it's not clear whether this version should set strpos or not
+ // strpos = pos;
+ return newStr;
+ }
+
+ void resetStrBuffer() {
+ strpos = 0;
+ }
+
+ int strIndexOf(char target) {
+ for (int i = 0; i < strpos; i++) {
+ if (str[i] == target) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Skip space.
+ * [5] 297:5
+ */
+ void skipSpace() throws IOException {
+ while (true) {
+ switch (ch) {
+ case '\n':
+ ln++;
+ ch = readCh();
+ lfCount++;
+ break;
+
+ case '\r':
+ ln++;
+ if ((ch = readCh()) == '\n') {
+ ch = readCh();
+ crlfCount++;
+ }
+ else {
+ crCount++;
+ }
+ break;
+ case ' ':
+ case '\t':
+ ch = readCh();
+ break;
+
+ default:
+ return;
+ }
+ }
+ }
+
+ /**
+ * Parse identifier. Uppercase characters are folded
+ * to lowercase when lower is true. Returns falsed if
+ * no identifier is found. [55] 346:17
+ */
+ boolean parseIdentifier(boolean lower) throws IOException {
+ switch (ch) {
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
+ case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
+ case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
+ case 'Y': case 'Z':
+ if (lower) {
+ ch = 'a' + (ch - 'A');
+ }
+
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
+ case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
+ case 's': case 't': case 'u': case 'v': case 'w': case 'x':
+ case 'y': case 'z':
+ break;
+
+ default:
+ return false;
+ }
+
+ while (true) {
+ addString(ch);
+
+ switch (ch = readCh()) {
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
+ case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
+ case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
+ case 'Y': case 'Z':
+ if (lower) {
+ ch = 'a' + (ch - 'A');
+ }
+
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
+ case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
+ case 's': case 't': case 'u': case 'v': case 'w': case 'x':
+ case 'y': case 'z':
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+
+ case '.': case '-':
+
+ case '_': // not officially allowed
+ break;
+
+ default:
+ return true;
+ }
+ }
+ }
+
+ /**
+ * Parse an entity reference. [59] 350:17
+ */
+ private char[] parseEntityReference() throws IOException {
+ int pos = strpos;
+
+ if ((ch = readCh()) == '#') {
+ int n = 0;
+ ch = readCh();
+ if ((ch >= '0') && (ch <= '9') ||
+ ch == 'x' || ch == 'X') {
+
+ if ((ch >= '0') && (ch <= '9')) {
+ // parse decimal reference
+ while ((ch >= '0') && (ch <= '9')) {
+ n = (n * 10) + ch - '0';
+ ch = readCh();
+ }
+ } else {
+ // parse hexadecimal reference
+ ch = readCh();
+ char lch = (char) Character.toLowerCase(ch);
+ while ((lch >= '0') && (lch <= '9') ||
+ (lch >= 'a') && (lch <= 'f')) {
+ if (lch >= '0' && lch <= '9') {
+ n = (n * 16) + lch - '0';
+ } else {
+ n = (n * 16) + lch - 'a' + 10;
+ }
+ ch = readCh();
+ lch = (char) Character.toLowerCase(ch);
+ }
+ }
+ switch (ch) {
+ case '\n':
+ ln++;
+ ch = readCh();
+ lfCount++;
+ break;
+
+ case '\r':
+ ln++;
+ if ((ch = readCh()) == '\n') {
+ ch = readCh();
+ crlfCount++;
+ }
+ else {
+ crCount++;
+ }
+ break;
+
+ case ';':
+ ch = readCh();
+ break;
+ }
+ char data[] = {mapNumericReference((char) n)};
+ return data;
+ }
+ addString('#');
+ if (!parseIdentifier(false)) {
+ error("ident.expected");
+ strpos = pos;
+ char data[] = {'&', '#'};
+ return data;
+ }
+ } else if (!parseIdentifier(false)) {
+ char data[] = {'&'};
+ return data;
+ }
+ switch (ch) {
+ case '\n':
+ ln++;
+ ch = readCh();
+ lfCount++;
+ break;
+
+ case '\r':
+ ln++;
+ if ((ch = readCh()) == '\n') {
+ ch = readCh();
+ crlfCount++;
+ }
+ else {
+ crCount++;
+ }
+ break;
+
+ case ';':
+ ch = readCh();
+ break;
+ }
+
+ String nm = getString(pos);
+ Entity ent = dtd.getEntity(nm);
+
+ // entities are case sensitive - however if strict
+ // is false then we will try to make a match by
+ // converting the string to all lowercase.
+ //
+ if (!strict && (ent == null)) {
+ ent = dtd.getEntity(nm.toLowerCase());
+ }
+ if ((ent == null) || !ent.isGeneral()) {
+
+ if (nm.length() == 0) {
+ error("invalid.entref", nm);
+ return new char[0];
+ }
+ /* given that there is not a match restore the entity reference */
+ String str = "&" + nm + ";";
+
+ char b[] = new char[str.length()];
+ str.getChars(0, b.length, b, 0);
+ return b;
+ }
+ return ent.getData();
+ }
+
+ /**
+ * Converts numeric character reference to Unicode character.
+ *
+ * Normally the code in a reference should be always converted
+ * to the Unicode character with the same code, but due to
+ * wide usage of Cp1252 charset most browsers map numeric references
+ * in the range 130-159 (which are control chars in Unicode set)
+ * to displayable characters with other codes.
+ *
+ * @param c the code of numeric character reference.
+ * @return the character corresponding to the reference code.
+ */
+ private char mapNumericReference(char c) {
+ if (c < 130 || c > 159) {
+ return c;
+ }
+ return cp1252Map[c - 130];
+ }
+
+ /**
+ * Parse a comment. [92] 391:7
+ */
+ void parseComment() throws IOException {
+
+ while (true) {
+ int c = ch;
+ switch (c) {
+ case '-':
+ /** Presuming that the start string of a comment "<!--" has
+ already been parsed, the '-' character is valid only as
+ part of a comment termination and further more it must
+ be present in even numbers. Hence if strict is true, we
+ presume the comment has been terminated and return.
+ However if strict is false, then there is no even number
+ requirement and this character can appear anywhere in the
+ comment. The parser reads on until it sees the following
+ pattern: "-->" or "--!>".
+ **/
+ if (!strict && (strpos != 0) && (str[strpos - 1] == '-')) {
+ if ((ch = readCh()) == '>') {
+ return;
+ }
+ if (ch == '!') {
+ if ((ch = readCh()) == '>') {
+ return;
+ } else {
+ /* to account for extra read()'s that happened */
+ addString('-');
+ addString('!');
+ continue;
+ }
+ }
+ break;
+ }
+
+ if ((ch = readCh()) == '-') {
+ ch = readCh();
+ if (strict || ch == '>') {
+ return;
+ }
+ if (ch == '!') {
+ if ((ch = readCh()) == '>') {
+ return;
+ } else {
+ /* to account for extra read()'s that happened */
+ addString('-');
+ addString('!');
+ continue;
+ }
+ }
+ /* to account for the extra read() */
+ addString('-');
+ }
+ break;
+
+ case -1:
+ handleEOFInComment();
+ return;
+
+ case '\n':
+ ln++;
+ ch = readCh();
+ lfCount++;
+ break;
+
+ case '>':
+ ch = readCh();
+ break;
+
+ case '\r':
+ ln++;
+ if ((ch = readCh()) == '\n') {
+ ch = readCh();
+ crlfCount++;
+ }
+ else {
+ crCount++;
+ }
+ c = '\n';
+ break;
+ default:
+ ch = readCh();
+ break;
+ }
+
+ addString(c);
+ }
+ }
+
+ /**
+ * Parse literal content. [46] 343:1 and [47] 344:1
+ */
+ void parseLiteral(boolean replace) throws IOException {
+ while (true) {
+ int c = ch;
+ switch (c) {
+ case -1:
+ error("eof.literal", stack.elem.getName());
+ endTag(true);
+ return;
+
+ case '>':
+ ch = readCh();
+ int i = textpos - (stack.elem.name.length() + 2), j = 0;
+
+ // match end tag
+ if ((i >= 0) && (text[i++] == '<') && (text[i] == '/')) {
+ while ((++i < textpos) &&
+ (Character.toLowerCase(text[i]) == stack.elem.name.charAt(j++)));
+ if (i == textpos) {
+ textpos -= (stack.elem.name.length() + 2);
+ if ((textpos > 0) && (text[textpos-1] == '\n')) {
+ textpos--;
+ }
+ endTag(false);
+ return;
+ }
+ }
+ break;
+
+ case '&':
+ char data[] = parseEntityReference();
+ if (textpos + data.length > text.length) {
+ char newtext[] = new char[Math.max(textpos + data.length + 128, text.length * 2)];
+ System.arraycopy(text, 0, newtext, 0, text.length);
+ text = newtext;
+ }
+ System.arraycopy(data, 0, text, textpos, data.length);
+ textpos += data.length;
+ continue;
+
+ case '\n':
+ ln++;
+ ch = readCh();
+ lfCount++;
+ break;
+
+ case '\r':
+ ln++;
+ if ((ch = readCh()) == '\n') {
+ ch = readCh();
+ crlfCount++;
+ }
+ else {
+ crCount++;
+ }
+ c = '\n';
+ break;
+ default:
+ ch = readCh();
+ break;
+ }
+
+ // output character
+ if (textpos == text.length) {
+ char newtext[] = new char[text.length + 128];
+ System.arraycopy(text, 0, newtext, 0, text.length);
+ text = newtext;
+ }
+ text[textpos++] = (char)c;
+ }
+ }
+
+ /**
+ * Parse attribute value. [33] 331:1
+ */
+ String parseAttributeValue(boolean lower) throws IOException {
+ int delim = -1;
+
+ // Check for a delimiter
+ switch(ch) {
+ case '\'':
+ case '"':
+ delim = ch;
+ ch = readCh();
+ break;
+ }
+
+ // Parse the rest of the value
+ while (true) {
+ int c = ch;
+
+ switch (c) {
+ case '\n':
+ ln++;
+ ch = readCh();
+ lfCount++;
+ if (delim < 0) {
+ return getString(0);
+ }
+ break;
+
+ case '\r':
+ ln++;
+
+ if ((ch = readCh()) == '\n') {
+ ch = readCh();
+ crlfCount++;
+ }
+ else {
+ crCount++;
+ }
+ if (delim < 0) {
+ return getString(0);
+ }
+ break;
+
+ case '\t':
+ if (delim < 0)
+ c = ' ';
+ case ' ':
+ ch = readCh();
+ if (delim < 0) {
+ return getString(0);
+ }
+ break;
+
+ case '>':
+ case '<':
+ if (delim < 0) {
+ return getString(0);
+ }
+ ch = readCh();
+ break;
+
+ case '\'':
+ case '"':
+ ch = readCh();
+ if (c == delim) {
+ return getString(0);
+ } else if (delim == -1) {
+ error("attvalerr");
+ if (strict || ch == ' ') {
+ return getString(0);
+ } else {
+ continue;
+ }
+ }
+ break;
+
+ case '=':
+ if (delim < 0) {
+ /* In SGML a construct like <img src=/cgi-bin/foo?x=1>
+ is considered invalid since an = sign can only be contained
+ in an attributes value if the string is quoted.
+ */
+ error("attvalerr");
+ /* If strict is true then we return with the string we have thus far.
+ Otherwise we accept the = sign as part of the attribute's value and
+ process the rest of the img tag. */
+ if (strict) {
+ return getString(0);
+ }
+ }
+ ch = readCh();
+ break;
+
+ case '&':
+ if (strict && delim < 0) {
+ ch = readCh();
+ break;
+ }
+
+ char data[] = parseEntityReference();
+ for (int i = 0 ; i < data.length ; i++) {
+ c = data[i];
+ addString((lower && (c >= 'A') && (c <= 'Z')) ? 'a' + c - 'A' : c);
+ }
+ continue;
+
+ case -1:
+ return getString(0);
+
+ default:
+ if (lower && (c >= 'A') && (c <= 'Z')) {
+ c = 'a' + c - 'A';
+ }
+ ch = readCh();
+ break;
+ }
+ addString(c);
+ }
+ }
+
+
+ /**
+ * Parse attribute specification List. [31] 327:17
+ */
+ void parseAttributeSpecificationList(Element elem) throws IOException {
+
+ while (true) {
+ skipSpace();
+
+ switch (ch) {
+ case '/':
+ case '>':
+ case '<':
+ case -1:
+ return;
+
+ case '-':
+ if ((ch = readCh()) == '-') {
+ ch = readCh();
+ parseComment();
+ strpos = 0;
+ } else {
+ error("invalid.tagchar", "-", elem.getName());
+ ch = readCh();
+ }
+ continue;
+ }
+
+ AttributeList att = null;
+ String attname = null;
+ String attvalue = null;
+
+ if (parseIdentifier(true)) {
+ attname = getString(0);
+ skipSpace();
+ if (ch == '=') {
+ ch = readCh();
+ skipSpace();
+ att = elem.getAttribute(attname);
+// Bug ID 4102750
+// Load the NAME of an Attribute Case Sensitive
+// The case of the NAME must be intact
+// MG 021898
+ attvalue = parseAttributeValue((att != null) && (att.type != CDATA) && (att.type != NOTATION) && (att.type != NAME));
+// attvalue = parseAttributeValue((att != null) && (att.type != CDATA) && (att.type != NOTATION));
+ } else {
+ attvalue = attname;
+ att = elem.getAttributeByValue(attvalue);
+ if (att == null) {
+ att = elem.getAttribute(attname);
+ if (att != null) {
+ attvalue = att.getValue();
+ }
+ else {
+ // Make it null so that NULL_ATTRIBUTE_VALUE is
+ // used
+ attvalue = null;
+ }
+ }
+ }
+ } else if (!strict && ch == ',') { // allows for comma separated attribute-value pairs
+ ch = readCh();
+ continue;
+ } else if (!strict && ch == '"') { // allows for quoted attributes
+ ch = readCh();
+ skipSpace();
+ if (parseIdentifier(true)) {
+ attname = getString(0);
+ if (ch == '"') {
+ ch = readCh();
+ }
+ skipSpace();
+ if (ch == '=') {
+ ch = readCh();
+ skipSpace();
+ att = elem.getAttribute(attname);
+ attvalue = parseAttributeValue((att != null) &&
+ (att.type != CDATA) &&
+ (att.type != NOTATION));
+ } else {
+ attvalue = attname;
+ att = elem.getAttributeByValue(attvalue);
+ if (att == null) {
+ att = elem.getAttribute(attname);
+ if (att != null) {
+ attvalue = att.getValue();
+ }
+ }
+ }
+ } else {
+ char str[] = {(char)ch};
+ error("invalid.tagchar", new String(str), elem.getName());
+ ch = readCh();
+ continue;
+ }
+ } else if (!strict && (attributes.isEmpty()) && (ch == '=')) {
+ ch = readCh();
+ skipSpace();
+ attname = elem.getName();
+ att = elem.getAttribute(attname);
+ attvalue = parseAttributeValue((att != null) &&
+ (att.type != CDATA) &&
+ (att.type != NOTATION));
+ } else if (!strict && (ch == '=')) {
+ ch = readCh();
+ skipSpace();
+ attvalue = parseAttributeValue(true);
+ error("attvalerr");
+ return;
+ } else {
+ char str[] = {(char)ch};
+ error("invalid.tagchar", new String(str), elem.getName());
+ if (!strict) {
+ ch = readCh();
+ continue;
+ } else {
+ return;
+ }
+ }
+
+ if (att != null) {
+ attname = att.getName();
+ } else {
+ error("invalid.tagatt", attname, elem.getName());
+ }
+
+ // Check out the value
+ if (attributes.isDefined(attname)) {
+ error("multi.tagatt", attname, elem.getName());
+ }
+ if (attvalue == null) {
+ attvalue = ((att != null) && (att.value != null)) ? att.value :
+ HTML.NULL_ATTRIBUTE_VALUE;
+ } else if ((att != null) && (att.values != null) && !att.values.contains(attvalue)) {
+ error("invalid.tagattval", attname, elem.getName());
+ }
+ HTML.Attribute attkey = HTML.getAttributeKey(attname);
+ if (attkey == null) {
+ attributes.addAttribute(attname, attvalue);
+ } else {
+ attributes.addAttribute(attkey, attvalue);
+ }
+ }
+ }
+
+ /**
+ * Parses th Document Declaration Type markup declaration.
+ * Currently ignores it.
+ */
+ public String parseDTDMarkup() throws IOException {
+
+ StringBuffer strBuff = new StringBuffer();
+ ch = readCh();
+ while(true) {
+ switch (ch) {
+ case '>':
+ ch = readCh();
+ return strBuff.toString();
+ case -1:
+ error("invalid.markup");
+ return strBuff.toString();
+ case '\n':
+ ln++;
+ ch = readCh();
+ lfCount++;
+ break;
+ case '"':
+ ch = readCh();
+ break;
+ case '\r':
+ ln++;
+ if ((ch = readCh()) == '\n') {
+ ch = readCh();
+ crlfCount++;
+ }
+ else {
+ crCount++;
+ }
+ break;
+ default:
+ strBuff.append((char)(ch & 0xFF));
+ ch = readCh();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Parse markup declarations.
+ * Currently only handles the Document Type Declaration markup.
+ * Returns true if it is a markup declaration false otherwise.
+ */
+ protected boolean parseMarkupDeclarations(StringBuffer strBuff) throws IOException {
+
+ /* Currently handles only the DOCTYPE */
+ if ((strBuff.length() == "DOCTYPE".length()) &&
+ (strBuff.toString().toUpperCase().equals("DOCTYPE"))) {
+ parseDTDMarkup();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Parse an invalid tag.
+ */
+ void parseInvalidTag() throws IOException {
+ // ignore all data upto the close bracket '>'
+ while (true) {
+ skipSpace();
+ switch (ch) {
+ case '>':
+ case -1:
+ ch = readCh();
+ return;
+ case '<':
+ return;
+ default:
+ ch = readCh();
+
+ }
+ }
+ }
+
+ /**
+ * Parse a start or end tag.
+ */
+ void parseTag() throws IOException {
+ Element elem = null;
+ boolean net = false;
+ boolean warned = false;
+ boolean unknown = false;
+
+ switch (ch = readCh()) {
+ case '!':
+ switch (ch = readCh()) {
+ case '-':
+ // Parse comment. [92] 391:7
+ while (true) {
+ if (ch == '-') {
+ if (!strict || ((ch = readCh()) == '-')) {
+ ch = readCh();
+ if (!strict && ch == '-') {
+ ch = readCh();
+ }
+ // send over any text you might see
+ // before parsing and sending the
+ // comment
+ if (textpos != 0) {
+ char newtext[] = new char[textpos];
+ System.arraycopy(text, 0, newtext, 0, textpos);
+ handleText(newtext);
+ lastBlockStartPos = currentBlockStartPos;
+ textpos = 0;
+ }
+ parseComment();
+ last = makeTag(dtd.getElement("comment"), true);
+ handleComment(getChars(0));
+ continue;
+ } else if (!warned) {
+ warned = true;
+ error("invalid.commentchar", "-");
+ }
+ }
+ skipSpace();
+ switch (ch) {
+ case '-':
+ continue;
+ case '>':
+ ch = readCh();
+ case -1:
+ return;
+ default:
+ ch = readCh();
+ if (!warned) {
+ warned = true;
+ error("invalid.commentchar",
+ String.valueOf((char)ch));
+ }
+ break;
+ }
+ }
+
+ default:
+ // deal with marked sections
+ StringBuffer strBuff = new StringBuffer();
+ while (true) {
+ strBuff.append((char)ch);
+ if (parseMarkupDeclarations(strBuff)) {
+ return;
+ }
+ switch(ch) {
+ case '>':
+ ch = readCh();
+ case -1:
+ error("invalid.markup");
+ return;
+ case '\n':
+ ln++;
+ ch = readCh();
+ lfCount++;
+ break;
+ case '\r':
+ ln++;
+ if ((ch = readCh()) == '\n') {
+ ch = readCh();
+ crlfCount++;
+ }
+ else {
+ crCount++;
+ }
+ break;
+
+ default:
+ ch = readCh();
+ break;
+ }
+ }
+ }
+
+ case '/':
+ // parse end tag [19] 317:4
+ switch (ch = readCh()) {
+ case '>':
+ ch = readCh();
+ case '<':
+ // empty end tag. either </> or </<
+ if (recent == null) {
+ error("invalid.shortend");
+ return;
+ }
+ elem = recent;
+ break;
+
+ default:
+ if (!parseIdentifier(true)) {
+ error("expected.endtagname");
+ return;
+ }
+ skipSpace();
+ switch (ch) {
+ case '>':
+ ch = readCh();
+ case '<':
+ break;
+
+ default:
+ error("expected", "'>'");
+ while ((ch != -1) && (ch != '\n') && (ch != '>')) {
+ ch = readCh();
+ }
+ if (ch == '>') {
+ ch = readCh();
+ }
+ break;
+ }
+ String elemStr = getString(0);
+ if (!dtd.elementExists(elemStr)) {
+ error("end.unrecognized", elemStr);
+ // Ignore RE before end tag
+ if ((textpos > 0) && (text[textpos-1] == '\n')) {
+ textpos--;
+ }
+ elem = dtd.getElement("unknown");
+ elem.name = elemStr;
+ unknown = true;
+ } else {
+ elem = dtd.getElement(elemStr);
+ }
+ break;
+ }
+
+
+ // If the stack is null, we're seeing end tags without any begin
+ // tags. Ignore them.
+
+ if (stack == null) {
+ error("end.extra.tag", elem.getName());
+ return;
+ }
+
+ // Ignore RE before end tag
+ if ((textpos > 0) && (text[textpos-1] == '\n')) {
+ // In a pre tag, if there are blank lines
+ // we do not want to remove the newline
+ // before the end tag. Hence this code.
+ //
+ if (stack.pre) {
+ if ((textpos > 1) && (text[textpos-2] != '\n')) {
+ textpos--;
+ }
+ } else {
+ textpos--;
+ }
+ }
+
+ // If the end tag is a form, since we did not put it
+ // on the tag stack, there is no corresponding start
+ // start tag to find. Hence do not touch the tag stack.
+ //
+
+ /*
+ if (!strict && elem.getName().equals("form")) {
+ if (lastFormSent != null) {
+ handleEndTag(lastFormSent);
+ return;
+ } else {
+ // do nothing.
+ return;
+ }
+ }
+ */
+
+ if (unknown) {
+ // we will not see a corresponding start tag
+ // on the the stack. If we are seeing an
+ // end tag, lets send this on as an empty
+ // tag with the end tag attribute set to
+ // true.
+ TagElement t = makeTag(elem);
+ handleText(t);
+ attributes.addAttribute(HTML.Attribute.ENDTAG, "true");
+ handleEmptyTag(makeTag(elem));
+ unknown = false;
+ return;
+ }
+
+ // find the corresponding start tag
+
+ // A commonly occuring error appears to be the insertion
+ // of extra end tags in a table. The intent here is ignore
+ // such extra end tags.
+ //
+ if (!strict) {
+ String stackElem = stack.elem.getName();
+
+ if (stackElem.equals("table")) {
+ // If it isnt a valid end tag ignore it and return
+ //
+ if (!elem.getName().equals(stackElem)) {
+ error("tag.ignore", elem.getName());
+ return;
+ }
+ }
+
+
+
+ if (stackElem.equals("tr") ||
+ stackElem.equals("td")) {
+ if ((!elem.getName().equals("table")) &&
+ (!elem.getName().equals(stackElem))) {
+ error("tag.ignore", elem.getName());
+ return;
+ }
+ }
+ }
+ TagStack sp = stack;
+
+ while ((sp != null) && (elem != sp.elem)) {
+ sp = sp.next;
+ }
+ if (sp == null) {
+ error("unmatched.endtag", elem.getName());
+ return;
+ }
+
+ // People put font ending tags in the darndest places.
+ // Don't close other contexts based on them being between
+ // a font tag and the corresponding end tag. Instead,
+ // ignore the end tag like it doesn't exist and allow the end
+ // of the document to close us out.
+ String elemName = elem.getName();
+ if (stack != sp &&
+ (elemName.equals("font") ||
+ elemName.equals("center"))) {
+
+ // Since closing out a center tag can have real wierd
+ // effects on the formatting, make sure that tags
+ // for which omitting an end tag is legimitate
+ // get closed out.
+ //
+ if (elemName.equals("center")) {
+ while(stack.elem.omitEnd() && stack != sp) {
+ endTag(true);
+ }
+ if (stack.elem == elem) {
+ endTag(false);
+ }
+ }
+ return;
+ }
+ // People do the same thing with center tags. In this
+ // case we would like to close off the center tag but
+ // not necessarily all enclosing tags.
+
+
+
+ // end tags
+ while (stack != sp) {
+ endTag(true);
+ }
+
+ endTag(false);
+ return;
+
+ case -1:
+ error("eof");
+ return;
+ }
+
+ // start tag [14] 314:1
+ if (!parseIdentifier(true)) {
+ elem = recent;
+ if ((ch != '>') || (elem == null)) {
+ error("expected.tagname");
+ return;
+ }
+ } else {
+ String elemStr = getString(0);
+
+ if (elemStr.equals("image")) {
+ elemStr = new String("img");
+ }
+
+ /* determine if this element is part of the dtd. */
+
+ if (!dtd.elementExists(elemStr)) {
+ // parseInvalidTag();
+ error("tag.unrecognized ", elemStr);
+ elem = dtd.getElement("unknown");
+ elem.name = elemStr;
+ unknown = true;
+ } else {
+ elem = dtd.getElement(elemStr);
+ }
+ }
+
+ // Parse attributes
+ parseAttributeSpecificationList(elem);
+
+ switch (ch) {
+ case '/':
+ net = true;
+ case '>':
+ ch = readCh();
+ if (ch == '>' && net) {
+ ch = readCh();
+ }
+ case '<':
+ break;
+
+ default:
+ error("expected", "'>'");
+ break;
+ }
+
+ if (!strict) {
+ if (elem.getName().equals("script")) {
+ error("javascript.unsupported");
+ }
+ }
+
+ // ignore RE after start tag
+ //
+ if (!elem.isEmpty()) {
+ if (ch == '\n') {
+ ln++;
+ lfCount++;
+ ch = readCh();
+ } else if (ch == '\r') {
+ ln++;
+ if ((ch = readCh()) == '\n') {
+ ch = readCh();
+ crlfCount++;
+ }
+ else {
+ crCount++;
+ }
+ }
+ }
+
+ // ensure a legal context for the tag
+ TagElement tag = makeTag(elem, false);
+
+
+ /** In dealing with forms, we have decided to treat
+ them as legal in any context. Also, even though
+ they do have a start and an end tag, we will
+ not put this tag on the stack. This is to deal
+ several pages in the web oasis that choose to
+ start and end forms in any possible location. **/
+
+ /*
+ if (!strict && elem.getName().equals("form")) {
+ if (lastFormSent == null) {
+ lastFormSent = tag;
+ } else {
+ handleEndTag(lastFormSent);
+ lastFormSent = tag;
+ }
+ } else {
+ */
+ // Smlly, if a tag is unknown, we will apply
+ // no legalTagContext logic to it.
+ //
+ if (!unknown) {
+ legalTagContext(tag);
+
+ // If skip tag is true, this implies that
+ // the tag was illegal and that the error
+ // recovery strategy adopted is to ignore
+ // the tag.
+ if (!strict && skipTag) {
+ skipTag = false;
+ return;
+ }
+ }
+ /*
+ }
+ */
+
+ startTag(tag);
+
+ if (!elem.isEmpty()) {
+ switch (elem.getType()) {
+ case CDATA:
+ parseLiteral(false);
+ break;
+ case RCDATA:
+ parseLiteral(true);
+ break;
+ default:
+ if (stack != null) {
+ stack.net = net;
+ }
+ break;
+ }
+ }
+ }
+
+ private static final String START_COMMENT = "<!--";
+ private static final String END_COMMENT = "-->";
+ private static final char[] SCRIPT_END_TAG = "</script>".toCharArray();
+ private static final char[] SCRIPT_END_TAG_UPPER_CASE =
+ "</SCRIPT>".toCharArray();
+
+ void parseScript() throws IOException {
+ char[] charsToAdd = new char[SCRIPT_END_TAG.length];
+
+ /* Here, ch should be the first character after <script> */
+ while (true) {
+ int i = 0;
+ while (i < SCRIPT_END_TAG.length
+ && (SCRIPT_END_TAG[i] == ch
+ || SCRIPT_END_TAG_UPPER_CASE[i] == ch)) {
+ charsToAdd[i] = (char) ch;
+ ch = readCh();
+ i++;
+ }
+ if (i == SCRIPT_END_TAG.length) {
+
+ /* '</script>' tag detected */
+ /* Here, ch == '>' */
+ ch = readCh();
+ /* Here, ch == the first character after </script> */
+ return;
+ } else {
+
+ /* To account for extra read()'s that happened */
+ for (int j = 0; j < i; j++) {
+ addString(charsToAdd[j]);
+ }
+
+ switch (ch) {
+ case -1:
+ error("eof.script");
+ return;
+ case '\n':
+ ln++;
+ ch = readCh();
+ lfCount++;
+ addString('\n');
+ break;
+ case '\r':
+ ln++;
+ if ((ch = readCh()) == '\n') {
+ ch = readCh();
+ crlfCount++;
+ } else {
+ crCount++;
+ }
+ addString('\n');
+ break;
+ default:
+ addString(ch);
+ ch = readCh();
+ break;
+ } // switch
+ }
+ } // while
+ }
+
+ /**
+ * Parse Content. [24] 320:1
+ */
+ void parseContent() throws IOException {
+ Thread curThread = Thread.currentThread();
+
+ for (;;) {
+ if (curThread.isInterrupted()) {
+ curThread.interrupt(); // resignal the interrupt
+ break;
+ }
+
+ int c = ch;
+ currentBlockStartPos = currentPosition;
+
+ if (recent == dtd.script) { // means: if after starting <script> tag
+
+ /* Here, ch has to be the first character after <script> */
+ parseScript();
+ last = makeTag(dtd.getElement("comment"), true);
+
+ /* Remove leading and trailing HTML comment declarations */
+ String str = new String(getChars(0)).trim();
+ int minLength = START_COMMENT.length() + END_COMMENT.length();
+ if (str.startsWith(START_COMMENT) && str.endsWith(END_COMMENT)
+ && str.length() >= (minLength)) {
+ str = str.substring(START_COMMENT.length(),
+ str.length() - END_COMMENT.length());
+ }
+
+ /* Handle resulting chars as comment */
+ handleComment(str.toCharArray());
+ endTag(false);
+ lastBlockStartPos = currentPosition;
+ } else {
+ switch (c) {
+ case '<':
+ parseTag();
+ lastBlockStartPos = currentPosition;
+ continue;
+
+ case '/':
+ ch = readCh();
+ if ((stack != null) && stack.net) {
+ // null end tag.
+ endTag(false);
+ continue;
+ }
+ break;
+
+ case -1:
+ return;
+
+ case '&':
+ if (textpos == 0) {
+ if (!legalElementContext(dtd.pcdata)) {
+ error("unexpected.pcdata");
+ }
+ if (last.breaksFlow()) {
+ space = false;
+ }
+ }
+ char data[] = parseEntityReference();
+ if (textpos + data.length + 1 > text.length) {
+ char newtext[] = new char[Math.max(textpos + data.length + 128, text.length * 2)];
+ System.arraycopy(text, 0, newtext, 0, text.length);
+ text = newtext;
+ }
+ if (space) {
+ space = false;
+ text[textpos++] = ' ';
+ }
+ System.arraycopy(data, 0, text, textpos, data.length);
+ textpos += data.length;
+ ignoreSpace = false;
+ continue;
+
+ case '\n':
+ ln++;
+ lfCount++;
+ ch = readCh();
+ if ((stack != null) && stack.pre) {
+ break;
+ }
+ if (textpos == 0) {
+ lastBlockStartPos = currentPosition;
+ }
+ if (!ignoreSpace) {
+ space = true;
+ }
+ continue;
+
+ case '\r':
+ ln++;
+ c = '\n';
+ if ((ch = readCh()) == '\n') {
+ ch = readCh();
+ crlfCount++;
+ }
+ else {
+ crCount++;
+ }
+ if ((stack != null) && stack.pre) {
+ break;
+ }
+ if (textpos == 0) {
+ lastBlockStartPos = currentPosition;
+ }
+ if (!ignoreSpace) {
+ space = true;
+ }
+ continue;
+
+
+ case '\t':
+ case ' ':
+ ch = readCh();
+ if ((stack != null) && stack.pre) {
+ break;
+ }
+ if (textpos == 0) {
+ lastBlockStartPos = currentPosition;
+ }
+ if (!ignoreSpace) {
+ space = true;
+ }
+ continue;
+
+ default:
+ if (textpos == 0) {
+ if (!legalElementContext(dtd.pcdata)) {
+ error("unexpected.pcdata");
+ }
+ if (last.breaksFlow()) {
+ space = false;
+ }
+ }
+ ch = readCh();
+ break;
+ }
+ }
+
+ // enlarge buffer if needed
+ if (textpos + 2 > text.length) {
+ char newtext[] = new char[text.length + 128];
+ System.arraycopy(text, 0, newtext, 0, text.length);
+ text = newtext;
+ }
+
+ // output pending space
+ if (space) {
+ if (textpos == 0) {
+ lastBlockStartPos--;
+ }
+ text[textpos++] = ' ';
+ space = false;
+ }
+ text[textpos++] = (char)c;
+ ignoreSpace = false;
+ }
+ }
+
+ /**
+ * Returns the end of line string. This will return the end of line
+ * string that has been encountered the most, one of \r, \n or \r\n.
+ */
+ String getEndOfLineString() {
+ if (crlfCount >= crCount) {
+ if (lfCount >= crlfCount) {
+ return "\n";
+ }
+ else {
+ return "\r\n";
+ }
+ }
+ else {
+ if (crCount > lfCount) {
+ return "\r";
+ }
+ else {
+ return "\n";
+ }
+ }
+ }
+
+ /**
+ * Parse an HTML stream, given a DTD.
+ */
+ public synchronized void parse(Reader in) throws IOException {
+ this.in = in;
+
+ this.ln = 1;
+
+ seenHtml = false;
+ seenHead = false;
+ seenBody = false;
+
+ crCount = lfCount = crlfCount = 0;
+
+ try {
+ ch = readCh();
+ text = new char[1024];
+ str = new char[128];
+
+ parseContent();
+ // NOTE: interruption may have occurred. Control flows out
+ // of here normally.
+ while (stack != null) {
+ endTag(true);
+ }
+ in.close();
+ } catch (IOException e) {
+ errorContext();
+ error("ioexception");
+ throw e;
+ } catch (Exception e) {
+ errorContext();
+ error("exception", e.getClass().getName(), e.getMessage());
+ e.printStackTrace();
+ } catch (ThreadDeath e) {
+ errorContext();
+ error("terminated");
+ e.printStackTrace();
+ throw e;
+ } finally {
+ for (; stack != null ; stack = stack.next) {
+ handleEndTag(stack.tag);
+ }
+
+ text = null;
+ str = null;
+ }
+
+ }
+
+
+ /*
+ * Input cache. This is much faster than calling down to a synchronized
+ * method of BufferedReader for each byte. Measurements done 5/30/97
+ * show that there's no point in having a bigger buffer: Increasing
+ * the buffer to 8192 had no measurable impact for a program discarding
+ * one character at a time (reading from an http URL to a local machine).
+ * NOTE: If the current encoding is bogus, and we read too much
+ * (past the content-type) we may suffer a MalformedInputException. For
+ * this reason the initial size is 1 and when the body is encountered the
+ * size is adjusted to 256.
+ */
+ private char buf[] = new char[1];
+ private int pos;
+ private int len;
+ /*
+ tracks position relative to the beginning of the
+ document.
+ */
+ private int currentPosition;
+
+
+ private final int readCh() throws IOException {
+
+ if (pos >= len) {
+
+ // This loop allows us to ignore interrupts if the flag
+ // says so
+ for (;;) {
+ try {
+ len = in.read(buf);
+ break;
+ } catch (InterruptedIOException ex) {
+ throw ex;
+ }
+ }
+
+ if (len <= 0) {
+ return -1; // eof
+ }
+ pos = 0;
+ }
+ ++currentPosition;
+
+ return buf[pos++];
+ }
+
+
+ protected int getCurrentPos() {
+ return currentPosition;
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/parser/ParserDelegator.java b/src/share/classes/javax/swing/text/html/parser/ParserDelegator.java
new file mode 100644
index 000000000..df48ca2ce
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/ParserDelegator.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 1998-2002 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html.parser;
+
+import javax.swing.text.html.HTMLEditorKit;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.DataInputStream;
+import java.io.ObjectInputStream;
+import java.io.Reader;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+
+/**
+ * Responsible for starting up a new DocumentParser
+ * each time its parse method is invoked. Stores a
+ * reference to the dtd.
+ *
+ * @author Sunita Mani
+ */
+
+public class ParserDelegator extends HTMLEditorKit.Parser implements Serializable {
+
+ private static DTD dtd = null;
+
+ protected static synchronized void setDefaultDTD() {
+ if (dtd == null) {
+ DTD _dtd = null;
+ // (PENDING) Hate having to hard code!
+ String nm = "html32";
+ try {
+ _dtd = DTD.getDTD(nm);
+ } catch (IOException e) {
+ // (PENDING) UGLY!
+ System.out.println("Throw an exception: could not get default dtd: " + nm);
+ }
+ dtd = createDTD(_dtd, nm);
+ }
+ }
+
+ protected static DTD createDTD(DTD dtd, String name) {
+
+ InputStream in = null;
+ boolean debug = true;
+ try {
+ String path = name + ".bdtd";
+ in = getResourceAsStream(path);
+ if (in != null) {
+ dtd.read(new DataInputStream(new BufferedInputStream(in)));
+ dtd.putDTDHash(name, dtd);
+ }
+ } catch (Exception e) {
+ System.out.println(e);
+ }
+ return dtd;
+ }
+
+
+ public ParserDelegator() {
+ if (dtd == null) {
+ setDefaultDTD();
+ }
+ }
+
+ public void parse(Reader r, HTMLEditorKit.ParserCallback cb, boolean ignoreCharSet) throws IOException {
+ new DocumentParser(dtd).parse(r, cb, ignoreCharSet);
+ }
+
+ /**
+ * Fetch a resource relative to the ParserDelegator classfile.
+ * If this is called on 1.2 the loading will occur under the
+ * protection of a doPrivileged call to allow the ParserDelegator
+ * to function when used in an applet.
+ *
+ * @param name the name of the resource, relative to the
+ * ParserDelegator class.
+ * @returns a stream representing the resource
+ */
+ static InputStream getResourceAsStream(String name) {
+ try {
+ return ResourceLoader.getResourceAsStream(name);
+ } catch (Throwable e) {
+ // If the class doesn't exist or we have some other
+ // problem we just try to call getResourceAsStream directly.
+ return ParserDelegator.class.getResourceAsStream(name);
+ }
+ }
+
+ private void readObject(ObjectInputStream s)
+ throws ClassNotFoundException, IOException {
+ s.defaultReadObject();
+ if (dtd == null) {
+ setDefaultDTD();
+ }
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/parser/ResourceLoader.java b/src/share/classes/javax/swing/text/html/parser/ResourceLoader.java
new file mode 100644
index 000000000..02e4fe31c
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/ResourceLoader.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 1999 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html.parser;
+
+import java.io.InputStream;
+
+/**
+ * Simple class to load resources using the 1.2
+ * security model. Since the html support is loaded
+ * lazily, it's resources are potentially fetched with
+ * applet code in the call stack. By providing this
+ * functionality in a class that is only built on 1.2,
+ * reflection can be used from the code that is also
+ * built on 1.1 to call this functionality (and avoid
+ * the evils of preprocessing). This functionality
+ * is called from ParserDelegator.getResourceAsStream.
+ *
+ * @author Timothy Prinzing
+ */
+class ResourceLoader implements java.security.PrivilegedAction {
+
+ ResourceLoader(String name) {
+ this.name = name;
+ }
+
+ public Object run() {
+ Object o = ParserDelegator.class.getResourceAsStream(name);
+ return o;
+ }
+
+ public static InputStream getResourceAsStream(String name) {
+ java.security.PrivilegedAction a = new ResourceLoader(name);
+ return (InputStream) java.security.AccessController.doPrivileged(a);
+ }
+
+ private String name;
+}
diff --git a/src/share/classes/javax/swing/text/html/parser/TagElement.java b/src/share/classes/javax/swing/text/html/parser/TagElement.java
new file mode 100644
index 000000000..0545010e1
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/TagElement.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html.parser;
+
+import javax.swing.text.html.HTML;
+/**
+ * A generic HTML TagElement class. The methods define how white
+ * space is interpreted around the tag.
+ *
+ * @author Sunita Mani
+ */
+
+public class TagElement {
+
+ Element elem;
+ HTML.Tag htmlTag;
+ boolean insertedByErrorRecovery;
+
+ public TagElement ( Element elem ) {
+ this(elem, false);
+ }
+
+ public TagElement (Element elem, boolean fictional) {
+ this.elem = elem;
+ htmlTag = HTML.getTag(elem.getName());
+ if (htmlTag == null) {
+ htmlTag = new HTML.UnknownTag(elem.getName());
+ }
+ insertedByErrorRecovery = fictional;
+ }
+
+ public boolean breaksFlow() {
+ return htmlTag.breaksFlow();
+ }
+
+ public boolean isPreformatted() {
+ return htmlTag.isPreformatted();
+ }
+
+ public Element getElement() {
+ return elem;
+ }
+
+ public HTML.Tag getHTMLTag() {
+ return htmlTag;
+ }
+
+ public boolean fictional() {
+ return insertedByErrorRecovery;
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/parser/TagStack.java b/src/share/classes/javax/swing/text/html/parser/TagStack.java
new file mode 100644
index 000000000..5fcb4d388
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/TagStack.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.text.html.parser;
+
+import java.util.BitSet;
+import java.util.Vector;
+import java.io.*;
+
+
+/**
+ * A stack of tags. Used while parsing an HTML document.
+ * It, together with the ContentModelStates, defines the
+ * complete state of the parser while reading a document.
+ * When a start tag is encountered an element is pushed onto
+ * the stack, when an end tag is enountered an element is popped
+ * of the stack.
+ *
+ * @see Parser
+ * @see DTD
+ * @see ContentModelState
+ * @author Arthur van Hoff
+ */
+final
+class TagStack implements DTDConstants {
+ TagElement tag;
+ Element elem;
+ ContentModelState state;
+ TagStack next;
+ BitSet inclusions;
+ BitSet exclusions;
+ boolean net;
+ boolean pre;
+
+ /**
+ * Construct a stack element.
+ */
+ TagStack(TagElement tag, TagStack next) {
+ this.tag = tag;
+ this.elem = tag.getElement();
+ this.next = next;
+
+ Element elem = tag.getElement();
+ if (elem.getContent() != null) {
+ this.state = new ContentModelState(elem.getContent());
+ }
+
+ if (next != null) {
+ inclusions = next.inclusions;
+ exclusions = next.exclusions;
+ pre = next.pre;
+ }
+ if (tag.isPreformatted()) {
+ pre = true;
+ }
+
+ if (elem.inclusions != null) {
+ if (inclusions != null) {
+ inclusions = (BitSet)inclusions.clone();
+ inclusions.or(elem.inclusions);
+ } else {
+ inclusions = elem.inclusions;
+ }
+ }
+ if (elem.exclusions != null) {
+ if (exclusions != null) {
+ exclusions = (BitSet)exclusions.clone();
+ exclusions.or(elem.exclusions);
+ } else {
+ exclusions = elem.exclusions;
+ }
+ }
+ }
+
+ /**
+ * Return the element that must come next in the
+ * input stream.
+ */
+ public Element first() {
+ return (state != null) ? state.first() : null;
+ }
+
+ /**
+ * Return the ContentModel that must be satisfied by
+ * what comes next in the input stream.
+ */
+ public ContentModel contentModel() {
+ if (state == null) {
+ return null;
+ } else {
+ return state.getModel();
+ }
+ }
+
+ /**
+ * Return true if the element that is contained at
+ * the index specified by the parameter is part of
+ * the exclusions specified in the DTD for the element
+ * currently on the TagStack.
+ */
+ boolean excluded(int elemIndex) {
+ return (exclusions != null) && exclusions.get(elem.getIndex());
+ }
+
+ /**
+ * Update the Vector elemVec with all the elements that
+ * are part of the inclusions listed in DTD for the element
+ * currently on the TagStack.
+ */
+ boolean included(Vector elemVec, DTD dtd) {
+
+ for (int i = 0 ; i < inclusions.size(); i++) {
+ if (inclusions.get(i)) {
+ elemVec.addElement(dtd.getElement(i));
+ System.out.println("Element add thru' inclusions: " + dtd.getElement(i).getName());
+ }
+ }
+ return (!elemVec.isEmpty());
+ }
+
+
+ /**
+ * Advance the state by reducing the given element.
+ * Returns false if the element is not legal and the
+ * state is not advanced.
+ */
+ boolean advance(Element elem) {
+ if ((exclusions != null) && exclusions.get(elem.getIndex())) {
+ return false;
+ }
+ if (state != null) {
+ ContentModelState newState = state.advance(elem);
+ if (newState != null) {
+ state = newState;
+ return true;
+ }
+ } else if (this.elem.getType() == ANY) {
+ return true;
+ }
+ return (inclusions != null) && inclusions.get(elem.getIndex());
+ }
+
+ /**
+ * Return true if the current state can be terminated.
+ */
+ boolean terminate() {
+ return (state == null) || state.terminate();
+ }
+
+ /**
+ * Convert to a string.
+ */
+ public String toString() {
+ return (next == null) ?
+ "<" + tag.getElement().getName() + ">" :
+ next + " <" + tag.getElement().getName() + ">";
+ }
+}
+
+class NPrintWriter extends PrintWriter {
+
+ private int numLines = 5;
+ private int numPrinted = 0;
+
+ public NPrintWriter (int numberOfLines) {
+ super(System.out);
+ numLines = numberOfLines;
+ }
+
+ public void println(char[] array) {
+ if (numPrinted >= numLines) {
+ return;
+ }
+
+ char[] partialArray = null;
+
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == '\n') {
+ numPrinted++;
+ }
+
+ if (numPrinted == numLines) {
+ System.arraycopy(array, 0, partialArray, 0, i);
+ }
+ }
+
+ if (partialArray != null) {
+ super.print(partialArray);
+ }
+
+ if (numPrinted == numLines) {
+ return;
+ }
+
+ super.println(array);
+ numPrinted++;
+ }
+}
diff --git a/src/share/classes/javax/swing/text/html/parser/html32.bdtd b/src/share/classes/javax/swing/text/html/parser/html32.bdtd
new file mode 100644
index 000000000..a48d51ce8
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/html32.bdtd
Binary files differ
diff --git a/src/share/classes/javax/swing/text/html/parser/package.html b/src/share/classes/javax/swing/text/html/parser/package.html
new file mode 100644
index 000000000..0121ae6b2
--- /dev/null
+++ b/src/share/classes/javax/swing/text/html/parser/package.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+Copyright 1999-2000 Sun Microsystems, Inc. All Rights Reserved.
+DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+
+This code is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 2 only, as
+published by the Free Software Foundation. Sun designates this
+particular file as subject to the "Classpath" exception as provided
+by Sun in the LICENSE file that accompanied this code.
+
+This code is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+version 2 for more details (a copy is included in the LICENSE file that
+accompanied this code).
+
+You should have received a copy of the GNU General Public License version
+2 along with this work; if not, write to the Free Software Foundation,
+Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+CA 95054 USA or visit www.sun.com if you need additional information or
+have any questions.
+-->
+
+</head>
+<body bgcolor="white">
+
+Provides the default HTML parser, along with support classes.
+As the stream is parsed,
+the parser notifies a delegate,
+which must implement
+the <code>HTMLEditorKit.ParserCallback</code> interface.
+
+<p>
+<strong>Note:</strong>
+Most of the Swing API is <em>not</em> thread safe.
+For details, see
+<a
+href="http://java.sun.com/docs/books/tutorial/uiswing/overview/threads.html"
+target="_top">Threads and Swing</a>,
+a section in
+<em><a href="http://java.sun.com/docs/books/tutorial/"
+target="_top">The Java Tutorial</a></em>.
+
+@see javax.swing.text.html.HTMLEditorKit.ParserCallback
+@since 1.2
+@serial exclude
+
+</body>
+</html>