/// <summary> /// Processes a list of format tokens into a string /// </summary> /// <param name="tokens">List of tokens to turn into a string</param> /// <param name="replacements">An <see cref="IDictionary"/> with keys and values to inject into the formatted result</param> /// <param name="missingKeyBehaviour">The behaviour to use when the format string contains a parameter that is not present in the lookup dictionary</param> /// <param name="fallbackReplacementValue">When the <see cref="MissingKeyBehaviour.ReplaceWithFallback"/> is specified, this string is used as a fallback replacement value when the parameter is present in the lookup dictionary.</param> /// <returns>The processed result of joining the tokens with the replacement dictionary.</returns> public static string ProcessTokens( IEnumerable <FormatToken> tokens, Func <string, ReplacementResult> handler, MissingKeyBehaviour missingKeyBehaviour, object fallbackReplacementValue, int outputLengthHint) { // create a StringBuilder to hold the resultant output string // use the input hint as the initial size StringBuilder resultBuilder = new StringBuilder(outputLengthHint); foreach (FormatToken thisToken in tokens) { if (thisToken.TokenType == TokenType.Text) { // token is a text token // add the token to the result string builder resultBuilder.Append(thisToken.SourceString, thisToken.StartIndex, thisToken.Length); } else if (thisToken.TokenType == TokenType.Parameter) { // token is a parameter token // perform parameter logic now. // append the replacement for this parameter ReplacementResult replacementResult = handler(thisToken.Value); if (replacementResult.Success) { // the key exists, add the replacement value // this does nothing if replacement value is null resultBuilder.Append(replacementResult.Value); } else { // the key does not exist, handle this using the missing key behaviour specified. switch (missingKeyBehaviour) { case MissingKeyBehaviour.ThrowException: // the key was not found as a possible replacement, throw exception throw new KeyNotFoundException($"The parameter \"{thisToken.Value}\" was not present in the lookup dictionary"); case MissingKeyBehaviour.ReplaceWithFallback: resultBuilder.Append(fallbackReplacementValue); break; case MissingKeyBehaviour.Ignore: // the replacement value is the input key as a parameter. // use source string and start/length directly with append rather than // parameter.ParameterKey to avoid allocating an extra string resultBuilder.Append(thisToken.SourceString, thisToken.StartIndex, thisToken.Length); break; } } } } // return the resultant string return(resultBuilder.ToString()); }
/// <summary> /// Processes a list of format tokens into a string /// </summary> /// <param name="tokens">List of tokens to turn into a string</param> /// <param name="replacements">An <see cref="IDictionary"/> with keys and values to inject into the formatted result</param> /// <param name="missingKeyBehaviour">The behaviour to use when the format string contains a parameter that is not present in the lookup dictionary</param> /// <param name="fallbackReplacementValue">When the <see cref="MissingKeyBehaviour.ReplaceWithFallback"/> is specified, this string is used as a fallback replacement value when the parameter is present in the lookup dictionary.</param> /// <returns>The processed result of joining the tokens with the replacement dictionary.</returns> public static FormattableString ProcessTokensIntoFormattableString( IEnumerable <FormatToken> tokens, Func <string, ReplacementResult> handler, MissingKeyBehaviour missingKeyBehaviour, object fallbackReplacementValue, int outputLengthHint) { List <object> replacementParams = new List <object>(); // create a StringBuilder to hold the resultant output string // use the input hint as the initial size StringBuilder resultBuilder = new StringBuilder(outputLengthHint); // this is the index of the current placeholder in the composite format string int placeholderIndex = 0; foreach (FormatToken thisToken in tokens) { if (thisToken.TokenType == TokenType.Text) { // token is a text token. // add the token to the result string builder. // because this text is going into a standard composite format string, // any instaces of { or } must be escaped with {{ and }} resultBuilder.AppendWithEscapedBrackets(thisToken.SourceString, thisToken.StartIndex, thisToken.Length); } else if (thisToken.TokenType == TokenType.Parameter) { // token is a parameter token // perform parameter logic now. var tokenKey = thisToken.Value; string format = null; var separatorIdx = tokenKey.IndexOf(":", StringComparison.Ordinal); if (separatorIdx > -1) { tokenKey = thisToken.Value.Substring(0, separatorIdx); format = thisToken.Value.Substring(separatorIdx + 1); } // append the replacement for this parameter ReplacementResult replacementResult = handler(tokenKey); string IndexAndFormat() { if (string.IsNullOrWhiteSpace(format)) { return("{" + placeholderIndex + "}"); } return("{" + placeholderIndex + ":" + format + "}"); } // append the replacement for this parameter if (replacementResult.Success) { // Instead of appending the replacement value directly as before, // append the next placeholder with the current placeholder index. // Add the actual replacement format item into the replacement values. resultBuilder.Append(IndexAndFormat()); placeholderIndex++; replacementParams.Add(replacementResult.Value); } else { // the key does not exist, handle this using the missing key behaviour specified. switch (missingKeyBehaviour) { case MissingKeyBehaviour.ThrowException: // the key was not found as a possible replacement, throw exception throw new KeyNotFoundException($"The parameter \"{thisToken.Value}\" was not present in the lookup dictionary"); case MissingKeyBehaviour.ReplaceWithFallback: // Instead of appending the replacement value directly as before, // append the next placeholder with the current placeholder index. // Add the actual replacement format item into the replacement values. resultBuilder.Append(IndexAndFormat()); placeholderIndex++; replacementParams.Add(fallbackReplacementValue); break; case MissingKeyBehaviour.Ignore: resultBuilder.AppendWithEscapedBrackets(thisToken.SourceString, thisToken.StartIndex, thisToken.Length); break; } } } } // return the resultant string return(FormattableStringFactory.Create(resultBuilder.ToString(), replacementParams.ToArray())); }