Package org.pentaho.reporting.libraries.css.resolver.impl

Source Code of org.pentaho.reporting.libraries.css.resolver.impl.SimpleStyleRuleMatcher

/*!
* 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 java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Locale;
import java.util.StringTokenizer;

import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.css.PseudoPage;
import org.pentaho.reporting.libraries.css.dom.DocumentContext;
import org.pentaho.reporting.libraries.css.dom.LayoutElement;
import org.pentaho.reporting.libraries.css.dom.StyleReference;
import org.pentaho.reporting.libraries.css.model.CSSCounterRule;
import org.pentaho.reporting.libraries.css.model.CSSPageRule;
import org.pentaho.reporting.libraries.css.model.CSSStyleRule;
import org.pentaho.reporting.libraries.css.model.StyleRule;
import org.pentaho.reporting.libraries.css.model.StyleSheet;
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.resolver.StyleRuleMatcher;
import org.pentaho.reporting.libraries.css.selectors.CSSSelector;
import org.pentaho.reporting.libraries.css.values.CSSValue;
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 org.w3c.css.sac.AttributeCondition;
import org.w3c.css.sac.CombinatorCondition;
import org.w3c.css.sac.Condition;
import org.w3c.css.sac.ConditionalSelector;
import org.w3c.css.sac.DescendantSelector;
import org.w3c.css.sac.ElementSelector;
import org.w3c.css.sac.NegativeCondition;
import org.w3c.css.sac.NegativeSelector;
import org.w3c.css.sac.Selector;
import org.w3c.css.sac.SiblingSelector;
import org.w3c.css.sac.SimpleSelector;

/**
* A stateless implementation of the style rule matching. This implementation is
* stateless within the current layout process.
*
* @author Thomas Morgner
*/
public class SimpleStyleRuleMatcher implements StyleRuleMatcher
{
  private DocumentContext layoutProcess;
  private ResourceManager resourceManager;
  private CSSStyleRule[] activeStyleRules;
  private CSSStyleRule[] activePseudoStyleRules;
  private CSSPageRule[] pageRules;
  private CSSCounterRule[] counterRules;
  private NamespaceCollection namespaces;

  public SimpleStyleRuleMatcher()
  {
  }

  public void initialize(final DocumentContext layoutProcess)
  {
    if (layoutProcess == null)
    {
      throw new NullPointerException();
    }
    this.layoutProcess = layoutProcess;
    this.resourceManager = layoutProcess.getResourceManager();

    final ArrayList pageRules = new ArrayList();
    final ArrayList counterRules = new ArrayList();
    final ArrayList styleRules = new ArrayList();
    final DocumentContext dc = this.layoutProcess;

    namespaces = dc.getNamespaces();
    final String[] nsUri = namespaces.getNamespaces();
    for (int i = 0; i < nsUri.length; i++)
    {
      final String uri = nsUri[i];
      final NamespaceDefinition nsDef = namespaces.getDefinition(uri);
      final ResourceKey rawKey = nsDef.getDefaultStyleSheetLocation();
      if (rawKey == null)
      {
        // there is no default stylesheet for that namespace.
        continue;
      }

      final ResourceKey baseKey = layoutProcess.getContextKey();
      final StyleSheet styleSheet = parseStyleSheet(rawKey, baseKey);
      if (styleSheet == null)
      {
        continue;
      }
//      Log.debug("Loaded stylesheet from " + rawKey + " for namespace " + nsDef.getURI());
      addStyleRules(styleSheet, styleRules);
      addPageRules(styleSheet, pageRules);
      addCounterRules(styleSheet, counterRules);
    }

    final StyleReference[] refs = dc.getStyleReferences();
    for (int i = 0; i < refs.length; i++)
    {
      final StyleReference ref = refs[i];
      if (ref.getType() == StyleReference.LINK)
      {
        handleLinkNode(dc, ref, styleRules, pageRules, counterRules);
      }
      else
      {
        handleStyleNode(dc, ref, styleRules, pageRules, counterRules);
      }
    }

    activeStyleRules = (CSSStyleRule[])
        styleRules.toArray(new CSSStyleRule[styleRules.size()]);
    this.pageRules = (CSSPageRule[])
        pageRules.toArray(new CSSPageRule[pageRules.size()]);
    this.counterRules = (CSSCounterRule[])
        counterRules.toArray(new CSSCounterRule[counterRules.size()]);

    styleRules.clear();
    for (int i = 0; i < activeStyleRules.length; i++)
    {
      final CSSStyleRule activeStyleRule = activeStyleRules[i];
      if (isPseudoElementRule(activeStyleRule) == false)
      {
        continue;
      }
      styleRules.add(activeStyleRule);
    }
    activePseudoStyleRules = (CSSStyleRule[])
        styleRules.toArray(new CSSStyleRule[styleRules.size()]);

  }

  private void handleLinkNode(final DocumentContext context,
                              final StyleReference node,
                              final ArrayList styleRules,
                              final ArrayList pageRules,
                              final ArrayList counterRules)
  {
    // do some external parsing
    // (Same as the <link> element of HTML)
    try
    {
      final String href = node.getStyleContent();
      final ResourceKey baseKey = context.getContextKey();

      final ResourceKey derivedKey;
      if (baseKey == null)
      {
        derivedKey = resourceManager.createKey(href);
      }
      else
      {
        derivedKey = resourceManager.deriveKey(baseKey, String.valueOf(href));
      }

      final StyleSheet styleSheet = parseStyleSheet(derivedKey, null);
      if (styleSheet == null)
      {
        return;
      }
      addStyleRules(styleSheet, styleRules);
      addPageRules(styleSheet, pageRules);
      addCounterRules(styleSheet, counterRules);
    }
    catch (ResourceKeyCreationException e)
    {
      e.printStackTrace();
    }
  }


  private void handleStyleNode(final DocumentContext context,
                               final StyleReference node,
                               final ArrayList styleRules,
                               final ArrayList pageRules,
                               final ArrayList counterRules)
  {
    // do some inline parsing
    // (Same as the <style> element of HTML)
    // we also accept preparsed content ...

    final String styleText = node.getStyleContent();

    try
    {
      final byte[] bytes = styleText.getBytes("UTF-8");
      final ResourceKey rawKey = resourceManager.createKey(bytes);

      final ResourceKey baseKey = context.getContextKey();
      final StyleSheet styleSheet = parseStyleSheet(rawKey, baseKey);
      if (styleSheet == null)
      {
        return;
      }
      addStyleRules(styleSheet, styleRules);
      addPageRules(styleSheet, pageRules);
      addCounterRules(styleSheet, counterRules);
    }
    catch (UnsupportedEncodingException e)
    {
      e.printStackTrace();
    }
    catch (ResourceKeyCreationException e)
    {
      e.printStackTrace();
    }
  }


  private void addCounterRules(final StyleSheet styleSheet,
                               final ArrayList rules)
  {
    final int sc = styleSheet.getStyleSheetCount();
    for (int i = 0; i < sc; i++)
    {
      addCounterRules(styleSheet.getStyleSheet(i), rules);
    }

    final int rc = styleSheet.getRuleCount();
    for (int i = 0; i < rc; i++)
    {
      final StyleRule rule = styleSheet.getRule(i);
      if (rule instanceof CSSCounterRule)
      {
        final CSSCounterRule drule = (CSSCounterRule) rule;
        rules.add(drule);
      }
    }
  }


  private void addPageRules(final StyleSheet styleSheet,
                            final ArrayList rules)
  {
    final int sc = styleSheet.getStyleSheetCount();
    for (int i = 0; i < sc; i++)
    {
      addPageRules(styleSheet.getStyleSheet(i), rules);
    }

    final int rc = styleSheet.getRuleCount();
    for (int i = 0; i < rc; i++)
    {
      final StyleRule rule = styleSheet.getRule(i);
      if (rule instanceof CSSPageRule)
      {
        final CSSPageRule drule = (CSSPageRule) rule;
        rules.add(drule);
      }
    }
  }


  private void addStyleRules(final StyleSheet styleSheet,
                             final ArrayList activeStyleRules)
  {
    final int sc = styleSheet.getStyleSheetCount();
    for (int i = 0; i < sc; i++)
    {
      addStyleRules(styleSheet.getStyleSheet(i), activeStyleRules);
    }

    final int rc = styleSheet.getRuleCount();
    for (int i = 0; i < rc; i++)
    {
      final StyleRule rule = styleSheet.getRule(i);
      if (rule instanceof CSSStyleRule)
      {
        final CSSStyleRule drule = (CSSStyleRule) rule;
        activeStyleRules.add(drule);
      }
    }
  }

  private StyleSheet parseStyleSheet(final ResourceKey key,
                                     final ResourceKey context)
  {
    try
    {
      final Resource resource = resourceManager.create
          (key, context, StyleSheet.class);
      return (StyleSheet) resource.getResource();
    }
    catch (ResourceException e)
    {
      // Log.info("Unable to parse StyleSheet: " + e.getLocalizedMessage());
    }
    return null;
  }

  private boolean isPseudoElementRule(final CSSStyleRule rule)
  {
    final CSSSelector selector = rule.getSelector();
    if (selector == null)
    {
      return false;
    }

    if (selector.getSelectorType() != Selector.SAC_CONDITIONAL_SELECTOR)
    {
      return false;
    }

    final ConditionalSelector cs = (ConditionalSelector) selector;
    final Condition condition = cs.getCondition();
    if (condition.getConditionType() != Condition.SAC_PSEUDO_CLASS_CONDITION)
    {
      return false;
    }
    return true;
  }

  public boolean isMatchingPseudoElement(final LayoutElement element, final String pseudo)
  {
    for (int i = 0; i < activePseudoStyleRules.length; i++)
    {
      final CSSStyleRule activeStyleRule = activePseudoStyleRules[i];

      final CSSSelector selector = activeStyleRule.getSelector();
      final ConditionalSelector cs = (ConditionalSelector) selector;
      final Condition condition = cs.getCondition();

      final AttributeCondition ac = (AttributeCondition) condition;
      if (ObjectUtilities.equal(ac.getValue(), pseudo) == false)
      {
        continue;
      }

      final SimpleSelector simpleSelector = cs.getSimpleSelector();
      if (isMatch(element, simpleSelector))
      {
        return true;
      }
    }
    return false;
  }

  /**
   * Creates an independent copy of this style rule matcher.
   *
   * @return this instance, as this implementation is stateless
   */
  public StyleRuleMatcher deriveInstance()
  {
    return this;
  }

  public CSSStyleRule[] getMatchingRules(final LayoutElement element)
  {
    final ArrayList retvals = new ArrayList();
    for (int i = 0; i < activeStyleRules.length; i++)
    {
      final CSSStyleRule activeStyleRule = activeStyleRules[i];
      final CSSSelector selector = activeStyleRule.getSelector();
      if (selector == null)
      {
        continue;
      }

      if (isMatch(element, selector))
      {
        retvals.add(activeStyleRule);
      }
    }

//    Log.debug ("Got " + retvals.size() + " matching rules for " +
//            layoutContext.getTagName() + ":" +
//            layoutContext.getPseudoElement());

    return (CSSStyleRule[]) retvals.toArray
        (new CSSStyleRule[retvals.size()]);
  }

  private boolean isMatch(final LayoutElement node,
                          final Selector selector)
  {
    final short selectorType = selector.getSelectorType();
    switch (selectorType)
    {
      case Selector.SAC_ANY_NODE_SELECTOR:
        return true;
      case Selector.SAC_ROOT_NODE_SELECTOR:
        return node.getParentLayoutElement() == null;
      case Selector.SAC_NEGATIVE_SELECTOR:
      {
        final NegativeSelector negativeSelector = (NegativeSelector) selector;
        return isMatch(node, negativeSelector) == false;
      }
      case Selector.SAC_DIRECT_ADJACENT_SELECTOR:
      {
        final SiblingSelector silbSelect = (SiblingSelector) selector;
        return isSilblingMatch(node, silbSelect);
      }
      case Selector.SAC_PSEUDO_ELEMENT_SELECTOR:
      {
        return node.isPseudoElement();
      }
      case Selector.SAC_ELEMENT_NODE_SELECTOR:
      {
        final ElementSelector es = (ElementSelector) selector;
        final String localName = es.getLocalName();
        if (localName != null)
        {
          if (localName.equals(node.getTagName()) == false)
          {
            return false;
          }
        }
        final String namespaceURI = es.getNamespaceURI();
        if (namespaceURI != null)
        {
          return containsNamespace(namespaceURI, layoutProcess.getNamespaces());
//          if (namespaceURI.equals(layoutProcess.getNamespaces()) == false)
//          {
//            return false;
//          }
        }
        return true;
      }
      case Selector.SAC_CHILD_SELECTOR:
      {
        final DescendantSelector ds = (DescendantSelector) selector;
        if (isMatch(node, ds.getSimpleSelector()) == false)
        {
          return false;
        }
        final LayoutElement parent = node.getParentLayoutElement();
        return (isMatch(parent, ds.getAncestorSelector()));
      }
      case Selector.SAC_DESCENDANT_SELECTOR:
      {
        final DescendantSelector ds = (DescendantSelector) selector;
        if (isMatch(node, ds.getSimpleSelector()) == false)
        {
          return false;
        }
        return (isDescendantMatch(node, ds.getAncestorSelector()));
      }
      case Selector.SAC_CONDITIONAL_SELECTOR:
      {
        final ConditionalSelector cs = (ConditionalSelector) selector;
        if (evaluateCondition(node, cs.getCondition()) == false)
        {
          return false;
        }
        if (isMatch(node, cs.getSimpleSelector()) == false)
        {
          return false;
        }
        return true;
      }
      default:
        return false;
    }
  }

  /**
   * Searches the namespace collection and indicates if the supplied namespace can be found within.
   *
   * @param namespaceURI the namespace used in the search
   * @param namespaces   the collection of namespaces being searched
   * @return <code>true</code> if the supplied namespace is contained in the namespace collection,
   *         <code>false</code> otherwise. If either the supplied namespace or the collection is <code>null</code>,
   *         this method will return <code>false</code>
   */
  private boolean containsNamespace(String namespaceURI, NamespaceCollection namespaces)
  {
    if (namespaces == null || namespaceURI == null)
    {
      return false;
    }
    String namespaceStrings[] = namespaces.getNamespaces();
    for (int i = 0; i < namespaceStrings.length; ++i)
    {
      if (namespaceURI.equals(namespaceStrings[i]))
      {
        // FOUND!
        return true;
      }
    }
    // None found
    return false;
  }

  private boolean evaluateCondition(final LayoutElement node,
                                    final Condition condition)
  {
    switch (condition.getConditionType())
    {
      case Condition.SAC_AND_CONDITION:
      {
        final CombinatorCondition cc = (CombinatorCondition) condition;
        return (evaluateCondition(node, cc.getFirstCondition()) &&
            evaluateCondition(node, cc.getSecondCondition()));
      }
      case Condition.SAC_OR_CONDITION:
      {
        final CombinatorCondition cc = (CombinatorCondition) condition;
        return (evaluateCondition(node, cc.getFirstCondition()) ||
            evaluateCondition(node, cc.getSecondCondition()));
      }
      case Condition.SAC_ATTRIBUTE_CONDITION:
      {
        final AttributeCondition ac = (AttributeCondition) condition;
        String namespaceURI = ac.getNamespaceURI();
        if (namespaceURI == null)
        {
          namespaceURI = node.getNamespace();
        }

        final Object attr = node.getAttribute
            (namespaceURI, ac.getLocalName());
        if (ac.getValue() == null)
        {
          // dont care what's inside, as long as there is a value ..
          return attr != null;
        }
        else
        {
          return ObjectUtilities.equal(attr, ac.getValue());
        }
      }
      case Condition.SAC_CLASS_CONDITION:
      {
        final AttributeCondition ac = (AttributeCondition) condition;
        final String namespace = node.getNamespace();
        if (namespace == null)
        {
          return false;
        }
        final NamespaceDefinition ndef = namespaces.getDefinition(namespace);
        if (ndef == null)
        {
          return false;
        }
        final String[] classAttribute = ndef.getClassAttribute(
            node.getTagName());
        for (int i = 0; i < classAttribute.length; i++)
        {
          final String attr = classAttribute[i];
          final String htmlAttr = (String) node.getAttribute(namespace, attr);
          if (isOneOfAttributes(htmlAttr, ac.getValue()))
          {
            return true;
          }
        }
        return false;
      }
      case Condition.SAC_ID_CONDITION:
      {
        final AttributeCondition ac = (AttributeCondition) condition;
        final Object id = node.getAttribute(Namespaces.XML_NAMESPACE,
            "id");
        return ObjectUtilities.equal(ac.getValue(), id);
      }
      case Condition.SAC_LANG_CONDITION:
      {
        final AttributeCondition ac = (AttributeCondition) condition;
        final Locale locale = node.getLanguage();
        final String lang = locale.getLanguage();
        return isBeginHyphenAttribute(lang, ac.getValue());
      }
      case Condition.SAC_NEGATIVE_CONDITION:
      {
        final NegativeCondition nc = (NegativeCondition) condition;
        return evaluateCondition(node, nc.getCondition()) == false;
      }
      case Condition.SAC_ONE_OF_ATTRIBUTE_CONDITION:
      {
        final AttributeCondition ac = (AttributeCondition) condition;
        final String attr = (String)
            node.getAttribute
                (ac.getNamespaceURI(), ac.getLocalName());
        return isOneOfAttributes(attr, ac.getValue());
      }
      case Condition.SAC_PSEUDO_CLASS_CONDITION:
      {
        final AttributeCondition ac = (AttributeCondition) condition;
        final String pseudoClass = node.getPseudoElement();
        if (pseudoClass == null)
        {
          return false;
        }
        if (pseudoClass.equals(ac.getValue()))
        {
          return true;
        }
        return false;
      }
      case Condition.SAC_ONLY_CHILD_CONDITION:
      case Condition.SAC_ONLY_TYPE_CONDITION:
      case Condition.SAC_POSITIONAL_CONDITION:
      case Condition.SAC_CONTENT_CONDITION:
      default:
      {
        // todo
        return false;
      }
    }
  }

  private boolean isOneOfAttributes(final String attrValue, final String value)
  {
    if (attrValue == null)
    {
      return false;
    }
    if (attrValue.equals(value))
    {
      return true;
    }

    final StringTokenizer strTok = new StringTokenizer(attrValue);
    while (strTok.hasMoreTokens())
    {
      final String token = strTok.nextToken();
      if (token.equals(value))
      {
        return true;
      }
    }
    return false;
  }

  private boolean isBeginHyphenAttribute(final String attrValue, final String value)
  {
    if (attrValue == null)
    {
      return false;
    }
    if (value == null)
    {
      return false;
    }
    return (attrValue.startsWith(value));

  }

  private boolean isDescendantMatch(final LayoutElement node,
                                    final Selector selector)
  {
    LayoutElement parent = node.getParentLayoutElement();
    while (parent != null)
    {
      if (isMatch(parent, selector))
      {
        return true;
      }
      parent = parent.getParentLayoutElement();
    }
    return false;
  }

  private boolean isSilblingMatch(final LayoutElement node,
                                  final SiblingSelector select)
  {
    LayoutElement pred = node.getPreviousLayoutElement();
    while (pred != null)
    {
      if (isMatch(pred, select))
      {
        return true;
      }
      pred = pred.getPreviousLayoutElement();
    }
    return false;
  }

  public CSSPageRule[] getPageRule(final CSSValue pageName, final PseudoPage[] pseudoPages)
  {
    final CSSPageRule[] pageRules = this.pageRules;
    final ArrayList rules = new ArrayList();
    for (int i = 0; i < pageRules.length; i++)
    {
      final CSSPageRule rule = pageRules[i];
      final String rulePageName = rule.getName();
      // Check the page name.
      if (rulePageName != null)
      {
        if (rulePageName.equals(pageName) == false)
        {
          continue;
        }
      }

      // And the pseudo page ..
      final String rulePseudoPage = rule.getPseudoPage();
      if (rulePseudoPage != null)
      {
        for (int j = 0; j < pseudoPages.length; j++)
        {
          final PseudoPage pseudoPage = pseudoPages[j];
          if (pseudoPage.toString().equalsIgnoreCase(rulePseudoPage))
          {
            rules.add(rule);
          }
        }
        continue;
      }

      rules.add(rule);
    }

    return (CSSPageRule[]) rules.toArray(new CSSPageRule[rules.size()]);
  }
}
TOP

Related Classes of org.pentaho.reporting.libraries.css.resolver.impl.SimpleStyleRuleMatcher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.