public new ParseResult Parse(ExtractResult er, DateObject refTime) { var referenceTime = refTime; DateTimeParseResult pr; // push, save teh MOD string bool hasBefore = false, hasAfter = false, hasSince = false; if (BeforeRegex.IsMatch(er.Text)) { hasBefore = true; } else if (AfterRegex.IsMatch(er.Text)) { hasAfter = true; } else if (SinceRegex.IsMatch(er.Text)) { hasSince = true; } if (er.Type.Equals(Constants.SYS_DATETIME_DATE, StringComparison.Ordinal)) { pr = this.Config.DateParser.Parse(er, referenceTime); } else if (er.Type.Equals(Constants.SYS_DATETIME_TIME, StringComparison.Ordinal)) { pr = this.Config.TimeParser.Parse(er, referenceTime); } else if (er.Type.Equals(Constants.SYS_DATETIME_DATETIME, StringComparison.Ordinal)) { pr = this.Config.DateTimeParser.Parse(er, referenceTime); } else if (er.Type.Equals(Constants.SYS_DATETIME_DATEPERIOD, StringComparison.Ordinal)) { pr = this.Config.DatePeriodParser.Parse(er, referenceTime); } else if (er.Type.Equals(Constants.SYS_DATETIME_TIMEPERIOD, StringComparison.Ordinal)) { pr = this.Config.TimePeriodParser.Parse(er, referenceTime); } else if (er.Type.Equals(Constants.SYS_DATETIME_DATETIMEPERIOD, StringComparison.Ordinal)) { pr = this.Config.DateTimePeriodParser.Parse(er, referenceTime); } else if (er.Type.Equals(Constants.SYS_DATETIME_DURATION, StringComparison.Ordinal)) { pr = this.Config.DurationParser.Parse(er, referenceTime); } else if (er.Type.Equals(Constants.SYS_DATETIME_SET, StringComparison.Ordinal)) { pr = this.Config.SetParser.Parse(er, referenceTime); } else { return(null); } // pop, restore the MOD string if (hasBefore) { var val = (DateTimeResolutionResult)pr.Value; if (val != null) { val.Mod = Constants.BEFORE_MOD; } pr.Value = val; } if (hasAfter) { var val = (DateTimeResolutionResult)pr.Value; if (val != null) { val.Mod = Constants.AFTER_MOD; } pr.Value = val; } if (hasSince) { var val = (DateTimeResolutionResult)pr.Value; if (val != null) { val.Mod = Constants.SINCE_MOD; } pr.Value = val; } pr.Value = DateTimeResolution(pr); var hasModifier = hasBefore || hasAfter || hasSince; // change the type at last for the after or before mode pr.Type = $"{ParserTypeName}.{DetermineDateTimeType(er.Type, hasModifier)}"; return(pr); }
private static bool IsMultipleDurationDate(ExtractResult er) { return(er.Data != null && er.Data.ToString() == Constants.MultipleDuration_Date); }
// Cases like "more than 3 days", "less than 4 weeks" private static bool IsInequalityDuration(ExtractResult er) { return(er.Data != null && (er.Data.ToString() == Constants.MORE_THAN_MOD || er.Data.ToString() == Constants.LESS_THAN_MOD)); }
public List <ExtractResult> Extract(string source) { var result = new List <ExtractResult>(); IOrderedEnumerable <ExtractResult> numbers; if (!PreCheckStr(source)) { return(result); } var mappingPrefix = new Dictionary <int, PrefixUnitResult>(); var sourceLen = source.Length; var prefixMatched = false; var unitIsPrefix = new List <bool>(); MatchCollection nonUnitMatches = null; var prefixMatches = prefixMatcher.Find(source).OrderBy(o => o.Start).ToList(); var suffixMatches = suffixMatcher.Find(source).OrderBy(o => o.Start).ToList(); // Remove matches with wrong length, e.g. both 'm2' and 'm 2' are extracted but only 'm2' represents a unit. for (int i = suffixMatches.Count - 1; i >= 0; i--) { var m = suffixMatches[i]; if (m.CanonicalValues.All(l => l.Length != m.Length)) { suffixMatches.RemoveAt(i); } } if (prefixMatches.Count > 0 || suffixMatches.Count > 0) { numbers = this.config.UnitNumExtractor.Extract(source).OrderBy(o => o.Start); // Checking if there are conflicting interpretations between currency unit as prefix and suffix for each number. // For example, in Chinese, "$20,300美圆" should be broken into two entities instead of treating 20,300 as one number: "$20" and "300美圆". if (numbers.Any() && CheckExtractorType(Constants.SYS_UNIT_CURRENCY) && prefixMatches.Any() && suffixMatches.Any()) { foreach (var number in numbers) { int start = (int)number.Start, length = (int)number.Length; var numberPrefix = prefixMatches.Any(o => o.Start + o.Length == number.Start); var numberSuffix = suffixMatches.Any(o => o.Start == number.Start + number.Length); if (numberPrefix != false && numberSuffix != false && number.Text.Contains(",")) { int commaIndex = (int)number.Start + number.Text.IndexOf(","); source = source.Substring(0, commaIndex) + " " + source.Substring(commaIndex + 1); } } numbers = this.config.UnitNumExtractor.Extract(source).OrderBy(o => o.Start); } // Special case for cases where number multipliers clash with unit var ambiguousMultiplierRegex = this.config.AmbiguousUnitNumberMultiplierRegex; if (ambiguousMultiplierRegex != null) { foreach (var number in numbers) { var match = ambiguousMultiplierRegex.Matches(number.Text); if (match.Count == 1) { var newLength = number.Text.Length - match[0].Length; number.Text = number.Text.Substring(0, newLength); number.Length = newLength; } } } foreach (var number in numbers) { if (number.Start == null || number.Length == null) { continue; } int start = (int)number.Start, length = (int)number.Length; var maxFindPref = Math.Min(maxPrefixMatchLen, number.Start.Value); var maxFindSuff = sourceLen - start - length; var closeMatch = false; if (maxFindPref != 0) { // Scan from left to right, find the longest match var lastIndex = start; MatchResult <string> bestMatch = null; foreach (var m in prefixMatches) { if (m.Length > 0 && m.End > start) { break; } var unitStr = source.Substring(m.Start, lastIndex - m.Start); if (m.Length > 0 && unitStr.Trim() == m.Text) { if (unitStr == m.Text) { closeMatch = true; } bestMatch = m; break; } } if (bestMatch != null) { var offSet = lastIndex - bestMatch.Start; var unitStr = source.Substring(bestMatch.Start, offSet); mappingPrefix[number.Start.Value] = new PrefixUnitResult { Offset = offSet, UnitStr = unitStr }; } } mappingPrefix.TryGetValue(start, out PrefixUnitResult prefixUnit); // For currency unit, such as "$ 10 $ 20", get candidate "$ 10" "10 $" "$20" then select to get result. // So add "$ 10" to result here, then get "10 $" in the suffixMatch. // But for case like "摄氏温度10度", "摄氏温度10" will skip this and continue to extend the suffix. if (prefixUnit != null && !prefixMatched && CheckExtractorType(Constants.SYS_UNIT_CURRENCY)) { var er = new ExtractResult { Start = number.Start - prefixUnit.Offset, Length = number.Length + prefixUnit.Offset, Text = prefixUnit.UnitStr + number.Text, Type = this.config.ExtractType, }; // Relative position will be used in Parser var numberData = number.Clone(); numberData.Start = start - er.Start; er.Data = numberData; result.Add(er); unitIsPrefix.Add(true); } if (maxFindSuff > 0) { // If the number already get close prefix currency unit, skip the suffix match. if (CheckExtractorType(Constants.SYS_UNIT_CURRENCY) && closeMatch) { continue; } // find the best suffix unit var maxlen = 0; var firstIndex = start + length; foreach (var m in suffixMatches) { if (m.Length > 0 && m.Start >= firstIndex) { var endpos = m.Start + m.Length - firstIndex; if (maxlen < endpos) { var midStr = source.Substring(firstIndex, m.Start - firstIndex); if (string.IsNullOrWhiteSpace(midStr) || midStr.Trim().Equals(this.config.ConnectorToken, StringComparison.Ordinal)) { maxlen = endpos; } } } } if (maxlen != 0) { var substr = source.Substring(start, length + maxlen); var er = new ExtractResult { Start = start, Length = length + maxlen, Text = substr, Type = this.config.ExtractType, }; if (prefixUnit != null && !CheckExtractorType(Constants.SYS_UNIT_CURRENCY)) { prefixMatched = true; er.Start -= prefixUnit.Offset; er.Length += prefixUnit.Offset; er.Text = prefixUnit.UnitStr + er.Text; } // Relative position will be used in Parser var numberData = number.Clone(); numberData.Start = start - er.Start; er.Data = numberData; // Special treatment, handle cases like '2:00 pm', '00 pm' is not dimension var isNotUnit = false; if (er.Type.Equals(Constants.SYS_UNIT_DIMENSION, StringComparison.Ordinal)) { if (nonUnitMatches == null) { nonUnitMatches = this.config.NonUnitRegex.Matches(source); } foreach (Match time in nonUnitMatches) { if (er.Start >= time.Index && er.Start + er.Length <= time.Index + time.Length) { isNotUnit = true; break; } } } if (isNotUnit) { continue; } result.Add(er); unitIsPrefix.Add(false); } } if (prefixUnit != null && !prefixMatched && !CheckExtractorType(Constants.SYS_UNIT_CURRENCY)) { var er = new ExtractResult { Start = number.Start - prefixUnit.Offset, Length = number.Length + prefixUnit.Offset, Text = prefixUnit.UnitStr + number.Text, Type = this.config.ExtractType, }; // Relative position will be used in Parser var numberData = number.Clone(); numberData.Start = start - er.Start; er.Data = numberData; result.Add(er); } } } else { numbers = null; } // Extract Separate unit if (separateRegex != null) { if (nonUnitMatches == null) { nonUnitMatches = this.config.NonUnitRegex.Matches(source); } ExtractSeparateUnits(source, result, nonUnitMatches); } // Remove common ambiguous cases result = FilterAmbiguity(result, source); if (CheckExtractorType(Constants.SYS_UNIT_CURRENCY)) { result = SelectCandidates(source, result, unitIsPrefix); } // Expand Chinese phrase to the `half` patterns when it follows closely origin phrase. this.config.ExpandHalfSuffix(source, ref result, numbers); return(result); }
public ParseResult Parse(ExtractResult extResult) { return(Parse(extResult, DateObject.Now)); }
public ParseResult Parse(ExtractResult result) { return(this.Parse(result, DateObject.Now)); }
public DateTimeParseResult Parse(ExtractResult er, DateObject refTime) { var referenceTime = refTime; DateTimeParseResult pr = null; var originText = er.Text; if ((this.Config.Options & DateTimeOptions.EnablePreview) != 0) { er.Text = MatchingUtil.PreProcessTextRemoveSuperfluousWords(er.Text, Config.SuperfluousWordMatcher, out var _); er.Length += er.Text.Length - originText.Length; } // Push, save the MOD string bool hasBefore = false, hasAfter = false, hasSince = false, hasAround = false, hasDateAfter = false; // "InclusiveModifier" means MOD should include the start/end time // For example, cases like "on or later than", "earlier than or in" have inclusive modifier bool hasInclusiveModifier = false; var modStr = string.Empty; var beforeMatch = Config.BeforeRegex.MatchBegin(er.Text, trim: true); var afterMatch = Config.AfterRegex.MatchBegin(er.Text, trim: true); var sinceMatch = Config.SinceRegex.MatchBegin(er.Text, trim: true); var aroundMatch = Config.AroundRegex.MatchBegin(er.Text, trim: true); if (beforeMatch.Success) { hasBefore = true; er.Start += beforeMatch.Length; er.Length -= beforeMatch.Length; er.Text = er.Text.Substring(beforeMatch.Length); modStr = beforeMatch.Value; if (!string.IsNullOrEmpty(beforeMatch.Groups["include"].Value)) { hasInclusiveModifier = true; } } else if (afterMatch.Success) { hasAfter = true; er.Start += afterMatch.Length; er.Length -= afterMatch.Length; er.Text = er.Text.Substring(afterMatch.Length); modStr = afterMatch.Value; if (!string.IsNullOrEmpty(afterMatch.Groups["include"].Value)) { hasInclusiveModifier = true; } } else if (sinceMatch.Success) { hasSince = true; er.Start += sinceMatch.Length; er.Length -= sinceMatch.Length; er.Text = er.Text.Substring(sinceMatch.Length); modStr = sinceMatch.Value; } else if (aroundMatch.Success) { hasAround = true; er.Start += aroundMatch.Length; er.Length -= aroundMatch.Length; er.Text = er.Text.Substring(aroundMatch.Length); modStr = aroundMatch.Value; } else if ((er.Type.Equals(Constants.SYS_DATETIME_DATEPERIOD, StringComparison.Ordinal) && Config.YearRegex.Match(er.Text).Success) || er.Type.Equals(Constants.SYS_DATETIME_DATE, StringComparison.Ordinal) || er.Type.Equals(Constants.SYS_DATETIME_TIME, StringComparison.Ordinal)) { // This has to be put at the end of the if, or cases like "before 2012" and "after 2012" would fall into this // 2012 or after/above // 3 pm or later var match = Config.SuffixAfter.MatchEnd(er.Text, trim: true); if (match.Success) { hasDateAfter = true; er.Length -= match.Length; er.Text = er.Text.Substring(0, er.Length ?? 0); modStr = match.Value; } } pr = ParseResult(er, referenceTime); if (pr == null) { return(null); } // Pop, restore the MOD string if (hasBefore && (pr != null && pr.Value != null)) { pr.Length += modStr.Length; pr.Start -= modStr.Length; pr.Text = modStr + pr.Text; var val = (DateTimeResolutionResult)pr.Value; if (!hasInclusiveModifier) { val.Mod = CombineMod(val.Mod, Constants.BEFORE_MOD); } else { val.Mod = CombineMod(val.Mod, Constants.UNTIL_MOD); } pr.Value = val; } if (hasAfter && (pr != null && pr.Value != null)) { pr.Length += modStr.Length; pr.Start -= modStr.Length; pr.Text = modStr + pr.Text; var val = (DateTimeResolutionResult)pr.Value; if (!hasInclusiveModifier) { val.Mod = CombineMod(val.Mod, Constants.AFTER_MOD); } else { val.Mod = CombineMod(val.Mod, Constants.SINCE_MOD); } pr.Value = val; } if (hasSince && (pr != null && pr.Value != null)) { pr.Length += modStr.Length; pr.Start -= modStr.Length; pr.Text = modStr + pr.Text; var val = (DateTimeResolutionResult)pr.Value; val.Mod = CombineMod(val.Mod, Constants.SINCE_MOD); pr.Value = val; } if (hasAround && (pr != null && pr.Value != null)) { pr.Length += modStr.Length; pr.Start -= modStr.Length; pr.Text = modStr + pr.Text; var val = (DateTimeResolutionResult)pr.Value; val.Mod = CombineMod(val.Mod, Constants.APPROX_MOD); pr.Value = val; } if (hasDateAfter && (pr != null && pr.Value != null)) { pr.Length += modStr.Length; pr.Text = pr.Text + modStr; var val = (DateTimeResolutionResult)pr.Value; val.Mod = CombineMod(val.Mod, Constants.SINCE_MOD); pr.Value = val; hasSince = true; } // For cases like "3 pm or later on monday" if ((pr != null && pr.Value != null) && Config.SuffixAfter.Match(pr.Text)?.Index != 0 && pr.Type.Equals(Constants.SYS_DATETIME_DATETIME, StringComparison.Ordinal)) { var val = (DateTimeResolutionResult)pr.Value; val.Mod = CombineMod(val.Mod, Constants.SINCE_MOD); pr.Value = val; hasSince = true; } if ((Config.Options & DateTimeOptions.SplitDateAndTime) != 0 && ((DateTimeResolutionResult)pr?.Value)?.SubDateTimeEntities != null) { if (pr != null) { pr.Value = DateTimeResolutionForSplit(pr); } } else { var hasModifier = hasBefore || hasAfter || hasSince; pr = SetParseResult(pr, hasModifier); } // In this version, ExperimentalMode only cope with the "IncludePeriodEnd" case if ((this.Config.Options & DateTimeOptions.ExperimentalMode) != 0) { if (pr?.Metadata != null && pr.Metadata.PossiblyIncludePeriodEnd) { pr = SetInclusivePeriodEnd(pr); } } if ((this.Config.Options & DateTimeOptions.EnablePreview) != 0) { pr.Length += originText.Length - pr.Text.Length; pr.Text = originText; } return(pr); }
public DateTimeParseResult Parse(ExtractResult er, DateObject refDate) { return(null); }
private void StripInequalityDuration(ExtractResult er) { StripInequalityPrefix(er, Config.MoreThanRegex); StripInequalityPrefix(er, Config.LessThanRegex); }
public static List <Token> ExtractorDurationWithBeforeAndAfter(string text, ExtractResult er, List <Token> ret, IDateTimeUtilityConfiguration utilityConfiguration) { var pos = (int)er.Start + (int)er.Length; if (pos <= text.Length) { var afterString = text.Substring(pos); var beforeString = text.Substring(0, (int)er.Start); var isTimeDuration = utilityConfiguration.TimeUnitRegex.Match(er.Text).Success; if (MatchingUtil.GetAgoLaterIndex(afterString, utilityConfiguration.AgoRegex, out var index)) { // We don't support cases like "5 minutes from today" for now // Cases like "5 minutes ago" or "5 minutes from now" are supported // Cases like "2 days before today" or "2 weeks from today" are also supported var isDayMatchInAfterString = utilityConfiguration.AgoRegex.Match(afterString).Groups["day"].Success; if (!(isTimeDuration && isDayMatchInAfterString)) { ret.Add(new Token(er.Start ?? 0, (er.Start + er.Length ?? 0) + index)); } if (utilityConfiguration.CheckBothBeforeAfter && !isDayMatchInAfterString) { // check if regex match is split between beforeString and afterString string beforeAfterStr = beforeString + afterString.Substring(0, index); if (MatchingUtil.GetAgoLaterIndexInBeforeString(beforeAfterStr, utilityConfiguration.AgoRegex, out var indexStart)) { isDayMatchInAfterString = utilityConfiguration.AgoRegex.Match(beforeAfterStr).Groups["day"].Success; if (isDayMatchInAfterString && !(isTimeDuration && isDayMatchInAfterString)) { ret.Add(new Token(indexStart, (er.Start + er.Length ?? 0) + index)); } } } } else if (utilityConfiguration.CheckBothBeforeAfter && MatchingUtil.GetAgoLaterIndexInBeforeString(beforeString, utilityConfiguration.AgoRegex, out index)) { // Check also beforeString var isDayMatchInBeforeString = utilityConfiguration.AgoRegex.Match(beforeString).Groups["day"].Success; if (!(isTimeDuration && isDayMatchInBeforeString)) { ret.Add(new Token(index, er.Start + er.Length ?? 0)); } } else if (MatchingUtil.GetAgoLaterIndex(afterString, utilityConfiguration.LaterRegex, out index) || (utilityConfiguration.CheckBothBeforeAfter && MatchingUtil.GetAgoLaterIndexInBeforeString(beforeString, utilityConfiguration.LaterRegex, out index))) { Token tokAfter = null, tokBefore = null; if (MatchingUtil.GetAgoLaterIndex(afterString, utilityConfiguration.LaterRegex, out index)) { var isDayMatchInAfterString = utilityConfiguration.LaterRegex.Match(afterString).Groups["day"].Success; if (!(isTimeDuration && isDayMatchInAfterString)) { tokAfter = new Token(er.Start ?? 0, (er.Start + er.Length ?? 0) + index); } } // Check also beforeString if (utilityConfiguration.CheckBothBeforeAfter && MatchingUtil.GetAgoLaterIndexInBeforeString(beforeString, utilityConfiguration.LaterRegex, out index)) { var isDayMatchInBeforeString = utilityConfiguration.LaterRegex.Match(beforeString).Groups["day"].Success; if (!(isTimeDuration && isDayMatchInBeforeString)) { tokBefore = new Token(index, er.Start + er.Length ?? 0); } } if (tokAfter != null && tokBefore != null && tokBefore.Start + tokBefore.Length > tokAfter.Start) { // merge overlapping tokens ret.Add(new Token(tokBefore.Start, tokAfter.Start + tokAfter.Length - tokBefore.Start)); } else if (tokAfter != null) { ret.Add(tokAfter); } else if (tokBefore != null) { ret.Add(tokBefore); } } else if (MatchingUtil.GetTermIndex(beforeString, utilityConfiguration.InConnectorRegex, out index)) { // For range unit like "week, month, year", it should output dateRange or datetimeRange if (!utilityConfiguration.RangeUnitRegex.IsMatch(er.Text)) { if (er.Start != null && er.Length != null && (int)er.Start >= index) { ret.Add(new Token((int)er.Start - index, (int)er.Start + (int)er.Length)); } } } else if (utilityConfiguration.CheckBothBeforeAfter && MatchingUtil.GetAgoLaterIndex(afterString, utilityConfiguration.InConnectorRegex, out index)) { // Check also afterString // For range unit like "week, month, year", it should output dateRange or datetimeRange if (!utilityConfiguration.RangeUnitRegex.IsMatch(er.Text)) { if (er.Start != null && er.Length != null) { ret.Add(new Token((int)er.Start, (int)er.Start + (int)er.Length + index)); } } } else if (MatchingUtil.GetTermIndex(beforeString, utilityConfiguration.WithinNextPrefixRegex, out index)) { // For range unit like "week, month, year, day, second, minute, hour", it should output dateRange or datetimeRange if (!utilityConfiguration.DateUnitRegex.IsMatch(er.Text) && !utilityConfiguration.TimeUnitRegex.IsMatch(er.Text)) { if (er.Start != null && er.Length != null && (int)er.Start >= index) { ret.Add(new Token((int)er.Start - index, (int)er.Start + (int)er.Length)); } } } else if (utilityConfiguration.CheckBothBeforeAfter && MatchingUtil.GetAgoLaterIndex(afterString, utilityConfiguration.WithinNextPrefixRegex, out index)) { // Check also afterString // For range unit like "week, month, year, day, second, minute, hour", it should output dateRange or datetimeRange if (!utilityConfiguration.DateUnitRegex.IsMatch(er.Text) && !utilityConfiguration.TimeUnitRegex.IsMatch(er.Text)) { if (er.Start != null && er.Length != null) { ret.Add(new Token((int)er.Start, (int)er.Start + (int)er.Length + index)); } } } } return(ret); }
public DateTimeParseResult Parse(ExtractResult er, DateObject refDate) { var referenceTime = refDate; object value = null; if (er.Type.Equals(ParserName, StringComparison.Ordinal)) { var innerResult = MergeDateAndTimePeriod(er.Text, referenceTime); if (!innerResult.Success) { innerResult = MergeTwoTimePoints(er.Text, referenceTime); } if (!innerResult.Success) { innerResult = ParseSpecificNight(er.Text, referenceTime); } if (!innerResult.Success) { innerResult = ParseNumberWithUnit(er.Text, referenceTime); } if (innerResult.Success) { innerResult.FutureResolution = new Dictionary <string, string> { { TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)innerResult.FutureValue).Item1) }, { TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)innerResult.FutureValue).Item2) }, }; innerResult.PastResolution = new Dictionary <string, string> { { TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)innerResult.PastValue).Item1) }, { TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)innerResult.PastValue).Item2) }, }; value = innerResult; } } var ret = new DateTimeParseResult { Text = er.Text, Start = er.Start, Length = er.Length, Type = er.Type, Data = er.Data, Value = value, TimexStr = value == null ? string.Empty : ((DateTimeResolutionResult)value).Timex, ResolutionStr = string.Empty, }; return(ret); }
private static DateTimeResolutionResult GetAgoLaterResult( DateTimeParseResult durationParseResult, string afterStr, string beforeStr, DateObject referenceTime, IParser numberParser, IDateTimeUtilityConfiguration utilityConfiguration, AgoLaterMode mode, SwiftDayDelegate swiftDay) { var ret = new DateTimeResolutionResult(); var resultDateTime = referenceTime; var timex = durationParseResult.TimexStr; if (((DateTimeResolutionResult)durationParseResult.Value).Mod == Constants.MORE_THAN_MOD) { ret.Mod = Constants.MORE_THAN_MOD; } else if (((DateTimeResolutionResult)durationParseResult.Value).Mod == Constants.LESS_THAN_MOD) { ret.Mod = Constants.LESS_THAN_MOD; } if (MatchingUtil.ContainsAgoLaterIndex(afterStr, utilityConfiguration.AgoRegex)) { var match = utilityConfiguration.AgoRegex.Match(afterStr); var swift = 0; // Handle cases like "3 days before yesterday" if (match.Success && !string.IsNullOrEmpty(match.Groups["day"].Value)) { swift = swiftDay(match.Groups["day"].Value); } else if (utilityConfiguration.CheckBothBeforeAfter && match.Success && !MatchingUtil.ContainsAgoLaterIndexInBeforeString(beforeStr, utilityConfiguration.AgoRegex)) { match = utilityConfiguration.AgoRegex.Match(beforeStr + " " + afterStr); if (match.Success && !string.IsNullOrEmpty(match.Groups["day"].Value)) { swift = swiftDay(match.Groups["day"].Value); } } resultDateTime = DurationParsingUtil.ShiftDateTime(timex, referenceTime.AddDays(swift), false); ((DateTimeResolutionResult)durationParseResult.Value).Mod = Constants.BEFORE_MOD; } else if (utilityConfiguration.CheckBothBeforeAfter && MatchingUtil.ContainsAgoLaterIndexInBeforeString(beforeStr, utilityConfiguration.AgoRegex)) { var match = utilityConfiguration.AgoRegex.Match(beforeStr); var swift = 0; // Handle cases like "3 days before yesterday" if (match.Success && !string.IsNullOrEmpty(match.Groups["day"].Value)) { swift = swiftDay(match.Groups["day"].Value); } resultDateTime = DurationParsingUtil.ShiftDateTime(timex, referenceTime.AddDays(swift), false); ((DateTimeResolutionResult)durationParseResult.Value).Mod = Constants.BEFORE_MOD; } else if (MatchingUtil.ContainsAgoLaterIndex(afterStr, utilityConfiguration.LaterRegex) || MatchingUtil.ContainsTermIndex(beforeStr, utilityConfiguration.InConnectorRegex) || (utilityConfiguration.CheckBothBeforeAfter && MatchingUtil.ContainsAgoLaterIndexInBeforeString(beforeStr, utilityConfiguration.LaterRegex))) { var match = utilityConfiguration.LaterRegex.Match(afterStr); var swift = 0; if (utilityConfiguration.CheckBothBeforeAfter && MatchingUtil.ContainsAgoLaterIndexInBeforeString(beforeStr, utilityConfiguration.LaterRegex) && string.IsNullOrEmpty(match.Groups["day"].Value)) { match = utilityConfiguration.LaterRegex.Match(beforeStr); } // Handle cases like "3 days after tomorrow" if (match.Success && !string.IsNullOrEmpty(match.Groups["day"].Value)) { swift = swiftDay(match.Groups["day"].Value); } var yearMatch = utilityConfiguration.SinceYearSuffixRegex.Match(afterStr); if (yearMatch.Success) { var yearString = yearMatch.Groups[Constants.YearGroupName].Value; var yearEr = new ExtractResult { Text = yearString }; var year = Convert.ToInt32((double)(numberParser.Parse(yearEr).Value ?? 0)); referenceTime = DateObject.MinValue.SafeCreateFromValue(year, 1, 1); } resultDateTime = DurationParsingUtil.ShiftDateTime(timex, referenceTime.AddDays(swift), true); ((DateTimeResolutionResult)durationParseResult.Value).Mod = Constants.AFTER_MOD; } else if (utilityConfiguration.CheckBothBeforeAfter && (MatchingUtil.ContainsAgoLaterIndexInBeforeString(beforeStr, utilityConfiguration.LaterRegex) || MatchingUtil.ContainsAgoLaterIndex(afterStr, utilityConfiguration.InConnectorRegex) || MatchingUtil.ContainsAgoLaterIndex(afterStr, utilityConfiguration.LaterRegex))) { // Check also beforeStr var match = utilityConfiguration.LaterRegex.Match(beforeStr); var swift = 0; if (MatchingUtil.ContainsAgoLaterIndex(afterStr, utilityConfiguration.LaterRegex) && string.IsNullOrEmpty(match.Groups["day"].Value)) { match = utilityConfiguration.LaterRegex.Match(beforeStr); } // Handle cases like "3 days after tomorrow" if (match.Success && !string.IsNullOrEmpty(match.Groups["day"].Value)) { swift = swiftDay(match.Groups["day"].Value); } var yearMatch = utilityConfiguration.SinceYearSuffixRegex.Match(beforeStr); if (yearMatch.Success) { var yearString = yearMatch.Groups[Constants.YearGroupName].Value; var yearEr = new ExtractResult { Text = yearString }; var year = Convert.ToInt32((double)(numberParser.Parse(yearEr).Value ?? 0)); referenceTime = DateObject.MinValue.SafeCreateFromValue(year, 1, 1); } resultDateTime = DurationParsingUtil.ShiftDateTime(timex, referenceTime.AddDays(swift), true); ((DateTimeResolutionResult)durationParseResult.Value).Mod = Constants.AFTER_MOD; } if (resultDateTime != referenceTime) { if (mode.Equals(AgoLaterMode.Date)) { ret.Timex = $"{DateTimeFormatUtil.LuisDate(resultDateTime)}"; } else if (mode.Equals(AgoLaterMode.DateTime)) { ret.Timex = $"{DateTimeFormatUtil.LuisDateTime(resultDateTime)}"; } ret.FutureValue = ret.PastValue = resultDateTime; ret.SubDateTimeEntities = new List <object> { durationParseResult }; ret.Success = true; } return(ret); }
public static bool IsCover(this ExtractResult er1, ExtractResult er2) { return(er2.Start < er1.Start && er2.Start + er2.Length >= er1.Start + er1.Length || er2.Start <= er1.Start && er2.Start + er2.Length > er1.Start + er1.Length); }
public static bool IsOverlap(this ExtractResult er1, ExtractResult er2) { return(!(er1.Start >= er2.Start + er2.Length) && !(er2.Start >= er1.Start + er1.Length)); }
public DateTimeParseResult Parse(ExtractResult er, DateObject refTime) { var referenceTime = refTime; object value = null; if (er.Type.Equals(ParserName, StringComparison.Ordinal)) { DateTimeResolutionResult innerResult; if (TimeZoneUtility.ShouldResolveTimeZone(er, config.Options)) { var metadata = er.Data as Dictionary <string, object>; var timezoneEr = metadata[Constants.SYS_DATETIME_TIMEZONE] as ExtractResult; var timezonePr = config.TimeZoneParser.Parse(timezoneEr); innerResult = InternalParse( er.Text.Substring(0, (int)(er.Length - timezoneEr.Length)), referenceTime); if (timezonePr != null && timezonePr.Value != null) { innerResult.TimeZoneResolution = ((DateTimeResolutionResult)timezonePr.Value).TimeZoneResolution; } } else { innerResult = InternalParse(er.Text, referenceTime); } if (innerResult.Success) { innerResult.FutureResolution = new Dictionary <string, string> { { TimeTypeConstants.START_TIME, DateTimeFormatUtil.FormatTime(((Tuple <DateObject, DateObject>)innerResult.FutureValue).Item1) }, { TimeTypeConstants.END_TIME, DateTimeFormatUtil.FormatTime(((Tuple <DateObject, DateObject>)innerResult.FutureValue).Item2) }, }; innerResult.PastResolution = new Dictionary <string, string> { { TimeTypeConstants.START_TIME, DateTimeFormatUtil.FormatTime(((Tuple <DateObject, DateObject>)innerResult.PastValue).Item1) }, { TimeTypeConstants.END_TIME, DateTimeFormatUtil.FormatTime(((Tuple <DateObject, DateObject>)innerResult.PastValue).Item2) }, }; value = innerResult; } } var ret = new DateTimeParseResult { Text = er.Text, Start = er.Start, Length = er.Length, Type = er.Type, Data = er.Data, Value = value, TimexStr = value == null ? string.Empty : ((DateTimeResolutionResult)value).Timex, ResolutionStr = string.Empty, }; return(ret); }
private bool IsDurationWithBeforeAndAfter(ExtractResult er) { return(er.Metadata != null && er.Metadata.IsDurationWithBeforeAndAfter); }
// Cases like "from 3:30 to 5" or "between 3:30am to 6pm", at least one of the time point contains colon private DateTimeResolutionResult ParseSpecificTimeCases(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); int year = referenceTime.Year, month = referenceTime.Month, day = referenceTime.Day; // Handle cases like "from 4:30 to 5" var match = config.SpecificTimeFromToRegex.MatchExact(text, trim: true); if (!match.Success) { // Handle cases like "between 5:10 and 7" match = config.SpecificTimeBetweenAndRegex.MatchExact(text, trim: true); } if (match.Success) { // Cases like "half past seven" are not handled here if (match.Groups[Constants.PrefixGroupName].Success) { return(ret); } // Cases like "4" is different with "4:00" as the Timex is different "T04H" vs "T04H00M" int beginHour; int beginMinute = Constants.InvalidMinute; int beginSecond = Constants.InvalidSecond; int endHour; int endMinute = Constants.InvalidMinute; int endSecond = Constants.InvalidSecond; // Get time1 and time2 var hourGroup = match.Groups[Constants.HourGroupName]; var hourStr = hourGroup.Captures[0].Value; if (config.Numbers.ContainsKey(hourStr)) { beginHour = config.Numbers[hourStr]; } else { beginHour = int.Parse(hourStr, CultureInfo.InvariantCulture); } hourStr = hourGroup.Captures[1].Value; if (config.Numbers.ContainsKey(hourStr)) { endHour = config.Numbers[hourStr]; } else { endHour = int.Parse(hourStr, CultureInfo.InvariantCulture); } var time1StartIndex = match.Groups["time1"].Index; var time1EndIndex = time1StartIndex + match.Groups["time1"].Length; var time2StartIndex = match.Groups["time2"].Index; var time2EndIndex = time2StartIndex + match.Groups["time2"].Length; // Get beginMinute (if exists) and endMinute (if exists) for (int i = 0; i < match.Groups[Constants.MinuteGroupName].Captures.Count; i++) { var minuteCapture = match.Groups[Constants.MinuteGroupName].Captures[i]; if (minuteCapture.Index >= time1StartIndex && minuteCapture.Index + minuteCapture.Length <= time1EndIndex) { beginMinute = int.Parse(minuteCapture.Value, CultureInfo.InvariantCulture); } else if (minuteCapture.Index >= time2StartIndex && minuteCapture.Index + minuteCapture.Length <= time2EndIndex) { endMinute = int.Parse(minuteCapture.Value, CultureInfo.InvariantCulture); } } // Get beginSecond (if exists) and endSecond (if exists) for (int i = 0; i < match.Groups[Constants.SecondGroupName].Captures.Count; i++) { var secondCapture = match.Groups[Constants.SecondGroupName].Captures[i]; if (secondCapture.Index >= time1StartIndex && secondCapture.Index + secondCapture.Length <= time1EndIndex) { beginSecond = int.Parse(secondCapture.Value, CultureInfo.InvariantCulture); } else if (secondCapture.Index >= time2StartIndex && secondCapture.Index + secondCapture.Length <= time2EndIndex) { endSecond = int.Parse(secondCapture.Value, CultureInfo.InvariantCulture); } } // Desc here means descriptions like "am / pm / o'clock" // Get leftDesc (if exists) and rightDesc (if exists) var leftDesc = match.Groups["leftDesc"].Value; var rightDesc = match.Groups["rightDesc"].Value; for (int i = 0; i < match.Groups[Constants.DescGroupName].Captures.Count; i++) { var descCapture = match.Groups[Constants.DescGroupName].Captures[i]; if (descCapture.Index >= time1StartIndex && descCapture.Index + descCapture.Length <= time1EndIndex && string.IsNullOrEmpty(leftDesc)) { leftDesc = descCapture.Value; } else if (descCapture.Index >= time2StartIndex && descCapture.Index + descCapture.Length <= time2EndIndex && string.IsNullOrEmpty(rightDesc)) { rightDesc = descCapture.Value; } } var beginDateTime = DateObject.MinValue.SafeCreateFromValue(year, month, day, beginHour, beginMinute >= 0 ? beginMinute : 0, beginSecond >= 0 ? beginSecond : 0); var endDateTime = DateObject.MinValue.SafeCreateFromValue(year, month, day, endHour, endMinute >= 0 ? endMinute : 0, endSecond >= 0 ? endSecond : 0); var hasLeftAm = !string.IsNullOrEmpty(leftDesc) && leftDesc.StartsWith("a", StringComparison.Ordinal); var hasLeftPm = !string.IsNullOrEmpty(leftDesc) && leftDesc.StartsWith("p", StringComparison.Ordinal); var hasRightAm = !string.IsNullOrEmpty(rightDesc) && rightDesc.StartsWith("a", StringComparison.Ordinal); var hasRightPm = !string.IsNullOrEmpty(rightDesc) && rightDesc.StartsWith("p", StringComparison.Ordinal); var hasLeft = hasLeftAm || hasLeftPm; var hasRight = hasRightAm || hasRightPm; // Both time point has description like 'am' or 'pm' if (hasLeft && hasRight) { if (hasLeftAm) { if (beginHour >= Constants.HalfDayHourCount) { beginDateTime = beginDateTime.AddHours(-Constants.HalfDayHourCount); } } else { if (beginHour < Constants.HalfDayHourCount) { beginDateTime = beginDateTime.AddHours(Constants.HalfDayHourCount); } } if (hasRightAm) { if (endHour > Constants.HalfDayHourCount) { endDateTime = endDateTime.AddHours(-Constants.HalfDayHourCount); } } else { if (endHour < Constants.HalfDayHourCount) { endDateTime = endDateTime.AddHours(Constants.HalfDayHourCount); } } } else if (hasLeft || hasRight) { // one of the time point has description like 'am' or 'pm' if (hasLeftAm) { if (beginHour >= Constants.HalfDayHourCount) { beginDateTime = beginDateTime.AddHours(-Constants.HalfDayHourCount); } if (endHour < Constants.HalfDayHourCount) { if (endDateTime < beginDateTime) { endDateTime = endDateTime.AddHours(Constants.HalfDayHourCount); } } } else if (hasLeftPm) { if (beginHour < Constants.HalfDayHourCount) { beginDateTime = beginDateTime.AddHours(Constants.HalfDayHourCount); } if (endHour < Constants.HalfDayHourCount) { if (endDateTime < beginDateTime) { var span = beginDateTime - endDateTime; endDateTime = endDateTime.AddHours(span.TotalHours >= Constants.HalfDayHourCount ? 24 : Constants.HalfDayHourCount); } } } if (hasRightAm) { if (endHour >= Constants.HalfDayHourCount) { endDateTime = endDateTime.AddHours(-Constants.HalfDayHourCount); } if (beginHour < Constants.HalfDayHourCount) { if (endDateTime < beginDateTime) { beginDateTime = beginDateTime.AddHours(-Constants.HalfDayHourCount); } } } else if (hasRightPm) { if (endHour < Constants.HalfDayHourCount) { endDateTime = endDateTime.AddHours(Constants.HalfDayHourCount); } if (beginHour < Constants.HalfDayHourCount) { if (endDateTime < beginDateTime) { beginDateTime = beginDateTime.AddHours(-Constants.HalfDayHourCount); } else { var span = endDateTime - beginDateTime; if (span.TotalHours > Constants.HalfDayHourCount) { beginDateTime = beginDateTime.AddHours(Constants.HalfDayHourCount); } } } } } // No 'am' or 'pm' indicator else if (beginHour <= Constants.HalfDayHourCount && endHour <= Constants.HalfDayHourCount) { if (beginHour > endHour) { if (beginHour == Constants.HalfDayHourCount) { beginDateTime = beginDateTime.AddHours(-Constants.HalfDayHourCount); } else { endDateTime = endDateTime.AddHours(Constants.HalfDayHourCount); } } ret.Comment = Constants.Comment_AmPm; } if (endDateTime < beginDateTime) { endDateTime = endDateTime.AddHours(24); } var beginStr = DateTimeFormatUtil.ShortTime(beginDateTime.Hour, beginMinute, beginSecond); var endStr = DateTimeFormatUtil.ShortTime(endDateTime.Hour, endMinute, endSecond); ret.Success = true; ret.Timex = $"({beginStr},{endStr},{DateTimeFormatUtil.LuisTimeSpan(endDateTime - beginDateTime)})"; ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>( beginDateTime, endDateTime); ret.SubDateTimeEntities = new List <object>(); // In SplitDateAndTime mode, time points will be get from these SubDateTimeEntities // Cases like "from 4 to 5pm", "4" should not be treated as SubDateTimeEntity if (hasLeft || beginMinute != Constants.InvalidMinute || beginSecond != Constants.InvalidSecond) { var er = new ExtractResult() { Start = time1StartIndex, Length = time1EndIndex - time1StartIndex, Text = text.Substring(time1StartIndex, time1EndIndex - time1StartIndex), Type = $"{Constants.SYS_DATETIME_TIME}", }; var pr = this.config.TimeParser.Parse(er, referenceTime); ret.SubDateTimeEntities.Add(pr); } // Cases like "from 4am to 5", "5" should not be treated as SubDateTimeEntity if (hasRight || endMinute != Constants.InvalidMinute || endSecond != Constants.InvalidSecond) { var er = new ExtractResult { Start = time2StartIndex, Length = time2EndIndex - time2StartIndex, Text = text.Substring(time2StartIndex, time2EndIndex - time2StartIndex), Type = $"{Constants.SYS_DATETIME_TIME}", }; var pr = this.config.TimeParser.Parse(er, referenceTime); ret.SubDateTimeEntities.Add(pr); } ret.Success = true; } return(ret); }
private bool ShouldSkipFromToMerge(ExtractResult er) { return(config.FromToRegex.IsMatch(er.Text)); }
public ParseResult Parse(ExtractResult er) { return(Parse(er, DateObject.Now)); }
// Merge a Date entity and a Time entity, like "at 7 tomorrow" public List <Token> MergeDateAndTime(string text, DateObject reference) { var ret = new List <Token>(); var dateErs = this.config.DatePointExtractor.Extract(text, reference); if (dateErs.Count == 0) { return(ret); } var timeErs = this.config.TimePointExtractor.Extract(text, reference); var timeNumMatches = this.config.NumberAsTimeRegex.Matches(text); if (timeErs.Count == 0 && timeNumMatches.Count == 0) { return(ret); } var ers = dateErs; ers.AddRange(timeErs); // handle cases which use numbers as time points // only enabled in CalendarMode if ((this.config.Options & DateTimeOptions.CalendarMode) != 0) { var numErs = new List <ExtractResult>(); for (var idx = 0; idx < timeNumMatches.Count; idx++) { var match = timeNumMatches[idx]; var node = new ExtractResult { Start = match.Index, Length = match.Length, Text = match.Value, Type = Number.Constants.SYS_NUM_INTEGER, }; numErs.Add(node); } ers.AddRange(numErs); } ers = ers.OrderBy(o => o.Start).ToList(); var i = 0; while (i < ers.Count - 1) { var j = i + 1; while (j < ers.Count && ers[i].IsOverlap(ers[j])) { j++; } if (j >= ers.Count) { break; } if ((ers[i].Type.Equals(Constants.SYS_DATETIME_DATE, StringComparison.Ordinal) && ers[j].Type.Equals(Constants.SYS_DATETIME_TIME, StringComparison.Ordinal)) || (ers[i].Type.Equals(Constants.SYS_DATETIME_TIME, StringComparison.Ordinal) && ers[j].Type.Equals(Constants.SYS_DATETIME_DATE, StringComparison.Ordinal)) || (ers[i].Type.Equals(Constants.SYS_DATETIME_DATE, StringComparison.Ordinal) && ers[j].Type.Equals(Number.Constants.SYS_NUM_INTEGER, StringComparison.Ordinal))) { var middleBegin = ers[i].Start + ers[i].Length ?? 0; var middleEnd = ers[j].Start ?? 0; if (middleBegin > middleEnd) { i = j + 1; continue; } var middleStr = text.Substring(middleBegin, middleEnd - middleBegin).Trim(); var valid = false; // for cases like "tomorrow 3", "tomorrow at 3" if (ers[j].Type.Equals(Number.Constants.SYS_NUM_INTEGER, StringComparison.Ordinal)) { var match = this.config.DateNumberConnectorRegex.Match(middleStr); if (string.IsNullOrEmpty(middleStr) || match.Success) { valid = true; } } else { // For case like "3 pm or later on monday" var match = this.config.SuffixAfterRegex.Match(middleStr); if (match.Success) { middleStr = middleStr.Substring(match.Index + match.Length, middleStr.Length - match.Length).Trim(); } if (!(match.Success && middleStr.Length == 0)) { if (this.config.IsConnector(middleStr)) { valid = true; } } } if (valid) { var begin = ers[i].Start ?? 0; var end = (ers[j].Start ?? 0) + (ers[j].Length ?? 0); ExtendWithDateTimeAndYear(ref begin, ref end, text, reference); ret.Add(new Token(begin, end)); i = j + 1; continue; } } i = j; } // Handle "in the afternoon" at the end of entity for (var idx = 0; idx < ret.Count; idx++) { var afterStr = text.Substring(ret[idx].End); var match = this.config.SuffixRegex.Match(afterStr); if (match.Success) { ret[idx] = new Token(ret[idx].Start, ret[idx].End + match.Length); } } // Handle "day" prefixes for (var idx = 0; idx < ret.Count; idx++) { var beforeStr = text.Substring(0, ret[idx].Start); var match = this.config.UtilityConfiguration.CommonDatePrefixRegex.Match(beforeStr); if (match.Success) { ret[idx] = new Token(ret[idx].Start - match.Length, ret[idx].End); } } return(ret); }
private DateTimeParseResult ParseResult(ExtractResult extractResult, DateObject referenceTime) { DateTimeParseResult parseResult = null; switch (extractResult.Type) { case Constants.SYS_DATETIME_DATE: if (extractResult.Metadata != null && extractResult.Metadata.IsHoliday) { parseResult = Config.HolidayParser.Parse(extractResult, referenceTime); } else { parseResult = this.Config.DateParser.Parse(extractResult, referenceTime); } break; case Constants.SYS_DATETIME_TIME: parseResult = this.Config.TimeParser.Parse(extractResult, referenceTime); break; case Constants.SYS_DATETIME_DATETIME: parseResult = this.Config.DateTimeParser.Parse(extractResult, referenceTime); break; case Constants.SYS_DATETIME_DATEPERIOD: parseResult = this.Config.DatePeriodParser.Parse(extractResult, referenceTime); break; case Constants.SYS_DATETIME_TIMEPERIOD: parseResult = this.Config.TimePeriodParser.Parse(extractResult, referenceTime); break; case Constants.SYS_DATETIME_DATETIMEPERIOD: parseResult = this.Config.DateTimePeriodParser.Parse(extractResult, referenceTime); break; case Constants.SYS_DATETIME_DURATION: parseResult = this.Config.DurationParser.Parse(extractResult, referenceTime); break; case Constants.SYS_DATETIME_SET: parseResult = this.Config.SetParser.Parse(extractResult, referenceTime); break; case Constants.SYS_DATETIME_DATETIMEALT: parseResult = this.Config.DateTimeAltParser.Parse(extractResult, referenceTime); break; case Constants.SYS_DATETIME_TIMEZONE: if ((Config.Options & DateTimeOptions.EnablePreview) != 0) { parseResult = this.Config.TimeZoneParser.Parse(extractResult, referenceTime); } break; default: return(null); } return(parseResult); }
public DateTimeParseResult Parse(ExtractResult er, DateObject refDate) { var referenceTime = refDate; // handle cases like "三年半" var hasHalfSuffix = false; if (er.Text.EndsWith("半")) { er.Length -= 1; er.Text = er.Text.Substring(0, er.Text.Length - 1); hasHalfSuffix = true; } var parseResult = InternalParser.Parse(er); var unitResult = parseResult.Value as UnitValue; if (unitResult == null) { return(null); } var dateTimeParseResult = new DateTimeResolutionResult(); var unitStr = unitResult.Unit; var numStr = unitResult.Number; if (hasHalfSuffix) { numStr = (double.Parse(numStr) + 0.5).ToString(CultureInfo.InvariantCulture); } dateTimeParseResult.Timex = "P" + (BaseDurationParser.IsLessThanDay(unitStr) ? "T" : string.Empty) + numStr + unitStr[0]; dateTimeParseResult.FutureValue = dateTimeParseResult.PastValue = double.Parse(numStr) * UnitValueMap[unitStr]; dateTimeParseResult.Success = true; if (dateTimeParseResult.Success) { dateTimeParseResult.FutureResolution = new Dictionary <string, string> { { TimeTypeConstants.DURATION, dateTimeParseResult.FutureValue.ToString() }, }; dateTimeParseResult.PastResolution = new Dictionary <string, string> { { TimeTypeConstants.DURATION, dateTimeParseResult.PastValue.ToString() }, }; } var ret = new DateTimeParseResult { Text = er.Text, Start = er.Start, Length = er.Length, Type = er.Type, Data = er.Data, Value = dateTimeParseResult, TimexStr = dateTimeParseResult.Timex, ResolutionStr = string.Empty, }; return(ret); }
private List <ExtractResult> MergeMultipleDuration(string text, List <ExtractResult> extractorResults) { if (extractorResults.Count <= 1) { return(extractorResults); } var unitMap = this.config.UnitMap; var unitValueMap = this.config.UnitValueMap; var unitRegex = this.config.DurationUnitRegex; List <ExtractResult> results = new List <ExtractResult>(); List <List <ExtractResult> > separateResults = new List <List <ExtractResult> >(); var firstExtractionIndex = 0; var timeUnit = 0; var totalUnit = 0; while (firstExtractionIndex < extractorResults.Count) { string curUnit = null; var unitMatch = unitRegex.Match(extractorResults[firstExtractionIndex].Text); if (unitMatch.Success && unitMap.ContainsKey(unitMatch.Groups["unit"].ToString())) { curUnit = unitMatch.Groups["unit"].ToString(); totalUnit++; if (DurationParsingUtil.IsTimeDurationUnit(unitMap[curUnit])) { timeUnit++; } } if (string.IsNullOrEmpty(curUnit)) { firstExtractionIndex++; continue; } // Add extraction to list of separate results (needed in case the extractions should not be merged) List <ExtractResult> separateList = new List <ExtractResult>() { extractorResults[firstExtractionIndex] }; var secondExtractionIndex = firstExtractionIndex + 1; while (secondExtractionIndex < extractorResults.Count) { var valid = false; var midStrBegin = extractorResults[secondExtractionIndex - 1].Start + extractorResults[secondExtractionIndex - 1].Length ?? 0; var midStrEnd = extractorResults[secondExtractionIndex].Start ?? 0; var midStr = text.Substring(midStrBegin, midStrEnd - midStrBegin); var match = this.config.DurationConnectorRegex.Match(midStr); if (match.Success) { unitMatch = unitRegex.Match(extractorResults[secondExtractionIndex].Text); if (unitMatch.Success && unitMap.ContainsKey(unitMatch.Groups["unit"].ToString())) { var nextUnitStr = unitMatch.Groups["unit"].ToString(); if (unitValueMap[nextUnitStr] != unitValueMap[curUnit]) { valid = true; if (unitValueMap[nextUnitStr] < unitValueMap[curUnit]) { curUnit = nextUnitStr; } } totalUnit++; if (DurationParsingUtil.IsTimeDurationUnit(unitMap[nextUnitStr])) { timeUnit++; } } } if (!valid) { break; } // Add extraction to list of separate results (needed in case the extractions should not be merged) separateList.Add(extractorResults[secondExtractionIndex]); secondExtractionIndex++; } if (secondExtractionIndex - 1 > firstExtractionIndex) { var node = new ExtractResult(); node.Start = extractorResults[firstExtractionIndex].Start; node.Length = extractorResults[secondExtractionIndex - 1].Start + extractorResults[secondExtractionIndex - 1].Length - node.Start; node.Text = text.Substring(node.Start ?? 0, node.Length ?? 0); node.Type = extractorResults[firstExtractionIndex].Type; // Add multiple duration type to extract result string type = Constants.MultipleDuration_DateTime; // Default type if (timeUnit == totalUnit) { type = Constants.MultipleDuration_Time; } else if (timeUnit == 0) { type = Constants.MultipleDuration_Date; } node.Data = type; results.Add(node); timeUnit = 0; totalUnit = 0; } else { results.Add(extractorResults[firstExtractionIndex]); } // Add list of separate extractions to separateResults, so that there is a 1 to 1 correspondence // between results (list of merged extractions) and separateResults (list of unmerged extractions) separateResults.Add(separateList); firstExtractionIndex = secondExtractionIndex; } // If the first and last elements of a group of contiguous extractions are both preceded/followed by modifiers, // they should not be merged, e.g. "last 2 weeks and 3 days ago" for (int i = results.Count - 1; i >= 0; i--) { var start = (int)results[i].Start; var end = start + (int)results[i].Length; var beforeStr = text.Substring(0, start); var afterStr = text.Substring(end); var beforeMod = this.config.ModPrefixRegex.MatchEnd(beforeStr, trim: true); var afterMod = this.config.ModSuffixRegex.MatchBegin(afterStr, trim: true); if (beforeMod.Success && afterMod.Success) { results.RemoveAt(i); results.InsertRange(i, separateResults[i]); } } return(results); }
public DateTimeParseResult Parse(ExtractResult er, DateObject refTime) { var referenceTime = refTime; object value = null; if (er.Type.Equals(ParserName, StringComparison.Ordinal)) { var innerResult = ParseMergedDuration(er.Text, referenceTime); if (!innerResult.Success) { innerResult = ParseNumberWithUnit(er.Text, referenceTime); } if (!innerResult.Success) { innerResult = ParseImplicitDuration(er.Text, referenceTime); } if (innerResult.Success) { innerResult.FutureResolution = new Dictionary <string, string> { { TimeTypeConstants.DURATION, innerResult.FutureValue.ToString() }, }; innerResult.PastResolution = new Dictionary <string, string> { { TimeTypeConstants.DURATION, innerResult.PastValue.ToString() }, }; value = innerResult; } } var res = (DateTimeResolutionResult)value; if (res != null && er.Data != null) { if (er.Data.Equals(Constants.MORE_THAN_MOD)) { res.Mod = Constants.MORE_THAN_MOD; } else if (er.Data.Equals(Constants.LESS_THAN_MOD)) { res.Mod = Constants.LESS_THAN_MOD; } } var ret = new DateTimeParseResult { Text = er.Text, Start = er.Start, Length = er.Length, Type = er.Type, Data = er.Data, Value = value, TimexStr = value == null ? string.Empty : ((DateTimeResolutionResult)value).Timex, ResolutionStr = string.Empty, }; return(ret); }
public DateTimeParseResult Parse(ExtractResult er, DateObject referenceTime) { DateTimeParseResult pr = null; // push, save teh MOD string bool hasBefore = false, hasAfter = false, hasUntil = false, hasSince = false; string modStr = string.Empty, modStrPrefix = string.Empty, modStrSuffix = string.Empty; var beforeMatch = config.BeforeRegex.Match(er.Text); var afterMatch = config.AfterRegex.Match(er.Text); var untilMatch = config.UntilRegex.Match(er.Text); var sinceMatchPrefix = config.SincePrefixRegex.Match(er.Text); var sinceMatchSuffix = config.SinceSuffixRegex.Match(er.Text); if (beforeMatch.Success && er.Text.EndsWith(beforeMatch.Value)) { hasBefore = true; er.Length -= beforeMatch.Length; er.Text = er.Text.Substring(0, er.Length ?? 0); modStr = beforeMatch.Value; } else if (afterMatch.Success && er.Text.EndsWith(afterMatch.Value)) { hasAfter = true; er.Length -= afterMatch.Length; er.Text = er.Text.Substring(0, er.Length ?? 0); modStr = afterMatch.Value; } else if (untilMatch.Success && untilMatch.Index == 0) { hasUntil = true; er.Start += untilMatch.Length; er.Length -= untilMatch.Length; er.Text = er.Text.Substring(untilMatch.Length); modStr = untilMatch.Value; } else { if (sinceMatchPrefix.Success && sinceMatchPrefix.Index == 0) { hasSince = true; er.Start += sinceMatchPrefix.Length; er.Length -= sinceMatchPrefix.Length; er.Text = er.Text.Substring(sinceMatchPrefix.Length); modStrPrefix = sinceMatchPrefix.Value; } if (sinceMatchSuffix.Success && er.Text.EndsWith(sinceMatchSuffix.Value)) { hasSince = true; er.Length -= sinceMatchSuffix.Length; er.Text = er.Text.Substring(0, er.Length ?? 0); modStrSuffix = sinceMatchSuffix.Value; } } if (er.Type.Equals(Constants.SYS_DATETIME_DATE)) { pr = config.DateParser.Parse(er, referenceTime); if (pr.Value == null) { pr = config.HolidayParser.Parse(er, referenceTime); } } else if (er.Type.Equals(Constants.SYS_DATETIME_TIME)) { pr = config.TimeParser.Parse(er, referenceTime); } else if (er.Type.Equals(Constants.SYS_DATETIME_DATETIME)) { pr = config.DateTimeParser.Parse(er, referenceTime); } else if (er.Type.Equals(Constants.SYS_DATETIME_DATEPERIOD)) { pr = config.DatePeriodParser.Parse(er, referenceTime); } else if (er.Type.Equals(Constants.SYS_DATETIME_TIMEPERIOD)) { pr = config.TimePeriodParser.Parse(er, referenceTime); } else if (er.Type.Equals(Constants.SYS_DATETIME_DATETIMEPERIOD)) { pr = config.DateTimePeriodParser.Parse(er, referenceTime); } else if (er.Type.Equals(Constants.SYS_DATETIME_DURATION)) { pr = config.DurationParser.Parse(er, referenceTime); } else if (er.Type.Equals(Constants.SYS_DATETIME_SET)) { pr = config.GetParser.Parse(er, referenceTime); } else { return(null); } // pop, restore the MOD string if (hasBefore) { pr.Length += modStr.Length; pr.Text = pr.Text + modStr; var val = (DateTimeResolutionResult)pr.Value; val.Mod = Constants.BEFORE_MOD; pr.Value = val; } if (hasAfter) { pr.Length += modStr.Length; pr.Text = pr.Text + modStr; var val = (DateTimeResolutionResult)pr.Value; val.Mod = Constants.AFTER_MOD; pr.Value = val; } if (hasUntil) { pr.Length += modStr.Length; pr.Start -= modStr.Length; pr.Text = modStr + pr.Text; var val = (DateTimeResolutionResult)pr.Value; val.Mod = Constants.BEFORE_MOD; pr.Value = val; hasBefore = true; } if (hasSince) { pr.Length += modStrPrefix.Length + modStrSuffix.Length; pr.Start -= modStrPrefix.Length; pr.Text = modStrPrefix + pr.Text + modStrSuffix; var val = (DateTimeResolutionResult)pr.Value; val.Mod = Constants.SINCE_MOD; pr.Value = val; } pr.Value = DateTimeResolution(pr, hasBefore, hasAfter, hasSince); //change the type at last for the after or before mode pr.Type = $"{ParserTypeName}.{DetermineDateTimeType(er.Type, hasBefore, hasAfter, hasSince)}"; return(pr); }
private void GetResolution(ExtractResult er, DateTimeParseResult pr, DateTimeResolutionResult ret) { var parentText = (string)((Dictionary <string, object>)er.Data)[ExtendedModelResult.ParentTextKey]; var type = pr.Type; var isPeriod = false; var isSinglePoint = false; string singlePointResolution = ""; string pastStartPointResolution = ""; string pastEndPointResolution = ""; string futureStartPointResolution = ""; string futureEndPointResolution = ""; string singlePointType = ""; string startPointType = ""; string endPointType = ""; if (type == Constants.SYS_DATETIME_DATEPERIOD || type == Constants.SYS_DATETIME_TIMEPERIOD || type == Constants.SYS_DATETIME_DATETIMEPERIOD) { isPeriod = true; switch (type) { case Constants.SYS_DATETIME_DATEPERIOD: startPointType = TimeTypeConstants.START_DATE; endPointType = TimeTypeConstants.END_DATE; pastStartPointResolution = FormatUtil.FormatDate(((Tuple <DateObject, DateObject>)ret.PastValue).Item1); pastEndPointResolution = FormatUtil.FormatDate(((Tuple <DateObject, DateObject>)ret.PastValue).Item2); futureStartPointResolution = FormatUtil.FormatDate(((Tuple <DateObject, DateObject>)ret.FutureValue).Item1); futureEndPointResolution = FormatUtil.FormatDate(((Tuple <DateObject, DateObject>)ret.FutureValue).Item2); break; case Constants.SYS_DATETIME_DATETIMEPERIOD: startPointType = TimeTypeConstants.START_DATETIME; endPointType = TimeTypeConstants.END_DATETIME; pastStartPointResolution = FormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)ret.PastValue).Item1); pastEndPointResolution = FormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)ret.PastValue).Item2); futureStartPointResolution = FormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)ret.FutureValue).Item1); futureEndPointResolution = FormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)ret.FutureValue).Item2); break; case Constants.SYS_DATETIME_TIMEPERIOD: startPointType = TimeTypeConstants.START_TIME; endPointType = TimeTypeConstants.END_TIME; pastStartPointResolution = FormatUtil.FormatTime(((Tuple <DateObject, DateObject>)ret.PastValue).Item1); pastEndPointResolution = FormatUtil.FormatTime(((Tuple <DateObject, DateObject>)ret.PastValue).Item2); futureStartPointResolution = FormatUtil.FormatTime(((Tuple <DateObject, DateObject>)ret.FutureValue).Item1); futureEndPointResolution = FormatUtil.FormatTime(((Tuple <DateObject, DateObject>)ret.FutureValue).Item2); break; } } else { isSinglePoint = true; switch (type) { case Constants.SYS_DATETIME_DATE: singlePointType = TimeTypeConstants.DATE; singlePointResolution = FormatUtil.FormatDate((DateObject)ret.FutureValue); break; case Constants.SYS_DATETIME_DATETIME: singlePointType = TimeTypeConstants.DATETIME; singlePointResolution = FormatUtil.FormatDateTime((DateObject)ret.FutureValue); break; case Constants.SYS_DATETIME_TIME: singlePointType = TimeTypeConstants.TIME; singlePointResolution = FormatUtil.FormatTime((DateObject)ret.FutureValue); break; } } if (isPeriod) { ret.FutureResolution = new Dictionary <string, string> { { startPointType, futureStartPointResolution }, { endPointType, futureEndPointResolution }, { ExtendedModelResult.ParentTextKey, parentText } }; ret.PastResolution = new Dictionary <string, string> { { startPointType, pastStartPointResolution }, { endPointType, pastEndPointResolution }, { ExtendedModelResult.ParentTextKey, parentText } }; } else if (isSinglePoint) { ret.FutureResolution = new Dictionary <string, string> { { singlePointType, singlePointResolution }, { ExtendedModelResult.ParentTextKey, parentText } }; ret.PastResolution = new Dictionary <string, string> { { singlePointType, singlePointResolution }, { ExtendedModelResult.ParentTextKey, parentText } }; } }
private static bool IsMultipleDuration(ExtractResult er) { return(er.Data != null && er.Data.ToString().StartsWith(Constants.MultipleDuration_Prefix, StringComparison.Ordinal)); }
// merge the entity with its related contexts and then parse the combine text private DateTimeResolutionResult ParseDateTimeAndTimeAlt(ExtractResult er, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); // Original type of the extracted entity var subType = ((Dictionary <string, object>)(er.Data))[Constants.SubType].ToString(); var dateTimeEr = new ExtractResult(); // e.g. {next week Mon} or {Tue}, formmer--"next week Mon" doesn't contain "context" key var hasContext = false; ExtractResult contextEr = null; if (((Dictionary <string, object>)er.Data).ContainsKey(Constants.Context)) { contextEr = (ExtractResult)((Dictionary <string, object>)er.Data)[Constants.Context]; if (contextEr.Type.Equals(Constants.ContextType_RelativeSuffix)) { dateTimeEr.Text = $"{er.Text} {contextEr.Text}"; } else { dateTimeEr.Text = $"{contextEr.Text} {er.Text}"; } hasContext = true; } else { dateTimeEr.Text = er.Text; } var dateTimePr = new DateTimeParseResult(); if (subType == Constants.SYS_DATETIME_DATE) { dateTimeEr.Type = Constants.SYS_DATETIME_DATE; dateTimePr = this.config.DateParser.Parse(dateTimeEr, referenceTime); } else if (subType == Constants.SYS_DATETIME_TIME) { if (!hasContext) { dateTimeEr.Type = Constants.SYS_DATETIME_TIME; dateTimePr = this.config.TimeParser.Parse(dateTimeEr, referenceTime); } else if (contextEr.Type == Constants.SYS_DATETIME_DATE || contextEr.Type == Constants.ContextType_RelativePrefix) { // For cases: // Monday 9 am or 11 am // next 9 am or 11 am dateTimeEr.Type = Constants.SYS_DATETIME_DATETIME; dateTimePr = this.config.DateTimeParser.Parse(dateTimeEr, referenceTime); } else if (contextEr.Type == Constants.ContextType_AmPm) { // For cases: in the afternoon 3 o'clock or 5 o'clock dateTimeEr.Type = Constants.SYS_DATETIME_TIME; dateTimePr = this.config.TimeParser.Parse(dateTimeEr, referenceTime); } } else if (subType == Constants.SYS_DATETIME_DATETIME) { // "next week Mon 9 am or Tue 1 pm" dateTimeEr.Type = Constants.SYS_DATETIME_DATETIME; dateTimePr = this.config.DateTimeParser.Parse(dateTimeEr, referenceTime); } else if (subType == Constants.SYS_DATETIME_TIMEPERIOD) { if (!hasContext) { dateTimeEr.Type = Constants.SYS_DATETIME_TIMEPERIOD; dateTimePr = this.config.TimePeriodParser.Parse(dateTimeEr, referenceTime); } else if (contextEr.Type == Constants.SYS_DATETIME_DATE || contextEr.Type == Constants.ContextType_RelativePrefix) { dateTimeEr.Type = Constants.SYS_DATETIME_DATETIMEPERIOD; dateTimePr = this.config.DateTimePeriodParser.Parse(dateTimeEr, referenceTime); } } else if (subType == Constants.SYS_DATETIME_DATETIMEPERIOD) { if (!hasContext) { dateTimeEr.Type = Constants.SYS_DATETIME_DATETIMEPERIOD; dateTimePr = this.config.DateTimePeriodParser.Parse(dateTimeEr, referenceTime); } } else if (subType == Constants.SYS_DATETIME_DATEPERIOD) { dateTimeEr.Type = Constants.SYS_DATETIME_DATEPERIOD; dateTimePr = this.config.DatePeriodParser.Parse(dateTimeEr, referenceTime); } if (dateTimePr.Value != null) { ret.FutureValue = ((DateTimeResolutionResult)dateTimePr.Value).FutureValue; ret.PastValue = ((DateTimeResolutionResult)dateTimePr.Value).PastValue; ret.Timex = dateTimePr.TimexStr; // Create resolution GetResolution(er, dateTimePr, ret); ret.Success = true; } return(ret); }
public int GetYearFromText(Match match) { int year = Constants.InvalidYear; var yearStr = match.Groups["year"].Value; if (!string.IsNullOrEmpty(yearStr)) { year = int.Parse(yearStr); if (year < 100 && year >= Constants.MinTwoDigitYearPastNum) { year += 1900; } else if (year >= 0 && year < Constants.MaxTwoDigitYearFutureNum) { year += 2000; } } else { var firstTwoYearNumStr = match.Groups["firsttwoyearnum"].Value; if (!string.IsNullOrEmpty(firstTwoYearNumStr)) { var er = new ExtractResult { Text = firstTwoYearNumStr, Start = match.Groups["firsttwoyearnum"].Index, Length = match.Groups["firsttwoyearnum"].Length }; var firstTwoYearNum = Convert.ToInt32((double)(this.config.NumberParser.Parse(er).Value ?? 0)); var lastTwoYearNum = 0; var lastTwoYearNumStr = match.Groups["lasttwoyearnum"].Value; if (!string.IsNullOrEmpty(lastTwoYearNumStr)) { er.Text = lastTwoYearNumStr; er.Start = match.Groups["lasttwoyearnum"].Index; er.Length = match.Groups["lasttwoyearnum"].Length; lastTwoYearNum = Convert.ToInt32((double)(this.config.NumberParser.Parse(er).Value ?? 0)); } // Exclude pure number like "nineteen", "twenty four" if (firstTwoYearNum < 100 && lastTwoYearNum == 0 || firstTwoYearNum < 100 && firstTwoYearNum % 10 == 0 && lastTwoYearNumStr.Trim().Split(' ').Length == 1) { year = Constants.InvalidYear; return(year); } if (firstTwoYearNum >= 100) { year = firstTwoYearNum + lastTwoYearNum; } else { year = firstTwoYearNum * 100 + lastTwoYearNum; } } } return(year); }
public static List <Token> ExtractorDurationWithBeforeAndAfter(string text, ExtractResult er, List <Token> ret, IDateTimeUtilityConfiguration utilityConfiguration) { var pos = (int)er.Start + (int)er.Length; if (pos <= text.Length) { var afterString = text.Substring(pos); var beforeString = text.Substring(0, (int)er.Start); var isTimeDuration = utilityConfiguration.TimeUnitRegex.Match(er.Text).Success; int index; bool isMatch = false; var agoLaterRegexes = new List <Regex> { utilityConfiguration.AgoRegex, utilityConfiguration.LaterRegex, }; foreach (var regex in agoLaterRegexes) { Token tokAfter = null, tokBefore = null; bool isDayMatch = false; // Check afterString if (MatchingUtil.GetAgoLaterIndex(afterString, regex, out index, inSuffix: true)) { // We don't support cases like "5 minutes from today" for now // Cases like "5 minutes ago" or "5 minutes from now" are supported // Cases like "2 days before today" or "2 weeks from today" are also supported isDayMatch = regex.Match(afterString).Groups["day"].Success; if (!(isTimeDuration && isDayMatch)) { tokAfter = new Token(er.Start ?? 0, (er.Start + er.Length ?? 0) + index); isMatch = true; } } if (utilityConfiguration.CheckBothBeforeAfter) { // Check if regex match is split between beforeString and afterString if (!isDayMatch && isMatch) { string beforeAfterStr = beforeString + afterString.Substring(0, index); var isRangeMatch = utilityConfiguration.RangePrefixRegex.MatchBegin(afterString.Substring(index), trim: true).Success; if (!isRangeMatch && MatchingUtil.GetAgoLaterIndex(beforeAfterStr, regex, out var indexStart, inSuffix: false)) { isDayMatch = regex.Match(beforeAfterStr).Groups["day"].Success; if (isDayMatch && !(isTimeDuration && isDayMatch)) { ret.Add(new Token(indexStart, (er.Start + er.Length ?? 0) + index)); isMatch = true; } } } // Check also beforeString if (MatchingUtil.GetAgoLaterIndex(beforeString, regex, out index, inSuffix: false)) { isDayMatch = regex.Match(beforeString).Groups["day"].Success; if (!(isTimeDuration && isDayMatch)) { tokBefore = new Token(index, er.Start + er.Length ?? 0); isMatch = true; } } } if (tokAfter != null && tokBefore != null && tokBefore.Start + tokBefore.Length > tokAfter.Start) { // Merge overlapping tokens ret.Add(new Token(tokBefore.Start, tokAfter.Start + tokAfter.Length - tokBefore.Start)); } else if (tokAfter != null) { ret.Add(tokAfter); } else if (tokBefore != null) { ret.Add(tokBefore); } if (isMatch) { break; } } if (!isMatch) { // Item1 is the main regex to be tested // Item2 is a list of unit regexes used to validate the extraction (in case of match, the extraction is discarded) var inWithinRegexTuples = new List <(Regex, List <Regex>)> { (utilityConfiguration.InConnectorRegex, new List <Regex> { utilityConfiguration.RangeUnitRegex }), (utilityConfiguration.WithinNextPrefixRegex, new List <Regex> { utilityConfiguration.DateUnitRegex, utilityConfiguration.TimeUnitRegex }), }; foreach (var regex in inWithinRegexTuples) { bool isMatchAfter = false; if (MatchingUtil.GetTermIndex(beforeString, regex.Item1, out index)) { isMatch = true; } else if (utilityConfiguration.CheckBothBeforeAfter && MatchingUtil.GetAgoLaterIndex(afterString, regex.Item1, out index, inSuffix: true)) { // Check also afterString isMatch = isMatchAfter = true; } if (isMatch) { // For InConnectorRegex and range unit like "week, month, year", it should output dateRange or datetimeRange // For WithinNextPrefixRegex and range unit like "week, month, year, day, second, minute, hour", it should output dateRange or datetimeRange bool isUnitMatch = false; foreach (var unitRegex in regex.Item2) { isUnitMatch = isUnitMatch || unitRegex.IsMatch(er.Text); } if (!isUnitMatch) { if (er.Start != null && er.Length != null && ((int)er.Start >= index || isMatchAfter)) { int start = (int)er.Start - (!isMatchAfter ? index : 0); int end = (int)er.Start + (int)er.Length + (isMatchAfter ? index : 0); ret.Add(new Token(start, end)); } } break; } } } } return(ret); }