diff -r fe49e33862e2 -r 04b7640f6fb5 themeinstaller/source/src/com/nokia/tools/themeinstaller/odtconverter/DOMExternalizer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/themeinstaller/source/src/com/nokia/tools/themeinstaller/odtconverter/DOMExternalizer.java Wed Sep 01 12:32:13 2010 +0100 @@ -0,0 +1,718 @@ +/* +* 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: Externalizes a DOM Document to a byte array. + * +*/ + + +package com.nokia.tools.themeinstaller.odtconverter; + +import java.awt.Color; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import com.nokia.tools.themeinstaller.cssparser.CSSDOMProcessor; +import com.nokia.tools.themeinstaller.cssparser.CSSHandler; +import com.nokia.tools.themeinstaller.cssparser.PseudoClassResolver; + +/** + * Externalizes DOM Document to a byte array. + */ +public class DOMExternalizer + { + + // CONSTANTS + // Text elements according to CXnDomVisitor::KXnElementVisitorTable and + // CXnODTParser + private static final String TEXT_ELEMENT = "text"; + private static final String DESC_ELEMENT = "desc"; + private static final String MARQUEE_ELEMENT = "marquee"; + private static final String OBJECT_ELEMENT = "object"; + private static final String NEWSTICKER_ELEMENT = "newsticker"; + + // Attribute name if node is refnode + private static final String REF_NODE = "ref"; + + // List types + private static final int UNKNOWN = -1; + private static final int NODE = 0; + private static final int ATTRIBUTE = 1; + private static final int PROPERTY = 2; + + // The Constant COLOR_TABLE_SIZE + private static final int COLOR_TABLE_SIZE = 3; + + // Pseudo class resolver + private PseudoClassResolver iPseudoResolver; + + // The value type resolver + private ValueTypeResolver iValueTypeResolver; + + // String pool + private StringPool iStringPool; + + // DOM Document + private Document iDocument; + + // Byte output stream for delivering the externalized data + private ByteArrayOutputStream iBaos; + + // Data output stream for writing to the baos + private ODTDataOutputStream iODTDos; + + // Node id and counter + private int iCurrentNodeId = 0; + + /** CSSDOMProsessor for checking property's inheritance value. */ + private CSSDOMProcessor iCSSDOMProsessor; + + /** + * Constructor. + * @param aDocument DOM Document to externalize + */ + public DOMExternalizer( Document aDocument ) + { + iPseudoResolver = new PseudoClassResolver(); + iValueTypeResolver = new ValueTypeResolver(); + iCSSDOMProsessor = new CSSDOMProcessor(); + iStringPool = new StringPool(); + iDocument = aDocument; + iBaos = new ByteArrayOutputStream(); + iODTDos = new ODTDataOutputStream( iBaos ); + } + + /* (non-Javadoc) + * @see java.lang.Object#finalize() + */ + protected void finalize() throws Throwable + { + iODTDos.close(); + iBaos.close(); + } + + /** + * Externalizes the DOM document and string pool to a byte array. + * @return Byte array containing the document + * @throws IOException if writing to a stream fails + * @throws ODTException + */ + public byte[] getByteArray() throws IOException, ODTException + { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] resultArray = null; + + try + { + // Externalize the DOM + doExternalize(); + + // Externalize the string pool + result.write( iStringPool.toByteArray() ); + + // Write the dom contents to the result baos + result.write( iBaos.toByteArray() ); + + resultArray = result.toByteArray(); + } + finally + { + if( result != null ) + { + result.close(); + } + } + + return resultArray; + } + + /** + * Do the externalization process. + * @throws IOException if data output stream can not be written + * @throws ODTException if there is an error with property value externalization + */ + private void doExternalize() throws IOException, ODTException + { + Node rootElement = iDocument.getDocumentElement(); + + if( rootElement != null ) + { + // Root node exists + iODTDos.writeBoolean( true ); + externalizeNode( ( Node )rootElement, true ); + } + else + { + // No root node + iODTDos.writeBoolean( false ); + } + } + + /** + * Externalizes a node in the DOM tree. + * @param aNode Node to externalize + * @param aRootNode true, if this is the root node of the tree + * @throws IOException if data output stream can not be written + * @throws ODTException if there is an error with property value externalization + */ + private void externalizeNode( Node aNode, boolean aRootNode ) + throws IOException, ODTException + { + // Write name + String name = aNode.getNodeName(); + int nameRef = iStringPool.addString( name ); + iODTDos.writeInt16( nameRef ); + + // Write name space + String ns = aNode.getNamespaceURI(); + int nsRef = iStringPool.addString( ns ); + iODTDos.writeInt16( nsRef ); + + // Check and write refnode boolean + iODTDos.writeBoolean( checkRefNode( aNode ) ); + + boolean textNodeFound = false; + + // Write parsed character data + StringBuffer pcData = new StringBuffer(); + if( TEXT_ELEMENT.equals( name ) || + DESC_ELEMENT.equals( name ) || + MARQUEE_ELEMENT.equals( name ) || + OBJECT_ELEMENT.equals( name ) || + NEWSTICKER_ELEMENT.equals( name ) ) + { + NodeList list = aNode.getChildNodes(); + + for( int i = 0; i < list.getLength() ; i++ ) + { + Node nod = list.item( i ); + if( nod.getNodeType() == Node.TEXT_NODE ) + { + textNodeFound = true; + String textNodeValue = nod.getNodeValue(); + + pcData.append( textNodeValue ); + } + } + } + + // Write data length and the data + iODTDos.writeBoolean( textNodeFound ); + if( textNodeFound ) + { + // Write text length + iODTDos.writeInt16( pcData.length() ); + + // Write text node value + iODTDos.writeString8( pcData.toString() ); + } + // Update the counter and write node id + iODTDos.writeInt32( iCurrentNodeId++ ); + + // Externalize the child list + externalizeNodeList( aNode ); + + // Externalize the attribute list + externalizeAttributeList( aNode ); + + // Externalize the property list + externalizePropertyList( aNode ); + } + + /** + * Checks if node is refnode. + * Node is refnode if some of node's attributes name is "ref". + * @param aNode Node containing the attributes + * @return boolean true if node is refnode, otherwise false + */ + private boolean checkRefNode( Node aNode ) + { + boolean refNode = false; + NamedNodeMap list = aNode.getAttributes(); + for ( int i = 0; i < list.getLength(); i++ ) + { + Node node = list.item( i ); + if ( nodeType( node ) == ATTRIBUTE ) + { + if( node.getNodeName().equals( REF_NODE ) ) + { + refNode = true; + } + } + } + return refNode; + } + + /** + * Externalizes attributes of a node. + * @param aNode Node containing the attributes + * @throws IOException if writing to a stream fails + * @throws ODTException + */ + private void externalizeAttributeNode( Node aNode ) + throws IOException, ODTException + { + // Write name + String name = aNode.getNodeName(); + int nameRef = iStringPool.addString( name ); + iODTDos.writeInt16( nameRef ); + + // Write value + String value = aNode.getNodeValue(); + int valueRef = iStringPool.addString( value ); + iODTDos.writeInt16( valueRef ); + } + + /** + * Externalizes a property node. + * + * @param aNode Node containing the properties + * @param aParentNode Node's parent node + * + * @throws IOException if data output stream can not be written + * @throws ODTException if there is an error with property + * value externalization + */ + private void externalizePropertyNode( Node aParentNode, Node aNode ) + throws IOException, ODTException + { + // Get property node attributes: property name, values and pseudo class + NamedNodeMap list = aNode.getAttributes(); + + // Property value list + boolean isInherited = externalizePropertyValueList( aParentNode, list ); + + iODTDos.writeBoolean( isInherited ); + + // Resolve pseudo class + int pseudoClass = PseudoClassResolver.NONE; + + int count = list.getLength(); + for( int i = 0; i < count; i++ ) + { + // Browse through all attributes of the style property element and + // seek for a pseudo class + Node item = list.item( i ); + if( item.getNodeName().equals( CSSDOMProcessor.STRING_PSEUDOCLASS ) ) + { + pseudoClass = iPseudoResolver.getKey( item.getNodeValue() ); + if( pseudoClass == UNKNOWN ) + { + throw new ODTException( "Error externalizing ODT/styles: " + + "unknown pseudo class" ); + } + break; + } + } + + // Pseudo class -> int8 + iODTDos.writeByte( pseudoClass ); + } + + /** + * Externalize a list of nodes. + * @param aParentNode Parent node + * @throws IOException if data output stream can not be written + * @throws ODTException if there is an error with property value externalization + */ + private void externalizeNodeList( Node aParentNode ) throws IOException, ODTException + { + // Write list type + iODTDos.writeByte( NODE ); + + NodeList list = aParentNode.getChildNodes(); + + // Count element nodes + int count = countNodes( list ); + + // Write element node count + iODTDos.writeInt32( count ); + + for ( int i = 0; i < list.getLength(); i++ ) + { + Node node = list.item( i ); + + if ( nodeType( node ) == NODE ) + { + externalizeNode( node, false ); + } + } + } + + /** + * Externalize a list of attributes. + * @param aParentNode Node containing the attributes. + * @throws IOException if writing to a stream fails + * @throws ODTException + */ + private void externalizeAttributeList( Node aParentNode ) + throws IOException, ODTException + { + // Write list type + iODTDos.writeByte( ATTRIBUTE ); + + NamedNodeMap list = aParentNode.getAttributes(); + + // Count element nodes + int count = countAttributes( list ); + + // Write element node count + iODTDos.writeInt32( count ); + + for ( int i = 0; i < list.getLength(); i++ ) + { + Node node = list.item( i ); + + if ( nodeType( node ) == ATTRIBUTE ) + { + externalizeAttributeNode( node ); + } + } + } + + /** + * Externalize a list of properties. + * @param aParentNode Node containing the properties + * @throws IOException if data output stream can not be written + * @throws ODTException if there is an error with property value externalization + */ + private void externalizePropertyList( Node aParentNode ) throws IOException, ODTException + { + // Write list type + iODTDos.writeByte( PROPERTY ); + + NodeList list = aParentNode.getChildNodes(); + + // Count element nodes + int count = countProperties( list ); + + // Write element node count + iODTDos.writeInt32( count ); + + for ( int i = 0; i < list.getLength(); i++ ) + { + Node node = list.item( i ); + + if ( nodeType( node ) == PROPERTY ) + { + externalizePropertyNode( aParentNode, node ); + } + } + } + + /** + * Parses a Color from string. + * + * @param aColor Color values in String made by CSSHandler + * + * @return The color + * + * @throws ODTException the ODT exception + */ + private Color parseColorFromString( String aColor ) throws ODTException + { + String sx[] = aColor.split( CSSHandler.COMMA ); + int ix[] = new int[ sx.length ]; + + // LexicalUnit.SAC_RGBCOLOR knows color values with format + // rgb(0, 0, 0) and #000. That is, with three parameters + if ( sx.length != COLOR_TABLE_SIZE ) + { + throw new ODTException( "Error in DOM/style data externalization: " + + "RGB Values: parameter amount" ); + } + + for ( int i = 0; i < sx.length; i++ ) + { + if ( sx[ i ].contains( CSSHandler.SEPARATOR ) ) + { + // Check if single color's value is integer + Short valueType = iValueTypeResolver.getValue( Short + .valueOf( sx[ i ].substring( 0, sx[ i ] + .indexOf( CSSHandler.SEPARATOR ) ) ) ); + + if ( valueType.intValue() != ValueTypeResolver.E_NUMBER ) + { + throw new ODTException( + "Error in DOM/style data externalization: " + + "RGB Values: R, G or B not interger" ); + } + String redGreenBlue = sx[ i ].substring( sx[ i ] + .indexOf( CSSHandler.SEPARATOR ) + 1, sx[ i ] + .length() ); + Integer rgbValue = Integer.valueOf( redGreenBlue ); + ix[ i ] = rgbValue.intValue(); + } + else + { + throw new ODTException( + "Error in DOM/style data externalization: " + + "RGB Values: can't resolve value type" ); + } + } + return new Color( ix[ 0 ], ix[ 1 ], ix[ 2 ] ); + } + + /** + * Parses the Value Type from Value. + * + * @param aAttrValue The attribute value + * + * @return Parsed value as a string + * + * @throws IOException Signals that an I/O exception has occurred. + * @throws ODTException + */ + private String parseAttrValue( String aAttrValue, PropertyValue aPropValue ) + throws IOException, ODTException + { + if ( aAttrValue.length() > 0 ) + { + if ( aAttrValue.contains( "|" ) ) + { + String type = aAttrValue + .substring( 0, aAttrValue.indexOf( '|' ) ); + String value = aAttrValue.substring( + aAttrValue.indexOf( '|' ) + 1, aAttrValue.length() ); + + Short lexicalUnit = Short.valueOf( type ); + Short primitiveValueType = + iValueTypeResolver.getValue( lexicalUnit ); + + switch ( primitiveValueType.intValue() ) + { + case ValueTypeResolver.E_NUMBER: + double doubleValueInteger = Double.valueOf( value ).doubleValue(); + aPropValue.setRealValue( doubleValueInteger, + primitiveValueType.shortValue() ); + break; + case ValueTypeResolver.E_UNIT_VALUE: + case ValueTypeResolver.E_PERCENTAGE: + case ValueTypeResolver.E_EMS: + case ValueTypeResolver.E_EXS: + case ValueTypeResolver.E_PX: + case ValueTypeResolver.E_CM: + case ValueTypeResolver.E_MM: + case ValueTypeResolver.E_IN: + case ValueTypeResolver.E_PT: + case ValueTypeResolver.E_PC: + case ValueTypeResolver.E_DEG: + case ValueTypeResolver.E_RAD: + case ValueTypeResolver.E_GRAD: + case ValueTypeResolver.E_MS: + case ValueTypeResolver.E_S: + case ValueTypeResolver.E_HZ: + case ValueTypeResolver.E_KHZ: + double doubleValuePercentage = Double.valueOf( value ).doubleValue(); + aPropValue.setRealValue( doubleValuePercentage, + primitiveValueType.shortValue() ); + break; + case ValueTypeResolver.E_IDENT: + case ValueTypeResolver.E_STRING: + case ValueTypeResolver.E_URI: + case ValueTypeResolver.E_ATTR: + aPropValue.setString( new String( value ), + primitiveValueType.shortValue() ); + break; + case ValueTypeResolver.E_RGB_COLOR: + Color color = parseColorFromString(value); + aPropValue.setRgbValue( color ); + break; + default: + break; + } + + return value; + } + return aAttrValue; + } + return aAttrValue; + } + + /** + * Externalize a list of property values. + * + * @param aNode the a node + * @param aAttrList the a attr list + * + * @return true, if property is inherited + * + * @throws IOException if data output stream can not be written + * @throws ODTException if there is an error with externalizing property values + */ + private boolean externalizePropertyValueList( Node aNode, NamedNodeMap aAttrList ) + throws IOException, ODTException + { + PropertyValueList valueList = new PropertyValueList( iStringPool ); + + boolean isInherited = false; + String name = null; + String value = null; + + for ( int i = 0; i < aAttrList.getLength(); i++ ) + { + Node node = aAttrList.item( i ); + + if ( nodeType( node ) == ATTRIBUTE && + !node.getNodeName().equals( CSSDOMProcessor.STRING_PSEUDOCLASS ) ) + { + String attrName = node.getNodeName(); + String attrValue = node.getNodeValue(); + + if( attrName.equals( CSSDOMProcessor.STRING_NAME ) && + name == null ) + { + name = attrValue; + } + else if( attrName.startsWith( CSSDOMProcessor.STRING_VALUE ) ) + { + value = attrValue; + + // Checking if property is inherited. + // This emulates the behavior of the Symbian side implementation + // where this is true in following cases: + // 1. Value equals "inherited" + // 2. Property is inheritable and the element can inherit properties + // + // This means that also some properties that aren't actually + // inherited, are also set with value isIherited == "true" + isInherited = iCSSDOMProsessor.canInherit( (Element) aNode, name, value ); + + PropertyValue propValue = valueList.newItem(); + parseAttrValue( value, propValue ); + } + else + { + throw new ODTException( "Error in DOM/style data externalization: " + + "Property values" ); + } + } + } + + if( name != null && + value != null ) + { + // Write property name + int nameRef = iStringPool.addString( name ); + iODTDos.writeInt16( nameRef ); + + // Externalize the property value list to the stream + valueList.externalize( iODTDos ); + } + else + { + throw new ODTException( "Error in DOM/style data externalization: " + + "Property values" ); + } + return isInherited; + } + + /** + * Count element nodes in a node list. + * @param aList Node list + * @param aNodeType Node type to count + * @return Count of found nodes + */ + private static int countNodes( NodeList aList ) + { + int count = 0; + for( int i = 0; i < aList.getLength(); i++ ) + { + // Exclude elements that are style properties + if( nodeType( aList.item( i ) ) == NODE ) + { + count++; + } + } + + return count; + } + + /** + * Count attributes in a named node map. + * @param aList Node map + * @param aNodeType Node type to count + * @return Count of found nodes + */ + private static int countAttributes( NamedNodeMap aList ) + { + int count = 0; + for( int i = 0; i < aList.getLength(); i++ ) + { + if( nodeType( aList.item( i ) ) == ATTRIBUTE ) + { + count++; + } + } + + return count; + } + + /** + * Count properties in a node list. + * @param aList Node list + * @param aNodeType Node type to count + * @return Count of found nodes + */ + private static int countProperties( NodeList aList ) + { + int count = 0; + for( int i = 0; i < aList.getLength(); i++ ) + { + // Include only elements that are style properties + if( nodeType( aList.item( i ) ) == PROPERTY ) + { + count++; + } + } + + return count; + } + + /** + * Resolve node type. Possible types: NODE, ATTRIBUTE or PROPERTY + * @param aItem Node to resolve + * @return Type of the node. If the type can not be determined, UNKNOWN is returned + */ + private static int nodeType( Node aItem ) + { + int type = UNKNOWN; + + // Element that is not a style property element + if( aItem.getNodeType() == Node.ELEMENT_NODE && + !aItem.getNodeName().equals( CSSDOMProcessor.STRING_PROPERTY ) ) + { + type = NODE; + } + // Attribute nodes + else if( aItem.getNodeType() == Node.ATTRIBUTE_NODE ) + { + type = ATTRIBUTE; + } + // Style property element + else if( aItem.getNodeType() == Node.ELEMENT_NODE && + aItem.getNodeName().equals( CSSDOMProcessor.STRING_PROPERTY ) ) + { + type = PROPERTY; + } + + return type; + } + + }