parse static method

CSSStyleRule? parse(
  1. String ruleText
)

Implementation

static CSSStyleRule? parse(String ruleText) {
  String selectorText = _EMPTY_STRING;
  Map<String, String> style = {};

  StringBuffer buffer = StringBuffer();
  int state = _SELECTOR;
  String propertyName = _EMPTY_STRING;
  bool isString = false;
  bool isCustomProperty = false;

  for (int pos = 0, length = ruleText.length; pos < length && state != _END; pos++) {
    int c = ruleText.codeUnitAt(pos);

    if (c == SINGLE_QUOTE_CODE || c == DOUBLE_QUOTE_CODE) {
      isString = !isString;
    }

    if (isString) {
      buffer.writeCharCode(c);
      continue;
    }

    switch (c) {
      case SPACE_CODE:
      case TAB_CODE:
      case CR_CODE:
      case FEED_CODE:
      case NEWLINE_CODE:
        if ((state == _SELECTOR || state == _VALUE) && pos > 0) {
          // Squash 2 or more white-spaces in the row into 1 space.
          switch (ruleText.codeUnitAt(pos - 1)) {
            case SPACE_CODE:
            case TAB_CODE:
            case CR_CODE:
            case FEED_CODE:
            case NEWLINE_CODE:
              break;
            default:
              buffer.writeCharCode(SPACE_CODE);
              break;
          }
        } else if (state == _FUNCTION) {
          buffer.writeCharCode(c);
        }
        break;
      case HYPHEN_CODE:
        if (state == _NAME && isCustomProperty == false) {
          int letter = ruleText.codeUnitAt(pos + 1);
          if (letter == HYPHEN_CODE) {
            // Ignore css custom properties: `--main-bg-color`.
            buffer.writeCharCode(c);
            isCustomProperty = true;
            break;
          }
          // Convert background-image to backgroundImage
          // a-z: 97-122
          if (letter > 96 && letter < 123) {
            // Convert to upper case: A-Z: 65-90
            letter = letter - 32;
            buffer.writeCharCode(letter);
            pos++;
          }
        } else {
          buffer.writeCharCode(c);
        }
        break;
      case SLASH_CODE:
        if (ruleText.codeUnitAt(pos + 1) == ASTERISK_CODE) {
          // This is a comment, find the end of the comment.
          pos += 2;
          int index = ruleText.indexOf(_END_OF_COMMENT, pos);
          if (index == -1) {
            // Unterminated comment
            state = _END;
          } else {
            pos = index + 2;
          }
        } else {
          buffer.writeCharCode(c);
        }
        break;
      case OPEN_CURLY_CODE:
        if (state == _SELECTOR) {
          selectorText = buffer.toString().trim();
          if (selectorText.isEmpty) {
            // Invalid syntax
            state = _END;
          }
          buffer.clear();
          state = _NAME;
        } else {
          // Unexpected { : `.foo { .foo {}; color: red}`
          buffer.writeCharCode(c);
        }
        break;
      case COLON_CODE:
        if (state == _NAME) {
          propertyName = buffer.toString().trim();
          buffer.clear();
          // Reset isCustomProperty flag.
          isCustomProperty = false;
          state = _VALUE;
        } else {
          buffer.writeCharCode(c);
        }
        break;
      case CLOSE_PARENTHESES_CODE:
        if (state == _FUNCTION) {
          buffer.writeCharCode(c);
          state = _VALUE;
        } else {
          buffer.writeCharCode(c);
        }
        break;
      case OPEN_PARENTHESES_CODE:
        // This is a function, find the end of the function.
        if (state == _VALUE) {
          state = _FUNCTION;
        }

        // Pseudo-class selector: `th:nth-child(4)`
        // Function value: `url()`, `rgb()`
        buffer.writeCharCode(c); // Write (
        break;
      case SEMICOLON_CODE:
        if (state == _FUNCTION) {
          // In data uri function
          buffer.writeCharCode(c);
        } else {
          // `{ col;or: red; }` will parsed as {col: '', or: 'red'}
          if (propertyName.isNotEmpty) {
            String value = buffer.toString().trim();
            if (value.isNotEmpty) style[propertyName] = value;
            propertyName = _EMPTY_STRING;
          }
          buffer.clear();
          // Skip empty property declaration like `color: red; ;;`.
          state = _NAME;
        }
        break;
      case CLOSE_CURLY_CODE:
        if (state == _VALUE && propertyName.isNotEmpty) {
          // `body { color: red }` that not end with semicolon is
          // also the end of the declaration.
          style[propertyName] = buffer.toString().trim();
          state = _END;
        } else if (state == _NAME) {
          // `.foo { .foo {}; color: red }`
          buffer.writeCharCode(c);
        } else {
          // Unexpected } : `.fo } { color: red }`
          state = _END;
        }
        break;
      default:
        buffer.writeCharCode(c);
    }
  }

  if (selectorText.isNotEmpty) {
    return CSSStyleRule(selectorText, style);
  }
}