themeinstaller/source/src/com/nokia/tools/themeinstaller/cssparser/CSSHandler.java
author Pat Downey <patd@symbian.org>
Wed, 01 Sep 2010 12:32:13 +0100
branchRCL_3
changeset 18 04b7640f6fb5
parent 0 05da4621cfb2
permissions -rw-r--r--
Revert incorrect RCL_3 drop: Revision: 201032 Kit: 201035

/*
* 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:  This class gets notified during the CSS file parsing
 *
*/


package com.nokia.tools.themeinstaller.cssparser;

import java.awt.Color;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Vector;

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.Parser;
import org.w3c.css.sac.SACMediaList;
import org.w3c.css.sac.SelectorList;
import org.w3c.css.sac.helpers.ParserFactory;
import org.w3c.dom.Document;

import com.nokia.tools.themeinstaller.logger.LogWriter;

/**
 * The Class CSSHandler. Informs application of basic parsing events. This
 * should be registered with CSS Parser with setDocumentHandler method.
 *
 * Handler gets notified during the CSS parsing:
 * 1. startSelector(), when new selector is noticed
 * 2. property(), when new property is noticed
 * 3. lexicalValue() is used to calculate the values of the parsed property
 *
 * If you are using CSS import with paths relative to main CSS, you need to
 * set the path of main CSS using setImportDirectory( String aImportDirectory )
 */

public class CSSHandler implements DocumentHandler
    {

    /** The style map for style property. */
    private HashMap iStyleMap;

    /** List of parsed rules. */
    private Vector iRuleList;

    /** The DOM document. */
    private Document iDocument;

    /** Color resolver for resolving colors in string form */
    private ColorResolver iColorResolver;

    /** The main CSS directory for finding imported CSS File's relative path. */
    private String iImportDirectory;

    /** The Constant FUNCTION_HSL. */
    public static final String FUNCTION_HSL = "hsl";

    /** The Constant COMMA. */
    public static final String COMMA = ",";

    /** The Constant CHAR_APOSTROPHE. */
    private static final char CHAR_APOSTROPHE = '"';

    /** The Constant CHAR_SPACE. */
    private static final char CHAR_SPACE = ' ';

    /** The Constant CHAR_COLON. */
    private static final char CHAR_COLON = ':';

    /** The Constant SEPARATOR. */
    public static final String SEPARATOR = "|";

    /** The Constant FORWARD_SLASH. */
    public static final String FORWARD_SLASH = "/";

    /** The Constant BACKWARD_SLASH. */
    public static final String BACKWARD_SLASH = "\\";

    /** The CSS to DOM Processor for applying changes to DOM. */
    private CSSDOMProcessor iCSSDOMProcessor;

    /**
     * Instantiates a new CSS handler.
     */
    public CSSHandler()
        {
        super();
        iCSSDOMProcessor = new CSSDOMProcessor();
        iStyleMap = new HashMap();
        iRuleList = new Vector();
        iColorResolver = new ColorResolver();
        }

    /**
     * Instantiates a new CSS handler. This one is used for handling imported
     * CSS files
     *
     * @param aHandler The handler to retrieve previous rule list and DOM document
     *            from
     */
    private CSSHandler( CSSHandler aHandler )
        {
        super();
        iCSSDOMProcessor = new CSSDOMProcessor();
        iStyleMap = new HashMap();

        iRuleList = aHandler.iRuleList;
        iDocument = aHandler.iDocument;
        iColorResolver = new ColorResolver();
        }

    /**
     * Sets the document.
     *
     * @param aDocument The new document
     */
    public void setDocument( Document aDocument )
        {
        iDocument = aDocument;
        }

    /**
     * Gets the document.
     *
     * @return the document
     */
    public Document getDocument()
        {
        return iDocument;
        }

    /**
     * Sets the directory of main CSS.
     *
     * @param aImportDirectory The directory of main CSS
     */
    public void setImportDirectory( String aImportDirectory )
        {
        iImportDirectory = aImportDirectory;
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#startDocument(org.w3c.css.sac.InputSource)
     */
    public void startDocument( InputSource arg0 ) throws CSSException
        {
        iRuleList = new Vector();
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#startSelector(org.w3c.css.sac.SelectorList)
     */
    public void startSelector( SelectorList aSelectorList ) throws CSSException
        {
        iStyleMap = new HashMap();
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#endSelector(org.w3c.css.sac.SelectorList)
     */
    public void endSelector( SelectorList aSelectors ) throws CSSException
        {
        if ( iStyleMap.size() != 0 )
            {
            for ( int i = 0; i < aSelectors.getLength(); i++ )
                {
//            	System.out.println("CSSHandler Selectors --> "+aSelectors.item(i));
                CSSRule rule = new CSSRule( aSelectors.item( i ), iStyleMap );
                iRuleList.add( rule );
                }
            }
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#property(java.lang.String,
     *      org.w3c.css.sac.LexicalUnit, boolean)
     */
    public void property( String aName, LexicalUnit aLexicalUnit,
            boolean aImportant ) throws CSSException
        {
        Vector propertyValues = new Vector();
        lexicalValue( aLexicalUnit, propertyValues );
        iStyleMap.put( aName, new CSSStyleProperty( propertyValues, aImportant ) );
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#endDocument(org.w3c.css.sac.InputSource)
     */
    public void endDocument( InputSource aInputSource ) throws CSSException
        {
        if ( iDocument == null )
            {
            throw new IllegalStateException( "CSSHandler has null DOM Document" );
            }
        
        iCSSDOMProcessor.applyRulesToDom( iDocument, iRuleList );
        }


    /**
     * Finds out the property values and adds them into a Vector
     * aPropertyValues. Property types that are taken into account are
     * from Symbian side: ...\Xuikon\Dom\src\xndompropertyvalue.cpp
     *
     * @param aLexicalUnit Value
     * @param aPropertyValues Vector for Property values
     */
    public void lexicalValue( LexicalUnit aLexicalUnit,
            Vector aPropertyValues )
        {
        while ( aLexicalUnit != null )
            {
            switch ( aLexicalUnit.getLexicalUnitType() )
                {
                case LexicalUnit.SAC_OPERATOR_COMMA:
                    break;
                // Allowed cases for integer value
                case LexicalUnit.SAC_INTEGER:
                    aPropertyValues
                            .add( new CSSPropertyValue(
                                    LexicalUnit.SAC_INTEGER, Integer
                                            .toString( aLexicalUnit
                                                    .getIntegerValue() ) ) );
                    break;
                // Allowed cases for real value
                // Fallthrough
                case LexicalUnit.SAC_REAL:
                case LexicalUnit.SAC_DIMENSION:
                case LexicalUnit.SAC_EM:
                case LexicalUnit.SAC_EX:
                case LexicalUnit.SAC_INCH:
                case LexicalUnit.SAC_PIXEL:
                case LexicalUnit.SAC_CENTIMETER:
                case LexicalUnit.SAC_MILLIMETER:
                case LexicalUnit.SAC_POINT:
                case LexicalUnit.SAC_PICA:
                case LexicalUnit.SAC_PERCENTAGE:
                case LexicalUnit.SAC_DEGREE:
                case LexicalUnit.SAC_GRADIAN:
                case LexicalUnit.SAC_RADIAN:
                case LexicalUnit.SAC_MILLISECOND:
                case LexicalUnit.SAC_SECOND:
                case LexicalUnit.SAC_HERTZ:
                case LexicalUnit.SAC_KILOHERTZ:
                    aPropertyValues.add( new CSSPropertyValue( aLexicalUnit
                            .getLexicalUnitType(), Float.toString( aLexicalUnit
                            .getFloatValue() ) ) );
                    break;
                // RGB Values
                case LexicalUnit.SAC_RGBCOLOR:
                    //Using StringBuffer to store values of RGB property
                    //in order to handle values (R,G and B) as one value
                    StringBuffer sb = new StringBuffer();
                    colorValue( aLexicalUnit.getParameters(), sb );
                    aPropertyValues.add( new CSSPropertyValue( aLexicalUnit
                            .getLexicalUnitType(), sb.toString() ) );
                    break;
                // Allowed cases for string values
                // Fallthrough
                case LexicalUnit.SAC_URI:
                case LexicalUnit.SAC_IDENT:
                case LexicalUnit.SAC_STRING_VALUE:
                case LexicalUnit.SAC_ATTR:
                    // Color value as string
                    if ( iColorResolver.get( aLexicalUnit.getStringValue() ) != null )
                        {
                        Color color = ( Color ) iColorResolver.get( aLexicalUnit
                                .getStringValue() );
                        aPropertyValues.add( new CSSPropertyValue(
                                LexicalUnit.SAC_RGBCOLOR,
                                LexicalUnit.SAC_INTEGER + SEPARATOR
                                        + color.getRed() + COMMA
                                        + LexicalUnit.SAC_INTEGER + SEPARATOR
                                        + color.getGreen() + COMMA
                                        + LexicalUnit.SAC_INTEGER + SEPARATOR
                                        + color.getBlue() ) );
                        }
                    // Space
                    else if ( aLexicalUnit.getStringValue()
                            .indexOf( CHAR_SPACE ) != -1 )
                        {
                        aPropertyValues.add( new CSSPropertyValue( aLexicalUnit
                                .getLexicalUnitType(), CHAR_APOSTROPHE
                                + aLexicalUnit.getStringValue()
                                + CHAR_APOSTROPHE ) );
                        }
                    // Text
                    else
                        {
                        aPropertyValues.add( new CSSPropertyValue( aLexicalUnit
                                .getLexicalUnitType(), aLexicalUnit
                                .getStringValue() ) );
                        }
                    break;
                // In case value is set as "inherit" already in CSS -file
                // This sets it to DOM as Ident value that is "inherit"
                case LexicalUnit.SAC_INHERIT:
                    aPropertyValues.add( new CSSPropertyValue(
                            LexicalUnit.SAC_IDENT,
                            CSSDOMProcessor.STRING_INHERIT ) );
                    break;
                case LexicalUnit.SAC_FUNCTION:
                    String functionName = aLexicalUnit.getFunctionName();
                    if ( functionName.equals( FUNCTION_HSL ) )
                        {
                        Color color = colorFromHsl( aLexicalUnit
                                .getParameters() );
                        aPropertyValues.add( new CSSPropertyValue(
                                LexicalUnit.SAC_RGBCOLOR,
                                LexicalUnit.SAC_INTEGER + SEPARATOR
                                        + color.getRed() + COMMA
                                        + LexicalUnit.SAC_INTEGER + SEPARATOR
                                        + color.getGreen() + COMMA
                                        + LexicalUnit.SAC_INTEGER + SEPARATOR
                                        + color.getBlue() ) );

                        }
                    else
                        {
                        throw new IllegalStateException(
                                "Unknown CSS Function : " + functionName );
                        }
                    break;
                default:
                    break;
                }
            aLexicalUnit = aLexicalUnit.getNextLexicalUnit();
            }
        }

    /**
     * Parses HSL Function using ColorResolver.
     *
     * @param aLexicalUnit Parameters containing color values
     *
     * @return the color
     */
    private Color colorFromHsl( LexicalUnit aLexicalUnit )
        {
        float h = 0;
        float s = 0;
        float l = 0;
        try
            {
            h = ( float ) aLexicalUnit.getIntegerValue();
            aLexicalUnit = aLexicalUnit.getNextLexicalUnit()
                    .getNextLexicalUnit();
            s = aLexicalUnit.getFloatValue();
            aLexicalUnit = aLexicalUnit.getNextLexicalUnit()
                    .getNextLexicalUnit();
            l = aLexicalUnit.getFloatValue();
            }
        catch ( RuntimeException e )
            {
            throw new IllegalStateException( "Illegal HSL Color values" );
            }
        if ( aLexicalUnit.getNextLexicalUnit() != null )
            {
            throw new IllegalStateException( "Too many parameters in HSL color" );
            }
        return iColorResolver.hslToRgb( h, s, l );
        }

    /**
     * Finds out the property values for RGB color and adds them into a
     * StringBuffer aProperty.
     *
     * @param aLexicalUnit Parameters containing color values
     * @param aProperty StringBuffer for storing property information
     */
    public void colorValue( LexicalUnit aLexicalUnit, StringBuffer aProperty )
        {
        while ( aLexicalUnit != null )
            {
            switch ( aLexicalUnit.getLexicalUnitType() )
                {
                // Operator ","
                case LexicalUnit.SAC_OPERATOR_COMMA:
                    aProperty.append( COMMA );
                    break;
                case LexicalUnit.SAC_INTEGER:
                    aProperty.append( aLexicalUnit.getLexicalUnitType() );
                    aProperty.append( SEPARATOR );
                    aProperty.append( aLexicalUnit.getIntegerValue() );
                    break;
                case LexicalUnit.SAC_PERCENTAGE:
                    aProperty.append( LexicalUnit.SAC_INTEGER );
                    aProperty.append( SEPARATOR );
                    aProperty.append( iColorResolver
                            .getColorValueFromPercentage( aLexicalUnit
                                    .getFloatValue() ) );
                    break;
                default:
                    break;
                }
            aLexicalUnit = aLexicalUnit.getNextLexicalUnit();
            }
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#startFontFace ()
     */
    public void startFontFace() throws CSSException
        {
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#startPage(java.lang.String,
     *      java.lang.String)
     */
    public void startPage( String arg0, String arg1 ) throws CSSException
        {
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#endFontFace ()
     */
    public void endFontFace() throws CSSException
        {
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#endPage(java.lang.String,
     *      java.lang.String)
     */
    public void endPage( String arg0, String arg1 ) throws CSSException
        {
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#comment (java.lang.String)
     */
    public void comment( String arg0 ) throws CSSException
        {
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#endMedia (org.w3c.css.sac.SACMediaList)
     */
    public void endMedia( SACMediaList arg0 ) throws CSSException
        {
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#ignorableAtRule (java.lang.String)
     */
    public void ignorableAtRule( String arg0 ) throws CSSException
        {
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#importStyle (java.lang.String,
     *      org.w3c.css.sac.SACMediaList, java.lang.String)
     */
    public void importStyle( String aCSSFileName, SACMediaList aMedia,
            String aNameSpaceUri ) throws CSSException
        {
        try
            {
            CSSHandler handler = new CSSHandler( this );
            ParserFactory factory = new ParserFactory();

            Parser parser = factory.makeParser();
            parser.setDocumentHandler( handler );
            Reader r = null;

            File cssImport = new File( aCSSFileName );
            if ( isAbsolutePath( aCSSFileName ) && cssImport.exists() )
                {
                // The imported CSS file is given with absolute path
                r = new FileReader( aCSSFileName );
                LogWriter.getInstance().logInfo(
                        "Imported CSS : " + cssImport.getAbsolutePath() );
                }
            else if ( !isAbsolutePath( aCSSFileName )
                    && iImportDirectory != null )
                {
                // If the given imported CSS File is not found,
                // try to locate it using path relative to main CSS
                File relativeCSS = new File( iImportDirectory + File.separator
                        + aCSSFileName );
                r = new FileReader( relativeCSS );
                LogWriter.getInstance().logInfo(
                        "Imported CSS : " + relativeCSS.getAbsolutePath() );
                }
            else
                {
                throw new IllegalStateException(
                        "Can't resolve imported CSS File: " + aCSSFileName );
                }

            InputSource is = new InputSource( r );
            parser.parseStyleSheet( is );
            }
        catch ( Exception e )
            {
            throw new CSSException( e.getMessage() );
            }
        }

    /**
     * Checks if path is absolute path.
     * Path is absolute if it starts with '/', '\'
     * or is of form [drive]:[path]
     * @param aFilePath the a file path
     *
     * @return true, if is absolute path
     */
    private boolean isAbsolutePath( String aFilePath )
        {
        if ( aFilePath.startsWith( FORWARD_SLASH ) )
            {
            return true;
            }
        if ( aFilePath.startsWith( BACKWARD_SLASH ) )
            {
            return true;
            }
        if ( aFilePath.length() > 1 && aFilePath.charAt( 1 ) == CHAR_COLON )
            {
            return true;
            }
        return false;
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#namespaceDeclaration(java.lang.String,
     *      java.lang.String)
     */
    public void namespaceDeclaration( String arg0, String arg1 )
            throws CSSException
        {
        }

    /*
     * (non-Javadoc)
     *
     * @see org.w3c.css.sac.DocumentHandler#startMedia(org.w3c.css.sac.SACMediaList)
     */
    public void startMedia( SACMediaList arg0 ) throws CSSException
        {
        }

    }