RouteTemplate.parse constructor

RouteTemplate.parse(
  1. String source, {
  2. bool allowQuery = true,
})

Implementation

factory RouteTemplate.parse(String source, {bool allowQuery = true}) {
  final tokens = <RouteTemplateToken>[];
  var literalStart = 0;
  var cursor = 0;

  void addLiteral(int end) {
    if (end > literalStart) {
      tokens.add(RouteTemplateToken.literal(
        source.substring(literalStart, end),
      ));
    }
  }

  while (cursor < source.length) {
    final character = source[cursor];
    if (allowQuery && (character == '?' || character == '#')) {
      addLiteral(cursor);
      return RouteTemplate._(tokens, source.substring(cursor));
    }
    if (character != ':' ||
        cursor + 1 == source.length ||
        !_isNameCharacter(source.codeUnitAt(cursor + 1))) {
      cursor++;
      continue;
    }

    final start = cursor > literalStart &&
            (source[cursor - 1] == '/' || source[cursor - 1] == '.')
        ? cursor - 1
        : cursor;
    addLiteral(start);
    final prefix = source.substring(start, cursor);
    final nameStart = ++cursor;
    while (cursor < source.length &&
        _isNameCharacter(source.codeUnitAt(cursor))) {
      cursor++;
    }
    final name = source.substring(nameStart, cursor);
    String? pattern;
    if (cursor < source.length && source[cursor] == '(') {
      final patternStart = ++cursor;
      var depth = 1;
      var inCharacterClass = false;
      while (cursor < source.length && depth > 0) {
        final character = source[cursor];
        if (character == r'\') {
          cursor += 2;
          continue;
        }
        if (character == '[') inCharacterClass = true;
        if (character == ']') inCharacterClass = false;
        if (!inCharacterClass) {
          if (character == '(') depth++;
          if (character == ')') depth--;
        }
        cursor++;
      }
      if (depth != 0) {
        throw FormatException('Unclosed route parameter pattern', source);
      }
      pattern = source.substring(patternStart, cursor - 1);
    }

    // A terminal '?' or one before another path separator modifies the
    // parameter. '?tab=posts' is an existing query string instead.
    var optional = false;
    if (cursor < source.length && source[cursor] == '?') {
      final next = cursor + 1 == source.length ? '' : source[cursor + 1];
      if (!allowQuery || ['', '/', '.', '?', '#', '*'].contains(next)) {
        optional = true;
        cursor++;
      }
    }
    final wildcard = cursor < source.length && source[cursor] == '*';
    if (wildcard) cursor++;
    tokens.add(RouteTemplateToken.parameter(
      source: source.substring(start, cursor),
      name: name,
      prefix: prefix,
      pattern: pattern,
      optional: optional,
      wildcard: wildcard,
    ));
    literalStart = cursor;
  }
  addLiteral(source.length);
  return RouteTemplate._(tokens, '');
}