/// <summary> /// Factory method that constructs a <see cref="BindingTemplateSource"/> from an input binding template pattern. /// </summary> /// <remarks> /// A template string may contain parameters embraced with curly brackets, which get replaced /// with values later when the template is bound. /// </remarks> /// <example> /// Below is a minimal template that illustrates a few basics: /// {p1}-p2/{{2014}}/folder/{name}.{ext} /// </example> /// <param name="pattern">A binding template pattern string in a supported format (see remarks). /// </param> /// <param name="ignoreCase">True if matches should be case insensitive.</param> /// <returns>An instance of <see cref="BindingTemplateSource"/> for the specified template pattern.</returns> public static BindingTemplateSource FromString(string pattern, bool ignoreCase = false) { IEnumerable <BindingTemplateToken> tokens = BindingTemplateParser.GetTokens(pattern); string capturePattern = BindingTemplateToken.BuildCapturePattern(tokens); RegexOptions options = RegexOptions.Compiled; if (ignoreCase) { options |= RegexOptions.IgnoreCase; } return(new BindingTemplateSource(pattern, new Regex(capturePattern, options))); }
/// <summary> /// Creates a streaming iterator to scan the input string and generate valid template tokens. /// </summary> /// <param name="input">A template pattern string in supported format.</param> /// <returns>A sequence of tokens.</returns> /// <exception cref="FormatException">Thrown when the input has unbalanced brackets, parameter name doesn't /// match C# identifier definition, or some other content validation rule fails.</exception> public static IEnumerable <BindingTemplateToken> GetTokens(string input) { // Validate token rule is up-to-date and input string matches a pattern of sequence of tokens. // Ensure input string has no unrecognized chunks. Debug.Assert(Regex.IsMatch(input, ValidateGrammar)); Regex grammarRegex = new Regex(TokenRule, RegexOptions.ExplicitCapture | RegexOptions.IgnorePatternWhitespace); const string EntirePatternGroupName = "0"; string[] groupNames = grammarRegex.GetGroupNames().Where( s => !String.Equals(s, EntirePatternGroupName)).ToArray(); // Outer loop iterates over all matched tokens in the input. foreach (Match m in grammarRegex.Matches(input)) { // Inner loops scans over possible token type alternatives of currently matched token. // It could be escaped character, parameter, or literal chunk. foreach (var name in groupNames.Where(n => m.Groups[n].Success)) { Group namedGroup = m.Groups[name]; switch (name) { case "escape": string value = Char.ToString(namedGroup.Value[0]); yield return(BindingTemplateToken.NewLiteral(value)); break; case "parameter": if (String.IsNullOrEmpty(namedGroup.Value)) { throw new FormatException(String.Format( "Invalid template '{0}'. The parameter name at position {1} is empty.", input, m.Index + 1)); } BindingTemplateToken token; try { token = BindingTemplateToken.NewExpression(namedGroup.Value); } catch (FormatException e) { throw new FormatException($"Invalid template '{input}'. {e.Message}"); } yield return(token); break; case "literal": yield return(BindingTemplateToken.NewLiteral(namedGroup.Value)); break; case "unbalanced": throw new FormatException(String.Format( "Invalid template '{0}'. Missing {1} bracket at position {2}.", input, namedGroup.Value[0] == '{' ? "closing" : "opening", m.Index + 1)); default: Debug.Fail("Unsupported named group!"); break; } } } }