All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.phloc.css.handler.CSSNodeToDomainObject Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (C) 2006-2014 phloc systems
 * http://www.phloc.com
 * office[at]phloc[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.phloc.css.handler;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.phloc.commons.ValueEnforcer;
import com.phloc.commons.annotations.Nonempty;
import com.phloc.css.ECSSVersion;
import com.phloc.css.decl.CSSDeclaration;
import com.phloc.css.decl.CSSDeclarationList;
import com.phloc.css.decl.CSSExpression;
import com.phloc.css.decl.CSSExpressionMemberFunction;
import com.phloc.css.decl.CSSExpressionMemberMath;
import com.phloc.css.decl.CSSExpressionMemberMathProduct;
import com.phloc.css.decl.CSSExpressionMemberMathUnitProduct;
import com.phloc.css.decl.CSSExpressionMemberMathUnitSimple;
import com.phloc.css.decl.CSSExpressionMemberTermSimple;
import com.phloc.css.decl.CSSExpressionMemberTermURI;
import com.phloc.css.decl.CSSFontFaceRule;
import com.phloc.css.decl.CSSImportRule;
import com.phloc.css.decl.CSSKeyframesBlock;
import com.phloc.css.decl.CSSKeyframesRule;
import com.phloc.css.decl.CSSMediaExpression;
import com.phloc.css.decl.CSSMediaQuery;
import com.phloc.css.decl.CSSMediaQuery.EModifier;
import com.phloc.css.decl.CSSMediaRule;
import com.phloc.css.decl.CSSNamespaceRule;
import com.phloc.css.decl.CSSPageRule;
import com.phloc.css.decl.CSSSelector;
import com.phloc.css.decl.CSSSelectorAttribute;
import com.phloc.css.decl.CSSSelectorMemberFunctionLike;
import com.phloc.css.decl.CSSSelectorMemberNot;
import com.phloc.css.decl.CSSSelectorSimpleMember;
import com.phloc.css.decl.CSSStyleRule;
import com.phloc.css.decl.CSSSupportsConditionDeclaration;
import com.phloc.css.decl.CSSSupportsConditionNegation;
import com.phloc.css.decl.CSSSupportsConditionNested;
import com.phloc.css.decl.CSSSupportsRule;
import com.phloc.css.decl.CSSURI;
import com.phloc.css.decl.CSSUnknownRule;
import com.phloc.css.decl.CSSViewportRule;
import com.phloc.css.decl.CascadingStyleSheet;
import com.phloc.css.decl.ECSSAttributeOperator;
import com.phloc.css.decl.ECSSExpressionOperator;
import com.phloc.css.decl.ECSSMathOperator;
import com.phloc.css.decl.ECSSSelectorCombinator;
import com.phloc.css.decl.ECSSSupportsConditionOperator;
import com.phloc.css.decl.ICSSExpressionMember;
import com.phloc.css.decl.ICSSSelectorMember;
import com.phloc.css.decl.ICSSSupportsConditionMember;
import com.phloc.css.media.ECSSMediaExpressionFeature;
import com.phloc.css.media.ECSSMedium;
import com.phloc.css.parser.CSSNode;
import com.phloc.css.parser.ParseUtils;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * This class converts the jjtree node to a domain object. This is where the
 * hard work happens.
 * 
 * @author Philip Helger
 */
@NotThreadSafe
final class CSSNodeToDomainObject
{
  private static final Logger s_aLogger = LoggerFactory.getLogger (CSSNodeToDomainObject.class);

  private final ECSSVersion m_eVersion;

  /**
   * Constructor
   * 
   * @param eVersion
   *        The CSS version to use. May not be null.
   */
  public CSSNodeToDomainObject (@Nonnull final ECSSVersion eVersion)
  {
    m_eVersion = ValueEnforcer.notNull (eVersion, "Version");
  }

  private void _expectNodeType (@Nonnull final CSSNode aNode, @Nonnull final ECSSNodeType eExpected)
  {
    if (!eExpected.isNode (aNode, m_eVersion))
      throw new CSSHandlingException (aNode, "Expected a '" +
                                             eExpected.getNodeName (m_eVersion) +
                                             "' node but received a '" +
                                             ECSSNodeType.getNodeName (aNode, m_eVersion) +
                                             "'");
  }

  private static void _throwUnexpectedChildrenCount (@Nonnull final CSSNode aNode, @Nonnull @Nonempty final String sMsg)
  {
    s_aLogger.error (sMsg);
    for (int i = 0; i < aNode.jjtGetNumChildren (); ++i)
      s_aLogger.error ("  " + aNode.jjtGetChild (i));
    throw new CSSHandlingException (aNode, sMsg);
  }

  @Nonnull
  private CSSImportRule _createImportRule (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.IMPORTRULE);
    final int nChildCount = aNode.jjtGetNumChildren ();
    if (nChildCount > 2)
      _throwUnexpectedChildrenCount (aNode, "Expected at last 2 children but got " + nChildCount + "!");

    CSSURI aImportURI = null;
    int nCurrentIndex = 0;
    if (nChildCount > 0)
    {
      final CSSNode aURINode = aNode.jjtGetChild (0);
      if (ECSSNodeType.URL.isNode (aURINode, m_eVersion))
      {
        aImportURI = new CSSURI (aURINode.getText ());
        aImportURI.setSourceLocation (aURINode.getSourceLocation ());
        ++nCurrentIndex;
      }
      else
        if (!ECSSNodeType.MEDIALIST.isNode (aURINode, m_eVersion))
          throw new IllegalStateException ("Expected an URI or MEDIALIST node but got " +
                                           ECSSNodeType.getNodeName (aURINode, m_eVersion));
    }

    if (aImportURI == null)
    {
      // No URI child node present, so the location is printed directly
      // E.g. @import "abc.css"
      aImportURI = new CSSURI (ParseUtils.extractStringValue (aNode.getText ()));
    }

    // Import rule
    final CSSImportRule ret = new CSSImportRule (aImportURI);
    ret.setSourceLocation (aNode.getSourceLocation ());
    if (nChildCount > nCurrentIndex)
    {
      // We have a media query present!
      final CSSNode aMediaListNode = aNode.jjtGetChild (nCurrentIndex);
      if (ECSSNodeType.MEDIALIST.isNode (aMediaListNode, m_eVersion))
      {
        for (final CSSNode aMediaQueryNode : aMediaListNode)
        {
          ret.addMediaQuery (_createMediaQuery (aMediaQueryNode));
        }
        ++nCurrentIndex;
      }
      else
        s_aLogger.error ("Expected an MEDIALIST node but got " + ECSSNodeType.getNodeName (aMediaListNode, m_eVersion));
    }

    if (nCurrentIndex < nChildCount)
      s_aLogger.error ("Import statement has children which are unhandled.");
    return ret;
  }

  @Nonnull
  private CSSSelectorAttribute _createSelectorAttribute (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.ATTRIB);
    final int nChildren = aNode.jjtGetNumChildren ();

    // Check if a namespace prefix is present
    String sNamespacePrefix = null;
    int nOperatorIndex = 0;
    if (nChildren > 0 && ECSSNodeType.NAMESPACEPREFIX.isNode (aNode.jjtGetChild (0), m_eVersion))
    {
      sNamespacePrefix = aNode.jjtGetChild (0).getText ();
      nOperatorIndex = 1;
    }
    final String sAttrName = aNode.getText ();

    CSSSelectorAttribute ret;
    if (nChildren == nOperatorIndex)
    {
      // Just check for existence of the attribute
      ret = new CSSSelectorAttribute (sNamespacePrefix, sAttrName);
    }
    else
    {
      final int nExpectedChildCount = nOperatorIndex + 2;
      if (nChildren != nExpectedChildCount)
        _throwUnexpectedChildrenCount (aNode, "Illegal number of children present (" +
                                              nChildren +
                                              ") - expected " +
                                              nExpectedChildCount);

      // With operator...
      final CSSNode aOperator = aNode.jjtGetChild (nOperatorIndex);
      _expectNodeType (aOperator, ECSSNodeType.ATTRIBOPERATOR);

      // ...and value
      final CSSNode aAttrValue = aNode.jjtGetChild (nOperatorIndex + 1);
      _expectNodeType (aAttrValue, ECSSNodeType.ATTRIBVALUE);

      ret = new CSSSelectorAttribute (sNamespacePrefix,
                                      sAttrName,
                                      ECSSAttributeOperator.getFromNameOrNull (aOperator.getText ()),
                                      aAttrValue.getText ());
    }
    ret.setSourceLocation (aNode.getSourceLocation ());
    return ret;
  }

  @Nullable
  private ICSSSelectorMember _createSelectorMember (final CSSNode aNode)
  {
    final int nChildCount = aNode.jjtGetNumChildren ();

    if (ECSSNodeType.NAMESPACEPREFIX.isNode (aNode, m_eVersion) ||
        ECSSNodeType.ELEMENTNAME.isNode (aNode, m_eVersion) ||
        ECSSNodeType.HASH.isNode (aNode, m_eVersion) ||
        ECSSNodeType.CLASS.isNode (aNode, m_eVersion))
    {
      if (nChildCount != 0)
        _throwUnexpectedChildrenCount (aNode, "CSS simple selector member expected 0 children and got " + nChildCount);
      final CSSSelectorSimpleMember ret = new CSSSelectorSimpleMember (aNode.getText ());
      ret.setSourceLocation (aNode.getSourceLocation ());
      return ret;
    }

    if (ECSSNodeType.ATTRIB.isNode (aNode, m_eVersion))
      return _createSelectorAttribute (aNode);

    if (ECSSNodeType.SELECTORCOMBINATOR.isNode (aNode, m_eVersion))
    {
      final String sText = aNode.getText ();
      final ECSSSelectorCombinator eCombinator = ECSSSelectorCombinator.getFromNameOrNull (sText);
      if (eCombinator == null)
        s_aLogger.error ("Failed to parse CSS selector combinator '" + sText + "'");
      return eCombinator;
    }

    if (ECSSNodeType.NEGATION.isNode (aNode, m_eVersion))
    {
      // Note: no children don't make sense but are syntactically allowed!
      final List  aNestedSelectors = new ArrayList  ();
      for (int i = 0; i < nChildCount; ++i)
      {
        final CSSNode aChildNode = aNode.jjtGetChild (0);
        final CSSSelector aSelector = _createSelector (aChildNode);
        aNestedSelectors.add (aSelector);
      }

      final CSSSelectorMemberNot ret = new CSSSelectorMemberNot (aNestedSelectors);
      ret.setSourceLocation (aNode.getSourceLocation ());
      return ret;
    }

    if (ECSSNodeType.PSEUDO.isNode (aNode, m_eVersion))
    {
      if (nChildCount == 0)
      {
        // E.g. ":focus" or ":hover"
        final CSSSelectorSimpleMember ret = new CSSSelectorSimpleMember (aNode.getText ());
        ret.setSourceLocation (aNode.getSourceLocation ());
        return ret;
      }

      if (nChildCount == 1)
      {
        final CSSNode aChildNode = aNode.jjtGetChild (0);
        if (ECSSNodeType.NTH.isNode (aChildNode, m_eVersion))
        {
          // Handle nth. E.g. ":nth-child(even)" or ":nth-child(3n+1)"
          final CSSSelectorSimpleMember ret = new CSSSelectorSimpleMember (aNode.getText () +
                                                                           aChildNode.getText () +
                                                                           ")");
          ret.setSourceLocation (aNode.getSourceLocation ());
          return ret;
        }

        // It's a function (e.g. ":lang(fr)")
        final CSSExpression aExpr = _createExpression (aChildNode);
        final CSSSelectorMemberFunctionLike ret = new CSSSelectorMemberFunctionLike (aNode.getText (), aExpr);
        ret.setSourceLocation (aNode.getSourceLocation ());
        return ret;
      }

      throw new UnsupportedOperationException ("Not supporting pseudo-selectors with functions and " +
                                               nChildCount +
                                               " args: " +
                                               aNode.toString ());
    }

    s_aLogger.error ("Unsupported selector child: " + ECSSNodeType.getNodeName (aNode, m_eVersion));
    return null;
  }

  @Nonnull
  private CSSSelector _createSelector (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.SELECTOR);
    final CSSSelector ret = new CSSSelector ();
    ret.setSourceLocation (aNode.getSourceLocation ());
    for (final CSSNode aChildNode : aNode)
    {
      final ICSSSelectorMember aMember = _createSelectorMember (aChildNode);
      if (aMember != null)
        ret.addMember (aMember);
    }
    return ret;
  }

  @Nonnull
  private CSSExpressionMemberMathProduct _createExpressionMathProduct (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.MATHPRODUCT);

    final CSSExpressionMemberMathProduct ret = new CSSExpressionMemberMathProduct ();
    ret.setSourceLocation (aNode.getSourceLocation ());

    // read all sums
    for (final CSSNode aChildNode : aNode)
    {
      if (ECSSNodeType.MATHUNIT.isNode (aChildNode, m_eVersion))
      {
        final int nChildCount = aChildNode.jjtGetNumChildren ();
        if (nChildCount == 0)
        {
          final CSSExpressionMemberMathUnitSimple aMember = new CSSExpressionMemberMathUnitSimple (aChildNode.getText ());
          aMember.setSourceLocation (aChildNode.getSourceLocation ());
          ret.addMember (aMember);
        }
        else
        {
          if (nChildCount != 1)
            _throwUnexpectedChildrenCount (aChildNode, "CSS math unit expected 1 child and got " + nChildCount);

          final CSSNode aChildChildNode = aChildNode.jjtGetChild (0);
          final CSSExpressionMemberMathProduct aNestedProduct = _createExpressionMathProduct (aChildChildNode);
          final CSSExpressionMemberMathUnitProduct aMember = new CSSExpressionMemberMathUnitProduct (aNestedProduct);
          // Source location is taken from aNestedProduct
          ret.addMember (aMember);
        }
      }
      else
        if (ECSSNodeType.MATHPRODUCTOPERATOR.isNode (aChildNode, m_eVersion))
        {
          final String sText = aChildNode.getText ();
          final ECSSMathOperator eMathOp = ECSSMathOperator.getFromNameOrNull (sText);
          if (eMathOp == null)
            s_aLogger.error ("Failed to parse math product operator '" + sText + "'");
          else
            ret.addMember (eMathOp);
        }
        else
          s_aLogger.error ("Unsupported child of " +
                           ECSSNodeType.getNodeName (aNode, m_eVersion) +
                           ": " +
                           ECSSNodeType.getNodeName (aChildNode, m_eVersion));
    }

    return ret;
  }

  @Nonnull
  private CSSExpressionMemberTermURI _createExpressionURL (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.URL);

    final int nChildCount = aNode.jjtGetNumChildren ();
    if (nChildCount > 0)
      _throwUnexpectedChildrenCount (aNode, "Expected 0 children but got " + nChildCount + "!");

    final CSSURI aURI = new CSSURI (aNode.getText ());
    aURI.setSourceLocation (aNode.getSourceLocation ());
    return new CSSExpressionMemberTermURI (aURI);
  }

  @Nonnull
  private CSSExpressionMemberFunction _createExpressionFunction (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.FUNCTION);

    final int nChildCount = aNode.jjtGetNumChildren ();
    if (nChildCount > 1)
      _throwUnexpectedChildrenCount (aNode, "Expected 0 or 1 children but got " + nChildCount + "!");

    final String sFunctionName = aNode.getText ();
    CSSExpressionMemberFunction aFunc;
    if (nChildCount == 1)
    {
      // Parameters present
      final CSSNode aFirstChild = aNode.jjtGetChild (0);
      final CSSExpression aFuncExpr = _createExpression (aFirstChild);
      aFunc = new CSSExpressionMemberFunction (sFunctionName, aFuncExpr);
    }
    else
    {
      // No parameters
      aFunc = new CSSExpressionMemberFunction (sFunctionName);
    }
    aFunc.setSourceLocation (aNode.getSourceLocation ());
    return aFunc;
  }

  @Nonnull
  private CSSExpressionMemberMath _createExpressionMathTerm (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.MATH);

    final CSSExpressionMemberMath ret = new CSSExpressionMemberMath ();
    ret.setSourceLocation (aNode.getSourceLocation ());

    // read all sums
    for (final CSSNode aChildNode : aNode)
    {
      if (ECSSNodeType.MATHPRODUCT.isNode (aChildNode, m_eVersion))
      {
        ret.addMember (_createExpressionMathProduct (aChildNode));
      }
      else
        if (ECSSNodeType.MATHSUMOPERATOR.isNode (aChildNode, m_eVersion))
        {
          final String sText = aChildNode.getText ();
          final ECSSMathOperator eMathOp = ECSSMathOperator.getFromNameOrNull (sText);
          if (eMathOp == null)
            s_aLogger.error ("Failed to parse math operator '" + sText + "'");
          else
            ret.addMember (eMathOp);
        }
        else
          s_aLogger.error ("Unsupported child of " +
                           ECSSNodeType.getNodeName (aNode, m_eVersion) +
                           ": " +
                           ECSSNodeType.getNodeName (aChildNode, m_eVersion));
    }

    return ret;
  }

  @Nonnull
  private ICSSExpressionMember _createExpressionTerm (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.EXPRTERM);
    final int nChildCount = aNode.jjtGetNumChildren ();
    if (nChildCount > 1)
      _throwUnexpectedChildrenCount (aNode, "Expected 0 or 1 children but got " + nChildCount + "!");

    // Simple value
    if (nChildCount == 0)
    {
      final CSSExpressionMemberTermSimple ret = new CSSExpressionMemberTermSimple (aNode.getText ());
      ret.setSourceLocation (aNode.getSourceLocation ());
      return ret;
    }

    final CSSNode aChildNode = aNode.jjtGetChild (0);

    if (ECSSNodeType.URL.isNode (aChildNode, m_eVersion))
    {
      // URI value
      return _createExpressionURL (aChildNode);
    }
    else
      if (ECSSNodeType.FUNCTION.isNode (aChildNode, m_eVersion))
      {
        // function value
        return _createExpressionFunction (aChildNode);
      }
      else
        if (ECSSNodeType.MATH.isNode (aChildNode, m_eVersion))
        {
          // Math value
          return _createExpressionMathTerm (aChildNode);
        }
        else
          throw new IllegalStateException ("Expected an expression term but got " +
                                           ECSSNodeType.getNodeName (aChildNode, m_eVersion));
  }

  @Nonnull
  private CSSExpression _createExpression (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.EXPR);
    final CSSExpression ret = new CSSExpression ();
    ret.setSourceLocation (aNode.getSourceLocation ());
    for (final CSSNode aChildNode : aNode)
    {
      if (ECSSNodeType.EXPRTERM.isNode (aChildNode, m_eVersion))
        ret.addMember (_createExpressionTerm (aChildNode));
      else
        if (ECSSNodeType.EXPROPERATOR.isNode (aChildNode, m_eVersion))
        {
          final String sText = aChildNode.getText ();
          final ECSSExpressionOperator eOp = ECSSExpressionOperator.getFromNameOrNull (sText);
          if (eOp == null)
            s_aLogger.error ("Failed to parse expression operator '" + sText + "'");
          else
            ret.addMember (eOp);
        }
        else
        {
          s_aLogger.error ("Unsupported child of " +
                           ECSSNodeType.getNodeName (aNode, m_eVersion) +
                           ": " +
                           ECSSNodeType.getNodeName (aChildNode, m_eVersion));
        }
    }
    return ret;
  }

  @Nullable
  private CSSDeclaration _createDeclaration (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.STYLEDECLARATION);
    final int nChildCount = aNode.jjtGetNumChildren ();
    if (nChildCount < 1 && nChildCount > 1)
      _throwUnexpectedChildrenCount (aNode, "Expected 1-3 children but got " + nChildCount + "!");

    if (nChildCount == 1)
    {
      // Syntax error. E.g. "color:;"
      return null;
    }

    if (!ECSSNodeType.EXPR.isNode (aNode.jjtGetChild (1), m_eVersion))
    {
      // Syntax error. E.g. "color: !important;"
      return null;
    }

    final String sProperty = aNode.jjtGetChild (0).getText ();
    final CSSExpression aExpression = _createExpression (aNode.jjtGetChild (1));
    boolean bImportant = false;
    if (nChildCount == 3)
    {
      // Must be an "!important" node
      final CSSNode aChildNode = aNode.jjtGetChild (2);
      if (ECSSNodeType.IMPORTANT.isNode (aChildNode, m_eVersion))
        bImportant = true;
      else
        s_aLogger.error ("Expected an " +
                         ECSSNodeType.IMPORTANT.getNodeName (m_eVersion) +
                         " token but got a " +
                         ECSSNodeType.getNodeName (aChildNode, m_eVersion));
    }

    final CSSDeclaration ret = new CSSDeclaration (sProperty, aExpression, bImportant);
    ret.setSourceLocation (aNode.getSourceLocation ());
    return ret;
  }

  @Nonnull
  private CSSStyleRule _createStyleRule (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.STYLERULE);
    final CSSStyleRule ret = new CSSStyleRule ();
    ret.setSourceLocation (aNode.getSourceLocation ());
    boolean bSelectors = true;
    for (final CSSNode aChildNode : aNode)
    {
      if (ECSSNodeType.SELECTOR.isNode (aChildNode, m_eVersion))
      {
        if (!bSelectors)
          s_aLogger.error ("Found a selector after a declaration!");

        ret.addSelector (_createSelector (aChildNode));
      }
      else
      {
        // OK, we're after the selectors
        bSelectors = false;
        if (ECSSNodeType.STYLEDECLARATIONLIST.isNode (aChildNode, m_eVersion))
        {
          // Read all contained declarations
          final int nDecls = aChildNode.jjtGetNumChildren ();
          for (int nDecl = 0; nDecl < nDecls; ++nDecl)
          {
            final CSSNode aChildChildNode = aChildNode.jjtGetChild (nDecl);
            if (!ECSSNodeType.isErrorNode (aChildChildNode, m_eVersion))
            {
              final CSSDeclaration aDeclaration = _createDeclaration (aChildChildNode);
              if (aDeclaration != null)
                ret.addDeclaration (aDeclaration);
            }
          }
        }
        else
          if (!ECSSNodeType.isErrorNode (aChildNode, m_eVersion))
            s_aLogger.error ("Unsupported child of " +
                             ECSSNodeType.getNodeName (aNode, m_eVersion) +
                             ": " +
                             ECSSNodeType.getNodeName (aChildNode, m_eVersion));
      }
    }
    return ret;
  }

  @Nonnull
  @SuppressFBWarnings ("IL_INFINITE_LOOP")
  private CSSPageRule _createPageRule (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.PAGERULE);

    final int nChildCount = aNode.jjtGetNumChildren ();
    String sPseudoPage = null;
    int nStartIndex = 0;
    if (nChildCount > 0)
    {
      final CSSNode aFirstChild = aNode.jjtGetChild (0);
      if (ECSSNodeType.PSEUDOPAGE.isNode (aFirstChild, m_eVersion))
      {
        sPseudoPage = aFirstChild.getText ();
        nStartIndex++;
      }
    }

    final CSSPageRule ret = new CSSPageRule (sPseudoPage);
    ret.setSourceLocation (aNode.getSourceLocation ());
    for (int nIndex = nStartIndex; nIndex < nChildCount; ++nIndex)
    {
      final CSSNode aChildNode = aNode.jjtGetChild (nIndex);

      if (ECSSNodeType.STYLEDECLARATIONLIST.isNode (aChildNode, m_eVersion))
      {
        // Read all contained declarations
        final int nDecls = aChildNode.jjtGetNumChildren ();
        for (int nDecl = 0; nDecl < nDecls; ++nDecl)
        {
          final CSSDeclaration aDeclaration = _createDeclaration (aChildNode.jjtGetChild (nDecl));
          if (aDeclaration != null)
            ret.addDeclaration (aDeclaration);
        }
      }
      else
        if (!ECSSNodeType.isErrorNode (aChildNode, m_eVersion))
          s_aLogger.error ("Unsupported page rule child: " + ECSSNodeType.getNodeName (aChildNode, m_eVersion));
    }
    return ret;
  }

  @Nonnull
  private CSSMediaRule _createMediaRule (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.MEDIARULE);
    final CSSMediaRule ret = new CSSMediaRule ();
    ret.setSourceLocation (aNode.getSourceLocation ());
    for (final CSSNode aChildNode : aNode)
    {
      if (ECSSNodeType.MEDIALIST.isNode (aChildNode, m_eVersion))
      {
        for (final CSSNode aMediaListChildNode : aChildNode)
          ret.addMediaQuery (_createMediaQuery (aMediaListChildNode));
      }
      else
        if (ECSSNodeType.STYLERULE.isNode (aChildNode, m_eVersion))
          ret.addRule (_createStyleRule (aChildNode));
        else
          if (ECSSNodeType.MEDIARULE.isNode (aChildNode, m_eVersion))
          {
            // Nested media rules are OK!
            ret.addRule (_createMediaRule (aChildNode));
          }
          else
            if (ECSSNodeType.PAGERULE.isNode (aChildNode, m_eVersion))
              ret.addRule (_createPageRule (aChildNode));
            else
              if (ECSSNodeType.FONTFACERULE.isNode (aChildNode, m_eVersion))
                ret.addRule (_createFontFaceRule (aChildNode));
              else
                if (ECSSNodeType.KEYFRAMESRULE.isNode (aChildNode, m_eVersion))
                  ret.addRule (_createKeyframesRule (aChildNode));
                else
                  if (ECSSNodeType.VIEWPORTRULE.isNode (aChildNode, m_eVersion))
                    ret.addRule (_createViewportRule (aChildNode));
                  else
                    if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode, m_eVersion))
                      ret.addRule (_createSupportsRule (aChildNode));
                    else
                      if (!ECSSNodeType.isErrorNode (aChildNode, m_eVersion))
                        s_aLogger.error ("Unsupported media-rule child: " +
                                         ECSSNodeType.getNodeName (aChildNode, m_eVersion));
    }
    return ret;
  }

  @Nonnull
  @SuppressFBWarnings ("IL_INFINITE_LOOP")
  private CSSMediaQuery _createMediaQuery (@Nonnull final CSSNode aNode)
  {
    if (ECSSNodeType.MEDIUM.isNode (aNode, m_eVersion))
    {
      // CSS 2.1 compatibility
      final String sMedium = aNode.getText ();
      if (ECSSMedium.getFromNameOrNull (sMedium) == null)
        s_aLogger.warn ("CSS " + m_eVersion.getVersionString () + " Media query uses unknown medium '" + sMedium + "'");
      final CSSMediaQuery ret = new CSSMediaQuery (EModifier.NONE, sMedium);
      ret.setSourceLocation (aNode.getSourceLocation ());
      return ret;
    }

    // CSS 3.0 media query
    _expectNodeType (aNode, ECSSNodeType.MEDIAQUERY);
    final int nChildCount = aNode.jjtGetNumChildren ();

    int nStartIndex = 0;
    EModifier eModifier = EModifier.NONE;

    // Check if a media modifier is present
    if (nChildCount > 0)
    {
      final CSSNode aFirstChildNode = aNode.jjtGetChild (0);
      if (ECSSNodeType.MEDIAMODIFIER.isNode (aFirstChildNode, m_eVersion))
      {
        final String sMediaModifier = aFirstChildNode.getText ();
        // The "mediaModifier" token might be present, but without text!!!
        if (sMediaModifier != null)
        {
          if ("not".equalsIgnoreCase (sMediaModifier))
            eModifier = EModifier.NOT;
          else
            if ("only".equalsIgnoreCase (sMediaModifier))
              eModifier = EModifier.ONLY;
            else
              s_aLogger.error ("Unsupported media modifier '" + sMediaModifier + "' found!");
        }
        ++nStartIndex;
      }
    }

    // Next check if a medium is present
    String sMedium = null;
    if (nChildCount > nStartIndex)
    {
      final CSSNode aNextChild = aNode.jjtGetChild (nStartIndex);
      if (ECSSNodeType.MEDIUM.isNode (aNextChild, m_eVersion))
      {
        sMedium = aNextChild.getText ();
        if (ECSSMedium.getFromNameOrNull (sMedium) == null)
          s_aLogger.warn ("CSS " +
                          m_eVersion.getVersionString () +
                          " media query uses unknown medium '" +
                          sMedium +
                          "'");
        ++nStartIndex;
      }
    }

    final CSSMediaQuery ret = new CSSMediaQuery (eModifier, sMedium);
    ret.setSourceLocation (aNode.getSourceLocation ());
    for (int i = nStartIndex; i < nChildCount; ++i)
    {
      final CSSNode aChildNode = aNode.jjtGetChild (i);
      if (ECSSNodeType.MEDIAEXPR.isNode (aChildNode, m_eVersion))
        ret.addMediaExpression (_createMediaExpr (aChildNode));
      else
        if (!ECSSNodeType.isErrorNode (aChildNode, m_eVersion))
          s_aLogger.error ("Unsupported media query child: " + ECSSNodeType.getNodeName (aChildNode, m_eVersion));
    }
    return ret;
  }

  @Nonnull
  private CSSMediaExpression _createMediaExpr (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.MEDIAEXPR);
    final int nChildCount = aNode.jjtGetNumChildren ();
    if (nChildCount != 1 && nChildCount != 2)
      _throwUnexpectedChildrenCount (aNode, "Expected 1 or 2 children but got " + nChildCount + "!");

    final CSSNode aFeatureNode = aNode.jjtGetChild (0);
    if (!ECSSNodeType.MEDIAFEATURE.isNode (aFeatureNode, m_eVersion))
      throw new IllegalStateException ("Expected a media feature but got " +
                                       ECSSNodeType.getNodeName (aFeatureNode, m_eVersion));
    final String sFeature = aFeatureNode.getText ();
    if (ECSSMediaExpressionFeature.getFromNameOrNull (sFeature) == null)
      s_aLogger.warn ("Media expression uses unknown feature '" + sFeature + "'");

    CSSMediaExpression ret;
    if (nChildCount == 1)
    {
      // Feature only
      ret = new CSSMediaExpression (sFeature);
    }
    else
    {
      // Feature + value
      final CSSNode aValueNode = aNode.jjtGetChild (1);
      ret = new CSSMediaExpression (sFeature, _createExpression (aValueNode));
    }
    ret.setSourceLocation (aNode.getSourceLocation ());
    return ret;
  }

  @Nonnull
  private CSSFontFaceRule _createFontFaceRule (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.FONTFACERULE);
    final CSSFontFaceRule ret = new CSSFontFaceRule ();
    ret.setSourceLocation (aNode.getSourceLocation ());
    for (final CSSNode aChildNode : aNode)
    {
      if (ECSSNodeType.STYLEDECLARATIONLIST.isNode (aChildNode, m_eVersion))
      {
        // Read all contained declarations
        final int nDecls = aChildNode.jjtGetNumChildren ();
        for (int nDecl = 0; nDecl < nDecls; ++nDecl)
        {
          final CSSDeclaration aDeclaration = _createDeclaration (aChildNode.jjtGetChild (nDecl));
          if (aDeclaration != null)
            ret.addDeclaration (aDeclaration);
        }
      }
      else
        if (!ECSSNodeType.isErrorNode (aChildNode, m_eVersion))
          s_aLogger.error ("Unsupported font-face rule child: " + ECSSNodeType.getNodeName (aChildNode, m_eVersion));
    }
    return ret;
  }

  @Nonnull
  private CSSKeyframesRule _createKeyframesRule (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.KEYFRAMESRULE);
    final int nChildCount = aNode.jjtGetNumChildren ();
    if (nChildCount == 0)
      _throwUnexpectedChildrenCount (aNode, "Expected at least 1 child but got " + nChildCount + "!");

    // Get the identifier (e.g. the default "@keyframes" or the non-standard
    // "@-webkit-keyframes")
    final String sKeyframesDeclaration = aNode.getText ();

    // get the name of the animation
    final CSSNode aAnimationNameNode = aNode.jjtGetChild (0);
    _expectNodeType (aAnimationNameNode, ECSSNodeType.KEYFRAMESIDENTIFIER);
    final String sAnimationName = aAnimationNameNode.getText ();

    final CSSKeyframesRule ret = new CSSKeyframesRule (sKeyframesDeclaration, sAnimationName);
    ret.setSourceLocation (aNode.getSourceLocation ());

    // Get the key frame blocks
    int nIndex = 1;
    CSSKeyframesBlock aBlock = null;
    while (nIndex < nChildCount)
    {
      final CSSNode aChildNode = aNode.jjtGetChild (nIndex);
      if (ECSSNodeType.KEYFRAMESSELECTOR.isNode (aChildNode, m_eVersion))
      {
        // Read all single selectors
        final List  aKeyframesSelectors = new ArrayList  ();
        for (final CSSNode aSelectorChild : aChildNode)
        {
          _expectNodeType (aSelectorChild, ECSSNodeType.SINGLEKEYFRAMESELECTOR);
          aKeyframesSelectors.add (aSelectorChild.getText ());
        }
        aBlock = new CSSKeyframesBlock (aKeyframesSelectors);
        aBlock.setSourceLocation (aChildNode.getSourceLocation ());
        ret.addBlock (aBlock);
      }
      else
        if (ECSSNodeType.STYLEDECLARATIONLIST.isNode (aChildNode, m_eVersion))
        {
          if (aBlock == null)
            throw new IllegalStateException ("No keyframes block present!");

          // Read all contained declarations
          final int nDecls = aChildNode.jjtGetNumChildren ();
          for (int nDecl = 0; nDecl < nDecls; ++nDecl)
          {
            final CSSDeclaration aDeclaration = _createDeclaration (aChildNode.jjtGetChild (nDecl));
            if (aDeclaration != null)
              aBlock.addDeclaration (aDeclaration);
          }
        }
        else
          if (!ECSSNodeType.isErrorNode (aChildNode, m_eVersion))
            s_aLogger.error ("Unsupported keyframes rule child: " + ECSSNodeType.getNodeName (aChildNode, m_eVersion));

      ++nIndex;
    }
    return ret;
  }

  @Nonnull
  private CSSViewportRule _createViewportRule (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.VIEWPORTRULE);

    // Get the identifier (e.g. the default "@viewport" or the non-standard
    // "@-ms-viewport")
    final String sViewportDeclaration = aNode.getText ();

    final CSSViewportRule ret = new CSSViewportRule (sViewportDeclaration);
    ret.setSourceLocation (aNode.getSourceLocation ());
    for (final CSSNode aChildNode : aNode)
    {
      if (ECSSNodeType.STYLEDECLARATIONLIST.isNode (aChildNode, m_eVersion))
      {
        // Read all contained declarations
        final int nDecls = aChildNode.jjtGetNumChildren ();
        for (int nDecl = 0; nDecl < nDecls; ++nDecl)
        {
          final CSSDeclaration aDeclaration = _createDeclaration (aChildNode.jjtGetChild (nDecl));
          if (aDeclaration != null)
            ret.addDeclaration (aDeclaration);
        }
      }
      else
        if (!ECSSNodeType.isErrorNode (aChildNode, m_eVersion))
          s_aLogger.error ("Unsupported viewport rule child: " + ECSSNodeType.getNodeName (aChildNode, m_eVersion));
    }
    return ret;
  }

  @Nonnull
  private CSSNamespaceRule _createNamespaceRule (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.NAMESPACERULE);
    final int nChildCount = aNode.jjtGetNumChildren ();
    if (nChildCount < 1 || nChildCount > 2)
      _throwUnexpectedChildrenCount (aNode, "Expected at least 1 child and at last 2 children but got " +
                                            nChildCount +
                                            "!");

    String sPrefix = null;
    int nURLIndex = 0;
    if (ECSSNodeType.NAMESPACERULEPREFIX.isNode (aNode.jjtGetChild (0), m_eVersion))
    {
      sPrefix = aNode.jjtGetChild (0).getText ();
      nURLIndex++;
    }

    final CSSNode aURLNode = aNode.jjtGetChild (nURLIndex);
    _expectNodeType (aURLNode, ECSSNodeType.NAMESPACERULEURL);
    final String sURL = ParseUtils.extractStringValue (aURLNode.getText ());

    final CSSNamespaceRule ret = new CSSNamespaceRule (sPrefix, sURL);
    ret.setSourceLocation (aNode.getSourceLocation ());
    return ret;
  }

  @Nullable
  private ICSSSupportsConditionMember _createSupportsConditionMemberRecursive (@Nonnull final CSSNode aNode)
  {
    final int nChildCount = aNode.jjtGetNumChildren ();

    if (ECSSNodeType.SUPPORTSCONDITIONOPERATOR.isNode (aNode, m_eVersion))
    {
      if (nChildCount != 0)
        _throwUnexpectedChildrenCount (aNode, "Expected no children but got " + nChildCount + "!");

      return ECSSSupportsConditionOperator.getFromNameCaseInsensitiveOrNull (aNode.getText ());
    }

    if (ECSSNodeType.SUPPORTSNEGATION.isNode (aNode, m_eVersion))
    {
      if (nChildCount != 1)
        _throwUnexpectedChildrenCount (aNode, "Expected at exactly 1 child but got " + nChildCount + "!");

      final ICSSSupportsConditionMember aNestedMember = _createSupportsConditionMemberRecursive (aNode.jjtGetChild (0));
      if (aNestedMember == null)
        return null;

      final CSSSupportsConditionNegation ret = new CSSSupportsConditionNegation (aNestedMember);
      ret.setSourceLocation (aNode.getSourceLocation ());
      return ret;
    }

    if (ECSSNodeType.SUPPORTSCONDITIONINPARENS.isNode (aNode, m_eVersion))
    {
      if (nChildCount != 1)
        _throwUnexpectedChildrenCount (aNode, "Expected at exactly 1 child but got " + nChildCount + "!");

      final CSSNode aChildNode = aNode.jjtGetChild (0);

      if (ECSSNodeType.STYLEDECLARATION.isNode (aChildNode, m_eVersion))
      {
        final CSSDeclaration aDeclaration = _createDeclaration (aChildNode);
        if (aDeclaration == null)
          throw new CSSHandlingException (aChildNode, "The style declaration in the @supports rule is invalid!");
        final CSSSupportsConditionDeclaration ret = new CSSSupportsConditionDeclaration (aDeclaration);
        ret.setSourceLocation (aNode.getSourceLocation ());
        return ret;
      }

      if (ECSSNodeType.SUPPORTSCONDITION.isNode (aChildNode, m_eVersion))
      {
        final CSSSupportsConditionNested ret = new CSSSupportsConditionNested ();
        for (final CSSNode aChildChildNode : aChildNode)
        {
          final ICSSSupportsConditionMember aMember = _createSupportsConditionMemberRecursive (aChildChildNode);
          if (aMember != null)
            ret.addMember (aMember);
        }
        return ret;
      }

      s_aLogger.error ("Unsupported supportsConditionInParents child: " +
                       ECSSNodeType.getNodeName (aChildNode, m_eVersion));
      return null;
    }

    if (!ECSSNodeType.isErrorNode (aNode, m_eVersion))
      s_aLogger.error ("Unsupported supports-condition child: " + ECSSNodeType.getNodeName (aNode, m_eVersion));

    return null;
  }

  @Nonnull
  private CSSSupportsRule _createSupportsRule (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.SUPPORTSRULE);
    final CSSSupportsRule ret = new CSSSupportsRule ();
    ret.setSourceLocation (aNode.getSourceLocation ());
    for (final CSSNode aChildNode : aNode)
    {
      if (ECSSNodeType.SUPPORTSCONDITION.isNode (aChildNode, m_eVersion))
      {
        for (final CSSNode aChildChildNode : aChildNode)
        {
          final ICSSSupportsConditionMember aMember = _createSupportsConditionMemberRecursive (aChildChildNode);
          if (aMember != null)
            ret.addSupportConditionMember (aMember);
        }
      }
      else
        if (ECSSNodeType.STYLERULE.isNode (aChildNode, m_eVersion))
          ret.addRule (_createStyleRule (aChildNode));
        else
          if (ECSSNodeType.MEDIARULE.isNode (aChildNode, m_eVersion))
            ret.addRule (_createMediaRule (aChildNode));
          else
            if (ECSSNodeType.PAGERULE.isNode (aChildNode, m_eVersion))
              ret.addRule (_createPageRule (aChildNode));
            else
              if (ECSSNodeType.FONTFACERULE.isNode (aChildNode, m_eVersion))
                ret.addRule (_createFontFaceRule (aChildNode));
              else
                if (ECSSNodeType.KEYFRAMESRULE.isNode (aChildNode, m_eVersion))
                  ret.addRule (_createKeyframesRule (aChildNode));
                else
                  if (ECSSNodeType.VIEWPORTRULE.isNode (aChildNode, m_eVersion))
                    ret.addRule (_createViewportRule (aChildNode));
                  else
                    if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode, m_eVersion))
                      ret.addRule (_createSupportsRule (aChildNode));
                    else
                      if (!ECSSNodeType.isErrorNode (aChildNode, m_eVersion))
                        s_aLogger.error ("Unsupported supports-rule child: " +
                                         ECSSNodeType.getNodeName (aChildNode, m_eVersion));
    }
    return ret;
  }

  @Nonnull
  private CSSUnknownRule _createUnknownRule (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.UNKNOWNRULE);

    final int nChildCount = aNode.jjtGetNumChildren ();
    if (nChildCount != 2)
      _throwUnexpectedChildrenCount (aNode, "Expected 2 children but got " + nChildCount + "!");

    final CSSNode aParameterList = aNode.jjtGetChild (0);
    _expectNodeType (aParameterList, ECSSNodeType.UNKNOWNRULEPARAMETERLIST);

    final CSSNode aBody = aNode.jjtGetChild (1);
    _expectNodeType (aBody, ECSSNodeType.UNKNOWNRULEBODY);

    // Get the name of the rule
    final String sRuleDeclaration = aNode.getText ();

    final CSSUnknownRule ret = new CSSUnknownRule (sRuleDeclaration);
    ret.setSourceLocation (aNode.getSourceLocation ());
    ret.setParameterList (aParameterList.getText ());
    ret.setBody (aBody.getText ());
    return ret;
  }

  @Nonnull
  public CascadingStyleSheet createCascadingStyleSheetFromNode (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.ROOT);
    final CascadingStyleSheet ret = new CascadingStyleSheet ();
    ret.setSourceLocation (aNode.getSourceLocation ());
    for (final CSSNode aChildNode : aNode)
    {
      if (ECSSNodeType.CHARSET.isNode (aChildNode, m_eVersion))
      {
        // Ignore because this was handled when reading!
      }
      else
        if (ECSSNodeType.IMPORTRULE.isNode (aChildNode, m_eVersion))
          ret.addImportRule (_createImportRule (aChildNode));
        else
          if (ECSSNodeType.NAMESPACERULE.isNode (aChildNode, m_eVersion))
            ret.addNamespaceRule (_createNamespaceRule (aChildNode));
          else
            if (ECSSNodeType.STYLERULE.isNode (aChildNode, m_eVersion))
              ret.addRule (_createStyleRule (aChildNode));
            else
              if (ECSSNodeType.PAGERULE.isNode (aChildNode, m_eVersion))
                ret.addRule (_createPageRule (aChildNode));
              else
                if (ECSSNodeType.MEDIARULE.isNode (aChildNode, m_eVersion))
                  ret.addRule (_createMediaRule (aChildNode));
                else
                  if (ECSSNodeType.FONTFACERULE.isNode (aChildNode, m_eVersion))
                    ret.addRule (_createFontFaceRule (aChildNode));
                  else
                    if (ECSSNodeType.KEYFRAMESRULE.isNode (aChildNode, m_eVersion))
                      ret.addRule (_createKeyframesRule (aChildNode));
                    else
                      if (ECSSNodeType.VIEWPORTRULE.isNode (aChildNode, m_eVersion))
                        ret.addRule (_createViewportRule (aChildNode));
                      else
                        if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode, m_eVersion))
                          ret.addRule (_createSupportsRule (aChildNode));
                        else
                          if (ECSSNodeType.UNKNOWNRULE.isNode (aChildNode, m_eVersion))
                          {
                            // Unknown rule indicates either
                            // 1. a parsing error
                            // 2. a non-standard rule
                            ret.addRule (_createUnknownRule (aChildNode));
                          }
                          else
                            s_aLogger.error ("Unsupported child of " +
                                             ECSSNodeType.getNodeName (aNode, m_eVersion) +
                                             ": " +
                                             ECSSNodeType.getNodeName (aChildNode, m_eVersion));
    }
    return ret;
  }

  @Nonnull
  public CSSDeclarationList createDeclarationListFromNode (@Nonnull final CSSNode aNode)
  {
    _expectNodeType (aNode, ECSSNodeType.STYLEDECLARATIONLIST);
    final CSSDeclarationList ret = new CSSDeclarationList ();
    ret.setSourceLocation (aNode.getSourceLocation ());
    final int nDecls = aNode.jjtGetNumChildren ();
    for (int nDecl = 0; nDecl < nDecls; ++nDecl)
    {
      final CSSDeclaration aDeclaration = _createDeclaration (aNode.jjtGetChild (nDecl));
      if (aDeclaration != null)
        ret.addDeclaration (aDeclaration);
    }
    return ret;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy