/******************************************************************************* * Copyright (c) 2007, 2015 Innoopract Informationssysteme GmbH and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Innoopract Informationssysteme GmbH - initial API and implementation * EclipseSource - ongoing development ******************************************************************************/ package org.eclipse.rap.rwt.internal.theme; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.eclipse.rap.rwt.internal.service.ServletLog; import org.eclipse.rap.rwt.internal.util.ParamCheck; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; /** * Reader for theme definition files. These are the "*.theme.xml" files * that define themeable properties of a certain widget. */ public final class ThemeDefinitionReader { private static final String ELEM_ROOT = "theme"; private static final String ELEM_ELEMENT = "element"; private static final String ELEM_PROPERTY = "property"; private static final String ELEM_STYLE = "style"; private static final String ELEM_STATE = "state"; private static final String ATTR_NAME = "name"; private static final String ATTR_SHORTHAND = "shorthand"; private static final String THEME_DEF_SCHEMA = "themedef.xsd"; private static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; private static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; private final InputStream inputStream; private final String fileName; private final Collection<CssElement> cssElements; /** * An instance of this class reads theme definitions from an XML resource. * * @param inputStream input stream from a theme definition XML * @param fileName the file name to refer to in (error) messages */ public ThemeDefinitionReader( InputStream inputStream, String fileName ) { ParamCheck.notNull( inputStream, "inputStream" ); this.inputStream = inputStream; this.fileName = fileName; cssElements = new ArrayList<>(); } /** * Reads a theme definition from the specified stream. The stream is kept open after reading. * * @throws IOException if a I/O error occurs * @throws SAXException if a parse error occurs */ public void read() throws SAXException, IOException { Document document; document = parseThemeDefinition( inputStream ); Node root = document.getElementsByTagName( ELEM_ROOT ).item( 0 ); NodeList childNodes = root.getChildNodes(); for( int i = 0; i < childNodes.getLength(); i++ ) { Node node = childNodes.item( i ); if( node.getNodeType() == Node.ELEMENT_NODE ) { if( ELEM_ELEMENT.equals( node.getNodeName() ) ) { readElement( node ); } } } } /** * Returns the CSS element names in the definition. */ public CssElement[] getThemeCssElements() { CssElement[] result = new CssElement[ cssElements.size() ]; cssElements.toArray( result ); return result; } private void readElement( Node node ) { String name = getAttributeValue( node, ATTR_NAME ); CssElementImpl themeElement = new CssElementImpl( name ); cssElements.add( themeElement ); NodeList childNodes = node.getChildNodes(); for( int i = 0; i < childNodes.getLength(); i++ ) { Node childNode = childNodes.item( i ); if( childNode.getNodeType() == Node.ELEMENT_NODE ) { if( ELEM_ELEMENT.equals( childNode.getNodeName() ) ) { readElement( childNode ); } else if( ELEM_PROPERTY.equals( childNode.getNodeName() ) ) { if( !isShorthandProperty( childNode ) ) { themeElement.addProperty( getAttributeValue( childNode, ATTR_NAME ) ); } } else if( ELEM_STYLE.equals( childNode.getNodeName() ) ) { themeElement.addStyle( getAttributeValue( childNode, ATTR_NAME ) ); } else if( ELEM_STATE.equals( childNode.getNodeName() ) ) { themeElement.addState( getAttributeValue( childNode, ATTR_NAME ) ); } } } } private static boolean isShorthandProperty( Node node ) { return "true".equals( getAttributeValue( node, ATTR_SHORTHAND ) ); } private Document parseThemeDefinition( InputStream is ) throws SAXException, IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware( true ); ClassLoader loader = ThemeDefinitionReader.class.getClassLoader(); final URL schema = loader.getResource( THEME_DEF_SCHEMA ); factory.setValidating( schema != null ); try { factory.setAttribute( JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA ); } catch( IllegalArgumentException iae ) { // XML-Processing does not support JAXP 1.2 or greater factory.setNamespaceAware( false ); factory.setValidating( false ); } DocumentBuilder builder; try { builder = factory.newDocumentBuilder(); } catch( ParserConfigurationException e ) { String message = "Failed to initialize parser for theme definition files"; throw new RuntimeException( message, e ); } builder.setErrorHandler( new ThemeDefinitionErrorHandler() ); return builder.parse( is ); } private static String getAttributeValue( Node node, String name ) { String result = null; NamedNodeMap attributes = node.getAttributes(); if( attributes != null ) { Node namedItem = attributes.getNamedItem( name ); if( namedItem != null ) { result = namedItem.getNodeValue(); } } return result; } private class ThemeDefinitionErrorHandler implements ErrorHandler { @Override public void error( SAXParseException spe ) throws SAXException { String msg = "Error parsing theme definition " + getPosition( spe ) + ":"; ServletLog.log( msg, null ); ServletLog.log( spe.getMessage(), null ); } @Override public void fatalError( SAXParseException spe ) throws SAXException { String msg = "Fatal error parsing theme definition " + getPosition( spe ) + ":"; ServletLog.log( msg, null ); ServletLog.log( spe.getMessage(), null ); } @Override public void warning( SAXParseException spe ) throws SAXException { String msg = "Warning parsing theme definition " + getPosition( spe ) + ":"; ServletLog.log( msg, null ); ServletLog.log( spe.getMessage(), null ); } private String getPosition( SAXParseException spe ) { return new StringBuilder() .append( "in file '" ) .append( fileName ) .append( "' at line " ) .append( spe.getLineNumber() ) .append( ", col " ) .append( spe.getColumnNumber() ).toString(); } } }