@Nullable
    private Pair<String, String> parseAttribute() {
      ZenCodingToken token = nextToken();
      if (!(token instanceof IdentifierToken)) {
        return null;
      }

      final String name = ((IdentifierToken) token).getText();

      myIndex++;
      token = nextToken();
      if (token != ZenCodingTokens.EQ) {
        return new Pair<String, String>(name, "");
      }

      myIndex++;
      final StringBuilder attrValueBuilder = new StringBuilder();
      String value;
      do {
        token = nextToken();
        value =
            token != null && token == ZenCodingTokens.SHARP
                ? token.toString()
                : getAttributeValueByToken(token);
        if (value != null) {
          attrValueBuilder.append(value);
          myIndex++;
        }
      } while (value != null);
      return new Pair<String, String>(name, attrValueBuilder.toString());
    }
    @SuppressWarnings("unchecked")
    @NotNull
    private List<Pair<String, String>> parseSelectors() {
      final List<Pair<String, String>> result = new ArrayList<Pair<String, String>>();

      int classAttrPosition = -1;
      int idAttrPosition = -1;

      final StringBuilder classAttrBuilder = new StringBuilder();
      final StringBuilder idAttrBuilder = new StringBuilder();

      while (true) {
        final List<Pair<String, String>> attrList = parseSelector();
        if (attrList == null) {
          if (classAttrPosition != -1) {
            result.set(
                classAttrPosition, new Pair<String, String>(CLASS, classAttrBuilder.toString()));
          }
          if (idAttrPosition != -1) {
            result.set(idAttrPosition, new Pair<String, String>(ID, idAttrBuilder.toString()));
          }
          return result;
        }

        for (Pair<String, String> attr : attrList) {
          if (CLASS.equals(attr.first)) {
            if (classAttrBuilder.length() > 0) {
              classAttrBuilder.append(' ');
            }
            classAttrBuilder.append(attr.second);
            if (classAttrPosition == -1) {
              classAttrPosition = result.size();
              result.add(attr);
            }
          } else if (ID.equals(attr.first)) {
            if (idAttrBuilder.length() > 0) {
              idAttrBuilder.append(' ');
            }
            idAttrBuilder.append(attr.second);
            if (idAttrPosition == -1) {
              idAttrPosition = result.size();
              result.add(attr);
            }
          } else {
            result.add(attr);
          }
        }
      }
    }
  @Nullable
  private static List<ZenCodingToken> lex(@NotNull String text) {
    text += MARKER;
    final List<ZenCodingToken> result = new ArrayList<ZenCodingToken>();

    boolean inQuotes = false;
    boolean inApostrophes = false;
    int bracesStack = 0;

    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < text.length(); i++) {
      final char c = text.charAt(i);

      if (inQuotes) {
        builder.append(c);
        if (c == '"') {
          inQuotes = false;
          result.add(new StringLiteralToken(builder.toString()));
          builder = new StringBuilder();
        }
        continue;
      }

      if (inApostrophes) {
        builder.append(c);
        if (c == '\'') {
          inApostrophes = false;
          result.add(new StringLiteralToken(builder.toString()));
          builder = new StringBuilder();
        }
        continue;
      }

      if (bracesStack > 0) {
        builder.append(c);
        if (c == '}') {
          bracesStack--;
          if (bracesStack == 0) {
            result.add(new TextToken(builder.toString()));
            builder = new StringBuilder();
          }
        } else if (c == '{') {
          bracesStack++;
        }
        continue;
      }

      if (DELIMS.indexOf(c) < 0) {
        builder.append(c);
      } else {
        // handle special case: ul+ template
        if (c == '+' && (i == text.length() - 2 || text.charAt(i + 1) == ')')) {
          builder.append(c);
          continue;
        }

        if (builder.length() > 0) {
          final String tokenText = builder.toString();
          final int n = parseNonNegativeInt(tokenText);
          if (n >= 0) {
            result.add(new NumberToken(n));
          } else {
            result.add(new IdentifierToken(tokenText));
          }
          builder = new StringBuilder();
        }
        if (c == '"') {
          inQuotes = true;
          builder.append(c);
        } else if (c == '\'') {
          inApostrophes = true;
          builder.append(c);
        } else if (c == '{') {
          bracesStack = 1;
          builder.append(c);
        } else if (c == '(') {
          result.add(ZenCodingTokens.OPENING_R_BRACKET);
        } else if (c == ')') {
          result.add(ZenCodingTokens.CLOSING_R_BRACKET);
        } else if (c == '[') {
          result.add(ZenCodingTokens.OPENING_SQ_BRACKET);
        } else if (c == ']') {
          result.add(ZenCodingTokens.CLOSING_SQ_BRACKET);
        } else if (c == '=') {
          result.add(ZenCodingTokens.EQ);
        } else if (c == '.') {
          result.add(ZenCodingTokens.DOT);
        } else if (c == '#') {
          result.add(ZenCodingTokens.SHARP);
        } else if (c == ',') {
          result.add(ZenCodingTokens.COMMA);
        } else if (c == ' ') {
          result.add(ZenCodingTokens.SPACE);
        } else if (c == '|') {
          result.add(ZenCodingTokens.PIPE);
        } else if (c != MARKER) {
          result.add(new OperationToken(c));
        }
      }
    }
    if (bracesStack != 0 || inQuotes || inApostrophes) {
      return null;
    }
    return result;
  }