3338 lines · 125.2 KB · Java
RawDownload
/* * Copyright (c) 1997, 2011, Oracle and/or its affiliates. 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.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.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.UIManager;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 * {@link #getViewAttributes getViewAttributes} * 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<String, ResolvedStyle>();        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<Element> 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 = 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 = 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<ResolvedStyle> values = resolvedStyles.elements();                        while (values.hasMoreElements()) {                            ResolvedStyle style = 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<StyleSheet>();            }            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

Lines 1–500 of 3338.