--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/themeinstaller/source/src/com/nokia/tools/themeinstaller/cssparser/CSSDOMProcessor.java Wed Sep 01 12:32:13 2010 +0100
@@ -0,0 +1,687 @@
+/*
+* Copyright (c) 2007 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description: CSSDOMProcessor applies given CSSRules to DOM Document.
+ *
+*/
+
+
+package com.nokia.tools.themeinstaller.cssparser;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Vector;
+
+import org.w3c.css.sac.LexicalUnit;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.traversal.DocumentTraversal;
+import org.w3c.dom.traversal.NodeFilter;
+import org.w3c.dom.traversal.NodeIterator;
+
+/**
+ * CSSDOMProcessor applies given CSSRules to DOM Document.
+ */
+public class CSSDOMProcessor
+ {
+ /** The Constant STRING_PROPERTY. */
+ public static final String STRING_PROPERTY = "styleproperty";
+
+ /** The Constant STRING_PSEUDOCLASS. */
+ public static final String STRING_PSEUDOCLASS = "pseudoclass";
+
+ /** The Constant STRING_NAME. */
+ public static final String STRING_NAME = "name";
+
+ /** The Constant STRING_VALUE. */
+ public static final String STRING_VALUE = "value";
+
+ /** The Constant STRING_INHERIT. */
+ public static final String STRING_INHERIT = "inherit";
+
+ /** The Constant CHAR_COLON. */
+ private static final char CHAR_COLON = ':';
+
+ /** The Constant STRING_SEPARATOR. */
+ private static final String STRING_SEPARATOR = "|";
+
+ /** The Constant PSEUDO_TABLE. */
+ private static final String[] UNSTYLABLE_ELEMENTS_TABLE = { "styleproperty" };
+
+ /** The Element type resolver. */
+ private static ElementTypeResolver iElementTypeResolver = new ElementTypeResolver();
+
+ /** The Constant INHERITABLE_PROPERTIES. */
+ public static final String[] INHERITABLE_PROPERTIES = { "visibility",
+ "block-progression", "direction", "color", "font-family",
+ "font-size", "font-weight", "font-style", "_s60-tab-style",
+ "_s60-tab-color" };
+
+ /** The Match Maker. */
+ private CSSMatchMaker iCSSMatchMaker;
+
+ /**
+ * Instantiates a new CSSDOM processor.
+ */
+ public CSSDOMProcessor()
+ {
+ iCSSMatchMaker = new CSSMatchMaker();
+ }
+
+ /**
+ * Sort rules.
+ *
+ * @param rules The rules to be sorted by priority
+ */
+ private static void sortRules( Vector rules )
+ {
+ Collections.sort( rules );
+ }
+
+ /**
+ * Checks if is stylable element.
+ *
+ * @param aElement The element to be checked
+ *
+ * @return true, if is stylable element
+ */
+ private static boolean isStylableElement(Element aElement){
+
+ for ( int i = 0; i < UNSTYLABLE_ELEMENTS_TABLE.length; i++ )
+ {
+ if ( UNSTYLABLE_ELEMENTS_TABLE[ i ].equals( aElement.getTagName() ))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Apply style rule to DOM. Walks through the DOM tree elements and finds
+ * those that match with the CSSRule Selector. If matching element is found,
+ * Rule is applied to DOM Element.
+ *
+ * @param aDocument The DOM document to apply the rules
+ * @param aRuleList List of CSS style rules
+ */
+ public void applyRulesToDom( Document aDocument,
+ Vector aRuleList )
+ {
+ DocumentTraversal traversal = ( DocumentTraversal ) aDocument;
+
+ NodeIterator iterator = traversal.createNodeIterator( aDocument
+ .getDocumentElement(), NodeFilter.SHOW_ELEMENT, null, true );
+
+ for ( Node node = iterator.nextNode(); node != null; node = iterator
+ .nextNode() )
+ {
+ Element element = ( Element ) node;
+ if ( isStylableElement(element) )
+ {
+ Vector rulesMatch = new Vector();
+
+ for ( int i = 0; i < aRuleList.size(); i++ )
+ {
+
+ CSSRule rule = ( CSSRule ) aRuleList.get( i );
+
+ if ( iCSSMatchMaker.match( rule, element ) )
+ {
+ rulesMatch.add( rule );
+ }
+ }
+
+ sortRules( rulesMatch );
+
+ for ( int j = 0; j < rulesMatch.size(); j++ )
+ {
+ CSSRule matchingRule = ( CSSRule ) rulesMatch.elementAt( j );
+// if(matchingRule.getSelector().toString().equals("box:edit")){
+// System.out.println("Party");
+// }
+
+ applyRuleToElement( element, matchingRule );
+ }
+ }
+ }
+ applyInheritance( aDocument );
+ }
+
+
+ /**
+ * Checks if given property is inheritable property.
+ *
+ * @param aProperty the a property
+ *
+ * @return true, if is inheritable property
+ */
+ public boolean isInheritableProperty( String aProperty )
+ {
+ for ( int i = 0; i < INHERITABLE_PROPERTIES.length; i++ )
+ {
+ if ( aProperty.equals( INHERITABLE_PROPERTIES[ i ] ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if given element can inherit given property name.
+ *
+ * @param aElement the a element
+ * @param aPropertyName the a value's name
+ *
+ * @return true, if element can inherit the property
+ */
+ public boolean canInherit( Element aElement, String aPropertyName )
+ {
+ return canInherit( aElement, aPropertyName, null );
+ }
+
+ /**
+ * Checks if given element can inherit given property.
+ *
+ * If property value equals "inherit", the property
+ * is interpreted as inherited.
+ *
+ * If property is inheritable and element can inherit
+ * properties, the element can inherit the property.
+ *
+ * @param aElement the a element
+ * @param aPropertyName the a property's name
+ * @param aPropertyValue the a property's value
+ *
+ * @return true, if element can inherit the property
+ */
+ public boolean canInherit( Element aElement, String aPropertyName,
+ String aPropertyValue )
+ {
+ if ( aPropertyValue != null && aPropertyValue.equals( STRING_INHERIT ) )
+ {
+ return true;
+ }
+
+ if ( !isInheritableProperty( aPropertyName ) )
+ {
+ return false;
+ }
+
+ if ( iElementTypeResolver.canInherit( aElement.getNodeName() ) )
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Apply inheritance for stylable nodes that can inherit properties from
+ * parent elements.
+ *
+ * @param aDocument The DOM document to be modified
+ */
+ private void applyInheritance( Document aDocument )
+ {
+ DocumentTraversal traversal = ( DocumentTraversal ) aDocument;
+
+ NodeIterator iterator = traversal.createNodeIterator( aDocument
+ .getDocumentElement(), NodeFilter.SHOW_ELEMENT, null, true );
+
+ for ( Node node = iterator.nextNode(); node != null; node = iterator
+ .nextNode() )
+ {
+ Element element = ( Element ) node;
+// if(node.getLocalName().equals("box"))
+// System.out.println("Kill me!!!");
+ if ( isStylableElement( element ) )
+ {
+ if ( iElementTypeResolver.canInherit( element.getNodeName() ) )
+ {
+ applyInheritance( element );
+ }
+ }
+ }
+ }
+
+ /**
+ * Apply inheritance for DOM Element. Checks if any of the elements parents
+ * has properties that can be inherited in the given element. If such
+ * property is found, new property to the element is added, unless there
+ * already exists a property with the same name.
+ *
+ * @param aElement The element to be modified
+ */
+ private void applyInheritance( Element aElement )
+ {
+ for ( int i = 0; i < INHERITABLE_PROPERTIES.length; i++ )
+ {
+ String inheritableProperty = INHERITABLE_PROPERTIES[ i ];
+ if ( iElementTypeResolver.canInherit( aElement.getNodeName() ) )
+ {
+ if ( !hasAttribute( aElement, inheritableProperty ) )
+ {
+ if ( hasParentElementWithAttribute( aElement,
+ inheritableProperty ) )
+ {
+ if ( hasChildElementWithAttribute( aElement,
+ STRING_PROPERTY, inheritableProperty ) == null )
+ {
+ addNewChildElement( aElement, STRING_PROPERTY,
+ inheritableProperty,
+ LexicalUnit.SAC_IDENT
+ + STRING_SEPARATOR + STRING_INHERIT );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks for parent element with attribute name.
+ *
+ * @param aElement The element that's parents are checked
+ * @param aAttributeName The name of the property
+ *
+ * @return true, aElement has parent node with given attribute name
+ */
+ private static boolean hasParentElementWithAttribute( Element aElement,
+ String aAttributeName )
+ {
+ Node n = aElement.getParentNode();
+ while ( n != null )
+ {
+ if ( n.getNodeType() == Node.ELEMENT_NODE )
+ {
+ if ( hasChildElementWithAttribute( ( Element ) n,
+ STRING_PROPERTY, aAttributeName ) != null )
+ {
+ return true;
+ }
+ }
+ n = n.getParentNode();
+ }
+ return false;
+ }
+
+ /**
+ * Apply rule to DOM Element. Creates child elements for aElement with style
+ * data. If the element already has matching child element, the value of it
+ * is overwritten.
+ *
+ * @param aElement The DOM Element
+ * @param aRule CSS style rule
+ */
+ private void applyRuleToElement( Element aElement, CSSRule aRule )
+ {
+ HashMap styleMap = aRule.getStyleMap();
+
+ Iterator itKeys = styleMap.keySet().iterator();
+ while ( itKeys.hasNext() )
+ {
+ String keyName = ( String ) itKeys.next();
+
+ // Case 1 : Rule has pseudo selector, add or replace Element
+ if ( aRule.isPseudo() )
+ {
+ String selectorString = aRule.getSelector().toString();
+ String pseudoPart = selectorString.substring( selectorString
+ .lastIndexOf( CHAR_COLON ) + 1 );
+
+ Vector elementsAttributes = new Vector();
+ elementsAttributes.add( new NameValuePair(STRING_NAME, keyName) );
+ elementsAttributes.add( new NameValuePair(STRING_PSEUDOCLASS, pseudoPart) );
+
+ Element e = hasChildElementWithAttributes( aElement,
+ STRING_PROPERTY, elementsAttributes );
+ if ( e != null && hasAttribute( e, STRING_NAME, keyName ) )
+ {
+
+ CSSStyleProperty property = ( CSSStyleProperty ) styleMap
+ .get( keyName );
+ Vector values = property.getValues();
+
+ CSSPropertyValue propertyValue = ( CSSPropertyValue ) values
+ .elementAt( 0 );
+
+ // 1st value has name "value"
+ e.setAttribute( STRING_VALUE, propertyValue
+ .getValueTypeAndValue() );
+
+ // if there are more values, their name is set to "value1,
+ // value 2, ..."
+ for ( int i = 1; i < values.size(); i++ )
+ {
+
+ propertyValue = ( CSSPropertyValue ) values
+ .elementAt( i );
+
+ e.setAttribute( STRING_VALUE + i, propertyValue
+ .getValueTypeAndValue() );
+ }
+ }
+ else
+ {
+
+ CSSStyleProperty property = ( CSSStyleProperty ) styleMap
+ .get( keyName );
+ Vector values = property.getValues();
+
+ CSSPropertyValue propertyValue = ( CSSPropertyValue ) values
+ .elementAt( 0 );
+
+ Element newElement = addNewChildElement( aElement,
+ STRING_PROPERTY, keyName, propertyValue
+ .getValueTypeAndValue(), pseudoPart );
+
+ // Rest of the values are set to element we just created
+ for ( int i = 1; i < values.size(); i++ )
+ {
+ propertyValue = ( CSSPropertyValue ) values
+ .elementAt( i );
+ newElement.setAttribute( STRING_VALUE + i,
+ propertyValue.getValueTypeAndValue() );
+ }
+ }
+ }
+ // Case 2 : Rule don't have pseudo selector, add or replace Element
+ else
+ {
+ Vector elementsAttributes = new Vector();
+ elementsAttributes.add( new NameValuePair(STRING_NAME, keyName) );
+
+ Element e = hasChildElementWithAttributes( aElement,
+ STRING_PROPERTY, elementsAttributes );
+
+ if ( e != null && hasAttribute( e, STRING_NAME, keyName ) )
+ {
+ CSSStyleProperty property = ( CSSStyleProperty ) styleMap
+ .get( keyName );
+ Vector values = property.getValues();
+
+ CSSPropertyValue propertyValue = ( CSSPropertyValue ) values
+ .elementAt( 0 );
+
+ // 1st value has name "value"
+ e.setAttribute( STRING_VALUE, propertyValue
+ .getValueTypeAndValue() );
+
+ // if there are more values, their name is set to "value1,
+ // value 2, ..."
+ for ( int i = 1; i < values.size(); i++ )
+ {
+ propertyValue = ( CSSPropertyValue ) values
+ .elementAt( i );
+ e.setAttribute( STRING_VALUE + i, propertyValue
+ .getValueTypeAndValue() );
+ }
+ }
+ else
+ {
+
+ CSSStyleProperty property = ( CSSStyleProperty ) styleMap
+ .get( keyName );
+ Vector values = property.getValues();
+
+ CSSPropertyValue propertyValue = ( CSSPropertyValue ) values
+ .elementAt( 0 );
+
+ Element newElement = addNewChildElement( aElement,
+ STRING_PROPERTY, keyName, propertyValue
+ .getValueTypeAndValue() );
+
+ // Rest of the values are set to element we just created
+ for ( int i = 1; i < values.size(); i++ )
+ {
+ propertyValue = ( CSSPropertyValue ) values
+ .elementAt( i );
+ newElement.setAttribute( STRING_VALUE + i,
+ propertyValue.getValueTypeAndValue() );
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if DOM Element has a attribute with given value.
+ *
+ * @param aElement The DOM element to check
+ * @param aValue Attributes value
+ *
+ * @return true, if successful
+ */
+ private static boolean hasAttribute( Element aElement, String aValue )
+ {
+ return hasAttribute( aElement, null, aValue );
+ }
+
+
+ /**
+ * Checks if DOM Element has a attribute with given name and value.
+ * Attribute's name can be null, when only the attributes value is checked
+ *
+ * @param aElement The DOM element to check
+ * @param aName Attributes name
+ * @param aValue Attributes value
+ *
+ * @return true, if successful
+ */
+ private static boolean hasAttribute( Element aElement, String aName,
+ String aValue )
+ {
+ if ( aElement == null )
+ {
+ return false;
+ }
+ if ( aElement.hasAttributes() )
+ {
+ NamedNodeMap nnm = aElement.getAttributes();
+ for ( int i = 0; i < nnm.getLength(); i++ )
+ {
+ Node att = nnm.item( i );
+ if ( aName != null )
+ {
+ if ( att.getNodeValue().equals( aValue )
+ && att.getNodeName().equals( aName ) )
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if ( att.getNodeValue().equals( aValue ) )
+ {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks for elements attributes.
+ *
+ * @param aElement The element to be checked
+ * @param aAttributes Attributes in name-value pairs
+ *
+ * @return true, if element has attributes given in aAttributes
+ */
+ private static boolean hasAttributes( Element aElement, Vector aAttributes )
+ {
+ if ( aElement == null )
+ {
+ return false;
+ }
+
+ if ( aElement.hasAttributes() )
+ {
+
+ for ( int i = 0; i < aAttributes.size(); i++ )
+ {
+
+ NameValuePair nameValuePair = ( NameValuePair ) aAttributes
+ .get( i );
+
+ String name = nameValuePair.getName();
+ String value = nameValuePair.getValue();
+
+ if ( !hasAttribute( aElement, name, value ) )
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks for child element with attribute's name.
+ *
+ * @param aParent The parent element
+ * @param aElementName The element tag name to be matched with child nodes
+ * name
+ * @param aName Attributes name to be matched
+ *
+ * @return Child element with attribute, null if none
+ */
+ private static Element hasChildElementWithAttribute( Element aParent,
+ String aElementName, String aName )
+ {
+ for ( Node n = aParent.getFirstChild(); n != null; n = n
+ .getNextSibling() )
+ {
+ if ( n.getNodeType() == Node.ELEMENT_NODE )
+ {
+ if ( hasAttribute( ( Element ) n, aName )
+ && n.getNodeName().equals( aElementName ) )
+ {
+ return ( Element ) n;
+ }
+ }
+ }
+ return null;
+ }
+
+
+
+ /**
+ * Checks for child element with attributes.
+ *
+ * @param aParent The parent element
+ * @param aElementName The element tag name to be matched with child nodes
+ * name
+ * @param aNameValuePairs The Name-Value pairs for attributes that need to be
+ * found
+ *
+ * @return Child element with attributes, null if none
+ */
+ private static Element hasChildElementWithAttributes( Element aParent,
+ String aElementName, Vector aNameValuePairs )
+ {
+ for ( Node n = aParent.getFirstChild(); n != null; n = n
+ .getNextSibling() )
+ {
+ if ( n.getNodeType() == Node.ELEMENT_NODE )
+ {
+ if ( n.getNodeName().equals( aElementName )
+ && hasAttributes( ( Element ) n, aNameValuePairs ) )
+ {
+ return ( Element ) n;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds the new node to DOM.
+ *
+ * @param aParent The parent of the new node
+ * @param aElementName Element name for new DOM node
+ * @param aKeyName Attribute name for new DOM node
+ * @param aKeyValue Attribute value for new DOM node
+ */
+ private Element addNewChildElement( Element aParent,
+ String aElementName, String aKeyName, String aKeyValue )
+ {
+ return addNewChildElement( aParent, aElementName, aKeyName, aKeyValue, null );
+ }
+
+ /**
+ * Adds the new node to DOM.
+ *
+ * @param aParent The parent of the new node
+ * @param aElementName Element name for new DOM node
+ * @param aKeyName Attribute name for new DOM node
+ * @param aKeyValue Attribute value for new DOM node
+ * @param aPseudo Pseudo attribute value for the DOM node
+ */
+ private Element addNewChildElement( Element aParent, String aElementName,
+ String aKeyName, String aKeyValue, String aPseudo )
+ {
+ Document document = aParent.getOwnerDocument();
+
+ String namespaceUri = aParent.getNamespaceURI();
+
+ Element newElement = document.createElementNS( namespaceUri,
+ aElementName );
+
+ if ( aPseudo != null )
+ {
+ newElement.setAttribute( STRING_PSEUDOCLASS, aPseudo );
+ }
+ newElement.setAttribute( STRING_NAME, aKeyName );
+ newElement.setAttribute( STRING_VALUE, aKeyValue );
+
+ aParent.appendChild( newElement );
+ return newElement;
+ }
+
+
+ /**
+ * Class for temporarily store Node's attribute name and value.
+ */
+ private class NameValuePair
+ {
+ private String iName;
+
+ private String iValue;
+
+ NameValuePair( String aName, String aValue )
+ {
+ iName = aName;
+ iValue = aValue;
+ }
+
+ public String getName()
+ {
+ return iName;
+ }
+
+ public String getValue()
+ {
+ return iValue;
+ }
+
+ }
+
+ }