|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//------------------------------------------------------------------------------
using System.Text;
namespace System.Data.Common
{
internal static class MultipartIdentifier
{
private const int MaxParts = 4;
internal const int ServerIndex = 0;
internal const int CatalogIndex = 1;
internal const int SchemaIndex = 2;
internal const int TableIndex = 3;
/*
Left quote strings need to correspond 1 to 1 with the right quote strings
example: "ab" "cd", passed in for the left and the right quote
would set a or b as a starting quote character.
If a is the starting quote char then c would be the ending quote char
otherwise if b is the starting quote char then d would be the ending quote character.
*/
internal static string?[] ParseMultipartIdentifier(string name, string leftQuote, string rightQuote, string property, bool ThrowOnEmptyMultipartName)
{
return ParseMultipartIdentifier(name, leftQuote, rightQuote, '.', MaxParts, true, property, ThrowOnEmptyMultipartName);
}
private enum MPIState
{
MPI_Value,
MPI_ParseNonQuote,
MPI_LookForSeparator,
MPI_LookForNextCharOrSeparator,
MPI_ParseQuote,
MPI_RightQuote,
}
/* Core function for parsing the multipart identifier string.
* parameters: name - string to parse
* leftquote: set of characters which are valid quoting characters to initiate a quote
* rightquote: set of characters which are valid to stop a quote, array index's correspond to the leftquote array.
* separator: separator to use
* limit: number of names to parse out
* removequote:to remove the quotes on the returned string
*/
private static void IncrementStringCount(string name, string?[] ary, ref int position, string property)
{
++position;
int limit = ary.Length;
if (position >= limit)
{
throw ADP.InvalidMultipartNameToManyParts(property, name, limit);
}
ary[position] = string.Empty;
}
private static bool IsWhitespace(char ch)
{
return char.IsWhiteSpace(ch);
}
internal static string?[] ParseMultipartIdentifier(string name, string leftQuote, string rightQuote, char separator, int limit, bool removequotes, string property, bool ThrowOnEmptyMultipartName)
{
if (limit <= 0)
{
throw ADP.InvalidMultipartNameToManyParts(property, name, limit);
}
if (leftQuote.Contains(separator) || rightQuote.Contains(separator) || leftQuote.Length != rightQuote.Length)
{
throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name);
}
string?[] parsedNames = new string?[limit]; // return string array
int stringCount = 0; // index of current string in the buffer
MPIState state = MPIState.MPI_Value; // Initialize the starting state
StringBuilder sb = new StringBuilder(name.Length); // String buffer to hold the string being currently built, init the string builder so it will never be resized
StringBuilder? whitespaceSB = null; // String buffer to hold whitespace used when parsing nonquoted strings 'a b . c d' = 'a b' and 'c d'
char rightQuoteChar = ' '; // Right quote character to use given the left quote character found.
for (int index = 0; index < name.Length; ++index)
{
char testchar = name[index];
switch (state)
{
case MPIState.MPI_Value:
{
int quoteIndex;
if (IsWhitespace(testchar))
{ // Is White Space then skip the whitespace
continue;
}
else
if (testchar == separator)
{ // If we found a separator, no string was found, initialize the string we are parsing to Empty and the next one to Empty.
// This is NOT a redundant setting of string.Empty it solves the case where we are parsing ".foo" and we should be returning null, null, empty, foo
parsedNames[stringCount] = string.Empty;
IncrementStringCount(name, parsedNames, ref stringCount, property);
}
else
if (-1 != (quoteIndex = leftQuote.IndexOf(testchar)))
{ // If we are a left quote
rightQuoteChar = rightQuote[quoteIndex]; // record the corresponding right quote for the left quote
sb.Length = 0;
if (!removequotes)
{
sb.Append(testchar);
}
state = MPIState.MPI_ParseQuote;
}
else
if (rightQuote.Contains(testchar))
{ // If we shouldn't see a right quote
throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name);
}
else
{
sb.Length = 0;
sb.Append(testchar);
state = MPIState.MPI_ParseNonQuote;
}
break;
}
case MPIState.MPI_ParseNonQuote:
{
if (testchar == separator)
{
parsedNames[stringCount] = sb.ToString(); // set the currently parsed string
IncrementStringCount(name, parsedNames, ref stringCount, property);
state = MPIState.MPI_Value;
}
else // Quotes are not valid inside a non-quoted name
if (rightQuote.Contains(testchar))
{
throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name);
}
else
if (leftQuote.Contains(testchar))
{
throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name);
}
else
if (IsWhitespace(testchar))
{ // If it is Whitespace
parsedNames[stringCount] = sb.ToString(); // Set the currently parsed string
if (null == whitespaceSB)
{
whitespaceSB = new StringBuilder();
}
whitespaceSB.Length = 0;
whitespaceSB.Append(testchar); // start to record the whitespace, if we are parsing a name like "foo bar" we should return "foo bar"
state = MPIState.MPI_LookForNextCharOrSeparator;
}
else
{
sb.Append(testchar);
}
break;
}
case MPIState.MPI_LookForNextCharOrSeparator:
{
if (!IsWhitespace(testchar))
{ // If it is not whitespace
if (testchar == separator)
{
IncrementStringCount(name, parsedNames, ref stringCount, property);
state = MPIState.MPI_Value;
}
else
{ // If its not a separator and not whitespace
sb.Append(whitespaceSB);
sb.Append(testchar);
parsedNames[stringCount] = sb.ToString(); // Need to set the name here in case the string ends here.
state = MPIState.MPI_ParseNonQuote;
}
}
else
{
whitespaceSB!.Append(testchar);
}
break;
}
case MPIState.MPI_ParseQuote:
{
if (testchar == rightQuoteChar)
{ // if se are on a right quote see if we are escaping the right quote or ending the quoted string
if (!removequotes)
{
sb.Append(testchar);
}
state = MPIState.MPI_RightQuote;
}
else
{
sb.Append(testchar); // Append what we are currently parsing
}
break;
}
case MPIState.MPI_RightQuote:
{
if (testchar == rightQuoteChar)
{ // If the next char is another right quote then we were escaping the right quote
sb.Append(testchar);
state = MPIState.MPI_ParseQuote;
}
else
if (testchar == separator)
{ // If its a separator then record what we've parsed
parsedNames[stringCount] = sb.ToString();
IncrementStringCount(name, parsedNames, ref stringCount, property);
state = MPIState.MPI_Value;
}
else
if (!IsWhitespace(testchar))
{ // If it is not whitespace we got problems
throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name);
}
else
{ // It is a whitespace character so the following char should be whitespace, separator, or end of string anything else is bad
parsedNames[stringCount] = sb.ToString();
state = MPIState.MPI_LookForSeparator;
}
break;
}
case MPIState.MPI_LookForSeparator:
{
if (!IsWhitespace(testchar))
{ // If it is not whitespace
if (testchar == separator)
{ // If it is a separator
IncrementStringCount(name, parsedNames, ref stringCount, property);
state = MPIState.MPI_Value;
}
else
{ // Otherwise not a separator
throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name);
}
}
break;
}
}
}
// Resolve final states after parsing the string
switch (state)
{
case MPIState.MPI_Value: // These states require no extra action
case MPIState.MPI_LookForSeparator:
case MPIState.MPI_LookForNextCharOrSeparator:
break;
case MPIState.MPI_ParseNonQuote: // Dump what ever was parsed
case MPIState.MPI_RightQuote:
parsedNames[stringCount] = sb.ToString();
break;
case MPIState.MPI_ParseQuote: // Invalid Ending States
default:
throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes(property, name);
}
if (parsedNames[0] == null)
{
if (ThrowOnEmptyMultipartName)
{
throw ADP.InvalidMultipartName(property, name); // Name is entirely made up of whitespace
}
}
else
{
// Shuffle the parsed name, from left justification to right justification, i.e. [a][b][null][null] goes to [null][null][a][b]
int offset = limit - stringCount - 1;
if (offset > 0)
{
for (int x = limit - 1; x >= offset; --x)
{
parsedNames[x] = parsedNames[x - offset];
parsedNames[x - offset] = null;
}
}
}
return parsedNames;
}
}
}
|