matchVCardPrefixedField static method

List<List<String>>? matchVCardPrefixedField(
  1. String prefix,
  2. String rawText,
  3. bool trim,
  4. bool parseFieldDivider,
)

Implementation

static List<List<String>>? matchVCardPrefixedField(
  String prefix,
  String rawText,
  bool trim,
  bool parseFieldDivider,
) {
  List<List<String>>? matches;
  int i = 0;
  final max = rawText.length;
  final reg = RegExp('(?:^|\n)$prefix(?:;([^:]*))?:', caseSensitive: false);
  while (i < max) {
    // At start or after newline, match prefix, followed by optional metadata
    // (led by ;) ultimately ending in colon
    final regMatches = reg.allMatches(rawText, i);

    if (regMatches.isEmpty) break;
    final matcher = regMatches.first;
    i = matcher.end; // group 0 = whole pattern; end(0) is past final colon

    if (i > 0) {
      //  i--; // Find from i-1 not i since looking at the preceding character
    }

    final metadataString = matcher.group(1); // group 1 = metadata substring
    List<String>? metadata;
    bool quotedPrintable = false;
    String? quotedPrintableCharset;
    String? valueType;
    if (metadataString != null) {
      for (String metadatum in metadataString.split(_semicolon)) {
        metadata ??= [];
        metadata.add(metadatum);
        final metadatumTokens = metadatum.split(_equal); // todo , 2
        if (metadatumTokens.length > 1) {
          final key = metadatumTokens[0];
          final value = metadatumTokens[1];
          if ('ENCODING' == key.toUpperCase() &&
              'QUOTED-PRINTABLE' == value.toUpperCase()) {
            quotedPrintable = true;
          } else if ('CHARSET' == key.toUpperCase()) {
            quotedPrintableCharset = value;
          } else if ('VALUE' == key.toUpperCase()) {
            valueType = value;
          }
        }
      }
    }

    final matchStart = i; // Found the start of a match here

    while ((i = rawText.indexOf('\n', i)) >= 0) {
      // Really, end in \r\n
      if (i < rawText.length - 1 && // But if followed by tab or space,
          (rawText[i + 1] == ' ' || // this is only a continuation
              rawText[i + 1] == '\t')) {
        i += 2; // Skip \n and continutation whitespace
      } else if (quotedPrintable && // If preceded by = in quoted printable
          ((i >= 1 && rawText[i - 1] == '=') || // this is a continuation
              (i >= 2 && rawText[i - 2] == '='))) {
        i++; // Skip \n
      } else {
        break;
      }
    }

    if (i < 0) {
      // No terminating end character? uh, done. Set i such that loop terminates and break
      i = max;
    } else if (i > matchStart) {
      // found a match
      matches ??= [];
      if (i >= 1 && rawText[i - 1] == '\r') {
        i--; // Back up over \r, which really should be there
      }
      String element = rawText.substring(matchStart, i);
      if (trim) {
        element = element.trim();
      }
      if (quotedPrintable) {
        element = _decodeQuotedPrintable(element, quotedPrintableCharset);
        if (parseFieldDivider) {
          element = element.replaceAll(_unescapedSemicolons, '\n').trim();
        }
      } else {
        if (parseFieldDivider) {
          element = element.replaceAll(_unescapedSemicolons, '\n').trim();
        }
        element = element.replaceAll(_crLfSpaceTab, '');
        element = element.replaceAll(_newlineEscape, '\n');
        element = element.replaceAllMapped(_vcardEscapes, (m) => '${m[1]}');
      }
      // Only handle VALUE=uri specially
      if ('uri' == valueType) {
        // Don't actually support dereferencing URIs, but use scheme-specific part not URI
        // as value, to support tel: and mailto:
        try {
          element = Uri.parse(element).path;
        } catch (_) {
          // IllegalArgumentException
          // ignore
        }
      }
      if (metadata == null) {
        final match = <String>[];
        match.add(element);
        matches.add(match);
      } else {
        metadata.insert(0, element);
        matches.add(metadata);
      }
      i++;
    } else {
      i++;
    }
  }

  return matches;
}