/*!
* 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 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;
import java.util.ArrayList;
import java.util.Collection;
/**
* 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();
}
}
}