toRomanNumeralValue method
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;
}