Example #1
0
        public virtual ParseResult Parse(ExtractResult extResult)
        {
            // Check if the parser is configured to support specific types
            if (SupportedTypes != null && !SupportedTypes.Any(t => extResult.Type.Equals(t, StringComparison.Ordinal)))
            {
                return(null);
            }

            ParseResult ret = null;

            if (!(extResult.Data is string extra))
            {
                extra = LongFormatRegex.Match(extResult.Text).Success ? Constants.NUMBER_SUFFIX : Config.LanguageMarker;
            }

            // Resolve symbol prefix
            var isNegative    = false;
            var matchNegative = Config.NegativeNumberSignRegex.Match(extResult.Text);

            if (matchNegative.Success)
            {
                isNegative     = true;
                extResult.Text = extResult.Text.Substring(matchNegative.Groups["negTerm"].Length);
            }

            // Assign resolution value
            if (extResult.Data is List <ExtractResult> ers)
            {
                var innerPrs  = ers.Select(Parse).ToList();
                var mergedPrs = new List <ParseResult>();

                double val   = 0;
                var    count = 0;

                for (var idx = 0; idx < innerPrs.Count; idx++)
                {
                    val += (double)innerPrs[idx].Value;

                    if (idx + 1 >= innerPrs.Count || !IsMergeable((double)innerPrs[idx].Value, (double)innerPrs[idx + 1].Value))
                    {
                        var start  = (int)ers[idx - count].Start;
                        var length = (int)(ers[idx].Start + ers[idx].Length - start);
                        mergedPrs.Add(new ParseResult
                        {
                            Start  = start,
                            Length = length,
                            Text   = extResult.Text.Substring((int)(start - extResult.Start), length),
                            Type   = extResult.Type,
                            Value  = val,
                            Data   = null,
                        });

                        val   = 0;
                        count = 0;
                    }
                    else
                    {
                        count++;
                    }
                }

                ret = new ParseResult(extResult)
                {
                    Value = val, Data = mergedPrs
                };
            }
            else if (extra.Contains(Constants.NUMBER_SUFFIX))
            {
                ret = DigitNumberParse(extResult);
            }
            else if (extra.Contains($"{Constants.FRACTION_PREFIX}{Config.LanguageMarker}"))
            {
                // Such fractions are special cases, parse via another method
                ret = FracLikeNumberParse(extResult);
            }
            else if (extra.Contains(Config.LanguageMarker))
            {
                ret = TextNumberParse(extResult);
            }
            else if (extra.Contains(Constants.POWER_SUFFIX))
            {
                ret = PowerNumberParse(extResult);
            }

            if (ret?.Data is List <ParseResult> prs)
            {
                foreach (var parseResult in prs)
                {
                    parseResult.ResolutionStr = GetResolutionStr(parseResult.Value);
                }
            }
            else if (ret?.Value != null)
            {
                if (isNegative)
                {
                    // Recover the original extracted Text
                    ret.Text  = matchNegative.Groups["negTerm"].Value + extResult.Text;
                    ret.Value = -(double)ret.Value;
                }

                ret.ResolutionStr = GetResolutionStr(ret.Value);
            }

            // Add "offset" and "relativeTo" for ordinal
            if (!string.IsNullOrEmpty(ret.Type) && ret.Type.Contains(Constants.MODEL_ORDINAL))
            {
                if ((this.Config.Config.Options & NumberOptions.SuppressExtendedTypes) == 0 && ret.Metadata.IsOrdinalRelative)
                {
                    var offset     = Config.RelativeReferenceOffsetMap[extResult.Text];
                    var relativeTo = Config.RelativeReferenceRelativeToMap[extResult.Text];

                    ret.Metadata.Offset     = offset;
                    ret.Metadata.RelativeTo = relativeTo;

                    // Add value for ordinal.relative
                    string sign = offset[0].Equals('-') ? string.Empty : "+";
                    ret.Value         = string.Concat(relativeTo, sign, offset);
                    ret.ResolutionStr = GetResolutionStr(ret.Value);
                }
                else
                {
                    ret.Metadata.Offset = ret.ResolutionStr;

                    // Every ordinal number is relative to the start
                    ret.Metadata.RelativeTo = Constants.RELATIVE_START;
                }
            }

            if (ret != null)
            {
                ret.Type = DetermineType(extResult);
                ret.Text = ret.Text.ToLowerInvariant();
            }

            return(ret);
        }
Example #2
0
        public virtual List <ExtractResult> Extract(string source)
        {
            if (string.IsNullOrEmpty(source))
            {
                return(new List <ExtractResult>());
            }

            var results     = new List <ExtractResult>();
            var matchSource = new Dictionary <Tuple <int, int>, string>();
            var matched     = new bool[source.Length];

            var collections = Regexes.ToDictionary(o => o.Key.Matches(source), p => p.Value);

            foreach (var collection in collections)
            {
                foreach (Match m in collection.Key)
                {
                    GetMatchedStartAndLength(m, collection.Value, source, out int start, out int length);

                    if (start >= 0 && length > 0)
                    {
                        for (var j = 0; j < length; j++)
                        {
                            matched[start + j] = true;
                        }

                        // Keep Source Data for extra information
                        matchSource.Add(new Tuple <int, int>(start, length), collection.Value);
                    }
                }
            }

            var last = -1;

            for (var i = 0; i < source.Length; i++)
            {
                if (matched[i])
                {
                    if (i + 1 == source.Length || !matched[i + 1])
                    {
                        var start  = last + 1;
                        var length = i - last;
                        var substr = source.Substring(start, length);

                        if (matchSource.Keys.Any(o => o.Item1 == start && o.Item2 == length))
                        {
                            var srcMatch = matchSource.Keys.First(o => o.Item1 == start && o.Item2 == length);
                            var er       = new ExtractResult
                            {
                                Start  = start,
                                Length = length,
                                Text   = substr,
                                Type   = ExtractType,
                                Data   = matchSource.ContainsKey(srcMatch) ? matchSource[srcMatch] : null,
                            };
                            results.Add(er);
                        }
                    }
                }
                else
                {
                    last = i;
                }
            }

            // In ExperimentalMode, cases like "from 3 to 5" and "between 10 and 15" are set to closed at both start and end
            if ((Options & NumberOptions.ExperimentalMode) != 0)
            {
                foreach (var result in results)
                {
                    if (result.Data.ToString() == NumberRangeConstants.TWONUMBETWEEN ||
                        result.Data.ToString() == NumberRangeConstants.TWONUMTILL)
                    {
                        result.Data = NumberRangeConstants.TWONUMCLOSED;
                    }
                }
            }

            return(results);
        }
Example #3
0
        public virtual ParseResult TextNumberParse(ExtractResult extResult)
        {
            var result = new ParseResult
            {
                Start    = extResult.Start,
                Length   = extResult.Length,
                Text     = extResult.Text,
                Type     = extResult.Type,
                Metadata = extResult.Metadata,
            };

            var handle = extResult.Text;

            handle = Config.HalfADozenRegex.Replace(handle, Config.HalfADozenText);

            // Handling cases like "last", "next one", "previous one"
            if ((this.Config.Config.Options & NumberOptions.SuppressExtendedTypes) == 0)
            {
                if (extResult.Metadata != null && extResult.Metadata.IsOrdinalRelative)
                {
                    return(result);
                }
            }

            var numGroup = handle.Split(Config.WrittenDecimalSeparatorTexts.ToArray(), StringSplitOptions.RemoveEmptyEntries);

            var intPart     = numGroup[0];
            var stringMatch = TextNumberRegex.Match(intPart);

            // Store all match str.
            var matchStrs = new List <string>();

            while (stringMatch.Success)
            {
                var matchStr = stringMatch.Groups[0].Value;
                matchStrs.Add(matchStr);
                stringMatch = stringMatch.NextMatch();
            }

            // Get the value recursively
            var intPartRet = GetIntValue(matchStrs);

            double pointPartRet = 0;

            if (numGroup.Length == 2)
            {
                var pointPart = numGroup[1];
                stringMatch = TextNumberRegex.Match(pointPart);
                matchStrs.Clear();

                while (stringMatch.Success)
                {
                    var matchStr = stringMatch.Groups[0].Value;
                    matchStrs.Add(matchStr);
                    stringMatch = stringMatch.NextMatch();
                }

                pointPartRet += GetPointValue(matchStrs);
            }

            result.Value = intPartRet + pointPartRet;

            return(result);
        }
Example #4
0
        public virtual ParseResult FracLikeNumberParse(ExtractResult extResult)
        {
            var result = new ParseResult
            {
                Start  = extResult.Start,
                Length = extResult.Length,
                Text   = extResult.Text,
                Type   = extResult.Type,
            };

            var resultText = extResult.Text;

            if (Config.FractionPrepositionRegex.IsMatch(resultText))
            {
                var match       = Config.FractionPrepositionRegex.Match(resultText);
                var numerator   = match.Groups["numerator"].Value;
                var denominator = match.Groups["denominator"].Value;

                var smallValue = char.IsDigit(numerator[0]) ?
                                 GetDigitalValue(numerator, 1) :
                                 GetIntValue(Utilities.RegExpUtility.GetMatches(this.TextNumberRegex, numerator));

                var bigValue = char.IsDigit(denominator[0]) ?
                               GetDigitalValue(denominator, 1) :
                               GetIntValue(Utilities.RegExpUtility.GetMatches(this.TextNumberRegex, denominator));

                result.Value = smallValue / bigValue;
            }
            else
            {
                var fracWords = Config.NormalizeTokenSet(resultText.Split(null), result).ToList();

                // Split fraction with integer
                var  splitIndex   = fracWords.Count - 1;
                var  currentValue = Config.ResolveCompositeNumber(fracWords[splitIndex]);
                long roundValue   = 1;

                // For case like "half"
                if (fracWords.Count == 1)
                {
                    result.Value = 1 / GetIntValue(fracWords);
                    return(result);
                }

                for (splitIndex = fracWords.Count - 2; splitIndex >= 0; splitIndex--)
                {
                    if (Config.WrittenFractionSeparatorTexts.Contains(fracWords[splitIndex]) ||
                        Config.WrittenIntegerSeparatorTexts.Contains(fracWords[splitIndex]))
                    {
                        continue;
                    }

                    var previousValue = currentValue;
                    currentValue = Config.ResolveCompositeNumber(fracWords[splitIndex]);

                    var hundredsSM = 100;

                    // Previous : hundred
                    // Current : one
                    if ((previousValue >= hundredsSM && previousValue > currentValue) ||
                        (previousValue < hundredsSM && IsComposable(currentValue, previousValue)))
                    {
                        if (previousValue < hundredsSM && currentValue >= roundValue)
                        {
                            roundValue = currentValue;
                        }
                        else if (previousValue < hundredsSM && currentValue < roundValue)
                        {
                            splitIndex++;
                            break;
                        }

                        // Current is the first word
                        if (splitIndex == 0)
                        {
                            // Scan, skip the first word
                            splitIndex = 1;
                            while (splitIndex <= fracWords.Count - 2)
                            {
                                // e.g. one hundred thousand
                                // frac[i+1] % 100 && frac[i] % 100 = 0
                                if (Config.ResolveCompositeNumber(fracWords[splitIndex]) >= hundredsSM &&
                                    !Config.WrittenFractionSeparatorTexts.Contains(fracWords[splitIndex + 1]) &&
                                    Config.ResolveCompositeNumber(fracWords[splitIndex + 1]) < hundredsSM)
                                {
                                    splitIndex++;
                                    break;
                                }

                                splitIndex++;
                            }

                            break;
                        }

                        continue;
                    }

                    splitIndex++;
                    break;
                }

                if (splitIndex < 0)
                {
                    splitIndex = 0;
                }

                var fracPart = new List <string>();
                for (var i = splitIndex; i < fracWords.Count; i++)
                {
                    if (fracWords[i].Contains("-"))
                    {
                        var split = fracWords[i].Split('-');
                        fracPart.Add(split[0]);
                        fracPart.Add("-");
                        fracPart.Add(split[1]);
                    }
                    else
                    {
                        fracPart.Add(fracWords[i]);
                    }
                }

                fracWords.RemoveRange(splitIndex, fracWords.Count - splitIndex);

                // Split mixed number with fraction
                var    denominator = GetIntValue(fracPart);
                double numerValue  = 0;
                double intValue    = 0;

                var mixedIndex = fracWords.Count;
                for (var i = fracWords.Count - 1; i >= 0; i--)
                {
                    if (i < fracWords.Count - 1 && Config.WrittenFractionSeparatorTexts.Contains(fracWords[i]))
                    {
                        var numerStr = string.Join(" ", fracWords.GetRange(i + 1, fracWords.Count - 1 - i));
                        numerValue = GetIntValue(Utilities.RegExpUtility.GetMatches(this.TextNumberRegex, numerStr));
                        mixedIndex = i + 1;
                        break;
                    }
                }

                var intStr = string.Join(" ", fracWords.GetRange(0, mixedIndex));
                intValue = GetIntValue(Utilities.RegExpUtility.GetMatches(this.TextNumberRegex, intStr));

                // Find mixed number
                if (mixedIndex != fracWords.Count && numerValue < denominator)
                {
                    result.Value = intValue + (numerValue / denominator);
                }
                else
                {
                    result.Value = (intValue + numerValue) / denominator;
                }
            }

            return(result);
        }
        protected ParseResult FracLikeNumberParse(ExtractResult extResult)
        {
            var result = new ParseResult
            {
                Start  = extResult.Start,
                Length = extResult.Length,
                Text   = extResult.Text,
                Type   = extResult.Type
            };

            var resultText = extResult.Text.ToLower();

            if (resultText.Contains($@"{config.FractionMarkerToken}"))
            {
                var overIndex = resultText.IndexOf(config.FractionMarkerToken, StringComparison.Ordinal);
                var smallPart = resultText.Substring(0, overIndex).Trim();
                var bigPart   = resultText.Substring(overIndex + config.FractionMarkerToken.Length,
                                                     resultText.Length - overIndex - config.FractionMarkerToken.Length).Trim();

                var smallValue = char.IsDigit(smallPart[0])
                    ? GetDigitalValue(smallPart, 1)
                    : GetIntValue(GetMatches(smallPart));

                var bigValue = char.IsDigit(bigPart[0])
                    ? GetDigitalValue(bigPart, 1)
                    : GetIntValue(GetMatches(bigPart));

                result.Value = smallValue / bigValue;
            }
            else
            {
                var fracWords = config.NormalizeTokenSet(resultText.Split(null), result).ToList();

                // Split fraction with integer
                var  splitIndex   = fracWords.Count - 1;
                var  currentValue = config.ResolveCompositeNumber(fracWords[splitIndex]);
                long roundValue   = 1;

                for (splitIndex = fracWords.Count - 2; splitIndex >= 0; splitIndex--)
                {
                    if (config.WrittenFractionSeparatorTexts.Contains(fracWords[splitIndex]))
                    {
                        continue;
                    }
                    var previousValue = currentValue;
                    currentValue = config.ResolveCompositeNumber(fracWords[splitIndex]);
                    // previous : hundred
                    // current : one
                    if ((previousValue >= 100 && previousValue > currentValue) ||
                        (previousValue < 100 && IsComposable(currentValue, previousValue)))
                    {
                        if (previousValue < 100 && currentValue >= roundValue)
                        {
                            roundValue = currentValue;
                        }
                        else if (previousValue < 100 && currentValue < roundValue)
                        {
                            splitIndex++;
                            break;
                        }
                        // current is the first word
                        if (splitIndex == 0)
                        {
                            // scan, skip the first word
                            splitIndex = 1;
                            while (splitIndex <= fracWords.Count - 2)
                            {
                                // e.g. one hundred thousand
                                // frac[i+1] % 100 && frac[i] % 100 = 0
                                if (config.ResolveCompositeNumber(fracWords[splitIndex]) >= 100 &&
                                    !config.WrittenFractionSeparatorTexts.Contains(fracWords[splitIndex + 1]) &&
                                    config.ResolveCompositeNumber(fracWords[splitIndex + 1]) < 100)
                                {
                                    splitIndex++;
                                    break;
                                }
                                splitIndex++;
                            }
                            break;
                        }
                        continue;
                    }
                    splitIndex++;
                    break;
                }

                var fracPart = new List <string>();
                for (var i = splitIndex; i < fracWords.Count; i++)
                {
                    if (fracWords[i].Contains("-"))
                    {
                        var split = fracWords[i].Split('-');
                        fracPart.Add(split[0]);
                        fracPart.Add("-");
                        fracPart.Add(split[1]);
                    }
                    else
                    {
                        fracPart.Add(fracWords[i]);
                    }
                }
                fracWords.RemoveRange(splitIndex, fracWords.Count - splitIndex);

                // denomi = denominator
                var denomiValue = GetIntValue(fracPart);
                // Split mixed number with fraction
                double numerValue = 0;
                double intValue   = 0;

                var mixedIndex = fracWords.Count;
                for (var i = fracWords.Count - 1; i >= 0; i--)
                {
                    if (i < fracWords.Count - 1 && config.WrittenFractionSeparatorTexts.Contains(fracWords[i]))
                    {
                        var numerStr = string.Join(" ", fracWords.GetRange(i + 1, fracWords.Count - 1 - i));
                        numerValue = GetIntValue(GetMatches(numerStr));
                        mixedIndex = i + 1;
                        break;
                    }
                }
                var intStr = string.Join(" ", fracWords.GetRange(0, mixedIndex));
                intValue = GetIntValue(GetMatches(intStr));

                // Find mixed number
                if (mixedIndex != fracWords.Count && numerValue < denomiValue)
                {
                    result.Value = intValue + numerValue / denomiValue;
                }
                else
                {
                    result.Value = (intValue + numerValue) / denomiValue;
                }
            }

            return(result);
        }
Example #6
0
        public virtual ParseResult PowerNumberParse(ExtractResult extResult)
        {
            var result = new ParseResult
            {
                Start  = extResult.Start,
                Length = extResult.Length,
                Text   = extResult.Text,
                Type   = extResult.Type,
            };

            var handle = extResult.Text.ToUpperInvariant();
            var isE    = !extResult.Text.Contains("^");

            // [1] 1e10
            // [2] 1.1^-23
            var calStack = new Queue <double>();

            double scale      = 10;
            var    dot        = false;
            var    isNegative = false;
            double tmp        = 0;

            for (var i = 0; i < handle.Length; i++)
            {
                var ch = handle[i];
                if (ch == '^' || ch == 'E')
                {
                    if (isNegative)
                    {
                        calStack.Enqueue(-tmp);
                    }
                    else
                    {
                        calStack.Enqueue(tmp);
                    }

                    tmp        = 0;
                    scale      = 10;
                    dot        = false;
                    isNegative = false;
                }
                else if (ch >= '0' && ch <= '9')
                {
                    if (dot)
                    {
                        tmp    = tmp + (scale * (ch - '0'));
                        scale *= 0.1;
                    }
                    else
                    {
                        tmp = (tmp * scale) + (ch - '0');
                    }
                }
                else if (ch == Config.DecimalSeparatorChar)
                {
                    dot   = true;
                    scale = 0.1;
                }
                else if (ch == '-')
                {
                    isNegative = !isNegative;
                }
                else if (ch == '+')
                {
                    continue;
                }

                if (i == handle.Length - 1)
                {
                    if (isNegative)
                    {
                        calStack.Enqueue(-tmp);
                    }
                    else
                    {
                        calStack.Enqueue(tmp);
                    }
                }
            }

            double ret;

            if (isE)
            {
                ret = calStack.Dequeue() * Math.Pow(10, calStack.Dequeue());
            }
            else
            {
                ret = Math.Pow(calStack.Dequeue(), calStack.Dequeue());
            }

            result.Value         = ret;
            result.ResolutionStr = ret.ToString(CultureInfo.InvariantCulture);

            return(result);
        }
        protected ParseResult TextNumberParse(ExtractResult extResult)
        {
            var result = new ParseResult
            {
                Start  = extResult.Start,
                Length = extResult.Length,
                Text   = extResult.Text,
                Type   = extResult.Type
            };
            var handle = extResult.Text.ToLower();

            #region dozen special

            handle = config.HalfADozenRegex.Replace(handle, config.HalfADozenText);

            #endregion

            var numGroup = handle.Split(config.WrittenDecimalSeparatorTexts.ToArray(),
                                        StringSplitOptions.RemoveEmptyEntries);

            #region intPart

            var intPart   = numGroup[0];
            var sMatch    = TextNumberRegex.Match(intPart);
            var matchStrs = new List <string>();
            //Store all match str.
            while (sMatch.Success)
            {
                var matchStr = sMatch.Groups[0].Value.ToLower();
                matchStrs.Add(matchStr);
                sMatch = sMatch.NextMatch();
            }

            //Get the value recursively
            var intPartRet = GetIntValue(matchStrs);

            #endregion

            #region PointPart

            double pointPartRet = 0;
            if (numGroup.Length == 2)
            {
                var pointPart = numGroup[1];
                sMatch = TextNumberRegex.Match(pointPart);
                matchStrs.Clear();
                while (sMatch.Success)
                {
                    var matchStr = sMatch.Groups[0].Value.ToLower();
                    matchStrs.Add(matchStr);
                    sMatch = sMatch.NextMatch();
                }
                pointPartRet += GetPointValue(matchStrs);
            }

            #endregion

            result.Value = intPartRet + pointPartRet;

            return(result);
        }
Example #8
0
        public virtual List <ExtractResult> Extract(string source)
        {
            if (string.IsNullOrEmpty(source))
            {
                return(new List <ExtractResult>());
            }

            var result      = new List <ExtractResult>();
            var matchSource = new Dictionary <Tuple <int, int>, string>();
            var matched     = new bool[source.Length];

            var collections = Regexes.ToDictionary(o => o.Key.Matches(source), p => p.Value);

            foreach (var collection in collections)
            {
                foreach (Match m in collection.Key)
                {
                    GetMatchedStartAndLength(m, collection.Value, source, out int start, out int length);

                    if (start >= 0 && length > 0)
                    {
                        for (var j = 0; j < length; j++)
                        {
                            matched[start + j] = true;
                        }

                        // Keep Source Data for extra information
                        matchSource.Add(new Tuple <int, int>(start, length), collection.Value);
                    }
                }
            }

            var last = -1;

            for (var i = 0; i < source.Length; i++)
            {
                if (matched[i])
                {
                    if (i + 1 == source.Length || !matched[i + 1])
                    {
                        var start  = last + 1;
                        var length = i - last;
                        var substr = source.Substring(start, length);

                        if (matchSource.Keys.Any(o => o.Item1 == start && o.Item2 == length))
                        {
                            var srcMatch = matchSource.Keys.First(o => o.Item1 == start && o.Item2 == length);
                            var er       = new ExtractResult
                            {
                                Start  = start,
                                Length = length,
                                Text   = substr,
                                Type   = ExtractType,
                                Data   = matchSource.ContainsKey(srcMatch) ? matchSource[srcMatch] : null
                            };
                            result.Add(er);
                        }
                    }
                }
                else
                {
                    last = i;
                }
            }

            return(result);
        }
Example #9
0
        public virtual List <ExtractResult> Extract(string source)
        {
            if (string.IsNullOrEmpty(source))
            {
                return(new List <ExtractResult>());
            }

            var results     = new List <ExtractResult>();
            var matchSource = new Dictionary <Tuple <int, int>, string>();
            var matched     = new bool[source.Length];

            var collections = Regexes.ToDictionary(o => o.Key.Matches(source), p => p.Value);

            foreach (var collection in collections)
            {
                foreach (Match m in collection.Key)
                {
                    GetMatchedStartAndLength(m, collection.Value, source, out int start, out int length);

                    if (start >= 0 && length > 0)
                    {
                        // Keep Source Data for extra information
                        matchSource.Add(new Tuple <int, int>(start, length), collection.Value);
                    }
                }
            }

            foreach (var match in matchSource)
            {
                var start  = match.Key.Item1;
                var length = match.Key.Item2;

                // Filter wrong two number ranges such as "more than 20 and less than 10" and "大于20小于10".
                if (match.Value.Equals(NumberRangeConstants.TWONUM))
                {
                    int moreIndex = 0, lessIndex = 0;

                    var text = source.Substring(match.Key.Item1, match.Key.Item2);

                    var er = numberExtractor.Extract(text);

                    if (er.Count != 2)
                    {
                        er = ordinalExtractor.Extract(text);

                        if (er.Count != 2)
                        {
                            continue;
                        }
                    }

                    var nums = er.Select(r => (double)(numberParser.Parse(r).Value ?? 0)).ToList();

                    moreIndex = matchSource.First(r => r.Value.Equals(NumberRangeConstants.MORE) && r.Key.Item1 >= start &&
                                                  r.Key.Item1 + r.Key.Item2 <= start + length).Key.Item1;
                    lessIndex = matchSource.First(r => r.Value.Equals(NumberRangeConstants.LESS) && r.Key.Item1 >= start &&
                                                  r.Key.Item1 + r.Key.Item2 <= start + length).Key.Item1;

                    if (!((nums[0] < nums[1] && moreIndex <= lessIndex) || (nums[0] > nums[1] && moreIndex >= lessIndex)))
                    {
                        continue;
                    }
                }

                // The entity is longer than 1, so don't mark the last char to represent the end.
                // To avoid no connector cases like "大于20小于10" being marked as a whole entity.
                for (var j = 0; j < length - 1; j++)
                {
                    matched[start + j] = true;
                }
            }

            var last = -1;

            for (var i = 0; i < source.Length; i++)
            {
                if (matched[i])
                {
                    if (i + 1 == source.Length || !matched[i + 1])
                    {
                        var start  = last + 1;
                        var length = i - last + 1;
                        var substr = source.Substring(start, length);

                        if (matchSource.Keys.Any(o => o.Item1 == start && o.Item2 == length))
                        {
                            var srcMatch = matchSource.Keys.First(o => o.Item1 == start && o.Item2 == length);
                            var er       = new ExtractResult
                            {
                                Start  = start,
                                Length = length,
                                Text   = substr,
                                Type   = ExtractType,
                                Data   = matchSource.ContainsKey(srcMatch) ? matchSource[srcMatch] : null,
                            };

                            results.Add(er);
                        }
                    }
                }
                else
                {
                    last = i;
                }
            }

            // In ExperimentalMode, cases like "from 3 to 5" and "between 10 and 15" are set to closed at both start and end
            if ((Config.Options & NumberOptions.ExperimentalMode) != 0)
            {
                foreach (var result in results)
                {
                    var data = result.Data.ToString();
                    if (data == NumberRangeConstants.TWONUMBETWEEN ||
                        data == NumberRangeConstants.TWONUMTILL)
                    {
                        result.Data = NumberRangeConstants.TWONUMCLOSED;
                    }
                }
            }

            return(results);
        }
        public override ParseResult Parse(ExtractResult extResult)
        {
            // check if the parser is configured to support specific types
            if (SupportedTypes != null && !SupportedTypes.Any(t => extResult.Type.Equals(t)))
            {
                return(null);
            }

            string      extra = null;
            ParseResult ret   = null;

            extra = extResult.Data as string;

            var getExtResult = new ExtractResult()
            {
                Start  = extResult.Start,
                Length = extResult.Length,
                Data   = extResult.Data,
                Text   = extResult.Text,
                Type   = extResult.Type
            };

            if (Config.CultureInfo.Name == "zh-CN")
            {
                getExtResult.Text = ReplaceTraWithSim(getExtResult.Text);
            }

            if (extra == null)
            {
                return(null);
            }
            if (extra.Contains("Integer"))
            {
                ret = ParseInteger(getExtResult);
            }
            else if (extra.Contains("Per"))
            {
                ret = ParsePercentage(getExtResult);
            }
            else if (extra.Contains("Num"))
            {
                getExtResult.Text = NormalizeCharWidth(getExtResult.Text);
                ret = DigitNumberParse(getExtResult);
                if (Config.NegativeNumberSignRegex.IsMatch(getExtResult.Text) && (double)ret.Value > 0)
                {
                    ret.Value = -(double)ret.Value;
                }
                ret.ResolutionStr = ret.Value.ToString();
            }
            else if (extra.Contains("Pow"))
            {
                getExtResult.Text = NormalizeCharWidth(getExtResult.Text);
                ret = PowerNumberParse(getExtResult);
                ret.ResolutionStr = ret.Value.ToString();
            }
            else if (extra.Contains("Frac"))
            {
                ret = ParseFraction(getExtResult);
            }
            else if (extra.Contains("Dou"))
            {
                ret = ParseDouble(getExtResult);
            }

            else if (extra.Contains("Ordinal"))
            {
                ret = ParseOrdinal(getExtResult);
            }

            if (ret != null)
            {
                ret.Text = extResult.Text;
            }

            return(ret);
        }
        // Parse percentage phrase.
        protected ParseResult ParsePercentage(ExtractResult extResult)
        {
            var result = new ParseResult
            {
                Start  = extResult.Start,
                Length = extResult.Length,
                Text   = extResult.Text,
                Type   = extResult.Type
            };

            var  resultText = extResult.Text;
            long power      = 1;

            if (extResult.Data.ToString().Contains("Spe"))
            {
                resultText = NormalizeCharWidth(resultText);
                resultText = ReplaceUnit(resultText);

                if (resultText == "半額" || resultText == "半値" || resultText == "半折")
                {
                    result.Value = 50;
                }
                else if (resultText == "10成" || resultText == "10割" || resultText == "十割")
                {
                    result.Value = 100;
                }
                else
                {
                    var    matches = Config.SpeGetNumberRegex.Matches(resultText);
                    double intNumber;

                    if (matches.Count == 2)
                    {
                        var intNumberChar = matches[0].Value[0];

                        if (intNumberChar == '対' || intNumberChar == '对')
                        {
                            intNumber = 5;
                        }
                        else if (intNumberChar == '十' || intNumberChar == '拾')
                        {
                            intNumber = 10;
                        }
                        else
                        {
                            intNumber = Config.ZeroToNineMap[intNumberChar];
                        }

                        var    pointNumberChar = matches[1].Value[0];
                        double pointNumber;
                        if (pointNumberChar == '半')
                        {
                            pointNumber = 0.5;
                        }
                        else
                        {
                            pointNumber = Config.ZeroToNineMap[pointNumberChar] * 0.1;
                        }

                        result.Value = (intNumber + pointNumber) * 10;
                    }
                    else if (matches.Count == 5)
                    {
                        // Deal the Japanese percentage case like "xxx割xxx分xxx厘", get the integer value and convert into result.
                        var intNumberChar   = matches[0].Value[0];
                        var pointNumberChar = matches[1].Value[0];
                        var dotNumberChar   = matches[3].Value[0];

                        double pointNumber = Config.ZeroToNineMap[pointNumberChar] * 0.1;
                        double dotNumber   = Config.ZeroToNineMap[dotNumberChar] * 0.01;

                        intNumber = Config.ZeroToNineMap[intNumberChar];

                        result.Value = (intNumber + pointNumber + dotNumber) * 10;
                    }
                    else
                    {
                        var intNumberChar = matches[0].Value[0];

                        if (intNumberChar == '対' || intNumberChar == '对')
                        {
                            intNumber = 5;
                        }
                        else if (intNumberChar == '十' || intNumberChar == '拾')
                        {
                            intNumber = 10;
                        }
                        else
                        {
                            intNumber = Config.ZeroToNineMap[intNumberChar];
                        }

                        result.Value = intNumber * 10;
                    }
                }
            }
            else if (extResult.Data.ToString().Contains("Num"))
            {
                var doubleText = Config.PercentageRegex.Match(resultText).Value;

                if (doubleText.Contains("k") || doubleText.Contains("K") || doubleText.Contains("k") ||
                    doubleText.Contains("K"))
                {
                    power = 1000;
                }

                if (doubleText.Contains("M") || doubleText.Contains("M"))
                {
                    power = 1000000;
                }

                if (doubleText.Contains("G") || doubleText.Contains("G"))
                {
                    power = 1000000000;
                }

                if (doubleText.Contains("T") || doubleText.Contains("T"))
                {
                    power = 1000000000000;
                }

                result.Value = GetDigitValue(resultText, power);
            }
            else
            {
                var doubleText = Config.PercentageRegex.Match(resultText).Value;
                doubleText = ReplaceUnit(doubleText);

                var splitResult = Config.PointRegex.Split(doubleText);
                if (splitResult[0] == "")
                {
                    splitResult[0] = "零";
                }

                var doubleValue = GetIntValue(splitResult[0]);
                if (splitResult.Length == 2)
                {
                    if (Config.NegativeNumberSignRegex.IsMatch(splitResult[0]))
                    {
                        doubleValue -= GetPointValue(splitResult[1]);
                    }
                    else
                    {
                        doubleValue += GetPointValue(splitResult[1]);
                    }
                }

                result.Value = doubleValue;
            }

            result.ResolutionStr = result.Value + @"%";
            return(result);
        }