public static BoundExpression Parse(string expression, Func <string, bool, string, IValueProvider> contextualResource) { if (expression == null) { throw new ArgumentNullException(nameof(expression)); } var i = 0; if (expression.StartsWith("\\")) { i = 1; } else if (expression.StartsWith("@")) { return(new BoundExpression(expression.Substring(1))); } var resources = new List <IValueProvider>(); var stringFormat = new StringBuilder(); var resourceType = new StringBuilder(); var resourceName = new StringBuilder(); var resourceConverter = new StringBuilder(); var resourceFormat = new StringBuilder(); var oneTimeBind = false; var length = expression.Length; char c; outside: if (i == length) { var format = stringFormat.ToString(); return(new BoundExpression(resources, format == "{0}" ? null : format)); } c = expression[i]; if (c == '{') { stringFormat.Append('{'); if (++i == expression.Length) { throw new FormatException("Invalid unescaped '{' at end of input."); } if (expression[i] == '{') { i++; stringFormat.Append('{'); goto outside; } goto readResource; } if (c == '}') { stringFormat.Append('}'); if (++i == expression.Length) { throw new FormatException("Invalid unescaped '}' at end of input."); } if (expression[i] == '}') { i++; stringFormat.Append('}'); goto outside; } throw new FormatException("Invalid unescaped '}'."); } stringFormat.Append(c); i++; goto outside; readResource: // A leading ^ indicates one time binding for contextual resources. if (expression[i] == '^') { if (++i == length) { throw new FormatException("Unexpected end of input."); } oneTimeBind = true; } // Resource type. while (char.IsLetter(c = expression[i])) { resourceType.Append(c); if (++i == length) { throw new FormatException("Unexpected end of input."); } } // Skip whitespace. while (char.IsWhiteSpace(expression[i])) { if (++i == length) { throw new FormatException("Unexpected end of input."); } } // Resource name. if (expression[i] == '\'') { i++; if (i == length) { throw new FormatException("Unexpected end of input."); } while ((c = expression[i]) != '\'') { resourceName.Append(c); if (++i == length) { throw new FormatException("Unexpected end of input."); } } i++; if (i == length) { throw new FormatException("Unexpected end of input."); } } else { while ((c = expression[i]) != ',' && c != ':' && c != '|') { if (c == '{') { if (++i == length) { throw new FormatException("Unexpected end of input."); } if (expression[i] != '{') { throw new FormatException("Invalid unescaped '{'."); } } else if (c == '}') { if (++i == length) { goto addResource; } if (expression[i] != '}') { goto addResource; } } resourceName.Append(c); if (++i == length) { throw new FormatException("Unexpected end of input."); } } } // Skip whitespace between name and converter/format/end. while (char.IsWhiteSpace(expression[i])) { if (++i == length) { throw new FormatException("Unexpected end of input."); } } c = expression[i]; if (c == '}') { // Resource can close at this point assuming no converter and no string format. if (++i == length) { goto addResource; } if (expression[i] != '}') { goto addResource; } throw new FormatException("Invalid '}}' sequence."); } // Value converter, read while character is letter. if (c == '|') { if (++i == length) { throw new FormatException("Unexpected end of input."); } while (char.IsLetter(c = expression[i])) { resourceConverter.Append(c); if (++i == length) { throw new FormatException("Unexpected end of input."); } } // Skip whitespace between converter to parameter/format/end. while (char.IsWhiteSpace(expression[i])) { if (++i == length) { throw new FormatException("Unexpected end of input."); } } // Check for value converter parameter. if (c == '(') { do { if (++i == length) { throw new FormatException("Unexpected end of input."); } } while (char.IsWhiteSpace(expression[i])); c = expression[i]; if (c == '\'') { resourceConverter.Append(':'); var converterParameter = new StringBuilder(); while (true) { if (++i == length) { throw new FormatException("Unexpected end of input."); } c = expression[i]; if (c != '\'') { converterParameter.Append(c); } else { if (++i == length) { throw new FormatException("Unexpected end of input."); } c = expression[i]; if (c == '\'') { converterParameter.Append('\''); } else { // End of string while (char.IsWhiteSpace(expression[i])) { if (++i == length) { throw new FormatException("Unexpected end of input."); } } c = expression[i]; if (c != ')') { throw new FormatException( "Expected closing parenthesis after converter parameter."); } if (++i == length) { throw new FormatException("Unexpected end of input."); } c = expression[i]; break; } } } resourceConverter .Append('\'') .Append(converterParameter); } else if (c != ')') { resourceConverter.Append(':'); var converterParameter = new StringBuilder(); converterParameter.Append(c); while (true) { if (++i == length) { throw new FormatException("Unexpected end of input."); } c = expression[i]; if (char.IsWhiteSpace(c)) { do { if (++i == length) { throw new FormatException("Unexpected end of input."); } } while (char.IsWhiteSpace(expression[i])); c = expression[i]; if (c != ')') { throw new FormatException("Expected closing parenthesis after converter parameter."); } } if (c == ')') { if (++i == length) { throw new FormatException("Unexpected end of input."); } c = expression[i]; break; } converterParameter.Append(c); } resourceConverter.Append(converterParameter.ToString().ToLowerInvariant()); } } // Skip whitespace between converter/parameter to format/end. while (char.IsWhiteSpace(expression[i])) { if (++i == length) { throw new FormatException("Unexpected end of input."); } } if (c == '}') { if (++i == length) { goto addResource; } if (expression[i] != '}') { goto addResource; } throw new FormatException("Converter name cannot contain braces."); } } // String format, read until single '}'. if (c == ',' || c == ':') { resourceFormat.Append(c); while (true) { if (++i == length) { throw new FormatException("Unexpected end of input."); } c = expression[i]; if (c == '}') { if (++i == length) { goto addResource; } if (expression[i] != '}') { goto addResource; } resourceFormat.Append('}'); } resourceFormat.Append(c); } } throw new FormatException($"Invalid character '{c}'"); addResource: var key = resourceName.ToString(); IValueProvider resource; var converter = resourceConverter.ToString(); var resourceTypeString = resourceType.ToString(); switch (resourceTypeString) { case "Binding": resource = new PropertyBinding(key, false, converter); break; case "Property": resource = new PropertyBinding(key, true, converter); break; case "StaticResource": resource = new StaticResource(key, converter); break; case "DynamicResource": resource = new DynamicResource(key, converter); break; case "ContextBinding": resource = new ContextPropertyBinding(key, false, converter); break; case "ContextProperty": resource = new ContextPropertyBinding(key, true, converter); break; case "Env": resource = new EnvironmentBinding(key, oneTimeBind, converter); break; case "FileBinding": resource = new FileBinding(key, true, converter); break; case "File": resource = new FileBinding(key, false, converter); break; default: resource = contextualResource?.Invoke(resourceTypeString + key, oneTimeBind, converter); if (resource != null) { break; } throw new FormatException("Invalid resource type."); } var index = resources.IndexOf(resource); if (index == -1) { index = resources.Count; resources.Add(resource); } stringFormat.Append(index); if (resourceFormat.Length != 0) { stringFormat.Append(resourceFormat); } stringFormat.Append('}'); resourceType.Clear(); resourceName.Clear(); resourceFormat.Clear(); resourceConverter.Clear(); oneTimeBind = false; goto outside; }