using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Abp.Collections.Extensions; using Abp.Text.Formatting; namespace Abp.Text { /// /// This class is used to extract dynamic values from a formatted string. /// It works as reverse of /// /// /// Say that str is "My name is Neo." and format is "My name is {name}.". /// Then Extract method gets "Neo" as "name". /// public class FormattedStringValueExtracter { /// /// Extracts dynamic values from a formatted string. /// /// String including dynamic values /// Format of the string /// True, to search case-insensitive. /// format is splitted using this character when provided. public ExtractionResult Extract(string str, string format, bool ignoreCase = false, char? splitformatCharacter = null) { var stringComparison = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; if (str == format) { return new ExtractionResult(true); } var formatTokens = TokenizeFormat(format, splitformatCharacter); if (formatTokens.IsNullOrEmpty()) { return new ExtractionResult(str == ""); } var result = new ExtractionResult(false); for (var i = 0; i < formatTokens.Count; i++) { var currentToken = formatTokens[i]; var previousToken = i > 0 ? formatTokens[i - 1] : null; if (currentToken.Type == FormatStringTokenType.ConstantText) { if (i == 0) { if (str.StartsWith(currentToken.Text, stringComparison)) { str = str.Substring(currentToken.Text.Length); } } else { var matchIndex = str.IndexOf(currentToken.Text, stringComparison); if (matchIndex >= 0) { Debug.Assert(previousToken != null, "previousToken can not be null since i > 0 here"); result.Matches.Add(new NameValue(previousToken.Text, str.Substring(0, matchIndex))); result.IsMatch = true; str = str.Substring(matchIndex + currentToken.Text.Length); } } } } var lastToken = formatTokens.Last(); if (lastToken.Type == FormatStringTokenType.DynamicValue) { result.Matches.Add(new NameValue(lastToken.Text, str)); result.IsMatch = true; } return result; } private List TokenizeFormat(string originalFormat, char? splitformatCharacter = null) { if (splitformatCharacter == null) { return new FormatStringTokenizer().Tokenize(originalFormat); } var result = new List(); var formats = originalFormat.Split(splitformatCharacter.Value); foreach (var format in formats) { result.AddRange(new FormatStringTokenizer().Tokenize(format)); } return result; } /// /// Checks if given fits to given . /// Also gets extracted values. /// /// String including dynamic values /// Format of the string /// Array of extracted values if matched /// True, to search case-insensitive /// True, if matched. public static bool IsMatch(string str, string format, out string[] values, bool ignoreCase = false) { var result = new FormattedStringValueExtracter().Extract(str, format, ignoreCase); if (!result.IsMatch) { values = new string[0]; return false; } values = result.Matches.Select(m => m.Value).ToArray(); return true; } /// /// Used as return value of method. /// public class ExtractionResult { /// /// Is fully matched. /// public bool IsMatch { get; set; } /// /// List of matched dynamic values. /// public List Matches { get; private set; } internal ExtractionResult(bool isMatch) { IsMatch = isMatch; Matches = new List(); } } } }