/*!
* 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 java.io.StringReader;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
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;
/**
* 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};
}
}