/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package org.apache.batik.css.engine;

import java.io.IOException;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.batik.css.engine.sac.CSSConditionFactory;
import org.apache.batik.css.engine.sac.CSSSelectorFactory;
import org.apache.batik.css.engine.sac.ExtendedSelector;
import org.apache.batik.css.engine.value.ComputedValue;
import org.apache.batik.css.engine.value.InheritValue;
import org.apache.batik.css.engine.value.ShorthandManager;
import org.apache.batik.css.engine.value.Value;
import org.apache.batik.css.engine.value.ValueManager;
import org.apache.batik.css.parser.ExtendedParser;
import org.apache.batik.util.CSSConstants;
import org.apache.batik.util.ParsedURL;
import org.w3c.css.sac.CSSException;
import org.w3c.css.sac.DocumentHandler;
import org.w3c.css.sac.InputSource;
import org.w3c.css.sac.LexicalUnit;
import org.w3c.css.sac.SACMediaList;
import org.w3c.css.sac.SelectorList;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.events.MutationEvent;

/**
 * This is the base class for all the CSS engines.
 *
 * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
 * @version $Id: CSSEngine.java,v 1.22 2003/07/09 02:10:10 deweese Exp $
 */
public abstract class CSSEngine {

    /**
     * Returns the next stylable parent of the given element.
     */
    public static CSSStylableElement getParentCSSStylableElement(Element elt) {
        Element e = getParentElement(elt);
        while (e != null) {
            if (e instanceof CSSStylableElement) {
                return (CSSStylableElement)e;
            }
            e = getParentElement(e);
        }
        return null;
    }

    /**
     * Returns the next parent element of the given element, from the
     * CSS point of view.
     */
    public static Element getParentElement(Element elt) {
        Node n = elt.getParentNode();
        while (n != null) {
            n = getLogicalParentNode(n);
            if (n.getNodeType() == Node.ELEMENT_NODE) {
                return (Element)n;
            }
            n = n.getParentNode();
        }
        return null;
    }

    /**
     * Returns the logical parent of a node, given its physical parent.
     */
    public static Node getLogicalParentNode(Node parent) {
        Node node = parent;
        if (node != null) {
            if (node instanceof CSSImportedElementRoot) {
                return ((CSSImportedElementRoot)node).getCSSParentElement();
            } else {
                return node;
            }
        }
        return null;
    }

    /**
     * Returns the imported child of the given node, if any.
     */
    public static Node getImportedChild(Node node) {
        if (node instanceof CSSImportNode) {
            CSSImportNode inode = (CSSImportNode)node;
            CSSImportedElementRoot r = inode.getCSSImportedElementRoot();
            if (r == null) {
                return null;
            }
            return r.getFirstChild();
        }
        return null;
    }

    /**
     * The CSS context.
     */
    protected CSSContext cssContext;
    
    /**
     * The associated document.
     */
    protected Document document;

    /**
     * The document URI.
     */
    protected URL documentURI;

    /**
     * The property/int mappings.
     */
    protected StringIntMap indexes;

    /**
     * The shorthand-property/int mappings.
     */
    protected StringIntMap shorthandIndexes;

    /**
     * The value managers.
     */
    protected ValueManager[] valueManagers;

    /**
     * The shorthand managers.
     */
    protected ShorthandManager[] shorthandManagers;

    /**
     * The CSS parser.
     */
    protected ExtendedParser parser;

    /**
     * The pseudo-element names.
     */
    protected String[] pseudoElementNames;

    /**
     * The font-size property index.
     */
    protected int fontSizeIndex = -1;

    /**
     * The line-height property index.
     */
    protected int lineHeightIndex = -1;

    /**
     * The color property index.
     */
    protected int colorIndex = -1;

    /**
     * The user-agent style-sheet.
     */
    protected StyleSheet userAgentStyleSheet;

    /**
     * The user style-sheet.
     */
    protected StyleSheet userStyleSheet;

    /**
     * The media to use to cascade properties.
     */
    protected SACMediaList media;

    /**
     * The DOM nodes which contains StyleSheets.
     */
    protected List styleSheetNodes;

    /**
     * The style attribute namespace URI.
     */
    protected String styleNamespaceURI;

    /**
     * The style attribute local name.
     */
    protected String styleLocalName;
    
    /**
     * The class attribute namespace URI.
     */
    protected String classNamespaceURI;

    /**
     * The class attribute local name.
     */
    protected String classLocalName;
    
    /**
     * The non CSS presentational hints.
     */
    protected Set nonCSSPresentationalHints;

    /**
     * The non CSS presentational hints namespace URI.
     */
    protected String nonCSSPresentationalHintsNamespaceURI;

    /**
     * The style declaration document handler.
     */
    protected StyleDeclarationDocumentHandler styleDeclarationDocumentHandler =
        new StyleDeclarationDocumentHandler();

    /**
     * The style declaration update handler.
     */
    protected StyleDeclarationUpdateHandler styleDeclarationUpdateHandler;

    /**
     * The style sheet document handler.
     */
    protected StyleSheetDocumentHandler styleSheetDocumentHandler =
        new StyleSheetDocumentHandler();

    /**
     * The style declaration document handler used to build a
     * StyleDeclaration object.
     */
    protected StyleDeclarationBuilder styleDeclarationBuilder =
        new StyleDeclarationBuilder();

    /**
     * The current element.
     */
    protected CSSStylableElement element;

    /**
     * The current base URI.
     */
    protected URL cssBaseURI;

    /**
     * The alternate stylesheet title.
     */
    protected String alternateStyleSheet;

    /**
     * The DOMAttrModified event listener.
     */
    protected EventListener domAttrModifiedListener;

    /**
     * The DOMNodeInserted event listener.
     */
    protected EventListener domNodeInsertedListener;

    /**
     * The DOMNodeRemoved event listener.
     */
    protected EventListener domNodeRemovedListener;

    /**
     * The DOMSubtreeModified event listener.
     */
    protected EventListener domSubtreeModifiedListener;

    /**
     * The DOMCharacterDataModified event listener.
     */
    protected EventListener domCharacterDataModifiedListener;

    /**
     * Whether a style sheet as been removed from the document.
     */
    protected boolean styleSheetRemoved;

    /**
     * The right sibling of the last removed node.
     */
    protected Node removedStylableElementSibling;

    /**
     * The listeners.
     */
    protected List listeners = Collections.synchronizedList(new LinkedList());

    /**
     * The attributes found in stylesheets selectors.
     */
    protected Set selectorAttributes;

    /**
     * Used to fire a change event for all the properties.
     */
    protected final int[] ALL_PROPERTIES;

    /**
     * The CSS condition factory.
     */
    protected CSSConditionFactory cssConditionFactory;

    /**
     * Creates a new CSSEngine.
     * @param doc The associated document.
     * @param uri The document URI.
     * @param p The CSS parser.
     * @param vm The property value managers.
     * @param sm The shorthand properties managers.
     * @param pe The pseudo-element names supported by the associated
     *           XML dialect. Must be null if no support for pseudo-
     *           elements is required.
     * @param sns The namespace URI of the style attribute.
     * @param sln The local name of the style attribute.
     * @param cns The namespace URI of the class attribute.
     * @param cln The local name of the class attribute.
     * @param hints Whether the CSS engine should support non CSS
     *              presentational hints.
     * @param hintsNS The hints namespace URI.
     * @param ctx The CSS context.
     */
    protected CSSEngine(Document doc,
                        URL uri,
                        ExtendedParser p,
                        ValueManager[] vm,
                        ShorthandManager[] sm,
                        String[] pe,
                        String sns,
                        String sln,
                        String cns,
                        String cln,
                        boolean hints,
                        String hintsNS,
                        CSSContext ctx) {
        document = doc;
        documentURI = uri;
        parser = p;
        pseudoElementNames = pe;
        styleNamespaceURI = sns;
        styleLocalName = sln;
        classNamespaceURI = cns;
        classLocalName = cln;
        cssContext = ctx;

        cssConditionFactory = new CSSConditionFactory(cns, cln, null, "id");

        int len = vm.length;
        indexes = new StringIntMap(len);
        valueManagers = vm;

        for (int i = len - 1; i >= 0; --i) {
            String pn = vm[i].getPropertyName();
            indexes.put(pn, i);
            if (fontSizeIndex == -1 &&
                pn.equals(CSSConstants.CSS_FONT_SIZE_PROPERTY)) {
                fontSizeIndex = i;
            }
            if (lineHeightIndex == -1 &&
                pn.equals(CSSConstants.CSS_LINE_HEIGHT_PROPERTY)) {
                lineHeightIndex = i;
            }
            if (colorIndex == -1 &&
                pn.equals(CSSConstants.CSS_COLOR_PROPERTY)) {
                colorIndex = i;
            }
        }

        len = sm.length;
        shorthandIndexes = new StringIntMap(len);
        shorthandManagers = sm;
        for (int i = len - 1; i >= 0; --i) {
            shorthandIndexes.put(sm[i].getPropertyName(), i);
        }

        if (hints) {
            len = vm.length;
            nonCSSPresentationalHints = new HashSet();
            nonCSSPresentationalHintsNamespaceURI = hintsNS;
            for (int i = len - 1; i >= 0; --i) {
                String pn = vm[i].getPropertyName();
                nonCSSPresentationalHints.add(pn);
            }
        }

        if (cssContext.isDynamic() &&
            (document instanceof EventTarget)) {
            // Attach the mutation events listeners.
            EventTarget et = (EventTarget)document;
            domAttrModifiedListener = new DOMAttrModifiedListener();
            et.addEventListener("DOMAttrModified",
                                domAttrModifiedListener,
                                false);
            domNodeInsertedListener = new DOMNodeInsertedListener();
            et.addEventListener("DOMNodeInserted",
                                domNodeInsertedListener,
                                false);
            domNodeRemovedListener = new DOMNodeRemovedListener();
            et.addEventListener("DOMNodeRemoved",
                                domNodeRemovedListener,
                                false);
            domSubtreeModifiedListener = new DOMSubtreeModifiedListener();
            et.addEventListener("DOMSubtreeModified",
                                domSubtreeModifiedListener,
                                false);
            domCharacterDataModifiedListener =
                new DOMCharacterDataModifiedListener();
            et.addEventListener("DOMCharacterDataModified",
                                domCharacterDataModifiedListener,
                                false);
            styleDeclarationUpdateHandler =
                new StyleDeclarationUpdateHandler();
        }

        ALL_PROPERTIES = new int[getNumberOfProperties()];
        for (int i = getNumberOfProperties() - 1; i >= 0; --i) {
            ALL_PROPERTIES[i] = i;
        }
    }

    /**
     * Disposes the CSSEngine and all the attached resources.
     */
    public void dispose() {
        disposeStyleMaps(document.getDocumentElement());
        if (document instanceof EventTarget) {
            // Detach the mutation events listeners.
            EventTarget et = (EventTarget)document;
            et.removeEventListener("DOMAttrModified",
                                   domAttrModifiedListener,
                                   false);
            et.removeEventListener("DOMNodeInserted",
                                   domNodeInsertedListener,
                                   false);
            et.removeEventListener("DOMNodeRemoved",
                                   domNodeRemovedListener,
                                   false);
            et.removeEventListener("DOMSubtreeModified",
                                   domSubtreeModifiedListener,
                                   false);
            et.removeEventListener("DOMCharacterDataModified",
                                   domCharacterDataModifiedListener,
                                   false);
        }
    }

    private void disposeStyleMaps(Node node) {
        if (node instanceof CSSStylableElement) {
            ((CSSStylableElement)node).setComputedStyleMap(null, null);
        }
        for (Node n = node.getFirstChild();
             n != null;
             n = n.getNextSibling()) {
            if (n.getNodeType() == n.ELEMENT_NODE) {
                disposeStyleMaps(n);
            }
            Node c = getImportedChild(n);
            if (c != null) {
                disposeStyleMaps(c);
            }
        }
    }

    /**
     * Returns the CSS context.
     */
    public CSSContext getCSSContext() {
        return cssContext;
    }

    /**
     * Returns the document associated with this engine.
     */
    public Document getDocument() {
        return document;
    }

    /**
     * Returns the font-size property index.
     */
    public int getFontSizeIndex() {
        return fontSizeIndex;
    }

    /**
     * Returns the line-height property index.
     */
    public int getLineHeightIndex() {
        return lineHeightIndex;
    }

    /**
     * Returns the color property index.
     */
    public int getColorIndex() {
        return colorIndex;
    }

    /**
     * Returns the number of properties.
     */
    public int getNumberOfProperties() {
        return valueManagers.length;
    }

    /**
     * Returns the property index, or -1.
     */
    public int getPropertyIndex(String name) {
        return indexes.get(name);
    }

    /**
     * Returns the shorthand property index, or -1.
     */
    public int getShorthandIndex(String name) {
        return shorthandIndexes.get(name);
    }

    /**
     * Returns the name of the property at the given index.
     */
    public String getPropertyName(int idx) {
        return valueManagers[idx].getPropertyName();
    }

    /**
     * Sets the user agent style-sheet.
     */
    public void setUserAgentStyleSheet(StyleSheet ss) {
        userAgentStyleSheet = ss;
    }

    /**
     * Sets the user style-sheet.
     */
    public void setUserStyleSheet(StyleSheet ss) {
        userStyleSheet = ss;
    }

    /**
     * Returns the ValueManagers.
     */
    public ValueManager[] getValueManagers() {
        return valueManagers;
    }

    /**
     * Sets the media to use to compute the styles.
     */
    public void setMedia(String str) {
        try {
            media = parser.parseMedia(str);
        } catch (Exception e) {
            String m = e.getMessage();
            String s =
                Messages.formatMessage("media.error",
                                       new Object[] { str,
                                                      (m == null) ? "" : m });
            throw new DOMException(DOMException.SYNTAX_ERR, s);
        }
    }

    /**
     * Sets the alternate style-sheet title.
     */
    public void setAlternateStyleSheet(String str) {
        alternateStyleSheet = str;
    }

    /**
     * Recursively imports the cascaded style from a source element
     * to an element of the current document.
     */
    public void importCascadedStyleMaps(Element src,
                                        CSSEngine srceng,
                                        Element dest) {
        if (src instanceof CSSStylableElement) {
            CSSStylableElement csrc  = (CSSStylableElement)src;
            CSSStylableElement cdest = (CSSStylableElement)dest;

            StyleMap sm = srceng.getCascadedStyleMap(csrc, null);
            sm.setFixedCascadedStyle(true);
            cdest.setComputedStyleMap(null, sm);

            if (pseudoElementNames != null) {
                int len = pseudoElementNames.length;
                for (int i = 0; i < len; i++) {
                    String pe = pseudoElementNames[i];
                    sm = srceng.getCascadedStyleMap(csrc, pe);
                    cdest.setComputedStyleMap(pe, sm);
                }
            }
        }

        for (Node dn = dest.getFirstChild(), sn = src.getFirstChild();
             dn != null;
             dn = dn.getNextSibling(), sn = sn.getNextSibling()) {
            if (sn.getNodeType() == Node.ELEMENT_NODE) {
                importCascadedStyleMaps((Element)sn, srceng, (Element)dn);
            }
        }
    }

    /**
     * Returns the current base-url.
     */
    public URL getCSSBaseURI() {
        if (cssBaseURI == null) {
            cssBaseURI = element.getCSSBase();
        }
        return cssBaseURI;
    }

    /**
     * Returns the cascaded style of the given element/pseudo-element.
     * @param elt The stylable element.
     * @param pseudo Optional pseudo-element string (null if none).
     */
    public StyleMap getCascadedStyleMap(CSSStylableElement elt,
                                        String pseudo) {
        int props = getNumberOfProperties();
        StyleMap result = new StyleMap(props);

        // Apply the user-agent style-sheet to the result.
        if (userAgentStyleSheet != null) {
            List rules = new ArrayList();
            addMatchingRules(rules, userAgentStyleSheet, elt, pseudo);
            addRules(elt, pseudo, result, rules, StyleMap.USER_AGENT_ORIGIN);
        }

        // Apply the user properties style-sheet to the result.
        if (userStyleSheet != null) {
            List rules = new ArrayList();
            addMatchingRules(rules, userStyleSheet, elt, pseudo);
            addRules(elt, pseudo, result, rules, StyleMap.USER_ORIGIN);
        }

        element = elt;

        // Apply the non-CSS presentational hints to the result.
        if (nonCSSPresentationalHints != null) {
            NamedNodeMap attrs = elt.getAttributes();
            int len = attrs.getLength();
            for (int i = 0; i < len; i++) {
                Node attr = attrs.item(i);
                String an = attr.getNodeName();
                if (nonCSSPresentationalHints.contains(an)) {
                    try {
                        LexicalUnit lu;
                        int idx = getPropertyIndex(an);
                        lu = parser.parsePropertyValue(attr.getNodeValue());
                        ValueManager vm = valueManagers[idx];
                        Value v = vm.createValue(lu, this);
                        putAuthorProperty(result, idx, v, false,
                                          StyleMap.NON_CSS_ORIGIN);
                    } catch (Exception e) {
                        String m = e.getMessage();
                        String s =
                            Messages.formatMessage("property.syntax.error.at",
                                new Object[] { documentURI.toString(),
                                               an,
                                               attr.getNodeValue(),
                                               (m == null) ? "" : m });
                        throw new DOMException(DOMException.SYNTAX_ERR, s);
                    }
                }
            }
        }

        // Apply the document style-sheets to the result.
        List snodes = getStyleSheetNodes();
        int slen = snodes.size();
        if (slen > 0) {
            List rules = new ArrayList();
            for (int i = 0; i < slen; i++) {
                CSSStyleSheetNode ssn = (CSSStyleSheetNode)snodes.get(i);
                StyleSheet ss = ssn.getCSSStyleSheet();
                if (ss != null &&
                    (!ss.isAlternate() ||
                     ss.getTitle() == null ||
                     ss.getTitle().equals(alternateStyleSheet)) &&
                    mediaMatch(ss.getMedia())) {
                    addMatchingRules(rules, ss, elt, pseudo);
                }
            }
            addRules(elt, pseudo, result, rules, StyleMap.AUTHOR_ORIGIN);
        }

        // Apply the inline style to the result.
        if (styleLocalName != null) {
            String style = elt.getAttributeNS(styleNamespaceURI,
                                              styleLocalName);
            if (style.length() > 0) {
                try {
                    parser.setSelectorFactory(CSSSelectorFactory.INSTANCE);
                    parser.setConditionFactory(cssConditionFactory);
                    styleDeclarationDocumentHandler.styleMap = result;
                    parser.setDocumentHandler(styleDeclarationDocumentHandler);
                    parser.parseStyleDeclaration(style);
                    styleDeclarationDocumentHandler.styleMap = null;
                } catch (Exception e) {
                    String m = e.getMessage();
                    String s =
                        Messages.formatMessage("style.syntax.error.at",
                                      new Object[] { documentURI.toString(),
                                                     styleLocalName,
                                                     style,
                                                     (m == null) ? "" : m });
                    throw new DOMException(DOMException.SYNTAX_ERR, s);
                }
            }
        }
        
        element = null;
        cssBaseURI = null;

        return result;
    }

    /**
     * Returns the computed style of the given element/pseudo for the
     * property corresponding to the given index.
     */
    public Value getComputedStyle(CSSStylableElement elt,
                                  String pseudo,
                                  int propidx) {
        StyleMap sm = elt.getComputedStyleMap(pseudo);
        if (sm == null) {
            sm = getCascadedStyleMap(elt, pseudo);
            elt.setComputedStyleMap(pseudo, sm);
        }

        Value value = sm.getValue(propidx);
        if (!sm.isComputed(propidx)) {
            Value result = value;
            ValueManager vm = valueManagers[propidx];
            CSSStylableElement p = getParentCSSStylableElement(elt);
            if (value == null && (!vm.isInheritedProperty() || p == null)) {
                result = vm.getDefaultValue();
            } else if (value != null &&
                       (value == InheritValue.INSTANCE) &&
                       p != null) {
                result = null;
            }
            if (result == null) {
                // Value is 'inherit' and p != null.
                // The pseudo class is not propagated.
                result = getComputedStyle(p, null, propidx);
                sm.putParentRelative(propidx, true);
            } else {
                // Maybe is it a relative value.
                result = vm.computeValue(elt, pseudo, this, propidx,
                                         sm, result);
            }
            if (value == null) {
                sm.putValue(propidx, result);
                sm.putNullCascaded(propidx, true);
            } else if (result != value) {
                ComputedValue cv = new ComputedValue(value);
                cv.setComputedValue(result);
                sm.putValue(propidx, cv);
                result = cv;
            }
            sm.putComputed(propidx, true);
            value = result;
        }
        return value;
    }

    /**
     * Returns the document CSSStyleSheetNodes in a list. This list is
     * updated as the document is modified.
     */
    public List getStyleSheetNodes() {
        if (styleSheetNodes == null) {
            styleSheetNodes = new ArrayList();
            selectorAttributes = new HashSet();
            // Find all the style-sheets in the document.
            findStyleSheetNodes(document);
            int len = styleSheetNodes.size();
            for (int i = 0; i < len; i++) {
                CSSStyleSheetNode ssn;
                ssn = (CSSStyleSheetNode)styleSheetNodes.get(i);
                StyleSheet ss = ssn.getCSSStyleSheet();
                if (ss != null) {
                    findSelectorAttributes(selectorAttributes, ss);
                }
            }
        }
        return styleSheetNodes;
    }

    /**
     * An auxiliary method for getStyleSheets().
     */
    protected void findStyleSheetNodes(Node n) {
        if (n instanceof CSSStyleSheetNode) {
            styleSheetNodes.add(n);
        }
        for (Node nd = n.getFirstChild();
             nd != null;
             nd = nd.getNextSibling()) {
            findStyleSheetNodes(nd);
        }
    }

    /**
     * Finds the selector attributes in the given stylesheet.
     */
    protected void findSelectorAttributes(Set attrs, StyleSheet ss) {
        int len = ss.getSize();
        for (int i = 0; i < len; i++) {
            Rule r = ss.getRule(i);
            switch (r.getType()) {
            case StyleRule.TYPE:
                StyleRule style = (StyleRule)r;
                SelectorList sl = style.getSelectorList();
                int slen = sl.getLength();
                for (int j = 0; j < slen; j++) {
                    ExtendedSelector s = (ExtendedSelector)sl.item(j);
                    s.fillAttributeSet(attrs);
                }
                break;

            case MediaRule.TYPE:
            case ImportRule.TYPE:
                MediaRule mr = (MediaRule)r;
                if (mediaMatch(mr.getMediaList())) {
                    findSelectorAttributes(attrs, mr);
                }
                break;
            }
        }
    }

    /**
     * Parses and creates a property value.
     * @param prop The property name.
     * @param value The property value.
     */
    public Value parsePropertyValue(CSSStylableElement elt,
                                    String prop, String value) {
        try {
            element = elt;
            LexicalUnit lu;
            int idx = getPropertyIndex(prop);
            lu = parser.parsePropertyValue(value);
            ValueManager vm = valueManagers[idx];
            return vm.createValue(lu, this);
        } catch (Exception e) {
            String m = e.getMessage();
            String s =
                Messages.formatMessage("property.syntax.error.at",
                                       new Object[] { documentURI.toString(),
                                                      prop,
                                                      value,
                                                      (m == null) ? "" : m });
            throw new DOMException(DOMException.SYNTAX_ERR, s);
        } finally {
            element = null;
        }

    }

    /**
     * Parses and creates a style declaration.
     * @param value The style declaration text.
     */
    public StyleDeclaration parseStyleDeclaration(String value) {
        try {
            parser.setSelectorFactory(CSSSelectorFactory.INSTANCE);
            parser.setConditionFactory(cssConditionFactory);
            cssBaseURI = documentURI;
            styleDeclarationBuilder.styleDeclaration = new StyleDeclaration();
            parser.setDocumentHandler(styleDeclarationBuilder);
            parser.parseStyleDeclaration(value);
            cssBaseURI = null;
            return styleDeclarationBuilder.styleDeclaration;
        } catch (Exception e) {
            String m = e.getMessage();
            String s =
                Messages.formatMessage("syntax.error.at",
                                       new Object[] { documentURI.toString(),
                                                      (m == null) ? "" : m });
            throw new DOMException(DOMException.SYNTAX_ERR, s);
        }
    }

    /**
     * Parses and creates a new style-sheet.
     * @param uri The style-sheet URI.
     * @param media The target media of the style-sheet.
     */
    public StyleSheet parseStyleSheet(URL uri, String media)
        throws DOMException {
        StyleSheet ss = new StyleSheet();
        try {
            ss.setMedia(parser.parseMedia(media));
        } catch (Exception e) {
            String m = e.getMessage();
            String s =
                Messages.formatMessage("syntax.error.at",
                                       new Object[] { documentURI.toString(),
                                                      (m == null) ? "" : m });
            throw new DOMException(DOMException.SYNTAX_ERR, s);
        }
        parseStyleSheet(ss, uri);
        return ss;
    }

    /**
     * Parses and creates a new style-sheet.
     * @param is The input source used to read the document.
     * @param uri The base URI.
     * @param media The target media of the style-sheet.
     */
    public StyleSheet parseStyleSheet(InputSource is, URL uri, String media)
        throws DOMException {
        StyleSheet ss = new StyleSheet();
        try {
            ss.setMedia(parser.parseMedia(media));
            parseStyleSheet(ss, is, uri);
        } catch (Exception e) {
            String m = e.getMessage();
            String s =
                Messages.formatMessage("syntax.error.at",
                                       new Object[] { documentURI.toString(),
                                                      (m == null) ? "" : m });
            throw new DOMException(DOMException.SYNTAX_ERR, s);
        }
        return ss;
    }

    /**
     * Parses and fills the given style-sheet.
     * @param ss The stylesheet to fill.
     * @param uri The base URI.
     */
    public void parseStyleSheet(StyleSheet ss, URL uri) throws DOMException {
        if (uri == null) {
            String s = Messages.formatMessage("syntax.error.at",
                                              new Object[] { "Null Document reference", 
                                                             "" });
            throw new DOMException(DOMException.SYNTAX_ERR, s);
        }

	try {
            // Check that access to the uri is allowed
             ParsedURL pDocURL = null;
             if (documentURI != null) {
                 pDocURL = new ParsedURL(documentURI);
             }
             ParsedURL pURL = null;
                 pURL = new ParsedURL(uri);
             cssContext.checkLoadExternalResource(pURL, pDocURL);
             
             parseStyleSheet(ss, new InputSource(uri.toString()), uri);
	} catch (SecurityException e) {
            throw e; 
        } catch (Exception e) {
            String m = e.getMessage();
            String s =
                Messages.formatMessage("syntax.error.at",
                                       new Object[] { uri.toString(),
                                                      (m == null) ? "" : m });
            throw new DOMException(DOMException.SYNTAX_ERR, s);
        }
    }

    /**
     * Parses and creates a new style-sheet.
     * @param rules The style-sheet rules to parse.
     * @param uri The style-sheet URI.
     * @param media The target media of the style-sheet.
     */
    public StyleSheet parseStyleSheet(String rules, URL uri, String media)
        throws DOMException {
        StyleSheet ss = new StyleSheet();
        try {
            ss.setMedia(parser.parseMedia(media));
        } catch (Exception e) {
            String m = e.getMessage();
            String s =
                Messages.formatMessage("syntax.error.at",
                                       new Object[] { documentURI.toString(),
                                                      (m == null) ? "" : m });
            throw new DOMException(DOMException.SYNTAX_ERR, s);
        }
        parseStyleSheet(ss, rules, uri);
        return ss;
    }

    /**
     * Parses and fills the given style-sheet.
     * @param ss The stylesheet to fill.
     * @param rules The style-sheet rules to parse.
     * @param uri The base URI.
     */
    public void parseStyleSheet(StyleSheet ss,
                                String rules,
                                URL uri) throws DOMException {
        try {
            parseStyleSheet(ss, new InputSource(new StringReader(rules)), uri);
	} catch (Exception e) {
            String m = e.getMessage();
            String s =
                Messages.formatMessage("stylesheet.syntax.error",
                                       new Object[] { uri.toString(),
                                                      rules,
                                                      (m == null) ? "" : m });
            throw new DOMException(DOMException.SYNTAX_ERR, s);
        }
    }

    /**
     * Parses and fills the given style-sheet.
     * @param ss The stylesheet to fill.
     * @param uri The base URI.
     */
    protected void parseStyleSheet(StyleSheet ss, InputSource is, URL uri)
        throws IOException {
        parser.setSelectorFactory(CSSSelectorFactory.INSTANCE);
        parser.setConditionFactory(cssConditionFactory);
        cssBaseURI = uri;
        styleSheetDocumentHandler.styleSheet = ss;
        parser.setDocumentHandler(styleSheetDocumentHandler);
        parser.parseStyleSheet(is);
        cssBaseURI = null;

        // Load the imported sheets.
        int len = ss.getSize();
        for (int i = 0; i < len; i++) {
            Rule r = (Rule)ss.getRule(i);
            if (r.getType() != ImportRule.TYPE) {
                // @import rules must be the first rules.
                break;
            }
            ImportRule ir = (ImportRule)r;
            parseStyleSheet(ir, ir.getURI());
        }
    }

    /**
     * Puts an author property from a style-map in another style-map,
     * if possible.
     */
    protected void putAuthorProperty(StyleMap dest,
                                     int idx,
                                     Value sval,
                                     boolean imp,
                                     short origin) {
        Value   dval = dest.getValue(idx);
        short   dorg = dest.getOrigin(idx);
        boolean dimp = dest.isImportant(idx);

        boolean cond = dval == null;
        if (!cond) {
            switch (dorg) {
            case StyleMap.USER_ORIGIN:
                cond = !dimp;
                break;
            case StyleMap.AUTHOR_ORIGIN:
                cond = !dimp || imp;
                break;
            default:
                cond = true;
            }
        }

        if (cond) {
            dest.putValue(idx, sval);
            dest.putImportant(idx, imp);
            dest.putOrigin(idx, origin);
        }
    }

    /**
     * Adds the rules matching the element/pseudo-element of given style
     * sheet to the list.
     */
    protected void addMatchingRules(List rules,
                                    StyleSheet ss,
                                    Element elt,
                                    String pseudo) {
        int len = ss.getSize();
        for (int i = 0; i < len; i++) {
            Rule r = ss.getRule(i);
            switch (r.getType()) {
            case StyleRule.TYPE:
                StyleRule style = (StyleRule)r;
                SelectorList sl = style.getSelectorList();
                int slen = sl.getLength();
                for (int j = 0; j < slen; j++) {
                    ExtendedSelector s = (ExtendedSelector)sl.item(j);
                    if (s.match(elt, pseudo)) {
                        rules.add(style);
                    }
                }
                break;

            case MediaRule.TYPE:
            case ImportRule.TYPE:
                MediaRule mr = (MediaRule)r;
                if (mediaMatch(mr.getMediaList())) {
                    addMatchingRules(rules, mr, elt, pseudo);
                }
                break;
            }
        }
    }

    /**
     * Adds the rules contained in the given list to a stylemap.
     */
    protected void addRules(Element elt,
                            String pseudo,
                            StyleMap sm,
                            List rules,
                            short origin) {
        sortRules(rules, elt, pseudo);
        int rlen = rules.size();
        int props = getNumberOfProperties();

        if (origin == StyleMap.AUTHOR_ORIGIN) {
            for (int r = 0; r < rlen; r++) {
                StyleRule sr = (StyleRule)rules.get(r);
                StyleDeclaration sd = sr.getStyleDeclaration();
                int len = sd.size();
                for (int i = 0; i < len; i++) {
                    putAuthorProperty(sm,
                                      sd.getIndex(i),
                                      sd.getValue(i),
                                      sd.getPriority(i),
                                      origin);
                }
            }
        } else {
            for (int r = 0; r < rlen; r++) {
                StyleRule sr = (StyleRule)rules.get(r);
                StyleDeclaration sd = sr.getStyleDeclaration();
                int len = sd.size();
                for (int i = 0; i < len; i++) {
                    int idx = sd.getIndex(i);
                    sm.putValue(idx, sd.getValue(i));
                    sm.putImportant(idx, sd.getPriority(i));
                    sm.putOrigin(idx, origin);
                }
            }
        }
    }

    /**
     * Sorts the rules matching the element/pseudo-element of given style
     * sheet to the list.
     */
    protected void sortRules(List rules, Element elt, String pseudo) {
        int len = rules.size();
        for (int i = 0; i < len - 1; i++) {
            int idx = i;
            int min = Integer.MAX_VALUE;
            for (int j = i; j < len; j++) {
                StyleRule r = (StyleRule)rules.get(j);
                SelectorList sl = r.getSelectorList();
                int spec = 0;
                int slen = sl.getLength();
                for (int k = 0; k < slen; k++) {
                    ExtendedSelector s = (ExtendedSelector)sl.item(k);
                    if (s.match(elt, pseudo)) {
                        int sp = s.getSpecificity();
                        if (sp > spec) {
                            spec = sp;
                        }
                    }
                }
                if (spec < min) {
                    min = spec;
                    idx = j;
                }
            }
            if (i != idx) {
                Object tmp = rules.get(i);
                rules.set(i, rules.get(idx));
                rules.set(idx, tmp);
            }
        }
    }

    /**
     * Whether the given media list matches the media list of this
     * CSSEngine object.
     */
    protected boolean mediaMatch(SACMediaList ml) {
	if (media == null ||
            ml == null ||
            media.getLength() == 0 ||
            ml.getLength() == 0) {
	    return true;
	}
	for (int i = 0; i < ml.getLength(); i++) {
	    for (int j = 0; j < media.getLength(); j++) {
		if (media.item(j).equalsIgnoreCase("all") ||
                    ml.item(i).equalsIgnoreCase(media.item(j))) {
		    return true;
		}
	    }
	}
	return false;
    }

    /**
     * To parse a style declaration.
     */
    protected class StyleDeclarationDocumentHandler
        extends DocumentAdapter
        implements ShorthandManager.PropertyHandler {
        public StyleMap styleMap;
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#property(String,LexicalUnit,boolean)}.
         */
        public void property(String name, LexicalUnit value, boolean important)
            throws CSSException {
            int i = getPropertyIndex(name);
            if (i == -1) {
                i = getShorthandIndex(name);
                if (i == -1) {
                    // Unknown property
                    return;
                }
                shorthandManagers[i].setValues(CSSEngine.this,
                                               this,
                                               value,
                                               important);
            } else {
                Value v = valueManagers[i].createValue(value, CSSEngine.this);
                putAuthorProperty(styleMap, i, v, important,
                                  StyleMap.INLINE_AUTHOR_ORIGIN);
            }
        }
    }

    /**
     * To build a StyleDeclaration object.
     */
    protected class StyleDeclarationBuilder
        extends DocumentAdapter
        implements ShorthandManager.PropertyHandler {
        public StyleDeclaration styleDeclaration;
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#property(String,LexicalUnit,boolean)}.
         */
        public void property(String name, LexicalUnit value, boolean important)
            throws CSSException {
            int i = getPropertyIndex(name);
            if (i == -1) {
                i = getShorthandIndex(name);
                if (i == -1) {
                    // Unknown property
                    return;
                }
                shorthandManagers[i].setValues(CSSEngine.this,
                                               this,
                                               value,
                                               important);
            } else {
                Value v = valueManagers[i].createValue(value, CSSEngine.this);
                styleDeclaration.append(v, i, important);
            }
        }
    }

    /**
     * To parse a style sheet.
     */
    protected class StyleSheetDocumentHandler
        extends DocumentAdapter
        implements ShorthandManager.PropertyHandler {
        public StyleSheet styleSheet;
        protected StyleRule styleRule;
        protected StyleDeclaration styleDeclaration;

        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#startDocument(InputSource)}.
         */
        public void startDocument(InputSource source)
            throws CSSException {
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#endDocument(InputSource)}.
         */
        public void endDocument(InputSource source) throws CSSException {
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * org.w3c.css.sac.DocumentHandler#ignorableAtRule(String)}.
         */
        public void ignorableAtRule(String atRule) throws CSSException {
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#importStyle(String,SACMediaList,String)}.
         */
        public void importStyle(String       uri,
                                SACMediaList media, 
                                String       defaultNamespaceURI)
            throws CSSException {
            ImportRule ir = new ImportRule();
            ir.setMediaList(media);
            ir.setParent(styleSheet);
            try {
                ir.setURI(new URL(getCSSBaseURI(), uri));
            } catch (MalformedURLException e) {
            }
            styleSheet.append(ir);
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * org.w3c.css.sac.DocumentHandler#startMedia(SACMediaList)}.
         */
        public void startMedia(SACMediaList media) throws CSSException {
            MediaRule mr = new MediaRule();
            mr.setMediaList(media);
            mr.setParent(styleSheet);
            styleSheet.append(mr);
            styleSheet = mr;
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * org.w3c.css.sac.DocumentHandler#endMedia(SACMediaList)}.
         */
        public void endMedia(SACMediaList media) throws CSSException {
            styleSheet = styleSheet.getParent();
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * org.w3c.css.sac.DocumentHandler#startPage(String,String)}.
         */    
        public void startPage(String name, String pseudo_page)
            throws CSSException {
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * org.w3c.css.sac.DocumentHandler#endPage(String,String)}.
         */
        public void endPage(String name, String pseudo_page)
            throws CSSException {
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * org.w3c.css.sac.DocumentHandler#startFontFace()}.
         */
        public void startFontFace() throws CSSException {
            styleDeclaration = new StyleDeclaration();
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * org.w3c.css.sac.DocumentHandler#endFontFace()}.
         */
        public void endFontFace() throws CSSException {
            // We should really register this fontFace Defn with the
            // BridgeContext fontFamilyMap, so if someone tries to
            // resolve this font we can look at the associated uri.
            // We know that if it has a fragment identifier then it
            // must be an SVG font otherwise it may be a stream
            // worth passing to Font.createFont(int type, InputStream);
            styleDeclaration = null;
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * org.w3c.css.sac.DocumentHandler#startSelector(SelectorList)}.
         */
        public void startSelector(SelectorList selectors) throws CSSException {
            styleRule = new StyleRule();
            styleRule.setSelectorList(selectors);
            styleDeclaration = new StyleDeclaration();
            styleRule.setStyleDeclaration(styleDeclaration);
            styleSheet.append(styleRule);
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * org.w3c.css.sac.DocumentHandler#endSelector(SelectorList)}.
         */
        public void endSelector(SelectorList selectors) throws CSSException {
            styleRule = null;
            styleDeclaration = null;
        }

        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#property(String,LexicalUnit,boolean)}.
         */
        public void property(String name, LexicalUnit value, boolean important)
            throws CSSException {
            int i = getPropertyIndex(name);
            if (i == -1) {
                i = getShorthandIndex(name);
                if (i == -1) {
                    // Unknown property
                    return;
                }
                shorthandManagers[i].setValues(CSSEngine.this,
                                               this,
                                               value,
                                               important);
            } else {
                Value v = valueManagers[i].createValue(value, CSSEngine.this);
                styleDeclaration.append(v, i, important);
            }
        }
    }

    /**
     * Provides an adapter for the DocumentHandler interface.
     */
    protected static class DocumentAdapter implements DocumentHandler {

        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#startDocument(InputSource)}.
         */
        public void startDocument(InputSource source)
            throws CSSException {
            throw new InternalError();
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#endDocument(InputSource)}.
         */
        public void endDocument(InputSource source) throws CSSException {
            throw new InternalError();
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#comment(String)}.
         */
        public void comment(String text) throws CSSException {
            // We always ignore the comments.
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#ignorableAtRule(String)}.
         */
        public void ignorableAtRule(String atRule) throws CSSException {
            throw new InternalError();
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#namespaceDeclaration(String,String)}.
         */
        public void namespaceDeclaration(String prefix, String uri) 
            throws CSSException {
            throw new InternalError();
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#importStyle(String,SACMediaList,String)}.
         */
        public void importStyle(String       uri,
                                SACMediaList media, 
                                String       defaultNamespaceURI)
            throws CSSException {
            throw new InternalError();
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#startMedia(SACMediaList)}.
         */
        public void startMedia(SACMediaList media) throws CSSException {
            throw new InternalError();
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#endMedia(SACMediaList)}.
         */
        public void endMedia(SACMediaList media) throws CSSException {
            throw new InternalError();
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#startPage(String,String)}.
         */    
        public void startPage(String name, String pseudo_page)
            throws CSSException {
            throw new InternalError();
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#endPage(String,String)}.
         */
        public void endPage(String name, String pseudo_page)
            throws CSSException {
            throw new InternalError();
        }
    
        /**
         * <b>SAC</b>: Implements {@link DocumentHandler#startFontFace()}.
         */
        public void startFontFace() throws CSSException {
            throw new InternalError();
        }
    
        /**
         * <b>SAC</b>: Implements {@link DocumentHandler#endFontFace()}.
         */
        public void endFontFace() throws CSSException {
            throw new InternalError();
        }
        
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#startSelector(SelectorList)}.
         */
        public void startSelector(SelectorList selectors) throws CSSException {
            throw new InternalError();
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#endSelector(SelectorList)}.
         */
        public void endSelector(SelectorList selectors) throws CSSException {
            throw new InternalError();
        }
    
        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#property(String,LexicalUnit,boolean)}.
         */
        public void property(String name, LexicalUnit value, boolean important)
            throws CSSException {
            throw new InternalError();
        }
    }

    // CSS events /////////////////////////////////////////////////////////
    
    protected final static CSSEngineListener[] LISTENER_ARRAY =
        new CSSEngineListener[0];

    /**
     * Adds a CSS engine listener.
     */
    public void addCSSEngineListener(CSSEngineListener l) {
        listeners.add(l);
    }

    /**
     * Removes a CSS engine listener.
     */
    public void removeCSSEngineListener(CSSEngineListener l) {
        listeners.remove(l);
    }

    /**
     * Fires a CSSEngineEvent, given a list of modified properties.
     */
    protected void firePropertiesChangedEvent(Element target, int[] props) {
        CSSEngineListener[] ll =
            (CSSEngineListener[])listeners.toArray(LISTENER_ARRAY);

        int len = ll.length;
        if (len > 0) {
            CSSEngineEvent evt = new CSSEngineEvent(this, target, props);
            for (int i = 0; i < len; i++) {
                ll[i].propertiesChanged(evt);
            }
        }
    }

    // Dynamic updates ////////////////////////////////////////////////////
    
    /**
     * Called when the inline style of the given element has been updated.
     */
    protected void inlineStyleAttributeUpdated(CSSStylableElement elt,
                                               StyleMap style,
                                               MutationEvent evt) {
        boolean[] updated = styleDeclarationUpdateHandler.updatedProperties;
        for (int i = getNumberOfProperties() - 1; i >= 0; --i) {
            updated[i] = false;
        }

        switch (evt.getAttrChange()) {
        case MutationEvent.ADDITION:
        case MutationEvent.MODIFICATION:
            String decl = evt.getNewValue();
            if (decl.length() > 0) {
                element = elt;
                try {
                    parser.setSelectorFactory(CSSSelectorFactory.INSTANCE);
                    parser.setConditionFactory(cssConditionFactory);
                    styleDeclarationUpdateHandler.styleMap = style;
                    parser.setDocumentHandler(styleDeclarationUpdateHandler);
                    parser.parseStyleDeclaration(decl);
                    styleDeclarationUpdateHandler.styleMap = null;
                } catch (Exception e) {
                    String m = e.getMessage();
                    String s =
                        Messages.formatMessage("style.syntax.error.at",
                                      new Object[] { documentURI.toString(),
                                                     styleLocalName,
                                                     decl,
                                                     (m == null) ? "" : m });
                    throw new DOMException(DOMException.SYNTAX_ERR, s);
                }
                element = null;
                cssBaseURI = null;
            }

            // Fall through

        case MutationEvent.REMOVAL:
            boolean removed = false;

            if (evt.getPrevValue() != null &&
                evt.getPrevValue().length() > 0) {
                // Check if the style map has cascaded styles which
                // come from the inline style attribute.
                for (int i = getNumberOfProperties() - 1; i >= 0; --i) {
                    if (style.isComputed(i) &&
                        style.getOrigin(i) == StyleMap.INLINE_AUTHOR_ORIGIN &&
                        !updated[i]) {
                        removed = true;
                        updated[i] = true;
                    }
                }
            }

            if (removed) {
                // Invalidate all the values.
                elt.setComputedStyleMap(null, null);

                firePropertiesChangedEvent(elt, ALL_PROPERTIES);
                    
                Node c = getImportedChild(elt);
                if (c != null) {
                    propagateChanges(c, ALL_PROPERTIES);
                }
                for (Node n = elt.getFirstChild();
                     n != null;
                     n = n.getNextSibling()) {
                    propagateChanges(n, ALL_PROPERTIES);
                }
            } else {
                int count = 0;
            

                // Invalidate the relative values
                boolean fs = (fontSizeIndex == -1)
                    ? false
                    : updated[fontSizeIndex];
                boolean lh = (lineHeightIndex == -1)
                    ? false
                    : updated[lineHeightIndex];
                boolean cl = (colorIndex == -1)
                    ? false
                    : updated[colorIndex];
                
                for (int i = getNumberOfProperties() - 1; i >= 0; --i) {
                    if (!updated[i]) {
                        if (style.isComputed(i)) {
                            if (fs && style.isFontSizeRelative(i)) {
                                updated[i] = true;
                                count++;
                                clearComputedValue(style, i);
                            }
                            if (lh && style.isLineHeightRelative(i)) {
                                updated[i] = true;
                                count++;
                                clearComputedValue(style, i);
                            }
                            if (cl && style.isColorRelative(i)) {
                                updated[i] = true;
                                count++;
                                clearComputedValue(style, i);
                            }
                        }
                    } else {
                        count++;
                    }
                }

                if (count > 0) {
                    int[] props = new int[count];
                    count = 0;
                    for (int i = getNumberOfProperties() - 1; i >= 0; --i) {
                        if (updated[i]) {
                            props[count++] = i;
                        }
                    }
                    firePropertiesChangedEvent(elt, props);
                    
                    Node c = getImportedChild(elt);
                    if (c != null) {
                        propagateChanges(c, props);
                    }
                    for (Node n = elt.getFirstChild();
                         n != null;
                         n = n.getNextSibling()) {
                        propagateChanges(n, props);
                    }
                }
            }
            break;

        default:
            // Must not happen
            throw new InternalError("Invalid attrChangeType");
        }
    }

    private static void clearComputedValue(StyleMap style, int n) {
        if (style.isNullCascaded(n)) {
            style.putValue(n, null);
        } else {
            Value v = style.getValue(n);
            if (v instanceof ComputedValue) {
                ComputedValue cv = (ComputedValue)v;
                v = cv.getCascadedValue();
                style.putValue(n, v);
            }
        }
        style.putComputed(n, false);
    }

    /**
     * Invalidates all the stylable elements descendant of the given
     * node, and the node.
     */
    protected void invalidateTreeProperties(Node node) {
        if (node instanceof CSSStylableElement) {
            CSSStylableElement elt = (CSSStylableElement)node;
            StyleMap style = elt.getComputedStyleMap(null);
            if (style != null) {
                elt.setComputedStyleMap(null, null);
                firePropertiesChangedEvent(elt, ALL_PROPERTIES);
            }
        }

        Node c = getImportedChild(node);
        if (c != null) {
            propagateChanges(c, ALL_PROPERTIES);
        }
        for (Node n = node.getFirstChild();
             n != null;
             n = n.getNextSibling()) {
            invalidateTreeProperties(n);
        }
    }

    /**
     * Invalidates all the properties of the given node.
     */
    protected void invalidateProperties(Node node) {
        if (node instanceof CSSStylableElement) {
            CSSStylableElement elt = (CSSStylableElement)node;
            StyleMap style = elt.getComputedStyleMap(null);
            if (style != null) {
                elt.setComputedStyleMap(null, null);
                firePropertiesChangedEvent(elt, ALL_PROPERTIES);
            }
        }

        Node c = getImportedChild(node);
        if (c != null) {
            propagateChanges(c, ALL_PROPERTIES);
        }
        for (Node n = node.getFirstChild();
             n != null;
             n = n.getNextSibling()) {
            propagateChanges(n, ALL_PROPERTIES);
        }
    }

    /**
     * Propagates the changes that occurs on the parent of the given node.
     */
    protected void propagateChanges(Node node, int[] props) {
        if (node instanceof CSSStylableElement) {
            CSSStylableElement elt = (CSSStylableElement)node;
            StyleMap style = elt.getComputedStyleMap(null);
            if (style != null) {
                boolean[] updated =
                    styleDeclarationUpdateHandler.updatedProperties;
                for (int i = getNumberOfProperties() - 1; i >= 0; --i) {
                    updated[i] = false;
                }
                for (int i = props.length - 1; i >= 0; --i) {
                    int idx = props[i];
                    if (style.isComputed(idx) &&
                        style.isParentRelative(idx)) {
                        updated[idx] = true;
                        clearComputedValue(style, idx);
                    }
                }

                // Invalidate the relative values
                boolean fs = (fontSizeIndex == -1)
                    ? false
                    : updated[fontSizeIndex];
                boolean lh = (lineHeightIndex == -1)
                    ? false
                    : updated[lineHeightIndex];
                boolean cl = (colorIndex == -1)
                    ? false
                    : updated[colorIndex];
                int count = 0;

                for (int i = getNumberOfProperties() - 1; i >= 0; --i) {
                    if (!updated[i]) {
                        if (style.isComputed(i)) {
                            if (fs && style.isFontSizeRelative(i)) {
                                updated[i] = true;
                                count++;
                                clearComputedValue(style, i);
                            }
                            if (lh && style.isLineHeightRelative(i)) {
                                updated[i] = true;
                                count++;
                                clearComputedValue(style, i);
                            }
                            if (cl && style.isColorRelative(i)) {
                                updated[i] = true;
                                count++;
                                clearComputedValue(style, i);
                            }
                        }
                    } else {
                        count++;
                    }
                }

                if (count > 0) {
                    props = new int[count];
                    count = 0;
                    for (int i = getNumberOfProperties() - 1; i >= 0; --i) {
                        if (updated[i]) {
                            props[count++] = i;
                        }
                    }
                    firePropertiesChangedEvent(elt, props);
                } else {
                    props = null;
                }
            }
        }
        if (props != null) {
            Node c = getImportedChild(node);
            if (c != null) {
                propagateChanges(c, props);
            }
            for (Node n = node.getFirstChild();
                 n != null;
                 n = n.getNextSibling()) {
                propagateChanges(n, props);
            }
        }
    }

    /**
     * To parse a style declaration and update a StyleMap.
     */
    protected class StyleDeclarationUpdateHandler
        extends DocumentAdapter
        implements ShorthandManager.PropertyHandler {
        public StyleMap styleMap;
        public boolean[] updatedProperties =
            new boolean[getNumberOfProperties()];

        /**
         * <b>SAC</b>: Implements {@link
         * DocumentHandler#property(String,LexicalUnit,boolean)}.
         */
        public void property(String name, LexicalUnit value, boolean important)
            throws CSSException {
            int i = getPropertyIndex(name);
            if (i == -1) {
                i = getShorthandIndex(name);
                if (i == -1) {
                    // Unknown property
                    return;
                }
                shorthandManagers[i].setValues(CSSEngine.this,
                                               this,
                                               value,
                                               important);
            } else {
                if (styleMap.isImportant(i)) {
                    // The previous value is important, and a value
                    // from a style attribute cannot be important...
                    return;
                }

                if (styleMap.isComputed(i)) {
                    updatedProperties[i] = true;
                }

                Value v = valueManagers[i].createValue(value, CSSEngine.this);
                styleMap.putMask(i, (short)0);
                styleMap.putValue(i, v);
                styleMap.putOrigin(i, StyleMap.INLINE_AUTHOR_ORIGIN);
            }
        }
    }

    /**
     * Called when a non-CSS presentational hint has been updated.
     */
    protected void nonCSSPresentationalHintUpdated(CSSStylableElement elt,
                                                   StyleMap style,
                                                   String property,
                                                   MutationEvent evt) {
        int idx = getPropertyIndex(property);

        if (style.isImportant(idx)) {
            // The current value is important, and a value
            // from an XML attribute cannot be important...
            return;
        }

        switch (style.getOrigin(idx)) {
        case StyleMap.AUTHOR_ORIGIN:
        case StyleMap.INLINE_AUTHOR_ORIGIN:
            // The current value has a greater priority
            return;
        }
        
        boolean comp = style.isComputed(idx);

        switch (evt.getAttrChange()) {
        case MutationEvent.ADDITION:
        case MutationEvent.MODIFICATION:
            element = elt;
            try {
                LexicalUnit lu;
                lu = parser.parsePropertyValue(evt.getNewValue());
                ValueManager vm = valueManagers[idx];
                Value v = vm.createValue(lu, CSSEngine.this);
                style.putMask(idx, (short)0);
                style.putValue(idx, v);
                style.putOrigin(idx, StyleMap.NON_CSS_ORIGIN);
            } catch (Exception e) {
                String m = e.getMessage();
                String s =
                    Messages.formatMessage("property.syntax.error.at",
                        new Object[]
                        { documentURI.toString(),
                          property,
                          evt.getNewValue(),
                          (m == null) ? "" : m });
                throw new DOMException(DOMException.SYNTAX_ERR, s);
            }
            element = null;
            cssBaseURI = null;
            break;

        case MutationEvent.REMOVAL:
            // Invalidate all the values.
            elt.setComputedStyleMap(null, null);
            
            firePropertiesChangedEvent(elt, ALL_PROPERTIES);
                    
            Node c = getImportedChild(elt);
            if (c != null) {
                propagateChanges(c, ALL_PROPERTIES);
            }
            for (Node n = elt.getFirstChild();
                 n != null;
                 n = n.getNextSibling()) {
                propagateChanges(n, ALL_PROPERTIES);
            }
            return;
        }

        if (!comp) {
            // The previous value was not computed: nobody is
            // interested by this property modifications
            return;
        }

        boolean[] updated = styleDeclarationUpdateHandler.updatedProperties;
        for (int i = getNumberOfProperties() - 1; i >= 0; --i) {
            updated[i] = false;
        }
        updated[idx] = true;

        // Invalidate the relative values
        boolean fs = idx == fontSizeIndex;
        boolean lh = idx == lineHeightIndex;
        boolean cl = idx == colorIndex;
        int count = 0;

        for (int i = getNumberOfProperties() - 1; i >= 0; --i) {
            if (!updated[i]) {
                if (style.isComputed(i)) {
                    if (fs && style.isFontSizeRelative(i)) {
                        updated[i] = true;
                        count++;
                        clearComputedValue(style, i);
                    }
                    if (lh && style.isLineHeightRelative(i)) {
                        updated[i] = true;
                        count++;
                        clearComputedValue(style, i);
                    }
                    if (cl && style.isColorRelative(i)) {
                        updated[i] = true;
                        count++;
                        clearComputedValue(style, i);
                    }
                }
            } else {
                count++;
            }
        }

        int[] props = new int[count];
        count = 0;
        for (int i = getNumberOfProperties() - 1; i >= 0; --i) {
            if (updated[i]) {
                props[count++] = i;
            }
        }
        firePropertiesChangedEvent(elt, props);

        Node c = getImportedChild(elt);
        if (c != null) {
            propagateChanges(c, props);
        }
        for (Node n = elt.getFirstChild();
             n != null;
             n = n.getNextSibling()) {
            propagateChanges(n, props);
        }
    }

    /**
     * To handle the insertion of a CSSStyleSheetNode in the
     * associated document.
     */
    protected class DOMNodeInsertedListener implements EventListener {
        public void handleEvent(Event evt) {
            EventTarget et = evt.getTarget();
            if (et instanceof CSSStyleSheetNode) {
                styleSheetNodes = null;

                // Invalidate all the CSSStylableElements in the document.
                invalidateTreeProperties(document.getDocumentElement());
                return;
            }
            if (et instanceof CSSStylableElement) {
                // Invalidate the CSSStylableElement siblings, to
                // correctly match the adjacent selectors and
                // first-child pseudo-class.
                for (Node n = ((Node)evt.getTarget()).getNextSibling();
                     n != null;
                     n = n.getNextSibling()) {
                    invalidateProperties(n);
                }
            }
        }
    }

    /**
     * To handle the removal of a CSSStyleSheetNode from the
     * associated document.
     */
    protected class DOMNodeRemovedListener implements EventListener {
        public void handleEvent(Event evt) {
            EventTarget et = evt.getTarget();
            if (et instanceof CSSStyleSheetNode) {
                // Wait for the DOMSubtreeModified to do the invalidations
                // because at this time the node is in the tree.
                styleSheetRemoved = true;
            } else if (et instanceof CSSStylableElement) {
                // Wait for the DOMSubtreeModified to do the invalidations
                // because at this time the node is in the tree.
                removedStylableElementSibling = ((Node)et).getNextSibling();
            }
            // Clears the computed styles in the removed tree.
            disposeStyleMaps((Node)et);
        }
    }

    /**
     * To handle the removal of a CSSStyleSheetNode from the
     * associated document.
     */
    protected class DOMSubtreeModifiedListener implements EventListener {
        public void handleEvent(Event evt) {
            if (styleSheetRemoved) {
                styleSheetRemoved = false;
                styleSheetNodes = null;

                // Invalidate all the CSSStylableElements in the document.
                invalidateTreeProperties(document.getDocumentElement());
            } else if (removedStylableElementSibling != null) {
                // Invalidate the CSSStylableElement siblings, to
                // correctly match the adjacent selectors and
                // first-child pseudo-class.
                for (Node n = removedStylableElementSibling;
                     n != null;
                     n = n.getNextSibling()) {
                    invalidateProperties(n);
                }
                removedStylableElementSibling = null;
            }
        }
    }

    /**
     * To handle the modification of a CSSStyleSheetNode.
     */
    protected class DOMCharacterDataModifiedListener implements EventListener {
        public void handleEvent(Event evt) {
            Node n = (Node)evt.getTarget();
            if (n.getParentNode() instanceof CSSStyleSheetNode) {
                styleSheetNodes = null;

                // Invalidate all the CSSStylableElements in the document.
                invalidateTreeProperties(document.getDocumentElement());
            }
        }
    }

    /**
     * To handle the element attributes modification in the associated
     * document.
     */
    protected class DOMAttrModifiedListener implements EventListener {
        public void handleEvent(Event evt) {
            EventTarget et = evt.getTarget();
            if (!(et instanceof CSSStylableElement)) {
                // Not a stylable element.
                return;
            }

            CSSStylableElement elt = (CSSStylableElement)et;
            StyleMap style = elt.getComputedStyleMap(null);
            if (style == null) {
                // Nobody ever asked for the computed style of the
                // element, so it does not require an update...
                return;
            }

            MutationEvent mevt = (MutationEvent)evt;
            Node attr = mevt.getRelatedNode();
            String attrNS = attr.getNamespaceURI();
            if ((attrNS == null && styleNamespaceURI == null) ||
                (attrNS != null && attrNS.equals(styleNamespaceURI))) {
                String name = (attrNS == null)
                    ? attr.getNodeName()
                    : attr.getLocalName();
                if (name.equals(styleLocalName)) {
                    // The style declaration attribute has been modified.

                    inlineStyleAttributeUpdated(elt, style, mevt);

                    return;
                }
            }

            String name = (attrNS == null)
                ? attr.getNodeName()
                : attr.getLocalName();
                    
            if (nonCSSPresentationalHints != null) {
                if ((attrNS == null &&
                     nonCSSPresentationalHintsNamespaceURI == null) ||
                    (attrNS != null &&
                     attrNS.equals(nonCSSPresentationalHintsNamespaceURI))) {
                    if (nonCSSPresentationalHints.contains(name)) {
                        // The 'name' attribute which represents a non CSS
                        // presentational hint has been modified.

                        nonCSSPresentationalHintUpdated(elt, style, name,
                                                        mevt);
                        return;
                    }
                }
            }

            if (selectorAttributes != null &&
                selectorAttributes.contains(name)) {
                // An attribute has been modified, invalidate all the
                // properties to correctly match attribute selectors.

                elt.setComputedStyleMap(null, null);

                firePropertiesChangedEvent(elt, ALL_PROPERTIES);
                
                Node c = getImportedChild(elt);
                if (c != null) {
                    propagateChanges(c, ALL_PROPERTIES);
                }
                for (Node n = elt.getFirstChild();
                     n != null;
                     n = n.getNextSibling()) {
                    propagateChanges(n, ALL_PROPERTIES);
                }
            }
        }
    }
}
