/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.libraries.css.parser; import org.pentaho.reporting.libraries.css.model.CSSDeclarationRule; import org.pentaho.reporting.libraries.css.model.CSSStyleRule; import org.pentaho.reporting.libraries.css.model.StyleKey; import org.pentaho.reporting.libraries.css.model.StyleKeyRegistry; import org.pentaho.reporting.libraries.css.model.StyleSheet; import org.pentaho.reporting.libraries.css.values.CSSValue; import org.pentaho.reporting.libraries.resourceloader.ResourceKey; import org.pentaho.reporting.libraries.resourceloader.ResourceManager; import org.w3c.css.sac.InputSource; import org.w3c.css.sac.LexicalUnit; import org.w3c.css.sac.Parser; import java.io.StringReader; import java.util.Iterator; import java.util.Map; import java.util.StringTokenizer; /** * A helper class that simplifies the parsing of stylesheets. * * @author Thomas Morgner */ public final class StyleSheetParserUtil { private static StyleSheetParserUtil singleton; private Parser parser; public StyleSheetParserUtil() { } public static synchronized StyleSheetParserUtil getInstance() { if ( singleton == null ) { singleton = new StyleSheetParserUtil(); } return singleton; } private void setupNamespaces( final Map namespaces, final StyleSheetHandler handler ) { if ( namespaces == null ) { return; } final Iterator entries = namespaces.entrySet().iterator(); while ( entries.hasNext() ) { final Map.Entry entry = (Map.Entry) entries.next(); final String prefix = (String) entry.getKey(); final String uri = (String) entry.getValue(); handler.registerNamespace( prefix, uri ); } } /** * Parses a single style value for the given key. Returns <code>null</code>, if the key denotes a compound definition, * which has no internal representation. * * @param namespaces an optional map of known namespaces (prefix -> uri) * @param key the stylekey to which the value should be assigned. * @param value the value text * @param baseURL an optional base url * @return the parsed value or null, if the value was not valid. */ public CSSValue parseStyleValue( final Map namespaces, final StyleKey key, final String value, final ResourceKey baseURL, final ResourceManager resourceManager, final StyleKeyRegistry styleKeyRegistry ) { if ( key == null ) { throw new NullPointerException(); } if ( value == null ) { throw new NullPointerException(); } try { final Parser parser = getParser(); synchronized( parser ) { final StyleSheetHandler handler = new StyleSheetHandler(); setupNamespaces( namespaces, handler ); handler.init( styleKeyRegistry, resourceManager, baseURL, -1, null ); final InputSource source = new InputSource(); source.setCharacterStream( new StringReader( value ) ); handler.initParseContext( source ); handler.setStyleRule( new CSSStyleRule( new StyleSheet(), null ) ); parser.setDocumentHandler( handler ); final LexicalUnit lu = parser.parsePropertyValue( source ); handler.property( key.getName(), lu, false ); final CSSStyleRule rule = (CSSStyleRule) handler.getStyleRule(); CSSParserContext.getContext().destroy(); return rule.getPropertyCSSValue( key ); } } catch ( Exception e ) { e.printStackTrace(); return null; } } /** * Parses a style rule. * * @param namespaces an optional map of known namespaces (prefix -> uri) * @param styleText the css text that should be parsed * @param baseURL an optional base url * @param baseRule an optional base-rule to which the result gets added. * @return the CSS-Style-Rule that contains all values for the given text. */ public CSSDeclarationRule parseStyleRule( final Map namespaces, final String styleText, final ResourceKey baseURL, final CSSDeclarationRule baseRule, final ResourceManager resourceManager, final StyleKeyRegistry styleKeyRegistry ) { if ( styleText == null ) { throw new NullPointerException( "Name is null" ); } if ( resourceManager == null ) { throw new NullPointerException( "ResourceManager must not be null" ); } if ( styleKeyRegistry == null ) { throw new NullPointerException( "Style-Key Registry must not be null" ); } try { final Parser parser = getParser(); synchronized( parser ) { final StyleSheetHandler handler = new StyleSheetHandler(); setupNamespaces( namespaces, handler ); handler.init( styleKeyRegistry, resourceManager, baseURL, -1, null ); final InputSource source = new InputSource(); source.setCharacterStream( new StringReader( styleText ) ); handler.initParseContext( source ); if ( baseRule != null ) { handler.setStyleRule( baseRule ); } else { handler.setStyleRule( new CSSStyleRule( new StyleSheet(), null ) ); } parser.setDocumentHandler( handler ); parser.parseStyleDeclaration( source ); final CSSDeclarationRule rule = handler.getStyleRule(); CSSParserContext.getContext().destroy(); return rule; } } catch ( Exception e ) { e.printStackTrace(); return null; } } /** * Parses a style value. If the style value is a compound key, the corresonding style entries will be added to the * style rule. * * @param namespaces an optional map of known namespaces (prefix -> uri) * @param name the stylekey-name to which the value should be assigned. * @param value the value text * @param baseURL an optional base url * @return the CSS-Style-Rule that contains all values for the given text. */ public CSSStyleRule parseStyles( final Map namespaces, final String name, final String value, final ResourceKey baseURL, final ResourceManager resourceManager, final StyleKeyRegistry styleKeyRegistry ) { final CSSStyleRule cssStyleRule = new CSSStyleRule( new StyleSheet(), null ); return parseStyles( namespaces, name, value, baseURL, cssStyleRule, resourceManager, styleKeyRegistry ); } /** * Parses a style value. If the style value is a compound key, the corresonding style entries will be added to the * style rule. * * @param namespaces an optional map of known namespaces (prefix -> uri) * @param name the stylekey-name to which the value should be assigned. * @param value the value text * @param baseURL an optional base url * @param baseRule an optional base-rule to which the result gets added. * @return the CSS-Style-Rule that contains all values for the given text. */ public CSSStyleRule parseStyles( final Map namespaces, final String name, final String value, final ResourceKey baseURL, final CSSDeclarationRule baseRule, final ResourceManager resourceManager, final StyleKeyRegistry styleKeyRegistry ) { if ( name == null ) { throw new NullPointerException( "Name is null" ); } if ( value == null ) { throw new NullPointerException( "Value is null" ); } try { final Parser parser = getParser(); synchronized( parser ) { final StyleSheetHandler handler = new StyleSheetHandler(); handler.init( styleKeyRegistry, resourceManager, baseURL, -1, null ); setupNamespaces( namespaces, handler ); final InputSource source = new InputSource(); source.setCharacterStream( new StringReader( value ) ); handler.initParseContext( source ); handler.setStyleRule( baseRule ); parser.setDocumentHandler( handler ); final LexicalUnit lu = parser.parsePropertyValue( source ); handler.property( name, lu, false ); final CSSStyleRule rule = (CSSStyleRule) handler.getStyleRule(); CSSParserContext.getContext().destroy(); return rule; } } catch ( Exception e ) { e.printStackTrace(); return null; } } /** * Returns the initialized parser. * * @return the parser's local instance. * @throws CSSParserInstantiationException if the parser cannot be instantiated. */ private synchronized Parser getParser() throws CSSParserInstantiationException { if ( parser == null ) { parser = CSSParserFactory.getInstance().createCSSParser(); } return parser; } /** * Parses a single namespace identifier. This simply splits the given attribute name when a namespace separator is * encountered ('|'). * * @param attrName the attribute name * @return the parsed attribute. */ public static String[] parseNamespaceIdent( final String attrName ) { final String name; final String namespace; final StringTokenizer strtok = new StringTokenizer( attrName, "|" ); final CSSParserContext context = CSSParserContext.getContext(); // explicitly undefined is different from default namespace.. // With that construct I definitly violate the standard, but // most stylesheets are not yet written with namespaces in mind // (and most tools dont support namespaces in CSS). // // by acknowledging the explicit rule but redefining the rule where // no namespace syntax is used at all, I create compatiblity. Still, // if the stylesheet does not carry a @namespace rule, this is the same // as if the namespace was omited. if ( strtok.countTokens() == 2 ) { final String tkNamespace = strtok.nextToken(); if ( tkNamespace.length() == 0 ) { namespace = null; } else if ( "*".equals( tkNamespace ) ) { namespace = "*"; } else { namespace = (String) context.getNamespaces().get( tkNamespace ); } name = strtok.nextToken(); } else { name = strtok.nextToken(); namespace = context.getDefaultNamespace(); } return new String[] { namespace, name }; } }