/*!
* 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.resolver.impl;
import org.pentaho.reporting.libraries.css.PageAreaType;
import org.pentaho.reporting.libraries.css.PseudoPage;
import org.pentaho.reporting.libraries.css.dom.DefaultLayoutStyle;
import org.pentaho.reporting.libraries.css.dom.DocumentContext;
import org.pentaho.reporting.libraries.css.dom.LayoutElement;
import org.pentaho.reporting.libraries.css.dom.LayoutStyle;
import org.pentaho.reporting.libraries.css.model.CSSDeclarationRule;
import org.pentaho.reporting.libraries.css.model.CSSPageAreaRule;
import org.pentaho.reporting.libraries.css.model.CSSPageRule;
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.namespace.NamespaceCollection;
import org.pentaho.reporting.libraries.css.namespace.NamespaceDefinition;
import org.pentaho.reporting.libraries.css.namespace.Namespaces;
import org.pentaho.reporting.libraries.css.parser.StyleSheetParserUtil;
import org.pentaho.reporting.libraries.css.resolver.StyleResolver;
import org.pentaho.reporting.libraries.css.resolver.StyleRuleMatcher;
import org.pentaho.reporting.libraries.css.selectors.CSSSelector;
import org.pentaho.reporting.libraries.css.selectors.SelectorWeight;
import org.pentaho.reporting.libraries.css.values.CSSInheritValue;
import org.pentaho.reporting.libraries.css.values.CSSValue;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
import java.util.ArrayList;
import java.util.Arrays;
/**
* A cascading style resolver. This resolver follows the cascading rules as outlined by the Cascading Stylesheet
* Standard.
*
* @author Thomas Morgner
*/
public class DefaultStyleResolver extends AbstractStyleResolver {
private boolean strictStyleMode;
private StyleRuleMatcher styleRuleMatcher;
private StyleKey[] inheritedKeys;
public DefaultStyleResolver() {
}
public void initialize( final DocumentContext layoutProcess ) {
super.initialize( layoutProcess );
this.styleRuleMatcher = new SimpleStyleRuleMatcher();
this.styleRuleMatcher.initialize( layoutProcess );
// this.strictStyleMode = Boolean.TRUE.equals
// (documentContext.getMetaAttribute(DocumentContext.STRICT_STYLE_MODE));
loadInitialStyle( layoutProcess );
}
// This one is expensive too: 6%
protected void resolveOutOfContext( final LayoutElement element ) {
// as this styleresolver is not statefull, we can safely call the resolve
// style method. A statefull resolver would have to find other means.
resolveStyle( element );
}
/**
* Performs tests, whether there is a pseudo-element definition for the given element. The element itself can be a
* pseudo-element as well.
*
* @param element
* @param pseudo
* @return
*/
public boolean isPseudoElementStyleResolvable( final LayoutElement element,
final String pseudo ) {
return styleRuleMatcher.isMatchingPseudoElement( element, pseudo );
}
/**
* Resolves the style. This is guaranteed to be called in the order of the document elements traversing the document
* tree using the 'deepest-node-first' strategy. (8% just for the first class calls (not counting the calls comming
* from resolveAnonymous (which is another 6%))
*
* @param element the elemen that should be resolved.
*/
public void resolveStyle( final LayoutElement element ) {
// this is a three stage process
final StyleKey[] keys = getKeys();
// Log.debug ("Resolving style for " +
// layoutContext.getTagName() + ":" +
// layoutContext.getPseudoElement());
final LayoutElement parent = element.getParentLayoutElement();
final LayoutStyle initialStyle = getInitialStyle();
final LayoutStyle style = element.getLayoutStyle();
// Stage 0: Initialize with the built-in defaults
// The copy will return false if it couldn't do the copy automatically
if ( style.copyFrom( initialStyle ) == false ) {
// manually copy all styles from the initial style-set..
for ( int i = 0; i < keys.length; i++ ) {
final StyleKey key = keys[ i ];
style.setValue( key, initialStyle.getValue( key ) );
}
}
// 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
if ( parent != null ) {
final LayoutStyle parentStyle;
parentStyle = parent.getLayoutStyle();
final StyleKey[] inheritedKeys = getInheritedKeys();
for ( int i = 0; i < inheritedKeys.length; i++ ) {
final StyleKey key = inheritedKeys[ i ];
style.setValue( key, parentStyle.getValue( key ) );
}
}
// 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, style );
// Stage 1c: Add the contents of the style attribute, if there is one ..
// the libLayout style is always added: This is a computed style and the hook
// for a element neutral user defined tweaking ..
final Object libLayoutStyleValue = element.getAttribute( Namespaces.LIBLAYOUT_NAMESPACE, "style" );
// You cannot override element specific styles with that. So an HTML-style
// attribute has more value than a LibLayout-style attribute.
addStyleFromAttribute( element, libLayoutStyleValue );
if ( strictStyleMode ) {
performStrictStyleAttr( element );
} else {
performCompleteStyleAttr( element );
}
// Stage 2: Compute the 'specified' set of values.
// Find all explicitly inherited styles and add them from the parent.
final CSSInheritValue inheritInstance = CSSInheritValue.getInstance();
if ( parent == null ) {
for ( int i = 0; i < keys.length; i++ ) {
final StyleKey key = keys[ i ];
final Object value = style.getValue( key );
if ( inheritInstance.equals( value ) ) {
style.setValue( key, initialStyle.getValue( key ) );
}
}
} else {
final LayoutStyle parentStyle = parent.getLayoutStyle();
for ( int i = 0; i < keys.length; i++ ) {
final StyleKey key = keys[ i ];
final Object value = style.getValue( key );
if ( inheritInstance.equals( value ) ) {
final CSSValue parentValue = parentStyle.getValue( key );
if ( parentValue == null ) {
style.setValue( key, initialStyle.getValue( key ) );
} else {
style.setValue( key, parentValue );
}
}
}
}
}
private StyleKey[] getInheritedKeys() {
if ( inheritedKeys == null ) {
final StyleKey[] keys = getKeys();
final ArrayList inheritedKeysList = new ArrayList();
for ( int i = 0; i < keys.length; i++ ) {
final StyleKey key = keys[ i ];
if ( key.isInherited() ) {
inheritedKeysList.add( key );
}
}
inheritedKeys = (StyleKey[])
inheritedKeysList.toArray( new StyleKey[ inheritedKeysList.size() ] );
}
return inheritedKeys;
}
/**
* Check, whether there is a known style attribute for the element's namespace and if so, grab its value. This method
* uses strict conformance to the XML rules and thus it does not evaluate foreign styles.
* <p/>
*
* @param node
*/
private void performStrictStyleAttr( final LayoutElement node ) {
final String namespace = node.getNamespace();
if ( namespace == null ) {
return;
}
final NamespaceCollection namespaces = getNamespaces();
final NamespaceDefinition ndef = namespaces.getDefinition( namespace );
if ( ndef == null ) {
return;
}
//final AttributeMap attributes = layoutContext.getAttributes();
final String[] styleAttrs = ndef.getStyleAttribute
( node.getTagName() );
for ( int i = 0; i < styleAttrs.length; i++ ) {
final String attr = styleAttrs[ i ];
final Object styleValue = node.getAttribute( namespace, attr );
addStyleFromAttribute( node, styleValue );
}
}
/**
* Check, whether there are known style attributes and if so, import them. This method uses a relaxed syntax and
* imports all known style attributes ignoring the element's defined namespace. This allows to add styles to elements
* which would not support styles otherwise, but may have .. chaotic .. side effects.
* <p/>
*
* @param node
*/
private void performCompleteStyleAttr( final LayoutElement node ) {
final NamespaceCollection namespaces = getNamespaces();
final String[] namespaceNames = namespaces.getNamespaces();
for ( int i = 0; i < namespaceNames.length; i++ ) {
final String namespace = namespaceNames[ i ];
final NamespaceDefinition ndef = namespaces.getDefinition( namespace );
if ( ndef == null ) {
continue;
}
final String[] styleAttrs = ndef.getStyleAttribute( node.getTagName() );
for ( int x = 0; x < styleAttrs.length; x++ ) {
final String attr = styleAttrs[ x ];
final Object styleValue = node.getAttribute( namespace, attr );
addStyleFromAttribute( node, styleValue );
}
}
}
private void addStyleFromAttribute( final LayoutElement node,
final Object styleValue ) {
if ( styleValue == null ) {
return;
}
if ( styleValue instanceof String ) {
final String styleText = (String) styleValue;
final ResourceManager resourceManager = getDocumentContext().getResourceManager();
final CSSDeclarationRule rule = StyleSheetParserUtil.getInstance().parseStyleRule
( null, styleText, null, null, resourceManager, StyleKeyRegistry.getRegistry() );
if ( rule != null ) {
copyStyleInformation( node.getLayoutStyle(), rule, node );
}
} else if ( styleValue instanceof CSSDeclarationRule ) {
final CSSDeclarationRule rule = (CSSDeclarationRule) styleValue;
copyStyleInformation( node.getLayoutStyle(), rule, node );
}
}
/**
* 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 LayoutElement element,
final LayoutStyle parentStyle ) {
final CSSStyleRule[] activeStyleRules = styleRuleMatcher.getMatchingRules( element );
// sort ...
Arrays.sort( activeStyleRules, new CSSStyleRuleComparator() );
SelectorWeight oldSelectorWeight = null;
for ( int i = 0; i < activeStyleRules.length; i++ ) {
final CSSStyleRule activeStyleRule = activeStyleRules[ i ];
final CSSSelector selector = activeStyleRule.getSelector();
final SelectorWeight activeWeight = selector.getWeight();
if ( oldSelectorWeight != null ) {
if ( oldSelectorWeight.compareTo( activeWeight ) > 0 ) {
oldSelectorWeight = activeWeight;
continue;
}
}
oldSelectorWeight = activeWeight;
copyStyleInformation( parentStyle, activeStyleRule, element );
}
}
public StyleResolver deriveInstance() {
return this;
}
public LayoutStyle resolvePageStyle( final CSSValue pageName,
final PseudoPage[] pseudoPages,
final PageAreaType pageArea ) {
final DefaultLayoutStyle style = new DefaultLayoutStyle();
final CSSPageRule[] pageRule =
styleRuleMatcher.getPageRule( pageName, pseudoPages );
for ( int i = 0; i < pageRule.length; i++ ) {
final CSSPageRule cssPageRule = pageRule[ i ];
copyStyleInformation( style, cssPageRule, null );
final int rc = cssPageRule.getRuleCount();
for ( int r = 0; r < rc; r++ ) {
final CSSPageAreaRule rule = cssPageRule.getRule( r );
if ( rule.getPageArea().equals( pageArea ) ) {
copyStyleInformation( style, rule, null );
}
}
}
return style;
}
}