diff -r 1bad536211fd -r cf26b8579a84 themeinstaller/source/src/com/nokia/tools/themeinstaller/cssparser/CSSDOMProcessor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/themeinstaller/source/src/com/nokia/tools/themeinstaller/cssparser/CSSDOMProcessor.java Mon Jan 18 21:36:16 2010 +0200 @@ -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; + } + + } + + }