isValidRomanNumeralValue method

bool isValidRomanNumeralValue({
  1. RomanNumeralsConfig? config,
})

Confirms or disconfirms a valid Roman numeral value. This may change for the same String depending on the RomanNumeralsConfig.

Implementation

bool isValidRomanNumeralValue({RomanNumeralsConfig? config}) {
  config ??= RomanNumerals.romanNumeralsConfig;

  // Is it literally just a nulla token?
  final nulla = config.nulla;
  if (nulla != null) {
    if (this == nulla) {
      return true;
    }
  }

  /*
     Match expression reads as:
     - ^                # From the beginnging of the line.
     - if Common Roman Numerals:
     -   (M{0,3})       # There are 3 maximum 'M' matches.
     - else if Apostrophus:
     -   if compact:
     -     ((ↈ){0,3})                  # Maxium 3 millions
     -     ((ↂↈ)|(ↂↇ)|(ↇ)?(ↂ){0,3}) # 900k, 400k, or 0 to 3 100k following 0 or 1 500k
     -     ((ↀↂ)|(ↀↁ)|(ↁ)?(ↀ){0,3})    # 90k, 40k or 0 to 3 10k following 0 o 1 50k
     -     ((Cↀ)|(CCCC)|(D)?C{0,3})     # Then, match for 900, 400 - 4 C's allowed!
     -                                  #   then 0 to 3 100/C's following 0 or one 500/D's.
     -   else:
     -     ((CCCCIↃↃↃↃ){0,3})
     -     ((CCCIↃↃↃCCCCIↃↃↃↃ)|(CCCIↃↃↃIↃↃↃↃ)|(IↃↃↃↃ)?(CCCIↃↃↃ){0,3})
     -     ((CCIↃↃCCCIↃↃↃ)|(CCIↃↃIↃↃↃ)|(IↃↃↃ)?(CCIↃↃ){0,3})
     -     ((CIↃCCIↃↃ)|(CIↃIↃↃ)|(ⅠↃↃ)?(CIↃ){0,3})
     -     ((CCIↃ)|(CCCC)|(IↃ)?C{0,3})
     -     # The matching follows Vinculum style, but substituing the
     -     # overline for specific sequences.
     -     # Note: 4 CCCC's used for 400!
     - else if Vinculum:
     -   (M̅{0,3})         # There are 3 maximum 'M̅' matches for 3,000,000
     -   (C̅M̅|C̅D̅|D̅?C̅{0,3}) # Then, match for 900,000/C̅M̅, 400,000/C̅D̅, or 0 to 3 100,000/C̅'s following 0 or one 500,000/D̅'s.
     -   (X̅C̅|X̅L̅|L̅?X̅{0,3}) # Then, match for  90,000/X̅C̅,  40,000/X̅L̅, or 0 to 3  10,000/X̅'s following 0 or one 50/L̅'s.
     -   (MX̅|MV̅|V̅?M{0,3}) # Then, match for   9,000/MX̅,   4,000/MV̅, or 0 to 3   1,000/M's following 0 or one 5/V̅'s.
     - (CM|CD|D?C{0,3}) # Then, match for 900/CM, 400/CD, or 0 to 3 100/C's following 0 or one 500/D's.
     - (XC|XL|L?X{0,3}) # Then, match for 90/XC,  40/XL,  or 0 to 3 10/X's  following 0 or one 50/L's.
     - (IX|IV|V?I{0,3}) # Then, match for 9/IX,   4/IV,   or 0 to 3 1/X's   following 0 or one 5/V's.
     - $                # To the end of the line.

     The options provide for case-insensitive matching.
     */
  var expString = r'(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})';
  switch (config.configType) {
    case RomanNumeralsType.common:
      expString = r'(M{0,3})(CM|CD|D?C{0,3})' + expString;
      break;
    case RomanNumeralsType.apostrophus:
      final aConfig = config as ApostrophusRomanNumeralsConfig;
      if (aConfig.compact) {
        expString = r'((ↈ){0,3})'
            r'((ↂↈ)|(ↂↇ)|(ↇ)?(ↂ){0,3})'
            r'((ↀↂ)|(ↀↁ)|(ↁ)?(ↀ){0,3})'
            r'((Cↀ)|(CCCC)|(D)?C{0,3})'
            '$expString';
      } else {
        expString = r'((CCCCIↃↃↃↃ){0,3})'
            r'((CCCIↃↃↃCCCCIↃↃↃↃ)|(CCCIↃↃↃIↃↃↃↃ)|(IↃↃↃↃ)?(CCCIↃↃↃ){0,3})'
            r'((CCIↃↃCCCIↃↃↃ)|(CCIↃↃIↃↃↃ)|(IↃↃↃ)?(CCIↃↃ){0,3})'
            r'((CIↃCCIↃↃ)|(CIↃIↃↃ)|(ⅠↃↃ)?(CIↃ){0,3})'
            r'((CCIↃ)|(CCCC)|(IↃ)?C{0,3})'
            '$expString';
      }
      break;
    case RomanNumeralsType.vinculum:
      // With the unicode flag on for the RegExp below, the sequences
      // in this string for unicode can be matched.
      // https://api.flutter.dev/flutter/dart-core/RegExp/isUnicode.html
      // > In Unicode mode, the syntax of the RegExp pattern is more
      // > restricted, but some pattern features, like Unicode property
      // > escapes, are only available in this mode.
      expString = r'((M\u{0305}){0,3})'
          r'((C\u{0305}M\u{0305})|(C\u{0305}D\u{0305})|(D\u{0305})?(C\u{0305}){0,3})'
          r'((X\u{0305}C\u{0305})|(X\u{0305}L\u{0305})|(L\u{0305})?(X\u{0305}){0,3})'
          r'((MX\u{0305})|(MV\u{0305})|(V\u{0305})?(M){0,3})'
          r'(CM|CD|D?C{0,3})'
          '$expString';
      break;
  }
  expString = r'^' + expString + r'$';
  final exp = RegExp(expString, caseSensitive: false, unicode: true);

  final matches = exp.allMatches(this);
  return matches.length == 1;
}