
/*
* NIST Utils Class Library
* clutils/Str.cc
* April 1997
* K. C. Morris
* David Sauder

* Development of this software was funded by the United States Government,
* and is not subject to copyright.
*/

#include <Str.h>
#ifndef STRING_DELIM
#define STRING_DELIM '\''
#endif
/******************************************************************
 ** Procedure:  string functions
 ** Description:  These functions take a character or a string and return
 ** a temporary copy of the string with the function applied to it.
 ** Parameters:
 ** Returns:  temporary copy of characters
 ** Side Effects:
 ** Status:  complete
 ******************************************************************/

char ToLower( const char c ) {
    if( isupper( c ) ) {
        return ( tolower( c ) );
    } else {
        return ( c );
    }

}

char ToUpper( const char c ) {
    if( islower( c ) ) {
        return ( toupper( c ) );
    } else {
        return ( c );
    }
}

// Place in strNew a lowercase version of strOld.
char * StrToLower( const char * strOld, char * strNew )
{
    int i = 0;

    while( strOld[i] != '\0' ) {
        strNew[i] = ToLower( strOld[i] );
        i++;
    }
    strNew[i] = '\0';
    return strNew;
}

const char * StrToLower( const char * word, std::string & s ) {
    char newword [BUFSIZ];
    int i = 0;

    while( word [i] != '\0' ) {
        newword [i] = ToLower( word [i] );
        ++i;
    }
    newword [i] = '\0';
    s = newword;
    return const_cast<char *>( s.c_str() );
}

const char * StrToUpper( const char * word, std::string & s ) {
    char newword [BUFSIZ];
    int i = 0;

    while( word [i] != '\0' ) {
        newword [i] = ToUpper( word [i] );
        ++i;
    }
    newword [i] = '\0';
    s = newword;
    return const_cast<char *>( s.c_str() );
}

const char * StrToConstant( const char * word, std::string & s ) {
    char newword [BUFSIZ];
    int i = 0;

    while( word [i] != '\0' ) {
        if( word [i] == '/' || word [i] == '.' ) {
            newword [i] = '_';
        } else {
            newword [i] = ToUpper( word [i] );
        }
        ++i;
    }
    newword [i] = '\0';
    s = newword;
    return const_cast<char *>( s.c_str() );
}

/******************************************************************
 ** Procedure:  PrettyName (char * oldname)
 ** Parameters:  oldname
 ** Returns:  a new capitalized name
 ** Description:  creates a new name with first character's in caps
 ** Side Effects:  PrettyNewName allocates memory for the new name
                   PrettyTmpName returns new name in a static buffer
 ** Status:   OK  7-Oct-1992 kcm
 ******************************************************************/
const char * PrettyTmpName( const char * oldname ) {
    int i = 0;
    static char newname [BUFSIZ];
    newname [0] = '\0';
    while( ( oldname [i] != '\0' ) && ( i < BUFSIZ ) ) {
        newname [i] = ToLower( oldname [i] );
        if( oldname [i] == '_' ) { /*  character is '_'   */
            ++i;
            newname [i] = ToUpper( oldname [i] );
        }
        if( oldname [i] != '\0' ) {
            ++i;
        }
    }
    newname [0] = ToUpper( oldname [0] );
    newname [i] = '\0';
    return newname;
}

char * PrettyNewName( const char * oldname ) {

    char * name = new char [strlen( oldname ) + 1];
    strcpy( name, PrettyTmpName( oldname ) );
    return name;
}

/*
 *  Extract the string conform to P21 from the istream
 */
std::string ToExpressStr( istream &in, ErrorDescriptor *err ) {
    char c;
    in >> ws; // skip white space
    in >> c;
    int i_quote = 0;
    size_t found;
    string s = "";
    if ( c == STRING_DELIM ) {
        s += c;
        while ( i_quote != -1 && in.get(c) ) {
            s += c;
            // to handle a string like 'hi'''
            if ( c == STRING_DELIM) {
                // to handle \S\'
                found = s.rfind("\\S\\");
                if ( !(found != string::npos && (s.size() == found + 2)) ) {
                    i_quote++;
                }
            } else {
                // # of quotes is odd
                if ( i_quote % 2 != 0 ) {
                    i_quote = -1;
                    // put back last char, take it off from s and update c
                    in.putback(c);
                    s = s.substr(0, s.size() - 1);
                    c = *s.rbegin();
                }
            }
        }

        if ( c != STRING_DELIM ) {
            // non-recoverable error
            err->AppendToDetailMsg("Missing closing quote on string value.\n");
            err->AppendToUserMsg("Missing closing quote on string value.\n");
            err->GreaterSeverity(SEVERITY_INPUT_ERROR);
        }
    } else {
        in.putback(c);
    }

    return s;
}

// This function is used to check an input stream following a read.  It writes
// error messages in the 'ErrorDescriptor &err' argument as appropriate.
// 'const char *tokenList' argument contains a string made up of delimiters
// that are used to move the file pointer in the input stream to the end of
// the value you are reading (i.e. the ending marked by the presence of the
// delimiter).  The file pointer is moved just prior to the delimiter.  If the
// tokenList argument is a null pointer then this function expects to find EOF.
//
// If input is being read from a stream then a tokenList should be provided so
// this function can push the file pointer up to but not past the delimiter
// (i.e. not removing the delimiter from the input stream).  If you have a
// string containing a single value and you expect the whole string to contain
// a valid value, you can change the string to an istrstream, read the value
// then send the istrstream to this function with tokenList set to null
// and this function will set an error for you if any input remains following
// the value.

// If the input stream can be readable again then
//  - any error states set for the the stream are cleared.
//  - white space skipped in the input stream
//  - if EOF is encountered it returns
//    otherwise it peeks at the next character
//  - if the tokenList argument exists (i.e. is not null)
//    then if looks to see if the char peeked at is in the tokenList string
//    if it is then no error is set in the ErrorDescriptor
//    if the char peeked at is not in the tokenList string that implies
//       that there is garbage following the value that was successfully
//       or unsuccessfully read.  The garbage is read until EOF or a
//       delimiter in the tokenList is found.
//       - EOF is found you did not recover -> SEVERITY_INPUT_ERROR
//       - delimiter found you recovered successfully => SEVERITY_WARNING
//  - if tokenList does not exist then it expects to find EOF, if it does
//    not then it is an error but the bad chars are not read since you have
//    no way to know when to stop.

Severity
CheckRemainingInput( istream & in, ErrorDescriptor * err,
                     const char * typeName, // used in error message
                     const char * tokenList ) { // e.g. ",)"
    std::string skipBuf;
    char name[64];
    name[0] = 0;

    // 1. CHECK to see if there is invalid input following what you read.
    //    good or fail means you can still read from the input stream.

    if( in.good() || in.fail() )
        // fail means that the input did not match the expected input.
    {
        // check for bad input following what you read (or tried to read) but
        // preceding a delimiter if you are expecting one.

        in.clear(); // clear the istreams error
        in >> ws; // skip whitespace
        if( in.eof() ) { // no input following the desired input (or following the
            // missing desired input)
            return err->severity();
        }
        char c;
        c = in.peek();

        if( tokenList ) { // are expecting a delim so read till you find it

            // 3. FIND a delimiter

            if( strchr( tokenList, c ) ) {
                // next char peeked at was delim expected => success
                return err->severity();
            } else { // next character was not a delim and thus is garbage
                // read bad input until you find the delim
                in.get( c );
                while( in && !strchr( tokenList, c ) ) {
                    // this could chew up the remainder of the input unless you
                    // give it the delim to end an entity value and it knows
                    // about strings
                    skipBuf += c;
                    in.get( c );
                }
                // ENHANCEMENT may want to add the bad data to the err msg.

                if( strchr( tokenList, c ) ) {
                    // congratulations you recovered
                    err->GreaterSeverity( SEVERITY_WARNING );
                    sprintf( name,
                             "\tFound invalid %s value...\n",
                             typeName );
                    err->AppendToUserMsg( name );
                    err->AppendToDetailMsg( name );
                    err->AppendToDetailMsg(
                        "\tdata lost looking for end of attribute: " );
                    err->AppendToDetailMsg( skipBuf.c_str() );
                    err->AppendToDetailMsg( "\n" );
                    in.putback( c );
                    // invalid input
                    // (though a valid value may have been assigned)
                    return err->severity();
                } else {
                    // could not recover (of course)
                    err->GreaterSeverity( SEVERITY_INPUT_ERROR );
                    sprintf( name,
                             "Unable to recover from input error while reading %s %s",
                             typeName, "value.\n" );
                    err->AppendToUserMsg( name );
                    err->AppendToDetailMsg( name );
                    // invalid input
                    // (though a valid value may have been assigned)
                    return err->severity();
                }
            }
        } else if( in.good() ) // found a char => error
            // i.e. skipping whitespace did not cause EOF so must have hit a char
        {
            // remember we are not expecting more input since no tokenList

            // or 3. since not expecting a delimiter and there is input there
            //       is an error

            err->GreaterSeverity( SEVERITY_WARNING );
            sprintf( name, "Invalid %s value.\n", typeName );
            err->AppendToUserMsg( name );
            err->AppendToDetailMsg( name );
            // invalid input
            // (though a valid value may have been assigned)
            return err->severity();
        }
    } else if( in.eof() ) {
        // hit EOF when reading for a value
        // don\'t set any error
        return err->severity();
    } else {
        // badbit set (in.bad()) means there was a problem when reading istream
        // this is bad news... it means the input stream is hopelessly messed up
        err->GreaterSeverity( SEVERITY_INPUT_ERROR );
        sprintf( name, "Invalid %s value.\n", typeName );
        err->AppendToUserMsg( name );
        err->AppendToDetailMsg( name );
        // invalid input
        // (though a valid value may have been assigned)
        return err->severity();
    }
    return err->severity();
}
