www.pudn.com > mitab.rar > cpl_string.cpp


/********************************************************************** 
 * $Id: cpl_string.cpp,v 1.42 2005/04/04 15:23:31 fwarmerdam Exp $ 
 * 
 * Name:     cpl_string.cpp 
 * Project:  CPL - Common Portability Library 
 * Purpose:  String and Stringlist manipulation functions. 
 * Author:   Daniel Morissette, danmo@videotron.ca 
 * 
 ********************************************************************** 
 * Copyright (c) 1998, Daniel Morissette 
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions: 
 *  
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software. 
 *  
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER  
 * DEALINGS IN THE SOFTWARE. 
 ********************************************************************** 
 * 
 * Independent Security Audit 2003/04/04 Andrey Kiselev: 
 *   Completed audit of this module. All functions may be used without buffer 
 *   overflows and stack corruptions with any kind of input data strings with 
 *   except of CPLSPrintf() and CSLAppendPrintf() (see note below). 
 *  
 * Security Audit 2003/03/28 warmerda: 
 *   Completed security audit.  I believe that this module may be safely used  
 *   to parse tokenize arbitrary input strings, assemble arbitrary sets of 
 *   names values into string lists, unescape and escape text even if provided 
 *   by a potentially hostile source.    
 * 
 *   CPLSPrintf() and CSLAppendPrintf() may not be safely invoked on  
 *   arbitrary length inputs since it has a fixed size output buffer on system  
 *   without vsnprintf().  
 * 
 * $Log: cpl_string.cpp,v $ 
 * Revision 1.42  2005/04/04 15:23:31  fwarmerdam 
 * some functions now CPL_STDCALL 
 * 
 * Revision 1.41  2004/09/17 21:26:28  fwarmerdam 
 * Yikes ... CPLEscapeString() was badly broken for BackslashEscapable. 
 * 
 * Revision 1.40  2004/08/16 20:23:46  warmerda 
 * added .csv escaping 
 * 
 * Revision 1.39  2004/07/12 21:50:38  warmerda 
 * Added SQL escaping style 
 * 
 * Revision 1.38  2004/04/23 22:23:32  warmerda 
 * Fixed key memory leak in seldom used CSLSetNameValueSeperator(). 
 * 
 * Revision 1.37  2003/12/02 15:56:47  warmerda 
 * avoid use of CSLAddString() in tokenize, manage list ourselves 
 * 
 * Revision 1.36  2003/08/29 17:32:27  warmerda 
 * Open file in binary mode for CSLLoad() since CPLReadline() works much 
 * better then. 
 * 
 * Revision 1.35  2003/07/17 10:15:40  dron 
 * CSLTestBoolean() added. 
 * 
 * Revision 1.34  2003/05/28 19:22:38  warmerda 
 * fixed docs 
 * 
 * Revision 1.33  2003/05/21 04:20:30  warmerda 
 * avoid warnings 
 * 
 * Revision 1.32  2003/04/04 14:57:38  dron 
 * _vsnprintf() hack moved to the cpl_config.h.vc. 
 * 
 * Revision 1.31  2003/04/04 14:16:07  dron 
 * Use _vsnprintf() in Windows environment. 
 * 
 * Revision 1.30  2003/03/28 05:29:53  warmerda 
 * Fixed buffer overflow risk in escaping code (for XML method).  Avoid 
 * use of CPLSPrintf() for name/value list assembly to avoid risk with long 
 * key names or values.  Use vsnprintf() in CPLSPrintf() on platforms where it 
 * is available.  Security audit complete. 
 * 
 * Revision 1.29  2003/03/27 21:32:08  warmerda 
 * Fixed bug with escaped spaces. 
 * 
 * Revision 1.28  2003/03/11 21:33:02  warmerda 
 * added URL encode/decode support, untested 
 * 
 * Revision 1.27  2003/01/30 19:15:55  warmerda 
 * added some docs 
 * 
 * Revision 1.26  2003/01/14 14:31:16  warmerda 
 * Added "OFF" as a negative response to CSLFetchBoolean(). 
 * 
 * Revision 1.25  2002/10/07 19:35:38  dron 
 * Fixed description for CSLFetchBoolean() 
 * 
 * Revision 1.24  2002/07/12 22:37:05  warmerda 
 * added CSLFetchBoolean 
 * 
 * Revision 1.23  2002/07/09 20:25:25  warmerda 
 * expand tabs 
 * 
 * Revision 1.22  2002/05/28 18:53:43  warmerda 
 * added XML escaping support 
 * 
 * Revision 1.21  2002/04/26 14:55:26  warmerda 
 * Added CPLEscapeString() and CPLUnescapeString() (unescape untested) 
 * 
 * Revision 1.20  2002/03/05 14:26:57  warmerda 
 * expanded tabs 
 * 
 * Revision 1.19  2002/01/16 03:59:27  warmerda 
 * added CPLTokenizeString2 
 * 
 * Revision 1.18  2001/12/11 22:40:26  warmerda 
 * cleanup CPLReadLine buffer in CSLLoad() 
 * 
 * Revision 1.17  2001/11/07 14:31:16  warmerda 
 * doc fix 
 * 
 * Revision 1.16  2001/07/18 04:00:49  warmerda 
 * added CPL_CVSID 
 * 
 * Revision 1.15  2001/01/19 21:16:41  warmerda 
 * expanded tabs 
 * 
 * Revision 1.14  2000/10/06 15:19:03  warmerda 
 * added CPLSetNameValueSeparator 
 * 
 * Revision 1.13  2000/08/22 17:47:50  warmerda 
 * Fixed declaration of gnCPLSPrintfBuffer. 
 * 
 * Revision 1.12  2000/08/18 21:20:54  svillene 
 * *** empty log message *** 
 * 
 * Revision 1.11  2000/03/30 05:38:48  warmerda 
 * added CPLParseNameValue 
 * 
 * Revision 1.10  1999/06/26 14:05:10  warmerda 
 * Added CSLFindString(). 
 * 
 * Revision 1.9  1999/04/28 02:33:02  danmo 
 * CSLInsertStrings(): make sure papszStrList is NULL-terminated properly 
 * 
 * Revision 1.8  1999/03/12 21:19:49  danmo 
 * Fixed TokenizeStringComplex() vs strings ending with empty token, 
 * and fixed a problem with CSLAdd/SetNameValue() vs empty string list. 
 * 
 * Revision 1.7  1999/03/09 21:29:57  warmerda 
 * Added backslash escaping within string constants for tokenize function. 
 * 
 * Revision 1.6  1999/02/25 04:40:46  danmo 
 * Modif. CSLLoad() to use CPLReadLine() (better handling of newlines) 
 * 
 * Revision 1.5  1999/02/17 01:41:58  warmerda 
 * Added CSLGetField 
 * 
 * Revision 1.4  1998/12/15 19:01:40  warmerda 
 * *** empty log message *** 
 * 
 * Revision 1.3  1998/12/05 23:04:21  warmerda 
 * Use EQUALN() instead of strincmp() which doesn't exist on Linux. 
 * 
 * Revision 1.2  1998/12/04 21:40:42  danmo 
 * Added more Name=Value manipulation fuctions 
 * 
 * Revision 1.1  1998/12/03 18:26:02  warmerda 
 * New 
 * 
 **********************************************************************/ 
 
#include "cpl_string.h" 
#include "cpl_vsi.h" 
 
CPL_CVSID("$Id: cpl_string.cpp,v 1.42 2005/04/04 15:23:31 fwarmerdam Exp $"); 
 
/*===================================================================== 
                    StringList manipulation functions. 
 =====================================================================*/ 
 
/********************************************************************** 
 *                       CSLAddString() 
 * 
 * Append a string to a StringList and return a pointer to the modified 
 * StringList. 
 * If the input StringList is NULL, then a new StringList is created. 
 **********************************************************************/ 
char **CSLAddString(char **papszStrList, const char *pszNewString) 
{ 
    int nItems=0; 
 
    if (pszNewString == NULL) 
        return papszStrList;    /* Nothing to do!*/ 
 
    /* Allocate room for the new string */ 
    if (papszStrList == NULL) 
        papszStrList = (char**) CPLCalloc(2,sizeof(char*)); 
    else 
    { 
        nItems = CSLCount(papszStrList); 
        papszStrList = (char**)CPLRealloc(papszStrList,  
                                          (nItems+2)*sizeof(char*)); 
    } 
 
    /* Copy the string in the list */ 
    papszStrList[nItems] = CPLStrdup(pszNewString); 
    papszStrList[nItems+1] = NULL; 
 
    return papszStrList; 
} 
 
/********************************************************************** 
 *                       CSLCount() 
 * 
 * Return the number of lines in a Stringlist. 
 **********************************************************************/ 
int CSLCount(char **papszStrList) 
{ 
    int nItems=0; 
 
    if (papszStrList) 
    { 
        while(*papszStrList != NULL) 
        { 
            nItems++; 
            papszStrList++; 
        } 
    } 
 
    return nItems; 
} 
 
 
/************************************************************************/ 
/*                            CSLGetField()                             */ 
/*                                                                      */ 
/*      Fetches the indicated field, being careful not to crash if      */ 
/*      the field doesn't exist within this string list.  The           */ 
/*      returned pointer should not be freed, and doesn't               */ 
/*      necessarily last long.                                          */ 
/************************************************************************/ 
 
const char * CSLGetField( char ** papszStrList, int iField ) 
 
{ 
    int         i; 
 
    if( papszStrList == NULL || iField < 0 ) 
        return( "" ); 
 
    for( i = 0; i < iField+1; i++ ) 
    { 
        if( papszStrList[i] == NULL ) 
            return ""; 
    } 
 
    return( papszStrList[iField] ); 
} 
 
/********************************************************************** 
 *                       CSLDestroy() 
 * 
 * Free all memory used by a StringList. 
 **********************************************************************/ 
void CPL_STDCALL CSLDestroy(char **papszStrList) 
{ 
    char **papszPtr; 
 
    if (papszStrList) 
    { 
        papszPtr = papszStrList; 
        while(*papszPtr != NULL) 
        { 
            CPLFree(*papszPtr); 
            papszPtr++; 
        } 
 
        CPLFree(papszStrList); 
    } 
} 
 
 
/********************************************************************** 
 *                       CSLDuplicate() 
 * 
 * Allocate and return a copy of a StringList. 
 **********************************************************************/ 
char    **CSLDuplicate(char **papszStrList) 
{ 
    char **papszNewList, **papszSrc, **papszDst; 
    int  nLines; 
 
    nLines = CSLCount(papszStrList); 
 
    if (nLines == 0) 
        return NULL; 
 
    papszNewList = (char **)CPLMalloc((nLines+1)*sizeof(char*)); 
    papszSrc = papszStrList; 
    papszDst = papszNewList; 
 
    while(*papszSrc != NULL) 
    { 
        *papszDst = CPLStrdup(*papszSrc); 
 
        papszSrc++; 
        papszDst++; 
    } 
    *papszDst = NULL; 
 
    return papszNewList; 
} 
 
/********************************************************************** 
 *                       CSLLoad() 
 * 
 * Load a test file into a stringlist. 
 * 
 * Lines are limited in length by the size of the CPLReadLine() buffer. 
 **********************************************************************/ 
char **CSLLoad(const char *pszFname) 
{ 
    FILE        *fp; 
    const char  *pszLine; 
    char        **papszStrList=NULL; 
 
    fp = VSIFOpen(pszFname, "rb"); 
 
    if (fp) 
    { 
        while(!VSIFEof(fp)) 
        { 
            if ( (pszLine = CPLReadLine(fp)) != NULL ) 
            { 
                papszStrList = CSLAddString(papszStrList, pszLine); 
            } 
        } 
 
        VSIFClose(fp); 
 
        CPLReadLine( NULL ); 
    } 
    else 
    { 
        /* Unable to open file */ 
        CPLError(CE_Failure, CPLE_OpenFailed, 
                 "CSLLoad(%s): %s", pszFname, strerror(errno)); 
    } 
 
    return papszStrList; 
} 
 
/********************************************************************** 
 *                       CSLSave() 
 * 
 * Write a stringlist to a text file. 
 * 
 * Returns the number of lines written, or 0 if the file could not  
 * be written. 
 **********************************************************************/ 
int  CSLSave(char **papszStrList, const char *pszFname) 
{ 
    FILE    *fp; 
    int     nLines = 0; 
 
    if (papszStrList) 
    { 
        if ((fp = VSIFOpen(pszFname, "wt")) != NULL) 
        { 
            while(*papszStrList != NULL) 
            { 
                if (VSIFPuts(*papszStrList, fp) == EOF || 
                    VSIFPutc('\n', fp) == EOF) 
                { 
                    CPLError(CE_Failure, CPLE_FileIO, 
                             "CSLSave(%s): %s", pszFname,  
                             strerror(errno)); 
                    break;  /* A Problem happened... abort */ 
                } 
 
                nLines++; 
                papszStrList++; 
            } 
 
            VSIFClose(fp); 
        } 
        else 
        { 
            /* Unable to open file */ 
            CPLError(CE_Failure, CPLE_OpenFailed, 
                     "CSLSave(%s): %s", pszFname, strerror(errno)); 
        } 
    } 
 
    return nLines; 
} 
 
/********************************************************************** 
 *                       CSLPrint() 
 * 
 * Print a StringList to fpOut.  If fpOut==NULL, then output is sent 
 * to stdout. 
 * 
 * Returns the number of lines printed. 
 **********************************************************************/ 
int  CSLPrint(char **papszStrList, FILE *fpOut) 
{ 
    int     nLines=0; 
 
    if (fpOut == NULL) 
        fpOut = stdout; 
 
    if (papszStrList) 
    { 
        while(*papszStrList != NULL) 
        { 
            VSIFPrintf(fpOut, "%s\n", *papszStrList); 
            nLines++; 
            papszStrList++; 
        } 
    } 
 
    return nLines; 
} 
 
 
/********************************************************************** 
 *                       CSLInsertStrings() 
 * 
 * Copies the contents of a StringList inside another StringList  
 * before the specified line. 
 * 
 * nInsertAtLineNo is a 0-based line index before which the new strings 
 * should be inserted.  If this value is -1 or is larger than the actual  
 * number of strings in the list then the strings are added at the end 
 * of the source StringList. 
 * 
 * Returns the modified StringList. 
 **********************************************************************/ 
char **CSLInsertStrings(char **papszStrList, int nInsertAtLineNo,  
                        char **papszNewLines) 
{ 
    int     i, nSrcLines, nDstLines, nToInsert; 
    char    **ppszSrc, **ppszDst; 
 
    if (papszNewLines == NULL || 
        ( nToInsert = CSLCount(papszNewLines) ) == 0) 
        return papszStrList;    /* Nothing to do!*/ 
 
    nSrcLines = CSLCount(papszStrList); 
    nDstLines = nSrcLines + nToInsert; 
 
    /* Allocate room for the new strings */ 
    papszStrList = (char**)CPLRealloc(papszStrList,  
                                      (nDstLines+1)*sizeof(char*)); 
 
    /* Make sure the array is NULL-terminated... it may not be if 
     * papszStrList was NULL before Realloc() 
     */ 
    papszStrList[nSrcLines] = NULL; 
 
    /* Make some room in the original list at the specified location  
     * Note that we also have to move the NULL pointer at the end of 
     * the source StringList. 
     */ 
    if (nInsertAtLineNo == -1 || nInsertAtLineNo > nSrcLines) 
        nInsertAtLineNo = nSrcLines; 
 
    ppszSrc = papszStrList + nSrcLines; 
    ppszDst = papszStrList + nDstLines; 
 
    for (i=nSrcLines; i>=nInsertAtLineNo; i--) 
    { 
        *ppszDst = *ppszSrc; 
        ppszDst--; 
        ppszSrc--; 
    } 
 
    /* Copy the strings to the list */ 
    ppszSrc = papszNewLines; 
    ppszDst = papszStrList + nInsertAtLineNo; 
 
    for (; *ppszSrc != NULL; ppszSrc++, ppszDst++) 
    { 
        *ppszDst = CPLStrdup(*ppszSrc); 
    } 
     
    return papszStrList; 
} 
 
/********************************************************************** 
 *                       CSLInsertString() 
 * 
 * Insert a string at a given line number inside a StringList  
 * 
 * nInsertAtLineNo is a 0-based line index before which the new string 
 * should be inserted.  If this value is -1 or is larger than the actual  
 * number of strings in the list then the string is added at the end 
 * of the source StringList. 
 * 
 * Returns the modified StringList. 
 **********************************************************************/ 
char **CSLInsertString(char **papszStrList, int nInsertAtLineNo,  
                           char *pszNewLine) 
{ 
    char *apszList[2]; 
 
    /* Create a temporary StringList and call CSLInsertStrings() 
     */ 
    apszList[0] = pszNewLine; 
    apszList[1] = NULL; 
 
    return CSLInsertStrings(papszStrList, nInsertAtLineNo, apszList); 
} 
 
 
/********************************************************************** 
 *                       CSLRemoveStrings() 
 * 
 * Remove strings inside a StringList  
 * 
 * nFirstLineToDelete is the 0-based line index of the first line to  
 * remove. If this value is -1 or is larger than the actual  
 * number of strings in list then the nNumToRemove last strings are 
 * removed. 
 * 
 * If ppapszRetStrings != NULL then the deleted strings won't be 
 * free'd, they will be stored in a new StringList and the pointer to 
 * this new list will be returned in *ppapszRetStrings. 
 * 
 * Returns the modified StringList. 
 **********************************************************************/ 
char **CSLRemoveStrings(char **papszStrList, int nFirstLineToDelete, 
                        int nNumToRemove, char ***ppapszRetStrings) 
{ 
    int     i, nSrcLines, nDstLines; 
    char    **ppszSrc, **ppszDst; 
 
    nSrcLines = CSLCount(papszStrList); 
    nDstLines = nSrcLines - nNumToRemove; 
 
    if (nNumToRemove < 1 || nSrcLines == 0) 
        return papszStrList;    /* Nothing to do!*/ 
 
    /* If operation will result in an empty StringList then don't waste 
     * time here! 
     */ 
    if (nDstLines < 1) 
    { 
        CSLDestroy(papszStrList); 
        return NULL; 
    } 
 
     
    /* Remove lines from the source StringList... 
     * Either free() each line or store them to a new StringList depending on 
     * the caller's choice. 
     */ 
    ppszDst = papszStrList + nFirstLineToDelete; 
 
    if (ppapszRetStrings == NULL) 
    { 
        /* free() all the strings that will be removed. 
         */ 
        for (i=0; i < nNumToRemove; i++) 
        { 
            CPLFree(*ppszDst); 
            *ppszDst = NULL; 
        } 
    } 
    else 
    { 
        /* Store the strings to remove in a new StringList 
         */ 
        *ppapszRetStrings = (char **)CPLCalloc(nNumToRemove+1, sizeof(char*)); 
 
        for (i=0; i < nNumToRemove; i++) 
        { 
            (*ppapszRetStrings)[i] = *ppszDst; 
            *ppszDst = NULL; 
            ppszDst++; 
        } 
    } 
 
 
    /* Shift down all the lines that follow the lines to remove. 
     */ 
    if (nFirstLineToDelete == -1 || nFirstLineToDelete > nSrcLines) 
        nFirstLineToDelete = nDstLines; 
 
    ppszSrc = papszStrList + nFirstLineToDelete + nNumToRemove; 
    ppszDst = papszStrList + nFirstLineToDelete; 
 
    for ( ; *ppszSrc != NULL; ppszSrc++, ppszDst++) 
    { 
        *ppszDst = *ppszSrc; 
    } 
    /* Move the NULL pointer at the end of the StringList     */ 
    *ppszDst = *ppszSrc;  
 
    /* At this point, we could realloc() papszStrList to a smaller size, but 
     * since this array will likely grow again in further operations on the 
     * StringList we'll leave it as it is. 
     */ 
 
    return papszStrList; 
} 
 
/************************************************************************/ 
/*                           CSLFindString()                            */ 
/*                                                                      */ 
/*      Find a string within a string list.  The string must match      */ 
/*      the full length, but the comparison is case insensitive.        */ 
/*      Return -1 on failure.                                           */ 
/************************************************************************/ 
 
int CSLFindString( char ** papszList, const char * pszTarget ) 
 
{ 
    int         i; 
 
    if( papszList == NULL ) 
        return -1; 
 
    for( i = 0; papszList[i] != NULL; i++ ) 
    { 
        if( EQUAL(papszList[i],pszTarget) ) 
            return i; 
    } 
 
    return -1; 
} 
 
/********************************************************************** 
 *                       CSLTokenizeString() 
 * 
 * Tokenizes a string and returns a StringList with one string for 
 * each token. 
 **********************************************************************/ 
char    **CSLTokenizeString( const char *pszString ) 
{ 
    return CSLTokenizeString2( pszString, " ", CSLT_HONOURSTRINGS ); 
} 
 
/************************************************************************/ 
/*                      CSLTokenizeStringComplex()                      */ 
/*                                                                      */ 
/*      Obsolete tokenizing api.                                        */ 
/************************************************************************/ 
 
char ** CSLTokenizeStringComplex( const char * pszString, 
                                  const char * pszDelimiters, 
                                  int bHonourStrings, int bAllowEmptyTokens ) 
 
{ 
    int         nFlags = 0; 
 
    if( bHonourStrings ) 
        nFlags |= CSLT_HONOURSTRINGS; 
    if( bAllowEmptyTokens ) 
        nFlags |= CSLT_ALLOWEMPTYTOKENS; 
 
    return CSLTokenizeString2( pszString, pszDelimiters, nFlags ); 
} 
 
/************************************************************************/ 
/*                         CSLTokenizeString2()                         */ 
/*                                                                      */ 
/*      The ultimate tokenizer?                                         */ 
/************************************************************************/ 
 
char ** CSLTokenizeString2( const char * pszString, 
                            const char * pszDelimiters, 
                            int nCSLTFlags ) 
 
{ 
    char        **papszRetList = NULL; 
    int         nRetMax = 0, nRetLen = 0; 
    char        *pszToken; 
    int         nTokenMax, nTokenLen; 
    int         bHonourStrings = (nCSLTFlags & CSLT_HONOURSTRINGS); 
    int         bAllowEmptyTokens = (nCSLTFlags & CSLT_ALLOWEMPTYTOKENS); 
 
    pszToken = (char *) CPLCalloc(10,1); 
    nTokenMax = 10; 
     
    while( pszString != NULL && *pszString != '\0' ) 
    { 
        int     bInString = FALSE; 
 
        nTokenLen = 0; 
         
        /* Try to find the next delimeter, marking end of token */ 
        for( ; *pszString != '\0'; pszString++ ) 
        { 
 
            /* End if this is a delimeter skip it and break. */ 
            if( !bInString && strchr(pszDelimiters, *pszString) != NULL ) 
            { 
                pszString++; 
                break; 
            } 
             
            /* If this is a quote, and we are honouring constant 
               strings, then process the constant strings, with out delim 
               but don't copy over the quotes */ 
            if( bHonourStrings && *pszString == '"' ) 
            { 
                if( nCSLTFlags & CSLT_PRESERVEQUOTES ) 
                { 
                    pszToken[nTokenLen] = *pszString; 
                    nTokenLen++; 
                } 
 
                if( bInString ) 
                { 
                    bInString = FALSE; 
                    continue; 
                } 
                else 
                { 
                    bInString = TRUE; 
                    continue; 
                } 
            } 
 
            /* Within string constants we allow for escaped quotes, but 
               in processing them we will unescape the quotes */ 
            if( bInString && pszString[0] == '\\' && pszString[1] == '"' ) 
            { 
                if( nCSLTFlags & CSLT_PRESERVEESCAPES ) 
                { 
                    pszToken[nTokenLen] = *pszString; 
                    nTokenLen++; 
                } 
 
                pszString++; 
            } 
 
            /* Within string constants a \\ sequence reduces to \ */ 
            else if( bInString  
                     && pszString[0] == '\\' && pszString[1] == '\\' ) 
            { 
                if( nCSLTFlags & CSLT_PRESERVEESCAPES ) 
                { 
                    pszToken[nTokenLen] = *pszString; 
                    nTokenLen++; 
                } 
                pszString++; 
            } 
 
            if( nTokenLen >= nTokenMax-3 ) 
            { 
                nTokenMax = nTokenMax * 2 + 10; 
                pszToken = (char *) CPLRealloc( pszToken, nTokenMax ); 
            } 
 
            pszToken[nTokenLen] = *pszString; 
            nTokenLen++; 
        } 
 
        pszToken[nTokenLen] = '\0'; 
 
        /* 
         * If the last token is an empty token, then we have to catch 
         * it now, otherwise we won't reenter the loop and it will be lost.  
         */ 
 
        if( (pszToken[0] != '\0' || bAllowEmptyTokens) 
            || (*pszString == '\0' && bAllowEmptyTokens 
                && strchr(pszDelimiters, *(pszString-1)) ) ) 
        { 
            if( nRetLen >= nRetMax - 1 ) 
            { 
                nRetMax = nRetMax * 2 + 10; 
                papszRetList = (char **)  
                    CPLRealloc(papszRetList, sizeof(char*) * nRetMax ); 
            } 
 
            papszRetList[nRetLen++] = CPLStrdup( pszToken ); 
            papszRetList[nRetLen] = NULL; 
        } 
    } 
 
    if( papszRetList == NULL ) 
        papszRetList = (char **) CPLCalloc(sizeof(char *),1); 
 
    CPLFree( pszToken ); 
 
    return papszRetList; 
} 
 
/********************************************************************** 
 *                       CPLSPrintf() 
 * 
 * My own version of CPLSPrintf() that works with 10 static buffer. 
 * 
 * It returns a ref. to a static buffer that should not be freed and 
 * is valid only until the next call to CPLSPrintf(). 
 * 
 * NOTE: This function should move to cpl_conv.cpp.  
 **********************************************************************/ 
/* For now, assume that a 8000 chars buffer will be enough. 
 */ 
#define CPLSPrintf_BUF_SIZE 8000 
#define CPLSPrintf_BUF_Count 10 
static char gszCPLSPrintfBuffer[CPLSPrintf_BUF_Count][CPLSPrintf_BUF_SIZE]; 
static int gnCPLSPrintfBuffer = 0; 
 
const char *CPLSPrintf(char *fmt, ...) 
{ 
    va_list args; 
 
    va_start(args, fmt); 
#if defined(HAVE_VSNPRINTF) 
    vsnprintf(gszCPLSPrintfBuffer[gnCPLSPrintfBuffer], CPLSPrintf_BUF_SIZE-1, 
              fmt, args); 
#else 
    vsprintf(gszCPLSPrintfBuffer[gnCPLSPrintfBuffer], fmt, args); 
#endif 
    va_end(args); 
     
   int nCurrent = gnCPLSPrintfBuffer; 
 
    if (++gnCPLSPrintfBuffer == CPLSPrintf_BUF_Count) 
      gnCPLSPrintfBuffer = 0; 
 
    return gszCPLSPrintfBuffer[nCurrent]; 
} 
 
/********************************************************************** 
 *                       CSLAppendPrintf() 
 * 
 * Use CPLSPrintf() to append a new line at the end of a StringList. 
 * 
 * Returns the modified StringList. 
 **********************************************************************/ 
char **CSLAppendPrintf(char **papszStrList, char *fmt, ...) 
{ 
    va_list args; 
 
    va_start(args, fmt); 
#if defined(HAVE_VSNPRINTF) 
    vsnprintf(gszCPLSPrintfBuffer[gnCPLSPrintfBuffer], CPLSPrintf_BUF_SIZE-1, 
              fmt, args); 
#else 
    vsprintf(gszCPLSPrintfBuffer[gnCPLSPrintfBuffer], fmt, args); 
#endif 
    va_end(args); 
 
    int nCurrent = gnCPLSPrintfBuffer; 
 
    if (++gnCPLSPrintfBuffer == CPLSPrintf_BUF_Count) 
      gnCPLSPrintfBuffer = 0; 
 
    return CSLAddString(papszStrList, gszCPLSPrintfBuffer[nCurrent]); 
} 
 
/************************************************************************/ 
/*                         CSLTestBoolean()                             */ 
/************************************************************************/ 
 
/** 
 * Test what boolean value contained in the string. 
 * 
 * If pszValue is "NO", "FALSE", "OFF" or "0" will be returned FALSE. 
 * Otherwise, TRUE will be returned. 
 * 
 * @param pszValue the string should be tested. 
 *  
 * @return TRUE or FALSE. 
 */ 
 
int CSLTestBoolean( const char *pszValue ) 
{ 
    if( EQUAL(pszValue,"NO") 
        || EQUAL(pszValue,"FALSE")  
        || EQUAL(pszValue,"OFF")  
        || EQUAL(pszValue,"0") ) 
        return FALSE; 
    else 
        return TRUE; 
} 
 
/********************************************************************** 
 *                       CSLFetchBoolean() 
 * 
 * Check for boolean key value. 
 * 
 * In a StringList of "Name=Value" pairs, look to see if there is a key 
 * with the given name, and if it can be interpreted as being TRUE.  If 
 * the key appears without any "=Value" portion it will be considered true.  
 * If the value is NO, FALSE or 0 it will be considered FALSE otherwise 
 * if the key appears in the list it will be considered TRUE.  If the key 
 * doesn't appear at all, the indicated default value will be returned.  
 *  
 * @param papszStrList the string list to search. 
 * @param pszKey the key value to look for (case insensitive). 
 * @param bDefault the value to return if the key isn't found at all.  
 *  
 * @return TRUE or FALSE  
 **********************************************************************/ 
 
int CSLFetchBoolean( char **papszStrList, const char *pszKey, int bDefault ) 
 
{ 
    const char *pszValue; 
 
    if( CSLFindString( papszStrList, pszKey ) != -1 ) 
        return TRUE; 
 
    pszValue = CSLFetchNameValue(papszStrList, pszKey ); 
    if( pszValue == NULL ) 
        return bDefault; 
    else  
        return CSLTestBoolean( pszValue ); 
} 
 
/********************************************************************** 
 *                       CSLFetchNameValue() 
 * 
 * In a StringList of "Name=Value" pairs, look for the 
 * first value associated with the specified name.  The search is not 
 * case sensitive. 
 * ("Name:Value" pairs are also supported for backward compatibility 
 * with older stuff.) 
 *  
 * Returns a reference to the value in the StringList that the caller 
 * should not attempt to free. 
 * 
 * Returns NULL if the name is not found. 
 **********************************************************************/ 
const char *CSLFetchNameValue(char **papszStrList, const char *pszName) 
{ 
    int nLen; 
 
    if (papszStrList == NULL || pszName == NULL) 
        return NULL; 
 
    nLen = strlen(pszName); 
    while(*papszStrList != NULL) 
    { 
        if (EQUALN(*papszStrList, pszName, nLen) 
            && ( (*papszStrList)[nLen] == '=' ||  
                 (*papszStrList)[nLen] == ':' ) ) 
        { 
            return (*papszStrList)+nLen+1; 
        } 
        papszStrList++; 
    } 
    return NULL; 
} 
 
/********************************************************************** 
 *                       CPLParseNameValue() 
 **********************************************************************/ 
 
/** 
 * Parse NAME=VALUE string into name and value components. 
 * 
 * Note that if ppszKey is non-NULL, the key (or name) portion will be 
 * allocated using VSIMalloc(), and returned in that pointer.  It is the 
 * applications responsibility to free this string, but the application should 
 * not modify or free the returned value portion.  
 * 
 * This function also support "NAME:VALUE" strings and will strip white 
 * space from around the delimeter when forming name and value strings. 
 * 
 * Eventually CSLFetchNameValue() and friends may be modified to use  
 * CPLParseNameValue().  
 *  
 * @param pszNameValue string in "NAME=VALUE" format.  
 * @param ppszKey optional pointer though which to return the name 
 * portion.  
 * @return the value portion (pointing into original string).  
 */ 
 
const char *CPLParseNameValue(const char *pszNameValue, char **ppszKey ) 
 
{ 
    int  i; 
    const char *pszValue; 
 
    for( i = 0; pszNameValue[i] != '\0'; i++ ) 
    { 
        if( pszNameValue[i] == '=' || pszNameValue[i] == ':' ) 
        { 
            pszValue = pszNameValue + i + 1; 
            while( *pszValue == ' ' || *pszValue == '\t' ) 
                pszValue++; 
 
            if( ppszKey != NULL ) 
            { 
                *ppszKey = (char *) CPLMalloc(i+1); 
                strncpy( *ppszKey, pszNameValue, i ); 
                (*ppszKey)[i] = '\0'; 
                while( i > 0 &&  
                       ( (*ppszKey)[i] == ' ' || (*ppszKey)[i] == '\t') ) 
                { 
                    (*ppszKey)[i] = '\0'; 
                    i--; 
                } 
            } 
 
            return pszValue; 
        } 
    } 
 
    return NULL; 
} 
 
/********************************************************************** 
 *                       CSLFetchNameValueMultiple() 
 * 
 * In a StringList of "Name=Value" pairs, look for all the 
 * values with the specified name.  The search is not case 
 * sensitive. 
 * ("Name:Value" pairs are also supported for backward compatibility 
 * with older stuff.) 
 *  
 * Returns stringlist with one entry for each occurence of the 
 * specified name.  The stringlist should eventually be destroyed 
 * by calling CSLDestroy(). 
 * 
 * Returns NULL if the name is not found. 
 **********************************************************************/ 
char **CSLFetchNameValueMultiple(char **papszStrList, const char *pszName) 
{ 
    int nLen; 
    char **papszValues = NULL; 
 
    if (papszStrList == NULL || pszName == NULL) 
        return NULL; 
 
    nLen = strlen(pszName); 
    while(*papszStrList != NULL) 
    { 
        if (EQUALN(*papszStrList, pszName, nLen) 
            && ( (*papszStrList)[nLen] == '=' ||  
                 (*papszStrList)[nLen] == ':' ) ) 
        { 
            papszValues = CSLAddString(papszValues,  
                                          (*papszStrList)+nLen+1); 
        } 
        papszStrList++; 
    } 
 
    return papszValues; 
} 
 
 
/********************************************************************** 
 *                       CSLAddNameValue() 
 * 
 * Add a new entry to a StringList of "Name=Value" pairs, 
 * ("Name:Value" pairs are also supported for backward compatibility 
 * with older stuff.) 
 *  
 * This function does not check if a "Name=Value" pair already exists 
 * for that name and can generate multiple entryes for the same name. 
 * Use CSLSetNameValue() if you want each name to have only one value. 
 * 
 * Returns the modified stringlist. 
 **********************************************************************/ 
char **CSLAddNameValue(char **papszStrList,  
                    const char *pszName, const char *pszValue) 
{ 
    char *pszLine; 
 
    if (pszName == NULL || pszValue==NULL) 
        return papszStrList; 
 
    pszLine = (char *) CPLMalloc(strlen(pszName)+strlen(pszValue)+2); 
    sprintf( pszLine, "%s=%s", pszName, pszValue ); 
    papszStrList = CSLAddString(papszStrList, pszLine); 
    CPLFree( pszLine ); 
 
    return papszStrList; 
} 
 
/************************************************************************/ 
/*                          CSLSetNameValue()                           */ 
/************************************************************************/ 
 
/** 
 * Assign value to name in StringList. 
 * 
 * Set the value for a given name in a StringList of "Name=Value" pairs 
 * ("Name:Value" pairs are also supported for backward compatibility 
 * with older stuff.) 
 *  
 * If there is already a value for that name in the list then the value 
 * is changed, otherwise a new "Name=Value" pair is added. 
 * 
 * @param papszList the original list, the modified version is returned. 
 * @param pszName the name to be assigned a value.  This should be a well 
 * formed token (no spaces or very special characters).  
 * @param pszValue the value to assign to the name.  This should not contain 
 * any newlines (CR or LF) but is otherwise pretty much unconstrained. 
 * 
 * @return modified stringlist. 
 */ 
 
char **CSLSetNameValue(char **papszList,  
                       const char *pszName, const char *pszValue) 
{ 
    char **papszPtr; 
    int nLen; 
 
    if (pszName == NULL || pszValue==NULL) 
        return papszList; 
 
    nLen = strlen(pszName); 
    papszPtr = papszList; 
    while(papszPtr && *papszPtr != NULL) 
    { 
        if (EQUALN(*papszPtr, pszName, nLen) 
            && ( (*papszPtr)[nLen] == '=' ||  
                 (*papszPtr)[nLen] == ':' ) ) 
        { 
            /* Found it!   
             * Change the value... make sure to keep the ':' or '=' 
             */ 
            char cSep; 
            cSep = (*papszPtr)[nLen]; 
 
            CPLFree(*papszPtr); 
            *papszPtr = (char *) CPLMalloc(strlen(pszName)+strlen(pszValue)+2); 
            sprintf( *papszPtr, "%s%c%s", pszName, cSep, pszValue ); 
            return papszList; 
        } 
        papszPtr++; 
    } 
 
    /* The name does not exist yet... create a new entry 
     */ 
    return CSLAddNameValue(papszList, pszName, pszValue); 
} 
 
/************************************************************************/ 
/*                      CSLSetNameValueSeparator()                      */ 
/************************************************************************/ 
 
/** 
 * Replace the default separator (":" or "=") with the passed separator 
 * in the given name/value list.  
 * 
 * Note that if a separator other than ":" or "=" is used, the resulting 
 * list will not be manipulatable by the CSL name/value functions any more. 
 * 
 * The CPLParseNameValue() function is used to break the existing lines,  
 * and it also strips white space from around the existing delimiter, thus 
 * the old separator, and any white space will be replaced by the new 
 * separator.  For formatting purposes it may be desireable to include some 
 * white space in the new separator.  eg. ": " or " = ". 
 *  
 * @param papszList the list to update.  Component strings may be freed 
 * but the list array will remain at the same location. 
 * 
 * @param pszSeparator the new separator string to insert.   
 * 
 */ 
 
void CSLSetNameValueSeparator( char ** papszList, const char *pszSeparator ) 
 
{ 
    int         nLines = CSLCount(papszList), iLine; 
 
    for( iLine = 0; iLine < nLines; iLine++ ) 
    { 
        char        *pszKey = NULL; 
        const char  *pszValue; 
        char        *pszNewLine; 
 
        pszValue = CPLParseNameValue( papszList[iLine], &pszKey ); 
         
        pszNewLine = (char *) CPLMalloc( strlen(pszValue) + strlen(pszKey) 
                                         + strlen(pszSeparator) + 1 ); 
        strcpy( pszNewLine, pszKey ); 
        strcat( pszNewLine, pszSeparator ); 
        strcat( pszNewLine, pszValue ); 
        CPLFree( papszList[iLine] ); 
        papszList[iLine] = pszNewLine; 
        CPLFree( pszKey ); 
    } 
} 
 
/************************************************************************/ 
/*                          CPLEscapeString()                           */ 
/************************************************************************/ 
 
/** 
 * Apply escaping to string to preserve special characters. 
 * 
 * This function will "escape" a variety of special characters 
 * to make the string suitable to embed within a string constant 
 * or to write within a text stream but in a form that can be 
 * reconstitued to it's original form.  The escaping will even preserve 
 * zero bytes allowing preservation of raw binary data. 
 * 
 * CPLES_BackslashQuotable(0): This scheme turns a binary string into  
 * a form suitable to be placed within double quotes as a string constant. 
 * The backslash, quote, '\0' and newline characters are all escaped in  
 * the usual C style.  
 * 
 * CPLES_XML(1): This scheme converts the '<', '<' and '&' characters into 
 * their XML/HTML equivelent (>, < and &) making a string safe 
 * to embed as CDATA within an XML element.  The '\0' is not escaped and  
 * should not be included in the input. 
 * 
 * CPLES_URL(2): Everything except alphanumerics and the underscore are  
 * converted to a percent followed by a two digit hex encoding of the character 
 * (leading zero supplied if needed).  This is the mechanism used for encoding 
 * values to be passed in URLs. 
 * 
 * CPLES_SQL(3): All single quotes are replaced with two single quotes.   
 * Suitable for use when constructing literal values for SQL commands where 
 * the literal will be enclosed in single quotes. 
 * 
 * CPLES_CSV(4): If the values contains commas, double quotes, or newlines it  
 * placed in double quotes, and double quotes in the value are doubled. 
 * Suitable for use when constructing field values for .csv files.  Note that 
 * CPLUnescapeString() currently does not support this format, only  
 * CPLEscapeString().  See cpl_csv.cpp for csv parsing support. 
 * 
 * @param pszInput the string to escape.   
 * @param nLength The number of bytes of data to preserve.  If this is -1 
 * the strlen(pszString) function will be used to compute the length. 
 * @param nScheme the encoding scheme to use.   
 * 
 * @return an escaped, zero terminated string that should be freed with  
 * CPLFree() when no longer needed. 
 */ 
 
char *CPLEscapeString( const char *pszInput, int nLength,  
                       int nScheme ) 
 
{ 
    char        *pszOutput; 
    char        *pszShortOutput; 
 
    if( nLength == -1 ) 
        nLength = strlen(pszInput); 
 
    pszOutput = (char *) CPLMalloc( nLength * 6 + 1 ); 
     
    if( nScheme == CPLES_BackslashQuotable ) 
    { 
        int iOut = 0, iIn; 
 
        for( iIn = 0; iIn < nLength; iIn++ ) 
        { 
            if( pszInput[iIn] == '\0' ) 
            { 
                pszOutput[iOut++] = '\\'; 
                pszOutput[iOut++] = '0'; 
            } 
            else if( pszInput[iIn] == '\n' ) 
            { 
                pszOutput[iOut++] = '\\'; 
                pszOutput[iOut++] = 'n'; 
            } 
            else if( pszInput[iIn] == '"' ) 
            { 
                pszOutput[iOut++] = '\\'; 
                pszOutput[iOut++] = '\"'; 
            } 
            else if( pszInput[iIn] == '\\' ) 
            { 
                pszOutput[iOut++] = '\\'; 
                pszOutput[iOut++] = '\\'; 
            } 
            else 
                pszOutput[iOut++] = pszInput[iIn]; 
        } 
        pszOutput[iOut] = '\0'; 
    } 
    else if( nScheme == CPLES_URL ) /* Untested at implementation */ 
    { 
        int iOut = 0, iIn; 
 
        for( iIn = 0; iIn < nLength; iIn++ ) 
        { 
            if( (pszInput[iIn] >= 'a' && pszInput[iIn] <= 'z') 
                || (pszInput[iIn] >= 'A' && pszInput[iIn] <= 'Z') 
                || (pszInput[iIn] >= '0' && pszInput[iIn] <= '9') 
                || pszInput[iIn] == '_' ) 
            { 
                pszOutput[iOut++] = pszInput[iIn]; 
            } 
            else 
            { 
                sprintf( pszOutput, "%%%02X", pszInput[iIn] ); 
                iOut += 3; 
            } 
        } 
        pszOutput[iOut] = '\0'; 
    } 
    else if( nScheme == CPLES_XML ) 
    { 
        int iOut = 0, iIn; 
 
        for( iIn = 0; iIn < nLength; iIn++ ) 
        { 
            if( pszInput[iIn] == '<' ) 
            { 
                pszOutput[iOut++] = '&'; 
                pszOutput[iOut++] = 'l'; 
                pszOutput[iOut++] = 't'; 
                pszOutput[iOut++] = ';'; 
            } 
            else if( pszInput[iIn] == '>' ) 
            { 
                pszOutput[iOut++] = '&'; 
                pszOutput[iOut++] = 'g'; 
                pszOutput[iOut++] = 't'; 
                pszOutput[iOut++] = ';'; 
            } 
            else if( pszInput[iIn] == '&' ) 
            { 
                pszOutput[iOut++] = '&'; 
                pszOutput[iOut++] = 'a'; 
                pszOutput[iOut++] = 'm'; 
                pszOutput[iOut++] = 'p'; 
                pszOutput[iOut++] = ';'; 
            } 
            else if( pszInput[iIn] == '"' ) 
            { 
                pszOutput[iOut++] = '&'; 
                pszOutput[iOut++] = 'q'; 
                pszOutput[iOut++] = 'u'; 
                pszOutput[iOut++] = 'o'; 
                pszOutput[iOut++] = 't'; 
                pszOutput[iOut++] = ';'; 
            } 
            else 
                pszOutput[iOut++] = pszInput[iIn]; 
        } 
        pszOutput[iOut] = '\0'; 
    } 
    else if( nScheme == CPLES_SQL ) 
    { 
        int iOut = 0, iIn; 
 
        for( iIn = 0; iIn < nLength; iIn++ ) 
        { 
            if( pszInput[iIn] == '\'' ) 
            { 
                pszOutput[iOut++] = '\''; 
                pszOutput[iOut++] = '\''; 
            } 
            else 
                pszOutput[iOut++] = pszInput[iIn]; 
        } 
        pszOutput[iOut] = '\0'; 
    } 
    else if( nScheme == CPLES_CSV ) 
    { 
        if( strchr( pszInput, '\"' ) == NULL 
            && strchr( pszInput, ',') == NULL 
            && strchr( pszInput, 10) == NULL  
            && strchr( pszInput, 13) == NULL ) 
        { 
            strcpy( pszOutput, pszInput ); 
        } 
        else 
        { 
            int iOut = 1, iIn; 
 
            pszOutput[0] = '\"'; 
 
            for( iIn = 0; iIn < nLength; iIn++ ) 
            { 
                if( pszInput[iIn] == '\"' ) 
                { 
                    pszOutput[iOut++] = '\"'; 
                    pszOutput[iOut++] = '\"'; 
                } 
                else if( pszInput[iIn] == 13 ) 
                    /* drop DOS LF's in strings. */; 
                else 
                    pszOutput[iOut++] = pszInput[iIn]; 
            } 
            pszOutput[iOut++] = '\"'; 
            pszOutput[iOut++] = '\0'; 
        } 
    } 
    else 
    { 
        pszOutput[0] = '\0'; 
        CPLError( CE_Failure, CPLE_AppDefined,  
                  "Undefined escaping scheme (%d) in CPLEscapeString()", 
                  nScheme ); 
    } 
 
    pszShortOutput = CPLStrdup( pszOutput ); 
    CPLFree( pszOutput ); 
 
    return pszShortOutput; 
} 
 
/************************************************************************/ 
/*                         CPLUnescapeString()                          */ 
/************************************************************************/ 
 
/** 
 * Unescape a string. 
 * 
 * This function does the opposite of CPLEscapeString().  Given a string 
 * with special values escaped according to some scheme, it will return a 
 * new copy of the string returned to it's original form.  
 * 
 * @param pszInput the input string.  This is a zero terminated string. 
 * @param pnLength location to return the length of the unescaped string,  
 * which may in some cases include embedded '\0' characters. 
 * @param nScheme the escaped scheme to undo (see CPLEscapeString() for a 
 * list).  
 *  
 * @return a copy of the unescaped string that should be freed by the  
 * application using CPLFree() when no longer needed. 
 */ 
 
char *CPLUnescapeString( const char *pszInput, int *pnLength, int nScheme ) 
 
{ 
    char *pszOutput; 
    int iOut=0, iIn; 
 
    pszOutput = (char *) CPLMalloc(strlen(pszInput)+1); 
    pszOutput[0] = '\0'; 
 
    if( nScheme == CPLES_XML ) 
    { 
        for( iIn = 0; pszInput[iIn] != '\0'; iIn++ ) 
        { 
            if( EQUALN(pszInput+iIn,"<",4) ) 
            { 
                pszOutput[iOut++] = '<'; 
                iIn += 3; 
            } 
            else if( EQUALN(pszInput+iIn,">",4) ) 
            { 
                pszOutput[iOut++] = '>'; 
                iIn += 3; 
            } 
            else if( EQUALN(pszInput+iIn,"&",5) ) 
            { 
                pszOutput[iOut++] = '&'; 
                iIn += 4; 
            } 
            else if( EQUALN(pszInput+iIn,""",6) ) 
            { 
                pszOutput[iOut++] = '"'; 
                iIn += 5; 
            } 
            else 
            { 
                pszOutput[iOut++] = pszInput[iIn]; 
            } 
        } 
    } 
    else if( nScheme == CPLES_URL ) 
    { 
        for( iIn = 0; pszInput[iIn] != '\0'; iIn++ ) 
        { 
            if( pszInput[iIn] == '%'  
                && pszInput[iIn+1] != '\0'  
                && pszInput[iIn+2] != '\0' ) 
            { 
                int nHexChar = 0; 
 
                if( pszInput[iIn+1] >= 'A' && pszInput[iIn+1] <= 'F' ) 
                    nHexChar += 16 * (pszInput[iIn+1] - 'A' + 10); 
                else if( pszInput[iIn+1] >= 'a' && pszInput[iIn+1] <= 'f' ) 
                    nHexChar += 16 * (pszInput[iIn+1] - 'a' + 10); 
                else if( pszInput[iIn+1] >= '0' && pszInput[iIn+1] <= '9' ) 
                    nHexChar += 16 * (pszInput[iIn+1] - '0'); 
                else 
                    CPLDebug( "CPL",  
                              "Error unescaping CPLES_URL text, percent not " 
                              "followed by two hex digits." ); 
                     
                if( pszInput[iIn+2] >= 'A' && pszInput[iIn+2] <= 'F' ) 
                    nHexChar += pszInput[iIn+2] - 'A' + 10; 
                else if( pszInput[iIn+2] >= 'a' && pszInput[iIn+2] <= 'f' ) 
                    nHexChar += pszInput[iIn+2] - 'a' + 10; 
                else if( pszInput[iIn+2] >= '0' && pszInput[iIn+2] <= '9' ) 
                    nHexChar += pszInput[iIn+2] - '0'; 
                else 
                    CPLDebug( "CPL",  
                              "Error unescaping CPLES_URL text, percent not " 
                              "followed by two hex digits." ); 
 
                pszOutput[iOut++] = (char) nHexChar; 
                iIn += 2; 
            } 
            else if( pszInput[iIn] == '+' ) 
            { 
                pszOutput[iOut++] = ' '; 
            }    
            else 
            { 
                pszOutput[iOut++] = pszInput[iIn]; 
            } 
        } 
    } 
    else if( nScheme == CPLES_SQL ) 
    { 
        for( iIn = 0; pszInput[iIn] != '\0'; iIn++ ) 
        { 
            if( pszInput[iIn] == '\'' && pszInput[iIn+1] == '\'' ) 
            { 
                iIn++; 
                pszOutput[iOut++] = pszInput[iIn]; 
            } 
            else 
            { 
                pszOutput[iOut++] = pszInput[iIn]; 
            } 
        } 
    } 
    else /* if( nScheme == CPLES_BackslashQuoteable ) */ 
    { 
        for( iIn = 0; pszInput[iIn] != '\0'; iIn++ ) 
        { 
            if( pszInput[iIn] == '\\' ) 
            { 
                iIn++; 
                if( pszInput[iIn] == 'n' ) 
                    pszOutput[iOut++] = '\n'; 
                else if( pszInput[iIn] == '0' ) 
                    pszOutput[iOut++] = '\0'; 
                else  
                    pszOutput[iOut++] = pszInput[iIn]; 
            } 
            else 
            { 
                pszOutput[iOut++] = pszInput[iIn]; 
            } 
        } 
    } 
 
    pszOutput[iOut] = '\0'; 
 
    if( pnLength != NULL ) 
        *pnLength = iOut; 
 
    return pszOutput; 
}