toRomanNumeralValue method

int? toRomanNumeralValue({
  1. RomanNumeralsConfig? config,
})

Create Roman numeral int from String. Rules for creation are read from the optional config.

Implementation

int? toRomanNumeralValue({RomanNumeralsConfig? config}) {
  config ??= RomanNumerals.romanNumeralsConfig;

  // Guard against malformed string sequences, and return early if the string itself is invalid
  if (!isValidRomanNumeralValue(config: config)) {
    return null;
  }

  // Since we used the internal validator, we can now naively (i.e. simply and readably) parse the
  // Roman numeral string, since we know that it is in a valid form.
  Map<String, int> useMap = {};
  switch (config.configType) {
    case RomanNumeralsType.common:
      useMap = {
        ..._sharedRomanLettersToNumbers,
        ..._commonRomanLettersToNumbers
      };
      break;
    case RomanNumeralsType.apostrophus:
      final aConfig = config as ApostrophusRomanNumeralsConfig;
      if (aConfig.compact) {
        useMap = {
          ..._sharedRomanLettersToNumbers,
          ..._compactApostrophusRomanLettersToNumbers
        };
      } else {
        useMap = {
          ..._sharedRomanLettersToNumbers,
          ..._apostrophusRomanLettersToNumbers
        };
      }
      break;
    case RomanNumeralsType.vinculum:
      useMap = {
        ..._sharedRomanLettersToNumbers,
        ..._commonRomanLettersToNumbers,
        ..._vinculumRomanLettersToNumbers
      };
      break;
  }

  final nulla = config.nulla;
  if (nulla != null) {
    useMap[nulla] = 0;
  }

  var accum = 0;
  var curMax = 0;
  var curVal = 0;
  var failed = false;

  var strIter = characters.toUpperCase().iteratorAtEnd;
  var isApostrophus = false;
  if (config.configType == RomanNumeralsType.apostrophus) {
    final aConfig = config as ApostrophusRomanNumeralsConfig;
    isApostrophus = !aConfig.compact;
  }
  var ad = _ApostrophusData();
  while (strIter.moveBack()) {
    var c = strIter.current;
    if (isApostrophus) {
      if (c == 'Ↄ') {
        if (ad.tally && ad.match > 0) {
          // If we have a tally, and there are already some matches,
          // then we cannot get another Ↄ until we've confirmed
          // it's not a 5'r or that the C/Ↄ is balanced.
          failed = true;
          break;
        } else if (ad.tally && ad.match == 0) {
          // We found a valid 5'er form. Move the iterator back so
          // it's found again next loop. Continue to calculate the 5'er.
          strIter.moveNext();
          c = ad.string;
          ad.reset();
        } else {
          // Keep counting the Ↄ's
          ad.count += 1;
          continue;
        }
      } else if (ad.count > 0) {
        if (c == 'I') {
          if (!ad.tally) {
            // If you hit a legit apostrophus I, continue at
            // the top of the loop, so we don't subtract the I.
            ad.tally = true;
            continue;
          } else {
            // Something is wrong, we just got an I, but we already
            // have an apostrophus I.
            failed = true;
            break;
          }
        } else if (c == 'C') {
          if (!ad.tally) {
            // You can't have a matching C w/o the tally mark I.
            failed = true;
            break;
          } else {
            // Keep counting the C's.
            ad.match += 1;
          }
        } // Else, you got some other chracter that might be starting
        // the next sequence around. Leave it be for now.

        if (ad.tally && ad.isBalanced) {
          // We have a valid 10's form of the apostrophus, we can
          // continue to calculating it's value and bumping the
          // accumulator.
          c = ad.string;
          ad.reset();
        } else {
          // If we aren't balanced for the 10's form of the apostrophus
          // we continue to the top of the loop.
          continue;
        }
      }
    }

    // If the Apostrophus parsing already failed, just return null now.
    if (failed) {
      return null;
    }

    // Alright, single character or apostrophus sequence, we can lookup
    // the value and calculate our accumulation.
    var got = useMap[c];
    if (got == null) {
      // This is an exceptional case. I shouild increase the loudness
      // if this happens.
      return null;
    } else {
      curVal = got;
      if (curVal >= curMax) {
        accum += curVal;
        curMax = curVal;
      } else {
        accum -= curVal;
      }
    }
  }
  return accum;
}