public FormattingInfo(FormattingInfo parent, FormatDetails formatDetails, Placeholder placeholder, object currentValue)
		{
			this.Parent = parent;
			this.FormatDetails = formatDetails;
			this.Placeholder = placeholder;
			this.Format = placeholder.Format;
			this.CurrentValue = currentValue;
		}
		public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
		{
			var format = formattingInfo.Format;
			var current = formattingInfo.CurrentValue;
			
			// This method needs the Highest priority so that it comes before the PluralLocalizationExtension and ConditionalExtension

			// This extension requires at least IEnumerable
			var enumerable = current as IEnumerable;
			if (enumerable == null) return false;
			// Ignore Strings, because they're IEnumerable.
			// This issue might actually need a solution
			// for other objects that are IEnumerable.
			if (current is string) return false;
			// If the object is IFormattable, ignore it
			if (current is IFormattable) return false;

			// This extension requires a | to specify the spacer:
			if (format == null) return false;
			var parameters = format.Split('|', 4);
			if (parameters.Count < 2) return false;

			// Grab all formatting options:
			// They must be in one of these formats:
			// itemFormat|spacer
			// itemFormat|spacer|lastSpacer
			// itemFormat|spacer|lastSpacer|twoSpacer
			var itemFormat = parameters[0];
			var spacer = (parameters.Count >= 2) ? parameters[1].GetLiteralText() : "";
			var lastSpacer = (parameters.Count >= 3) ? parameters[2].GetLiteralText() : spacer;
			var twoSpacer = (parameters.Count >= 4) ? parameters[3].GetLiteralText() : lastSpacer;

			// TODO: [Obsolete] Not necessary, should remove:
			if (!itemFormat.HasNested)
			{
				// The format is not nested,
				// so we will treat it as an itemFormat:
				var newItemFormat = new Format(itemFormat.baseString);
				newItemFormat.startIndex = itemFormat.startIndex;
				newItemFormat.endIndex = itemFormat.endIndex;
				newItemFormat.HasNested = true;
				var newPlaceholder = new Placeholder(newItemFormat, itemFormat.startIndex, 0);
				newPlaceholder.Format = itemFormat;
				newPlaceholder.endIndex = itemFormat.endIndex;
				newItemFormat.Items.Add(newPlaceholder);
				itemFormat = newItemFormat;
			}

			// Let's buffer all items from the enumerable (to ensure the Count without double-enumeration):
			ICollection items = current as ICollection;
			if (items == null)
			{
				var allItems = new List<object>();
				foreach (var item in enumerable)
				{
					allItems.Add(item);
				}
				items = allItems;
			}

			int oldCollectionIndex = CollectionIndex; // In case we have nested arrays, we might need to restore the CollectionIndex
			CollectionIndex = -1;
			foreach (object item in items) {
				CollectionIndex += 1; // Keep track of the index

				// Determine which spacer to write:
				if (spacer == null || CollectionIndex == 0)
				{
					// Don't write the spacer.
				}
				else if (CollectionIndex < items.Count - 1) {
					formattingInfo.Write(spacer);
				}
				else if (CollectionIndex == 1)
				{
					formattingInfo.Write(twoSpacer);
				}
				else
				{
					formattingInfo.Write(lastSpacer);
				}

				// Output the nested format for this item:
				formattingInfo.Write(itemFormat, item);
			}

			CollectionIndex = oldCollectionIndex; // Restore the CollectionIndex

			return true;
		}
Example #3
0
 public Format(SmartSettings smartSettings, Placeholder parent, int startIndex) : base(smartSettings, parent,
                                                                                       startIndex)
 {
     this.parent = parent;
     Items       = new List <FormatItem>();
 }
Example #4
0
        /// <summary>
        /// Parses a format string.
        /// </summary>
        /// <param name="format"></param>
        /// <param name="formatterExtensionNames"></param>
        /// <returns>Returns the <see cref="Format"/> for the parsed string.</returns>
        public Format ParseFormat(string format, string[] formatterExtensionNames)
        {
            var         result                          = new Format(Settings, format);
            var         current                         = result;
            Placeholder?currentPlaceholder              = null;
            var         namedFormatterStartIndex        = -1;
            var         namedFormatterOptionsStartIndex = -1;
            var         namedFormatterOptionsEndIndex   = -1;

            // Store parsing errors until the end:
            var parsingErrors    = new ParsingErrors(result);
            var parsingErrorText = new ParsingErrorText();

            // Cache properties:
            var openingBrace = _openingBrace;
            var closingBrace = _closingBrace;


            var nestedDepth   = 0;
            var lastI         = 0;
            var operatorIndex = 0;
            var selectorIndex = 0;

            for (int i = 0, length = format.Length; i < length; i++)
            {
                var c = format[i];
                if (currentPlaceholder == null)
                {
                    if (c == openingBrace)
                    {
                        // Finish the last text item:
                        if (i != lastI)
                        {
                            current.Items.Add(new LiteralText(Settings, current, lastI)
                            {
                                endIndex = i
                            });
                        }
                        lastI = i + 1;

                        // See if this brace should be escaped:
                        if (!_alternativeEscaping)
                        {
                            var nextI = lastI;
                            if (nextI < length && format[nextI] == openingBrace)
                            {
                                i++;
                                continue;
                            }
                        }

                        // New placeholder:
                        nestedDepth++;
                        currentPlaceholder = new Placeholder(Settings, current, i, nestedDepth);
                        current.Items.Add(currentPlaceholder);
                        current.HasNested        = true;
                        operatorIndex            = i + 1;
                        selectorIndex            = 0;
                        namedFormatterStartIndex = -1;
                    }
                    else if (c == closingBrace)
                    {
                        // Finish the last text item:
                        if (i != lastI)
                        {
                            current.Items.Add(new LiteralText(Settings, current, lastI)
                            {
                                endIndex = i
                            });
                        }
                        lastI = i + 1;

                        // See if this brace should be escaped:
                        if (!_alternativeEscaping)
                        {
                            var nextI = lastI;
                            if (nextI < length && format[nextI] == closingBrace)
                            {
                                i++;
                                continue;
                            }
                        }

                        // Make sure that this is a nested placeholder before we un-nest it:
                        if (current.parent == null)
                        {
                            parsingErrors.AddIssue(current, parsingErrorText[ParsingError.TooManyClosingBraces], i,
                                                   i + 1);
                            continue;
                        }

                        // End of the placeholder's Format:
                        nestedDepth--;
                        current.endIndex        = i;
                        current.parent.endIndex = i + 1;
                        current = current.parent.parent;
                        namedFormatterStartIndex = -1;
                    }
                    else if (c == CharLiteralEscapeChar && Settings.ConvertCharacterStringLiterals ||
                             _alternativeEscaping && c == _alternativeEscapeChar)
                    {
                        namedFormatterStartIndex = -1;

                        // See that is the next character
                        var nextI = i + 1;

                        // **** Alternative brace escaping with { or } following the escape character ****
                        if (nextI < length && (format[nextI] == openingBrace || format[nextI] == closingBrace))
                        {
                            // Finish the last text item:
                            if (i != lastI)
                            {
                                current.Items.Add(new LiteralText(Settings, current, lastI)
                                {
                                    endIndex = i
                                });
                            }
                            lastI = i + 1;

                            i++;
                        }
                        else
                        {
                            // **** Escaping of charater literals like \t, \n, \v etc. ****

                            // Finish the last text item:
                            if (i != lastI)
                            {
                                current.Items.Add(new LiteralText(Settings, current, lastI)
                                {
                                    endIndex = i
                                });
                            }
                            lastI = i + 2;
                            if (lastI > length)
                            {
                                lastI = length;
                            }

                            // Next add the character literal INCLUDING the escape character, which LiteralText will expect
                            current.Items.Add(new LiteralText(Settings, current, i)
                            {
                                endIndex = lastI
                            });

                            i++;
                        }
                    }
                    else if (namedFormatterStartIndex != -1)
                    {
                        if (c == '(')
                        {
                            var emptyName = namedFormatterStartIndex == i;
                            if (emptyName)
                            {
                                namedFormatterStartIndex = -1;
                                continue;
                            }

                            namedFormatterOptionsStartIndex = i;
                        }
                        else if (c == ')' || c == ':')
                        {
                            if (c == ')')
                            {
                                var hasOpeningParenthesis = namedFormatterOptionsStartIndex != -1;

                                // ensure no trailing chars past ')'
                                var nextI           = i + 1;
                                var nextCharIsValid = nextI < format.Length &&
                                                      (format[nextI] == ':' || format[nextI] == closingBrace);

                                if (!hasOpeningParenthesis || !nextCharIsValid)
                                {
                                    namedFormatterStartIndex = -1;
                                    continue;
                                }

                                namedFormatterOptionsEndIndex = i;

                                if (format[nextI] == ':')
                                {
                                    i++;                       // Consume the ':'
                                }
                            }

                            var nameIsEmpty = namedFormatterStartIndex == i;
                            var missingClosingParenthesis =
                                namedFormatterOptionsStartIndex != -1 && namedFormatterOptionsEndIndex == -1;
                            if (nameIsEmpty || missingClosingParenthesis)
                            {
                                namedFormatterStartIndex = -1;
                                continue;
                            }


                            lastI = i + 1;

                            var parentPlaceholder = current.parent;

                            if (namedFormatterOptionsStartIndex == -1)
                            {
                                var formatterName = format.Substring(namedFormatterStartIndex,
                                                                     i - namedFormatterStartIndex);

                                if (FormatterNameExists(formatterName, formatterExtensionNames))
                                {
                                    if (parentPlaceholder != null)
                                    {
                                        parentPlaceholder.FormatterName = formatterName;
                                    }
                                }
                                else
                                {
                                    lastI = current.startIndex;
                                }
                            }
                            else
                            {
                                var formatterName = format.Substring(namedFormatterStartIndex,
                                                                     namedFormatterOptionsStartIndex - namedFormatterStartIndex);

                                if (FormatterNameExists(formatterName, formatterExtensionNames))
                                {
                                    if (parentPlaceholder != null)
                                    {
                                        parentPlaceholder.FormatterName    = formatterName;
                                        parentPlaceholder.FormatterOptions = format.Substring(
                                            namedFormatterOptionsStartIndex + 1,
                                            namedFormatterOptionsEndIndex - (namedFormatterOptionsStartIndex + 1));
                                    }
                                }
                                else
                                {
                                    lastI = current.startIndex;
                                }
                            }

                            current.startIndex = lastI;

                            namedFormatterStartIndex = -1;
                        }
                    }
                }
                else
                {
                    // Placeholder is NOT null, so that means
                    // we're parsing the selectors:
                    if (_operators.IndexOf(c) != -1)
                    {
                        // Add the selector:
                        if (i != lastI)
                        {
                            currentPlaceholder.Selectors.Add(new Selector(Settings, format, lastI, i, operatorIndex,
                                                                          selectorIndex));
                            selectorIndex++;
                            operatorIndex = i;
                        }

                        lastI = i + 1;
                    }
                    else if (c == ':')
                    {
                        // Add the selector:
                        if (i != lastI)
                        {
                            currentPlaceholder.Selectors.Add(new Selector(Settings, format, lastI, i, operatorIndex,
                                                                          selectorIndex));
                        }
                        else if (operatorIndex != i)
                        {
                            parsingErrors.AddIssue(current, parsingErrorText[ParsingError.TrailingOperatorsInSelector],
                                                   operatorIndex, i);
                        }
                        lastI = i + 1;

                        // Start the format:
                        currentPlaceholder.Format = new Format(Settings, currentPlaceholder, i + 1);
                        current                         = currentPlaceholder.Format;
                        currentPlaceholder              = null;
                        namedFormatterStartIndex        = lastI;
                        namedFormatterOptionsStartIndex = -1;
                        namedFormatterOptionsEndIndex   = -1;
                    }
                    else if (c == closingBrace)
                    {
                        // Add the selector:
                        if (i != lastI)
                        {
                            currentPlaceholder.Selectors.Add(new Selector(Settings, format, lastI, i, operatorIndex,
                                                                          selectorIndex));
                        }
                        else if (operatorIndex != i)
                        {
                            parsingErrors.AddIssue(current, parsingErrorText[ParsingError.TrailingOperatorsInSelector],
                                                   operatorIndex, i);
                        }
                        lastI = i + 1;

                        // End the placeholder with no format:
                        nestedDepth--;
                        currentPlaceholder.endIndex = i + 1;
                        current            = currentPlaceholder.parent;
                        currentPlaceholder = null;
                    }
                    else
                    {
                        // Let's make sure the selector characters are valid:
                        // Make sure it's alphanumeric:
                        var isValidSelectorChar =
                            '0' <= c && c <= '9' ||
                            _alphanumericSelectors && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') ||
                            _allowedSelectorChars.IndexOf(c) != -1;
                        if (!isValidSelectorChar)
                        {
                            parsingErrors.AddIssue(current, parsingErrorText[ParsingError.InvalidCharactersInSelector],
                                                   i, i + 1);
                        }
                    }
                }
            }

            // finish the last text item:
            if (lastI != format.Length)
            {
                current.Items.Add(new LiteralText(Settings, current, lastI)
                {
                    endIndex = format.Length
                });
            }

            // Check that the format is finished:
            if (current.parent != null || currentPlaceholder != null)
            {
                parsingErrors.AddIssue(current, parsingErrorText[ParsingError.MissingClosingBrace], format.Length,
                                       format.Length);
                current.endIndex = format.Length;
                while (current.parent != null)
                {
                    current          = current.parent.parent;
                    current.endIndex = format.Length;
                }
            }

            // Check for any parsing errors:
            if (parsingErrors.HasIssues)
            {
                OnParsingFailure?.Invoke(this,
                                         new ParsingErrorEventArgs(parsingErrors, Settings.ParseErrorAction == ErrorAction.ThrowError));

                return(HandleParsingErrors(parsingErrors, result));
            }

            return(result);
        }
Example #5
0
		public Format ParseFormat(string format, string[] formatterExtensionNames)
		{
			var result = new Format(format);
			var current = result;
			Placeholder currentPlaceholder = null;
			var namedFormatterStartIndex = -1;
			var namedFormatterOptionsStartIndex = -1;
			var namedFormatterOptionsEndIndex = -1;

			// Store parsing errors until the end:
			var parsingErrors = new ParsingErrors(result);
			var parsingErrorText = new ParsingErrorText();

			// Cache properties:
			var openingBrace = _openingBrace;
			var closingBrace = _closingBrace;


			var nestedDepth = 0;
			var lastI = 0;
			var operatorIndex = 0;
			var selectorIndex = 0;
			for (int i = 0, length = format.Length; i < length; i++)
			{
				var c = format[i];
				if (currentPlaceholder == null)
				{
					if (c == openingBrace)
					{
						// Finish the last text item:
						if (i != lastI)
						{
							current.Items.Add(new LiteralText(current, lastI) { endIndex = i });
						}
						lastI = i + 1;

						// See if this brace should be escaped:
						if (!_alternativeEscaping)
						{
							var nextI = lastI;
							if (nextI < length && format[nextI] == openingBrace)
							{
								i++;
								continue;
							}
						}

						// New placeholder:
						nestedDepth++;
						currentPlaceholder = new Placeholder(current, i, nestedDepth);
						current.Items.Add(currentPlaceholder);
						current.HasNested = true;
						operatorIndex = i+1;
						selectorIndex = 0;
						namedFormatterStartIndex = -1;
					}
					else if (c == closingBrace)
					{
						// Finish the last text item:
						if (i != lastI)
							current.Items.Add(new LiteralText(current, lastI) { endIndex = i });
						lastI = i + 1;

						// See if this brace should be escaped:
						if (!_alternativeEscaping)
						{
							var nextI = lastI;
							if (nextI < length && format[nextI] == closingBrace)
							{
								i++;
								continue;
							}
						}

						// Make sure that this is a nested placeholder before we un-nest it:
						if (current.parent == null)
						{
							parsingErrors.AddIssue(current, parsingErrorText[ParsingError.TooManyClosingBraces], i, i + 1);
							OnParsingFailure?.Invoke(this, new ParsingErrorEventArgs(current.RawText, i, i+1, ParsingError.TooManyClosingBraces, ErrorAction != ErrorAction.ThrowError));
							continue;
						}
						// End of the placeholder's Format:
						nestedDepth--;
						current.endIndex = i;
						current.parent.endIndex = i + 1;
						current = current.parent.parent;
						namedFormatterStartIndex = -1;
					}
					else if (_alternativeEscaping && c == _alternativeEscapeChar)
					{
						namedFormatterStartIndex = -1;
						// See if the next char is a brace that should be escaped:
						var nextI = i + 1;
						if (nextI < length && (format[nextI] == openingBrace || format[nextI] == closingBrace))
						{
							// Finish the last text item:
							if (i != lastI)
							{
								current.Items.Add(new LiteralText(current, lastI) { endIndex = i });
							}
							lastI = i + 1;

							i++;
							continue;
						}
					}
					else if (namedFormatterStartIndex != -1)
					{
						if (c == '(')
						{
							var emptyName = (namedFormatterStartIndex == i);
							if (emptyName)
							{
								namedFormatterStartIndex = -1;
								continue;
							}
							namedFormatterOptionsStartIndex = i;
						}
						else if (c == ')' || c == ':')
						{
							if (c == ')')
							{
								var hasOpeningParenthesis = (namedFormatterOptionsStartIndex != -1);

								// ensure no trailing chars past ')'
								var nextI = i + 1;
								var nextCharIsValid = (nextI < format.Length && (format[nextI] == ':' || format[nextI] == closingBrace));

								if (!hasOpeningParenthesis || !nextCharIsValid)
								{
									namedFormatterStartIndex = -1;
									continue;
								}

								namedFormatterOptionsEndIndex = i;

								if (format[nextI] == ':')
								{
									i++; // Consume the ':'
								}

							}
							
							var nameIsEmpty = (namedFormatterStartIndex == i);
							var missingClosingParenthesis = (namedFormatterOptionsStartIndex != -1 && namedFormatterOptionsEndIndex == -1);
							if (nameIsEmpty || missingClosingParenthesis)
							{
								namedFormatterStartIndex = -1;
								continue;
							}


							lastI = i + 1;

							var parentPlaceholder = current.parent;

							if (namedFormatterOptionsStartIndex == -1)
							{
								var formatterName = format.Substring(namedFormatterStartIndex, i - namedFormatterStartIndex);
								
								if (FormatterNameExists(formatterName, formatterExtensionNames))
								{
									parentPlaceholder.FormatterName = formatterName;
								}
								else
								{
									lastI = current.startIndex;
								}

							}
							else
							{
								var formatterName = format.Substring(namedFormatterStartIndex, namedFormatterOptionsStartIndex - namedFormatterStartIndex);

								if (FormatterNameExists(formatterName, formatterExtensionNames))
								{
									parentPlaceholder.FormatterName = formatterName;
									parentPlaceholder.FormatterOptions = format.Substring(namedFormatterOptionsStartIndex + 1, namedFormatterOptionsEndIndex - (namedFormatterOptionsStartIndex + 1));
								}
								else
								{
									lastI = current.startIndex;
								}
							}
							current.startIndex = lastI;

							namedFormatterStartIndex = -1;
						}
					}
				}
				else
				{
					// Placeholder is NOT null, so that means 
					// we're parsing the selectors:
					if (_operators.IndexOf(c) != -1)
					{
						// Add the selector:
						if (i != lastI)
						{   
							currentPlaceholder.Selectors.Add(new Selector(format, lastI, i, operatorIndex, selectorIndex));
							selectorIndex++;
							operatorIndex = i;
						}

						lastI = i + 1;
					}
					else if (c == ':')
					{
						// Add the selector:
						if (i != lastI)
						{
							currentPlaceholder.Selectors.Add(new Selector(format, lastI, i, operatorIndex, selectorIndex));
						}
						else if (operatorIndex != i)
						{
							// There are trailing operators. For now, this is an error.
							parsingErrors.AddIssue(current, parsingErrorText[ParsingError.TrailingOperatorsInSelector], operatorIndex, i);
							OnParsingFailure?.Invoke(this, new ParsingErrorEventArgs(current.RawText, operatorIndex, i + 1, ParsingError.TrailingOperatorsInSelector, ErrorAction != ErrorAction.ThrowError));
						}
						lastI = i + 1;

						// Start the format:
						currentPlaceholder.Format = new Format(currentPlaceholder, i + 1);
						current = currentPlaceholder.Format;
						currentPlaceholder = null;
						namedFormatterStartIndex = lastI;
						namedFormatterOptionsStartIndex = -1;
						namedFormatterOptionsEndIndex = -1;
					}
					else if (c == closingBrace)
					{
						// Add the selector:
						if (i != lastI)
							currentPlaceholder.Selectors.Add(new Selector(format, lastI, i, operatorIndex, selectorIndex));
						else if (operatorIndex != i)
						{
							// There are trailing operators.  For now, this is an error.
							parsingErrors.AddIssue(current, parsingErrorText[ParsingError.TrailingOperatorsInSelector], operatorIndex, i);
							OnParsingFailure?.Invoke(this, new ParsingErrorEventArgs(current.RawText, operatorIndex, i, ParsingError.TrailingOperatorsInSelector, ErrorAction != ErrorAction.ThrowError));
						}
						lastI = i + 1;

						// End the placeholder with no format:
						nestedDepth--;
						currentPlaceholder.endIndex = i + 1;
						current = currentPlaceholder.parent;
						currentPlaceholder = null;
					}
					else
					{
						// Let's make sure the selector characters are valid:
						// Make sure it's alphanumeric:
						var isValidSelectorChar =
							('0' <= c && c <= '9')
							|| (_alphanumericSelectors && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'))
							|| (_allowedSelectorChars.IndexOf(c) != -1);
						if (!isValidSelectorChar)
						{
							// Invalid character in the selector.
							parsingErrors.AddIssue(current, parsingErrorText[ParsingError.InvalidCharactersInSelector], i, i + 1);
							OnParsingFailure?.Invoke(this, new ParsingErrorEventArgs(current.RawText, i, i + 1, ParsingError.InvalidCharactersInSelector, ErrorAction != ErrorAction.ThrowError));
						}
					}
				}
			}

			// finish the last text item:
			if (lastI != format.Length)
				current.Items.Add(new LiteralText(current, lastI) { endIndex = format.Length });

			// Check that the format is finished:
			if (current.parent != null || currentPlaceholder != null)
			{
				parsingErrors.AddIssue(current, parsingErrorText[ParsingError.MissingClosingBrace], format.Length, format.Length);
				OnParsingFailure?.Invoke(this, new ParsingErrorEventArgs(current.RawText, format.Length, format.Length, ParsingError.MissingClosingBrace, ErrorAction != ErrorAction.ThrowError));
				current.endIndex = format.Length;
				while (current.parent != null)
				{
					current = current.parent.parent;
					current.endIndex = format.Length;
				}
			}

			// Check if there were any parsing errors:
			if (parsingErrors.HasIssues && ErrorAction == ErrorAction.ThrowError) throw parsingErrors;

			return result;
		}
Example #6
0
 /// <summary>
 /// Initializes the instance of <see cref="Format"/>.
 /// </summary>
 /// <param name="smartSettings"></param>
 /// <param name="parent">The parent <see cref="Placeholder"/>.</param>
 /// <param name="startIndex">The start index within the format base string.</param>
 /// <returns>This <see cref="Format"/> instance.</returns>
 public Format Initialize(SmartSettings smartSettings, Placeholder parent, int startIndex)
 {
     base.Initialize(smartSettings, parent, parent.BaseString, startIndex, parent.EndIndex);
     ParentPlaceholder = parent;
     return(this);
 }
Example #7
0
 public Format(Placeholder parent, int startIndex) : base(parent, startIndex)
 {
     this.parent = parent;
     Items       = new List <FormatItem>();
 }
		public FormattingInfo CreateChild(Placeholder placeholder)
		{
			return new FormattingInfo(this, this.FormatDetails, placeholder, this.CurrentValue);
		}
Example #9
0
        public Format ParseFormat(string format)
        {
            var         result                          = new Format(format);
            var         current                         = result;
            Placeholder currentPlaceholder              = null;
            var         namedFormatterStartIndex        = -1;
            var         namedFormatterOptionsStartIndex = -1;
            var         namedFormatterOptionsEndIndex   = -1;

            // Store parsing errors until the end:
            var parsingErrors = new ParsingErrors(result);

            // Cache properties:
            var openingBrace = this.openingBrace;
            var closingBrace = this.closingBrace;


            int nestedDepth   = 0;
            int lastI         = 0;
            int operatorIndex = 0;
            int selectorIndex = 0;

            for (int i = 0, length = format.Length; i < length; i++)
            {
                var c = format[i];
                if (currentPlaceholder == null)
                {
                    if (c == openingBrace)
                    {
                        // Finish the last text item:
                        if (i != lastI)
                        {
                            current.Items.Add(new LiteralText(current, lastI)
                            {
                                endIndex = i
                            });
                        }
                        lastI = i + 1;

                        // See if this brace should be escaped:
                        if (!this.alternativeEscaping)
                        {
                            var nextI = lastI;
                            if (nextI < length && format[nextI] == openingBrace)
                            {
                                i++;
                                continue;
                            }
                        }

                        // New placeholder:
                        nestedDepth++;
                        currentPlaceholder = new Placeholder(current, i, nestedDepth);
                        current.Items.Add(currentPlaceholder);
                        current.HasNested        = true;
                        operatorIndex            = i + 1;
                        selectorIndex            = 0;
                        namedFormatterStartIndex = -1;
                    }
                    else if (c == closingBrace)
                    {
                        // Finish the last text item:
                        if (i != lastI)
                        {
                            current.Items.Add(new LiteralText(current, lastI)
                            {
                                endIndex = i
                            });
                        }
                        lastI = i + 1;

                        // See if this brace should be escaped:
                        if (!this.alternativeEscaping)
                        {
                            var nextI = lastI;
                            if (nextI < length && format[nextI] == closingBrace)
                            {
                                i++;
                                continue;
                            }
                        }

                        // Make sure that this is a nested placeholder before we un-nest it:
                        if (current.parent == null)
                        {
                            parsingErrors.AddIssue(current, "Format string has too many closing braces", i, i + 1);
                            continue;
                        }
                        // End of the placeholder's Format:
                        nestedDepth--;
                        current.endIndex        = i;
                        current.parent.endIndex = i + 1;
                        current = current.parent.parent;
                        namedFormatterStartIndex = -1;
                    }
                    else if (this.alternativeEscaping && c == this.alternativeEscapeChar)
                    {
                        namedFormatterStartIndex = -1;
                        // See if the next char is a brace that should be escaped:
                        var nextI = i + 1;
                        if (nextI < length && (format[nextI] == openingBrace || format[nextI] == closingBrace))
                        {
                            // Finish the last text item:
                            if (i != lastI)
                            {
                                current.Items.Add(new LiteralText(current, lastI)
                                {
                                    endIndex = i
                                });
                            }
                            lastI = i + 1;

                            i++;
                            continue;
                        }
                    }
                    else if (namedFormatterStartIndex != -1)
                    {
                        if (c == '(')
                        {
                            var emptyName = (namedFormatterStartIndex == i);
                            if (emptyName)
                            {
                                namedFormatterStartIndex = -1;
                                continue;
                            }
                            namedFormatterOptionsStartIndex = i;
                        }
                        else if (c == ')' || c == ':')
                        {
                            if (c == ')')
                            {
                                var hasOpeningParenthesis = (namedFormatterOptionsStartIndex != -1);

                                // ensure no trailing chars past ')'
                                var nextI           = i + 1;
                                var nextCharIsValid = (nextI < format.Length && (format[nextI] == ':' || format[nextI] == closingBrace));

                                if (!hasOpeningParenthesis || !nextCharIsValid)
                                {
                                    namedFormatterStartIndex = -1;
                                    continue;
                                }

                                namedFormatterOptionsEndIndex = i;

                                if (format[nextI] == ':')
                                {
                                    i++;                                     // Consume the ':'
                                }
                            }

                            var nameIsEmpty = (namedFormatterStartIndex == i);
                            var missingClosingParenthesis = (namedFormatterOptionsStartIndex != -1 && namedFormatterOptionsEndIndex == -1);
                            if (nameIsEmpty || missingClosingParenthesis)
                            {
                                namedFormatterStartIndex = -1;
                                continue;
                            }


                            lastI = i + 1;

                            var parentPlaceholder = current.parent;
                            if (namedFormatterOptionsStartIndex == -1)
                            {
                                parentPlaceholder.FormatterName = format.Substring(namedFormatterStartIndex, i - namedFormatterStartIndex);
                            }
                            else
                            {
                                parentPlaceholder.FormatterName    = format.Substring(namedFormatterStartIndex, namedFormatterOptionsStartIndex - namedFormatterStartIndex);
                                parentPlaceholder.FormatterOptions = format.Substring(namedFormatterOptionsStartIndex + 1, namedFormatterOptionsEndIndex - (namedFormatterOptionsStartIndex + 1));
                            }
                            current.startIndex = lastI;

                            namedFormatterStartIndex = -1;
                        }
                    }
                }
                else
                {
                    // Placeholder is NOT null, so that means
                    // we're parsing the selectors:
                    if (operators.IndexOf(c) != -1)
                    {
                        // Add the selector:
                        if (i != lastI)
                        {
                            currentPlaceholder.Selectors.Add(new Selector(format, lastI, i, operatorIndex, selectorIndex));
                            selectorIndex++;
                            operatorIndex = i;
                        }

                        lastI = i + 1;
                    }
                    else if (c == ':')
                    {
                        // Add the selector:
                        if (i != lastI)
                        {
                            currentPlaceholder.Selectors.Add(new Selector(format, lastI, i, operatorIndex, selectorIndex));
                        }
                        else if (operatorIndex != i)
                        {
                            // There are trailing operators.  For now, this is an error.
                            parsingErrors.AddIssue(current, "There are trailing operators in the selector", operatorIndex, i);
                        }
                        lastI = i + 1;

                        // Start the format:
                        currentPlaceholder.Format = new Format(currentPlaceholder, i + 1);
                        current                         = currentPlaceholder.Format;
                        currentPlaceholder              = null;
                        namedFormatterStartIndex        = lastI;
                        namedFormatterOptionsStartIndex = -1;
                        namedFormatterOptionsEndIndex   = -1;
                    }
                    else if (c == closingBrace)
                    {
                        // Add the selector:
                        if (i != lastI)
                        {
                            currentPlaceholder.Selectors.Add(new Selector(format, lastI, i, operatorIndex, selectorIndex));
                        }
                        else if (operatorIndex != i)
                        {
                            // There are trailing operators.  For now, this is an error.
                            parsingErrors.AddIssue(current, "There are trailing operators in the selector", operatorIndex, i);
                        }
                        lastI = i + 1;

                        // End the placeholder with no format:
                        nestedDepth--;
                        currentPlaceholder.endIndex = i + 1;
                        current            = currentPlaceholder.parent;
                        currentPlaceholder = null;
                    }
                    else
                    {
                        // Let's make sure the selector characters are valid:
                        // Make sure it's alphanumeric:
                        var isValidSelectorChar =
                            ('0' <= c && c <= '9') ||
                            (alphanumericSelectors && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z')) ||
                            (allowedSelectorChars.IndexOf(c) != -1);
                        if (!isValidSelectorChar)
                        {
                            // Invalid character in the selector.
                            parsingErrors.AddIssue(current, "Invalid character in the selector", i, i + 1);
                        }
                    }
                }
            }

            // finish the last text item:
            if (lastI != format.Length)
            {
                current.Items.Add(new LiteralText(current, lastI)
                {
                    endIndex = format.Length
                });
            }

            // Check that the format is finished:
            if (current.parent != null || currentPlaceholder != null)
            {
                parsingErrors.AddIssue(current, "Format string is missing a closing brace", format.Length, format.Length);
                current.endIndex = format.Length;
                while (current.parent != null)
                {
                    current          = current.parent.parent;
                    current.endIndex = format.Length;
                }
            }

            // Check if there were any parsing errors:
            if (parsingErrors.HasIssues && ErrorAction == ErrorAction.ThrowError)
            {
                throw parsingErrors;
            }

            return(result);
        }