File: System\Data\Common\MultipartIdentifier.cs
Project: ndp\fx\src\data\System.Data.csproj (System.Data)
//------------------------------------------------------------------------------
// <copyright file="CommandBuilder.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
 
namespace System.Data.Common {
    using System.Diagnostics;
    using System.Text;
    using System.Globalization;
 
    internal 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 identifer string.
            * paramaters: name - string to parse
            * leftquote:  set of characters which are valid quoteing characters to initiate a quote
            * rightquote: set of characters which are valid to stop a quote, array index's correspond to the 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 (-1 != leftQuote.IndexOf(separator) || -1 != rightQuote.IndexOf(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;        // Initalize 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 white space 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, initalize the string we are parsing to Empty and the next one to Empty.
                            // This is NOT a redundent 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 (-1 != rightQuote.IndexOf(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 (-1 != rightQuote.IndexOf (testchar))  {  
                            throw ADP.InvalidMultipartNameIncorrectUsageOfQuotes (property, name);
                        }
                        else
                        if (-1 != leftQuote.IndexOf(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 white space, 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 escapeing 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 a another right quote then we were escapeing 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 white space 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 { // Othewise 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, ie [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;
        }               
    }
}