- /*
- * @(#)StyleSheet.java 1.63 00/02/02
- *
- * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
- *
- * This software is the proprietary information of Sun Microsystems, Inc.
- * Use is subject to license terms.
- *
- */
- package javax.swing.text.html;
- 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>
- *
- * import java.util.*;
- * import javax.swing.text.*;
- * import javax.swing.text.html.*;
- *
- * public class ShowStyles {
- *
- * public static void main(String[] args) {
- * HTMLEditorKit kit = new HTMLEditorKit();
- * HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
- * StyleSheet styles = doc.getStyleSheet();
- *
- * Enumeration rules = styles.getStyleNames();
- * while (rules.hasMoreElements()) {
- * String name = (String) rules.nextElement();
- * Style rule = styles.getStyle(name);
- * System.out.println(rule.toString());
- * }
- * System.exit(0);
- * }
- * }
- *
- * </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><body bgcolor=red></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
- * @version 1.63 02/02/00
- */
- public class StyleSheet extends StyleContext {
- /**
- * Construct a StyleSheet
- */
- public StyleSheet() {
- super();
- selectorMapping = new Hashtable();
- 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.
- * @returns 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 <style> tag.
- */
- public void addRule(String rule) {
- if (rule != null) {
- 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) {
- Object mapping = getRootSelectorMapping();
- for (int i = selectors.length - 1; i >= 0; i--) {
- mapping = getSelectorMapping(mapping, selectors[i], true);
- }
- Style rule = getMappingStyle(mapping);
- if (rule != null) {
- removeMappingStyle(mapping);
- 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)) {
- linkedStyleSheets.insertElementAt(ss, 0);
- linkStyleSheetAt(ss, 0);
- }
- }
- }
- /**
- * 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 AttributeSet containing the HTML attributes.
- * @param AttributeSet containing the corresponding CSS attributes.
- * The AttributeSet will be empty if there are no mapping
- * CSS 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) {
- 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) {
- 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) {
- 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) {
- 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) {
- 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);
- }
- /**
- * 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);
- }
- /**
- * 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 attr 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);
- }
- public void setBaseFontSize(int sz) {
- css.setBaseFontSize(sz);
- }
- public void setBaseFontSize(String size) {
- css.setBaseFontSize(size);
- }
- public static int getIndexOfSize(float pt) {
- return CSS.getIndexOfSize(pt);
- }
- /**
- * Returns the point size, given a size index.
- */
- public float getPointSize(int index) {
- return css.getPointSize(index);
- }
- /**
- * Given a string such as "+2", "-2", or "2",
- * returns a point size value.
- */
- public float getPointSize(String size) {
- return css.getPointSize(size);
- }
- /**
- * 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) {
- Object mapping = getRootSelectorMapping();
- for (int i = n - 1; i >= 0; i--) {
- mapping = getSelectorMapping(mapping, selector[i], true);
- }
- rule = getMappingStyle(mapping);
- if (rule == null) {
- rule = createStyleForSelector(selectorName, mapping,
- altRule);
- refreshResolvedRules(selectorName, selector, rule,
- getSpecificity(mapping));
- }
- }
- }
- 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
- * too. This is an inverted graph of the selectors.
- */
- private Object getRootSelectorMapping() {
- return selectorMapping;
- }
- /**
- * Returns the child mapping of <code>parent</code> for
- * <code>selector</code>. If there is no mapping for <code>selector</code>
- * and <code>create</code> is false, this will return null.
- */
- private synchronized Object getSelectorMapping(Object parent,
- String selector,
- boolean create) {
- Hashtable retValue = (Hashtable)((Hashtable)parent).get(selector);
- if (retValue == null && create) {
- retValue = new Hashtable(7);
- ((Hashtable)parent).put(selector, retValue);
- // Update specificity for child.
- int specificity = 0;
- if (parent != null) {
- Object pSpec = ((Hashtable)parent).get(SPECIFICITY);
- if (pSpec != null) {
- specificity = ((Integer)pSpec).intValue();
- }
- }
- // class (.) 100
- // id (#) 10000
- char firstChar = selector.charAt(0);
- if (firstChar == '.') {
- specificity += 100;
- }
- else if (firstChar == '#') {
- specificity += 10000;
- }
- else {
- specificity += 1;
- if (selector.indexOf('.') != -1) {
- specificity += 100;
- }
- if (selector.indexOf('#') != -1) {
- specificity += 10000;
- }
- }
- retValue.put(SPECIFICITY, new Integer(specificity));
- }
- return retValue;
- }
- /**
- * 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 specificity of the passed in mapping.
- */
- private int getSpecificity(Object mapping) {
- Object pSpec = ((Hashtable)mapping).get(SPECIFICITY);
- if (pSpec != null) {
- return ((Integer)pSpec).intValue();
- }
- return 0;
- }
- /**
- * Returns the style for the passed in mapping.
- */
- private Style getMappingStyle(Object mapping) {
- return (Style)((Hashtable)mapping).get(RULE);
- }
- /**
- * Removes the previously added mapping style.
- */
- private void removeMappingStyle(Object mapping) {
- ((Hashtable)mapping).remove(RULE);
- }
- /**
- * 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 Style appropriate for <code>selector</code> and
- * <code>mapping</code>. If a Style does not currently exist,
- * <code>altStyle</code> will be used.
- */
- private synchronized Style createStyleForSelector(String selector,
- Object mapping,
- Style altStyle) {
- Style style = (Style)((Hashtable)mapping).get(RULE);
- if (style == null) {
- style = altStyle;
- ((Hashtable)mapping).put(RULE, altStyle);
- }
- return style;
- }
- /**
- * 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(Object mapping, Vector elements) {
- int size = elements.size();
- if (size > 0) {
- int specificity = getSpecificity(mapping);
- for (int counter = 0; counter < size; counter++) {
- if (specificity >= getSpecificity(elements.
- elementAt(counter))) {
- 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(Object 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 = getMappingStyle(parentMapping);
- if (style != null) {
- addSortedStyle(parentMapping, styles);
- }
- for (int counter = index; counter < numElements; counter++) {
- String tagString = tags[counter];
- if (tagString != null) {
- Object childMapping = getSelectorMapping(parentMapping,
- tagString, false);
- if (childMapping != null) {
- getStyles(childMapping, styles, tags, ids, classes,
- counter + 1, numElements, alreadyChecked);
- }
- if (classes[counter] != null) {
- String className = classes[counter];
- childMapping = getSelectorMapping(parentMapping,
- tagString + "." + className, false);
- if (childMapping != null) {
- getStyles(childMapping, styles, tags, ids, classes,
- counter + 1, numElements, alreadyChecked);
- }
- childMapping = getSelectorMapping(parentMapping, "." +
- className, false);
- if (childMapping != null) {
- getStyles(childMapping, styles, tags, ids, classes,
- counter + 1, numElements, alreadyChecked);
- }
- }
- if (ids[counter] != null) {
- String idName = ids[counter];
- childMapping = getSelectorMapping(parentMapping,
- tagString + "#" + idName, false);
- if (childMapping != null) {
- getStyles(childMapping, styles, tags, ids, classes,
- counter + 1, numElements, alreadyChecked);
- }
- childMapping = getSelectorMapping(parentMapping, "#" +
- 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 {
- Object mapping = getRootSelectorMapping();
- int numElements = tags.length;
- String tagString = tags[0];
- Object childMapping = getSelectorMapping(mapping, tagString,
- false);
- if (childMapping != null) {
- getStyles(childMapping, tempVector, tags, ids, classes, 1,
- numElements, tempHashtable);
- }
- if (classes[0] != null) {
- String className = classes[0];
- childMapping = getSelectorMapping(mapping, tagString + "." +
- className, false);
- if (childMapping != null) {
- getStyles(childMapping, tempVector, tags, ids, classes, 1,
- numElements, tempHashtable);
- }
- childMapping = getSelectorMapping(mapping, "." + className,
- false);
- if (childMapping != null) {
- getStyles(childMapping, tempVector, tags, ids, classes,
- 1, numElements, tempHashtable);
- }
- }
- if (ids[0] != null) {
- String idName = ids[0];
- childMapping = getSelectorMapping(mapping, tagString + "#" +
- idName, false);
- if (childMapping != null) {
- getStyles(childMapping, tempVector, tags, ids, classes,
- 1, numElements, tempHashtable);
- }
- childMapping = getSelectorMapping(mapping, "#" + 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] = getMappingStyle(tempVector.
- elementAt(counter));
- }
- // 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