/*!
* 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.engine.classic.core.style.css;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.AttributeNames;
import org.pentaho.reporting.engine.classic.core.ReportDefinition;
import org.pentaho.reporting.engine.classic.core.ReportElement;
import org.pentaho.reporting.engine.classic.core.style.ElementStyleSheet;
import org.pentaho.reporting.engine.classic.core.style.ResolverStyleSheet;
import org.pentaho.reporting.engine.classic.core.style.StyleKey;
import org.pentaho.reporting.engine.classic.core.style.css.namespaces.NamespaceCollection;
import org.pentaho.reporting.engine.classic.core.style.css.selector.SelectorWeight;
import org.pentaho.reporting.engine.classic.core.style.resolver.SimpleStyleResolver;
import org.pentaho.reporting.engine.classic.core.style.resolver.StyleResolver;
import org.pentaho.reporting.libraries.resourceloader.Resource;
import org.pentaho.reporting.libraries.resourceloader.ResourceException;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceKeyCreationException;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
/**
* A cascading style resolver. This resolver follows the cascading rules
* as outlined by the Cascading Stylesheet Standard.
*
* @author Thomas Morgner
*/
public class CSSStyleResolver implements StyleResolver, Cloneable
{
private static final Log logger = LogFactory.getLog(CSSStyleResolver.class);
private StyleRuleMatcher styleRuleMatcher;
private DocumentContext documentContext;
private NamespaceCollection namespaces;
private SimpleStyleResolver simpleStyleResolver;
public CSSStyleResolver()
{
this(false);
}
public CSSStyleResolver(final boolean designTime)
{
this.simpleStyleResolver = new SimpleStyleResolver(designTime);
}
public static StyleResolver createDesignTimeResolver(final ReportDefinition report,
final ResourceManager resourceManager,
final ResourceKey contentBase,
final boolean designTime)
{
final ElementStyleDefinition styleDefinition = createStyleDefinition(report, resourceManager, contentBase);
if (styleDefinition.getRuleCount() == 0 && styleDefinition.getStyleSheetCount() == 0)
{
return new SimpleStyleResolver(designTime);
}
else
{
final CSSStyleResolver resolver = new CSSStyleResolver(designTime);
final NamespaceCollection namespaceCollection = StyleSheetParserUtil.getInstance().getNamespaceCollection();
final DefaultDocumentContext documentContext =
new DefaultDocumentContext(namespaceCollection, resourceManager, contentBase, null, styleDefinition);
resolver.initialize(documentContext);
return resolver;
}
}
private static ElementStyleDefinition createStyleDefinition(final ReportDefinition reportDefinition,
final ResourceManager resourceManager,
final ResourceKey contentBase)
{
final ElementStyleDefinition styleDefinition;
final Object maybeStyleSheet = reportDefinition.getAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.STYLE_SHEET);
if (maybeStyleSheet instanceof ElementStyleDefinition)
{
final ElementStyleDefinition sd = (ElementStyleDefinition) maybeStyleSheet;
styleDefinition = sd.clone();
}
else
{
styleDefinition = new ElementStyleDefinition();
}
final Object styleSheetRefs = reportDefinition.getAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.STYLE_SHEET_REFERENCE);
final ArrayList<Object> styleRefs = new ArrayList<Object>();
if (styleSheetRefs instanceof Object[])
{
final Object[] styleArray = (Object[]) styleSheetRefs;
for (int i = 0; i < styleArray.length; i++)
{
styleRefs.add(styleArray[i]);
}
}
else if (styleSheetRefs instanceof Collection)
{
final Collection c = (Collection) styleSheetRefs;
styleRefs.addAll(c);
}
else if (styleSheetRefs != null)
{
styleRefs.add(styleSheetRefs);
}
for (int i = 0; i < styleRefs.size(); i++)
{
final Object o = styleRefs.get(i);
try
{
final ResourceKey key = resourceManager.createOrDeriveKey(contentBase, o, null);
final Resource resource = resourceManager.create(key, key, ElementStyleDefinition.class);
final ElementStyleDefinition definition = (ElementStyleDefinition) resource.getResource();
styleDefinition.addStyleSheet(definition);
}
catch (ResourceKeyCreationException e)
{
logger.debug("Failed to load referenced style-sheet: " + o, e);
}
catch (ResourceException e)
{
logger.info("Failed to load referenced style-sheet: " + o, e);
}
}
return styleDefinition;
}
public void initialize(final DocumentContext layoutProcess)
{
this.documentContext = layoutProcess;
this.namespaces = documentContext.getNamespaces();
this.styleRuleMatcher = new SimpleStyleRuleMatcher();
this.styleRuleMatcher.initialize(layoutProcess);
}
protected DocumentContext getDocumentContext()
{
return documentContext;
}
protected NamespaceCollection getNamespaces()
{
return namespaces;
}
public void resolve(final ReportElement element, final ResolverStyleSheet resolverTarget)
{
resolverTarget.clear();
resolverTarget.setId(element.getStyle().getId());
// Stage 1a: Add the parent styles (but only the one marked as inheritable).
// If our element has a parent, get the parent's style information
// so we can "inherit" the styles that support that kind of thing
simpleStyleResolver.resolveParent(element, resolverTarget);
// At this point, the parentStyle contains the "foundation" from which
// the current element's style information will come....
// Stage 1b: Find all matching stylesheet styles for the given element.
performSelectionStep(element, resolverTarget);
// Stage 2: Compute the 'specified' set of values.
// Find all explicitly inherited styles and add them from the parent.
// does not apply, we have no ability to specify an explicit INHERIT value.
resolverTarget.addAll(element.getStyle());
resolverTarget.addDefault(element.getDefaultStyleSheet());
}
/*
* Todo: Make sure that the 'activeStyles' are sorted and then apply them with the
* lowest style first. All Matching styles have to be added.
*/
private void performSelectionStep(final ReportElement element,
final ElementStyleSheet target)
{
final StyleRuleMatcher.MatcherResult[] activeStyleRules = styleRuleMatcher.getMatchingRules(element);
final SelectorWeight[] weights = new SelectorWeight[target.getPropertyKeys().length];
for (int i = 0; i < activeStyleRules.length; i++)
{
final StyleRuleMatcher.MatcherResult activeStyleRule = activeStyleRules[i];
final ElementStyleRule rule = activeStyleRule.getRule();
final SelectorWeight weight = activeStyleRule.getWeight();
final StyleKey[] definedPropertyNamesArray = rule.getDefinedPropertyNamesArray();
for (int j = 0; j < definedPropertyNamesArray.length; j++)
{
final StyleKey styleKey = definedPropertyNamesArray[j];
if (styleKey == null)
{
continue;
}
final SelectorWeight selectorWeight = weights[j];
if (selectorWeight == null || (selectorWeight.compareTo(weight) > 0))
{
final Object styleProperty = rule.getStyleProperty(styleKey);
if (styleProperty != null)
{
target.setStyleProperty(styleKey, styleProperty);
weights[j] = weight;
}
}
}
}
}
public StyleResolver clone()
{
try
{
return (StyleResolver) super.clone();
}
catch (CloneNotSupportedException e)
{
throw new IllegalStateException();
}
}
}