private const string _formatItemsRegex = @"({+)[a-zA-Z_]+(?:(?:\.[a-zA-Z_])?[a-zA-Z0-9_]*)*(?::(?:n|N))?(}+)"; // {fieldPath[:indicator]}, e.g. {field}, {field.field:n}

        public static string FormatString(string input, out IList<FormatItem> items)
        {
            Debug.Assert(input != null);

            items = new List<FormatItem>();
            var matches = Regex.Matches(input, _formatItemsRegex);
            var message = new StringBuilder();

            Match prev = null;
            for (var i = 0; i < matches.Count; i++)
            {
                var match = matches[i];
                var item = match.Value;
                var leftBraces = match.Groups[1];
                var rightBraces = match.Groups[2];

                if (leftBraces.Length != rightBraces.Length)
                    throw new FormatException("Input string was not in a correct format.");

                var start = prev != null ? prev.Index + prev.Length : 0;
                var chars = match.Index - start;
                message.Append(input.Substring(start, chars));
                prev = match;

                var added = items.SingleOrDefault(x => x.Body == item);
                if (added != null)
                {
                    message.Append(added.Uuid);
                    continue;
                }

                var length = leftBraces.Length;

                // flatten each pair of braces into single brace in order to escape them (just like string.Format() does)
                var leftBracesFlattened = new string('{', length / 2);
                var rightBracesFlattened = new string('}', length / 2);

                var uuid = Guid.NewGuid();
                var param = item.Substring(length, item.Length - 2 * length);
                var current = new FormatItem
                {
                    Uuid = uuid,
                    Body = item,
                    Constant = length % 2 == 0,
                    FieldPath = param.Contains(":") ? param.Substring(0, param.IndexOf(":", StringComparison.Ordinal)) : param,
                    Indicator = param.Contains(":") ? param.Substring(param.IndexOf(":", StringComparison.Ordinal) + 1) : null,
                    Substitute = $"{leftBracesFlattened}{(length%2 != 0 ? uuid.ToString() : param)}{rightBracesFlattened}" // for odd number of braces, substitute param with respective value (just like string.Format() does)
                };
                items.Add(current);
                message.Append(current.Uuid);
            }

            if(prev != null)            
                message.Append(input.Substring(prev.Index + prev.Length));

            return message.Length > 0 ? message.ToString() : input;
        }
        private const string _formatItemsRegex = @"({+)[a-zA-Z_]+(?:(?:\.[a-zA-Z_])?[a-zA-Z0-9_]*)*(?::(?:n|N))?(}+)"; // {fieldPath[:indicator]}, e.g. {field}, {field.field:n}

        public static string FormatString(string input, out IList <FormatItem> items)
        {
            Debug.Assert(input != null);

            items = new List <FormatItem>();
            var matches = Regex.Matches(input, _formatItemsRegex);
            var message = new StringBuilder();

            Match prev = null;

            for (var i = 0; i < matches.Count; i++)
            {
                var match       = matches[i];
                var item        = match.Value;
                var leftBraces  = match.Groups[1];
                var rightBraces = match.Groups[2];

                if (leftBraces.Length != rightBraces.Length)
                {
                    throw new FormatException("Input string was not in a correct format.");
                }

                var start = prev != null ? prev.Index + prev.Length : 0;
                var chars = match.Index - start;
                message.Append(input.Substring(start, chars));
                prev = match;

                var added = items.SingleOrDefault(x => x.Body == item);
                if (added != null)
                {
                    message.Append(added.Uuid);
                    continue;
                }

                var length = leftBraces.Length;

                // flatten each pair of braces into single brace in order to escape them (just like string.Format() does)
                var leftBracesFlattened  = new string('{', length / 2);
                var rightBracesFlattened = new string('}', length / 2);

                var uuid    = Guid.NewGuid();
                var param   = item.Substring(length, item.Length - 2 * length);
                var current = new FormatItem
                {
                    Uuid       = uuid,
                    Body       = item,
                    Constant   = length % 2 == 0,
                    FieldPath  = param.Contains(":") ? param.Substring(0, param.IndexOf(":", StringComparison.Ordinal)) : param,
                    Indicator  = param.Contains(":") ? param.Substring(param.IndexOf(":", StringComparison.Ordinal) + 1) : null,
                    Substitute = $"{leftBracesFlattened}{(length%2 != 0 ? uuid.ToString() : param)}{rightBracesFlattened}" // for odd number of braces, substitute param with respective value (just like string.Format() does)
                };
                items.Add(current);
                message.Append(current.Uuid);
            }

            if (prev != null)
            {
                message.Append(input.Substring(prev.Index + prev.Length));
            }

            return(message.Length > 0 ? message.ToString() : input);
        }