/// <summary> /// Internal implemention of the message reference and interpolation token /// Parsing used by tokenizeInterpolation() and replaceMessageReferences(). /// </summary> public static List <string> TokenizeInterpolation(string message, bool parseInterpolationTokens = true) { var tokens = new List <string>(); var chars = message.ToCharArray().ToList(); // Parse the message with a finite state machine. // 0 - Base case. // 1 - % found // 2 - Digit found. // 3 - Message ref found var state = 0; var buffer = new List <string>(); string number = ""; for (var i = 0; i < chars.Count + 1; i++) { var c = i == chars.Count ? ' ' : chars[i]; if (state == 0 && i != chars.Count) { if (c == '%') { var text = string.Join("", buffer.ToArray()); if (!string.IsNullOrEmpty(text)) { tokens.Add(text); } buffer.Clear(); state = 1; // Start escape. } else { buffer.Add(c.ToString()); // Regular char } } else if (state == 1) { if (c == '%') { buffer.Add(c.ToString()); // Escaped %: %% state = 0; } else if (parseInterpolationTokens && '0' <= c && c <= '9') { state = 2; number = c.ToString(); buffer.Add(""); var text = string.Join("", buffer.ToArray()); if (!string.IsNullOrEmpty(text)) { tokens.Add(text); } buffer.Clear(); } else if (c == '{') { state = 3; } else { buffer.Add("%"); // Not recognized. Return as literal. if (i != chars.Count) { buffer.Add(c.ToString()); } state = 0; // add parse as string literl } } else if (state == 2) { if ('0' <= c && c <= '9') { number += c; // Multi-digit number. } else { tokens.Add(int.Parse(number).ToString()); i--; // Parse this char agian. state = 0; } } else if (state == 3) { if (i == chars.Count) { // Premature end before closing '}' buffer.Insert(0, "%{"); // Re-insert leading delimiter i--; // Parse this char again. state = 0; // add parse as string literal. } else if (c != '}') { buffer.Add(c.ToString()); } else { var rawKey = string.Join("", buffer.ToArray()); if (new Regex("[a-zA-Z][a-zAZ0-9_]").Match(rawKey).Success) // Strict matching { // Found a valied string key. Attempt case insensitive match. var keyUpper = rawKey.ToUpper(); // BKY_ is the prefix used to namespace the string s used in Blockly // core files adn the predefined blocks in ../blocks/. These strings // are defined in ../msgs/ files. var bklyKey = keyUpper.StartsWith("BKY_") ? keyUpper.Substring(4) : null; if (!string.IsNullOrEmpty(bklyKey) && I18n.Contains(bklyKey)) { var rawValue = I18n.Get(bklyKey); tokens.AddRange(TokenizeInterpolation(rawValue)); } else { tokens.Add("%{" + rawKey + "}"); } buffer.Clear(); // Clear the array state = 0; } else { tokens.Add("%{" + rawKey + "}"); buffer.Clear(); state = 0; // and parse as string literal. } } } } var text1 = string.Join("", buffer.ToArray()); if (!string.IsNullOrEmpty(text1)) { tokens.Add(text1); } // Merge adjacent text tokens into a single string. var mergedTokens = new List <string>(); buffer.Clear(); for (int i = 0; i < tokens.Count; i++) { int tokenNum = 0; if (int.TryParse(tokens[i], out tokenNum)) { text1 = string.Join("", buffer.ToArray()); if (!string.IsNullOrEmpty(text1)) { mergedTokens.Add(text1); } buffer.Clear(); mergedTokens.Add(tokens[i]); } else { buffer.Add(tokens[i]); } } text1 = string.Join("", buffer.ToArray()); if (!string.IsNullOrEmpty(text1)) { mergedTokens.Add(text1); } buffer.Clear(); // remove last return(mergedTokens); }