processDirective method

CSSRule? processDirective()

Directive grammar:

import:             '@import' [string | URI] media_list?
media:              '@media' media_query_list '{' ruleset '}'
page:               '@page' [':' IDENT]? '{' declarations '}'
stylet:             '@stylet' IDENT '{' ruleset '}'
media_query_list:   IDENT [',' IDENT]
keyframes:          '@-webkit-keyframes ...' (see grammar below).
font_face:          '@font-face' '{' declarations '}'
namespace:          '@namespace name url("xmlns")
host:               '@host '{' ruleset '}'
mixin:              '@mixin name [(args,...)] '{' declarations/ruleset '}'
include:            '@include name [(@arg,@arg1)]
                    '@include name [(@arg...)]
content:            '@content'
-moz-document:      '@-moz-document' [ <url> | url-prefix(<string>) |
                        domain(<string>) | regexp(<string) ]# '{'
                      declarations
                    '}'
supports:           '@supports' supports_condition group_rule_body

Implementation

CSSRule? processDirective() {
  var tokenId = _peek();
  switch (tokenId) {
    case TokenKind.DIRECTIVE_IMPORT:
      _next();
      // Parse @import url or string and optional media list
      String importHref = '';
      String? mediaText;

      // @import "file.css" ...;
      if (_peek() == TokenKind.SINGLE_QUOTE || _peek() == TokenKind.DOUBLE_QUOTE) {
        importHref = processQuotedString(false);
      } else if (_peekIdentifier() && _peekToken.text.toLowerCase() == 'url') {
        // @import url(file.css) ...;
        // Consume 'url' and parse inside parentheses.
        _next();
        // processQuotedString(true) will optionally consume '('
        final urlParam = processQuotedString(true);
        // If processQuotedString(true) didn't consume a trailing ')', do it here.
        if (_peek() == TokenKind.RPAREN) {
          _next();
        }
        importHref = urlParam.trim();
      }

      // Parse optional media list until ';'
      if (!_peekKind(TokenKind.SEMICOLON)) {
        final buf = StringBuffer();
        while (!_peekKind(TokenKind.SEMICOLON) && !_peekKind(TokenKind.END_OF_FILE) && !_peekKind(TokenKind.LBRACE)) {
          buf.write(_next().text);
        }
        final text = buf.toString().trim();
        if (text.isNotEmpty) mediaText = text;
      }
      _maybeEat(TokenKind.SEMICOLON);
      return CSSImportRule(importHref, media: mediaText);

    case TokenKind.DIRECTIVE_MEDIA:
      _next();
      // print('processDirective CSSMediaDirective start -----  TokenKind.DIRECTIVE_MEDIA');
      CSSMediaQuery? cssMediaQuery = processMediaQuery();
      if (cssMediaQuery != null) {
        _next();
      }
      List<CSSRule>? rules = [];
      do {
        List<CSSRule>? rule = processRule();
        if (rule != null) {
          rules.addAll(rule);
        }
        else {
          break;
        }
      } while (!_maybeEat(TokenKind.RBRACE));
      // rules.forEach((rule) {
      //   if (rule is CSSStyleRule) {
      //     print(' ----> processDirective CSSMediaDirective forEach ${rule.selectorGroup.selectorText}, color ${rule.declaration.getPropertyValue('color')}');
      //   } else {
      //     print(' ----> processDirective CSSMediaDirective forEach ${rule.runtimeType}');
      //   }
      // });
      // print('processDirective CSSMediaDirective end -----   rules ${rules.length}  TokenKind.DIRECTIVE_MEDIA');
      return CSSMediaDirective(cssMediaQuery, rules);
    case TokenKind.DIRECTIVE_HOST:
      _next();

      return null;

    case TokenKind.DIRECTIVE_PAGE:
      // @page S* IDENT? pseudo_page?
      //      S* '{' S*
      //      [ declaration | margin ]?
      //      [ ';' S* [ declaration | margin ]? ]* '}' S*
      //
      // pseudo_page :
      //      ':' [ "left" | "right" | "first" ]
      //
      // margin :
      //      margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
      //
      // margin_sym : @top-left-corner, @top-left, @bottom-left, etc.
      //
      // See http://www.w3.org/TR/css3-page/#CSS21
      _next();

      return null;
    case TokenKind.DIRECTIVE_CHARSET:
      // @charset S* STRING S* ';'
      _next();

      processQuotedString(false);

      return null;

    // TODO(terry): Workaround Dart2js bug continue not implemented in switch
    //              see https://code.google.com/p/dart/issues/detail?id=8270
    /*
    case TokenKind.DIRECTIVE_MS_KEYFRAMES:
      // TODO(terry): For now only IE 10 (are base level) supports @keyframes,
      // -moz- has only been optional since Oct 2012 release of Firefox, not
      // all versions of webkit support @keyframes and opera doesn't yet
      // support w/o -o- prefix.  Add more warnings for other prefixes when
      // they become optional.
      if (isChecked) {
        _warning('@-ms-keyframes should be @keyframes');
      }
      continue keyframeDirective;

    keyframeDirective:
    */
    case TokenKind.DIRECTIVE_KEYFRAMES:
    case TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES:
    case TokenKind.DIRECTIVE_MOZ_KEYFRAMES:
    case TokenKind.DIRECTIVE_O_KEYFRAMES:
    // TODO(terry): Remove workaround when bug 8270 is fixed.
    case TokenKind.DIRECTIVE_MS_KEYFRAMES:
      if (tokenId == TokenKind.DIRECTIVE_MS_KEYFRAMES && isChecked) {
        _warning('@-ms-keyframes should be @keyframes');
      }
      // TODO(terry): End of workaround.

      // Key frames grammar:
      //
      //     @[browser]? keyframes [IDENT|STRING] '{' keyframes-blocks '}';
      //
      //     browser: [-webkit-, -moz-, -ms-, -o-]
      //
      //     keyframes-blocks:
      //       [keyframe-selectors '{' declarations '}']* ;
      //
      //     keyframe-selectors:
      //       ['from'|'to'|PERCENTAGE] [',' ['from'|'to'|PERCENTAGE] ]* ;
      _next();

      String name = '';
      if (_peekIdentifier()) {
        name = identifier().name;
      }
      assert(name.isNotEmpty, 'keyframes rule name must not be null');
      _eat(TokenKind.LBRACE);

      var keyframe = CSSKeyframesRule(tokenId, name);
      do {
        List<String> selectors = [];
        do {
          var selector = _next().text;
          final text = _peekToken.text;
          // ignore unit type
          if (TokenKind.matchUnits(text, 0, text.length) != -1) {
            if (_peekToken.kind == TokenKind.PERCENT) {
              selector += text; // join selector & unit
            }
            _next();
          }
          selectors.add(selector);
        } while (_maybeEat(TokenKind.COMMA));

        final declarations = processDeclarations();
        if (declarations.last is CSSStyleDeclaration) {
          keyframe.add(KeyFrameBlock(selectors, declarations.last));
        }
      } while (!_maybeEat(TokenKind.RBRACE) && !isPrematureEndOfFile());

      return keyframe;

    case TokenKind.DIRECTIVE_FONTFACE:
      _next();
      _eat(TokenKind.LBRACE);
      List data = processDeclarations();
      assert(data.isNotEmpty);
      return CSSFontFaceRule(data[0]);
    case TokenKind.DIRECTIVE_STYLET:
      // Stylet grammar:
      //
      //     @stylet IDENT '{'
      //       ruleset
      //     '}'
      _next();

      return null;
    case TokenKind.DIRECTIVE_NAMESPACE:
      // Namespace grammar:
      //
      // @namespace S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
      // namespace_prefix : IDENT
      _next();

      return null;

    case TokenKind.DIRECTIVE_MIXIN:
      return null;

    case TokenKind.DIRECTIVE_INCLUDE:
      return null;
    case TokenKind.DIRECTIVE_CONTENT:
      // TODO(terry): TBD
      _warning('@content not implemented.');
      return null;
    case TokenKind.DIRECTIVE_MOZ_DOCUMENT:
      return null;
    case TokenKind.DIRECTIVE_SUPPORTS:
      return null;
    case TokenKind.DIRECTIVE_VIEWPORT:
    case TokenKind.DIRECTIVE_MS_VIEWPORT:
      return null;
  }
  return null;
}