@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 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 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;
  }
 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
  @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
 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;
 }
  @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 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
 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 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 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 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 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(CSSParseHelper.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 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 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;
 }
  @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 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
  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
 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
  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 = CSSParseHelper.extractStringValue(aURLNode.getText());

    final CSSNamespaceRule ret = new CSSNamespaceRule(sPrefix, sURL);
    ret.setSourceLocation(aNode.getSourceLocation());
    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
  @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;
  }
  @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<CSSSelector> aNestedSelectors = new ArrayList<CSSSelector>();
      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;
  }
  @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 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;
  }
  @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<String> aKeyframesSelectors = new ArrayList<String>();
        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;
  }