public static IEnumerable <ITemplateToken> Tokenize([CanBeNull] string template, [NotNull] INamedTokenFactory namedTokenFactory)
        {
            if (string.IsNullOrEmpty(template))
            {
                yield break;
            }

            var nextIndex = 0;

            while (true)
            {
                // (iloktionov): Consume text until we find a beginning of a named token:
                var text = ConsumeText(template, nextIndex, out nextIndex);
                if (text != null)
                {
                    yield return(text);
                }

                if (nextIndex == template.Length)
                {
                    yield break;
                }

                // (iloktionov): Try to consume a named token. If it's incorrect, consume it as text:
                var namedToken = ParseNamedToken(template, nextIndex, out nextIndex, namedTokenFactory);
                if (namedToken != null)
                {
                    yield return(namedToken);
                }

                if (nextIndex == template.Length)
                {
                    yield break;
                }
            }
        }
        private static ITemplateToken ParseNamedToken(string template, int offset, out int next, INamedTokenFactory factory)
        {
            var beginning = offset++;

            // (iloktionov): Just move on until we encounter something that should not be in a named token:
            while (offset < template.Length && IsValidInNamedToken(template[offset]))
            {
                offset++;
            }

            // (iloktionov): If we reached the end of template or didn't stop on a closing brace, there will be no named token:
            if (offset == template.Length || template[offset] != ClosingBrace)
            {
                next = offset;

                return(CreateTextToken(template, beginning, offset - beginning));
            }

            next = offset + 1;

            // (iloktionov): Raw content is token with braces included, like '{prop:format}'.
            var rawOffset = beginning;
            var rawLength = next - rawOffset;

            // (iloktionov): Token content is token without braces, like 'prop:format'.
            var tokenOffset = rawOffset + 1;
            var tokenLength = rawLength - 2;

            if (TryParseNamedToken(template, tokenOffset, tokenLength, out var name, out var format))
            {
                return(factory.Create(name, format));
            }

            return(CreateTextToken(template, rawOffset, rawLength));
        }