/// <summary> /// Handles <see cref="ParsingError"/>s as defined in <see cref="SmartSettings.ParseErrorAction"/>, /// which leads to results similar to <see cref="SmartSettings.FormatErrorAction"/>s /// </summary> /// <param name="parsingErrors"></param> /// <param name="currentResult"></param> /// <returns>The <see cref="Format"/> which will be further processed with formatting.</returns> private Format HandleParsingErrors(ParsingErrors parsingErrors, Format currentResult) { switch (Settings.ParseErrorAction) { case ErrorAction.ThrowError: throw parsingErrors; case ErrorAction.MaintainTokens: var fmt = new Format(Settings, currentResult.baseString) { startIndex = 0, endIndex = currentResult.baseString.Length }; fmt.Items.Add(new LiteralText(Settings, fmt)); return(fmt); case ErrorAction.Ignore: return(new Format(Settings, string.Empty)); case ErrorAction.OutputErrorInResult: fmt = new Format(Settings, parsingErrors.Message) { startIndex = 0, endIndex = parsingErrors.Message.Length }; fmt.Items.Add(new LiteralText(Settings, fmt)); return(fmt); default: return(currentResult); } }
/// <summary> /// Handles <see cref="ParsingError"/>s as defined in <see cref="SmartSettings.ParseErrorAction"/>. /// </summary> /// <param name="parsingErrors"></param> /// <param name="currentResult"></param> /// <returns>The <see cref="Format"/> which will be further processed by the formatter.</returns> private Format HandleParsingErrors(ParsingErrors parsingErrors, Format currentResult) { switch (Settings.ParseErrorAction) { case ErrorAction.ThrowError: throw parsingErrors; case ErrorAction.MaintainTokens: // Replace erroneous Placeholders with tokens as LiteralText // Placeholder without issues are left unmodified for (var i = 0; i < currentResult.Items.Count; i++) { if (currentResult.Items[i] is Placeholder ph && parsingErrors.Issues.Any(errItem => errItem.Index >= currentResult.Items[i].startIndex && errItem.Index <= currentResult.Items[i].endIndex)) { currentResult.Items[i] = new LiteralText(Settings, ph.Format ?? new Format(Settings, ph.baseString), ph.startIndex) { endIndex = ph.endIndex }; } } return(currentResult); case ErrorAction.Ignore: // Replace erroneous Placeholders with an empty LiteralText for (var i = 0; i < currentResult.Items.Count; i++) { if (currentResult.Items[i] is Placeholder ph && parsingErrors.Issues.Any(errItem => errItem.Index >= currentResult.Items[i].startIndex && errItem.Index <= currentResult.Items[i].endIndex)) { currentResult.Items[i] = new LiteralText(Settings, ph.Format ?? new Format(Settings, ph.baseString), ph.startIndex) { endIndex = ph.startIndex }; } } return(currentResult); case ErrorAction.OutputErrorInResult: var fmt = new Format(Settings, parsingErrors.Message) { startIndex = 0, endIndex = parsingErrors.Message.Length }; fmt.Items.Add(new LiteralText(Settings, fmt)); return(fmt); default: throw new ArgumentException("Illegal type for ParsingErrors", parsingErrors); } }
/// <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); }
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; }
internal ParsingErrorEventArgs(ParsingErrors errors, bool throwsException) { Errors = errors; ThrowsException = throwsException; }
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); }