text: /******************************************************************************
   * $Id$
   *
   * Project:  GDAL
   * Purpose:  CPLString implementation.
   * Author:   Frank Warmerdam, warmerdam@pobox.com
   *
   ******************************************************************************
   * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
   * Copyright (c) 2011, Even Rouault <even dot rouault at mines-paris dot org>
   *
   * 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.
   ****************************************************************************/
  
  #include "cpl_string.h"
  #include <string>
  
  CPL_CVSID("$Id$");
  
  /*
   * The CPLString class is derived from std::string, so the vast majority
   * of the implementation comes from that.  This module is just the extensions
   * we add.
   */
  
  /************************************************************************/
  /*                               Printf()                               */
  /************************************************************************/
  
  CPLString &CPLString::Printf( const char *pszFormat, ... )
  
  {
      va_list args;
  
      va_start( args, pszFormat );
      vPrintf( pszFormat, args );
      va_end( args );
  
      return *this;
  }
  
  /************************************************************************/
  /*                              vPrintf()                               */
  /************************************************************************/
  
  CPLString &CPLString::vPrintf( const char *pszFormat, va_list args )
  
  {
  /* -------------------------------------------------------------------- */
  /*      This implementation for platforms without vsnprintf() will      */
  /*      just plain fail if the formatted contents are too large.        */
  /* -------------------------------------------------------------------- */
  
  #if !defined(HAVE_VSNPRINTF)
      char *pszBuffer = (char *) CPLMalloc(30000);
      if( CPLvsnprintf( pszBuffer, 30000, pszFormat, args) > 29998 )
      {
          CPLError( CE_Fatal, CPLE_AppDefined,
                    "CPLString::vPrintf() ... buffer overrun." );
      }
      *this = pszBuffer;
      CPLFree( pszBuffer );
  
  /* -------------------------------------------------------------------- */
  /*      This should grow a big enough buffer to hold any formatted      */
  /*      result.                                                         */
  /* -------------------------------------------------------------------- */
  #else
      va_list wrk_args;
  
  #ifdef va_copy
      va_copy( wrk_args, args );
  #else
      wrk_args = args;
  #endif
  
      char szModestBuffer[500];
      szModestBuffer[0] = '\0';
      int nPR = CPLvsnprintf( szModestBuffer, sizeof(szModestBuffer), pszFormat,
                               wrk_args );
      if( nPR == -1 || nPR >= (int) sizeof(szModestBuffer)-1 )
      {
          int nWorkBufferSize = 2000;
          char *pszWorkBuffer = reinterpret_cast<char *>(
              CPLMalloc(nWorkBufferSize));
  
  #ifdef va_copy
          va_end( wrk_args );
          va_copy( wrk_args, args );
  #else
          wrk_args = args;
  #endif
          while( (nPR=CPLvsnprintf( pszWorkBuffer, nWorkBufferSize, pszFormat,wrk_args))
                 >= nWorkBufferSize-1 
                 || nPR == -1 )
          {
              nWorkBufferSize *= 4;
              pszWorkBuffer = (char *) CPLRealloc(pszWorkBuffer, 
                                                  nWorkBufferSize );
  #ifdef va_copy
              va_end( wrk_args );
              va_copy( wrk_args, args );
  #else
              wrk_args = args;
  #endif
          }
          *this = pszWorkBuffer;
          CPLFree( pszWorkBuffer );
      }
      else
      {
          *this = szModestBuffer;
      }
  #ifdef va_copy
      va_end( wrk_args );
  #endif
  
  #endif /* !defined(HAVE_VSNPRINTF) */
  
      return *this;
  }
  
  /************************************************************************/
  /*                              FormatC()                               */
  /************************************************************************/
  
  /**
   * Format double in C locale.
   *
   * The passed value is formatted using the C locale (period as decimal 
   * separator) and appended to the target CPLString. 
   *
   * @param dfValue the value to format. 
   * @param pszFormat the sprintf() style format to use or omit for default.
   * Note that this format string should only include one substitution argument 
   * and it must be for a double (%f or %g). 
   *
   * @return a reference to the CPLString. 
   */
  
  CPLString &CPLString::FormatC( double dfValue, const char *pszFormat )
  
  {
      if( pszFormat == NULL )
          pszFormat = "%g";
  
      // presumably long enough for any number.
      const size_t buf_size = 512;
      char szWork[buf_size];
  
      CPLsnprintf( szWork, buf_size, pszFormat, dfValue );
  
      *this += szWork;
  
      return *this;
  }
  
  /************************************************************************/
  /*                                Trim()                                */
  /************************************************************************/
  
  /**
   * Trim white space.
   *
   * Trims white space off the let and right of the string.  White space
   * is any of a space, a tab, a newline ('\n') or a carriage control ('\r').
   *
   * @return a reference to the CPLString. 
   */
  
  CPLString &CPLString::Trim()
  
  {
      static const char szWhitespace[] = " \t\r\n";
  
      const size_t iLeft = find_first_not_of( szWhitespace );
      const size_t iRight = find_last_not_of( szWhitespace );
  
      if( iLeft == std::string::npos )
      {
          erase();
          return *this;
      }
  
      assign( substr( iLeft, iRight - iLeft + 1 ) );
  
      return *this;
  }
  
  /************************************************************************/
  /*                               Recode()                               */
  /************************************************************************/
  
  CPLString &CPLString::Recode( const char *pszSrcEncoding,
                                const char *pszDstEncoding )
  
  {
      if( pszSrcEncoding == NULL )
          pszSrcEncoding = CPL_ENC_UTF8;
      if( pszDstEncoding == NULL )
          pszDstEncoding = CPL_ENC_UTF8;
  
      if( strcmp(pszSrcEncoding,pszDstEncoding) == 0 )
          return *this;
  
      char *pszRecoded = CPLRecode( c_str(), 
                                    pszSrcEncoding,
                                    pszDstEncoding );
  
      if( pszRecoded == NULL )
          return *this;
  
      assign( pszRecoded );
      CPLFree( pszRecoded );
  
      return *this;
  }
  
  /************************************************************************/
  /*                               ifind()                                */
  /************************************************************************/
  
  /**
   * Case insensitive find() alternative.
   *
   * @param str substring to find.
   * @param pos offset in the string at which the search starts.
   * @return the position of substring in the string or std::string::npos if not found.
   * @since GDAL 1.9.0
   */
  
  size_t CPLString::ifind( const std::string & str, size_t pos ) const
  
  {
      return ifind( str.c_str(), pos );
  }
  
  /**
   * Case insensitive find() alternative.
   *
   * @param s substring to find.
   * @param nPos offset in the string at which the search starts.
   * @return the position of the substring in the string or std::string::npos if not found.
   * @since GDAL 1.9.0
   */
  
  size_t CPLString::ifind( const char *s, size_t nPos ) const
  
  {
      const char *pszHaystack = c_str();
      char chFirst = (char) ::tolower( s[0] );
      size_t nTargetLen = strlen(s);
  
      if( nPos > size() )
          nPos = size();
  
      pszHaystack += nPos;
  
      while( *pszHaystack != '\0' )
      {
          if( chFirst == ::tolower(*pszHaystack) )
          {
              if( EQUALN(pszHaystack,s,nTargetLen) )
                  return nPos;
          }
  
          nPos++;
          pszHaystack++;
      }
  
      return std::string::npos;
  }
  
  /************************************************************************/
  /*                              toupper()                               */
  /************************************************************************/
  
  /**
   * Convert to upper case in place.
   */
  
  CPLString &CPLString::toupper()
  
  {
      for( size_t i = 0; i < size(); i++ )
          (*this)[i] = (char) ::toupper( (*this)[i] );
  
      return *this;
  }
  
  /************************************************************************/
  /*                              tolower()                               */
  /************************************************************************/
  
  /**
   * Convert to lower case in place.
   */
  
  CPLString &CPLString::tolower()
  
  {
      for(size_t i = 0; i < size(); i++ )
          (*this)[i] = (char) ::tolower( (*this)[i] );
  
      return *this;
  }
  
  /************************************************************************/
  /*                         CPLURLGetValue()                             */
  /************************************************************************/
  
  /**
   * Return the value matching a key from a key=value pair in a URL.
   *
   * @param pszURL the URL.
   * @param pszKey the key to find.
   * @return the value of empty string if not found.
   * @since GDAL 1.9.0
   */
  CPLString CPLURLGetValue(const char* pszURL, const char* pszKey)
  {
      CPLString osKey(pszKey);
      osKey += "=";
      size_t nKeyPos = CPLString(pszURL).ifind(osKey);
      if (nKeyPos != std::string::npos && nKeyPos > 0 &&
          (pszURL[nKeyPos-1] == '?' || pszURL[nKeyPos-1] == '&'))
      {
          CPLString osValue(pszURL + nKeyPos + strlen(osKey));
          const char* pszValue = osValue.c_str();
          const char* pszSep = strchr(pszValue, '&');
          if (pszSep)
          {
              osValue.resize(pszSep - pszValue);
          }
          return osValue;
      }
      return "";
  }
  
  /************************************************************************/
  /*                          CPLURLAddKVP()                              */
  /************************************************************************/
  
  /**
   * Return a new URL with a new key=value pair.
   *
   * @param pszURL the URL.
   * @param pszKey the key to find.
   * @param pszValue the value of the key (may be NULL to unset an existing KVP).
   * @return the modified URL.
   * @since GDAL 1.9.0
   */
  CPLString CPLURLAddKVP(const char* pszURL, const char* pszKey,
                         const char* pszValue)
  {
      CPLString osURL(pszURL);
      if (strchr(osURL, '?') == NULL)
          osURL += "?";
      pszURL = osURL.c_str();
  
      CPLString osKey(pszKey);
      osKey += "=";
      size_t nKeyPos = osURL.ifind(osKey);
      if (nKeyPos != std::string::npos && nKeyPos > 0 &&
          (pszURL[nKeyPos-1] == '?' || pszURL[nKeyPos-1] == '&'))
      {
          CPLString osNewURL(osURL);
          osNewURL.resize(nKeyPos);
          if (pszValue)
          {
              osNewURL += osKey;
              osNewURL += pszValue;
          }
          const char* pszNext = strchr(pszURL + nKeyPos, '&');
          if (pszNext)
          {
              if (osNewURL[osNewURL.size()-1] == '&'
                  || osNewURL[osNewURL.size()-1] == '?' )
                  osNewURL += pszNext + 1;
              else
                  osNewURL += pszNext;
          }
          return osNewURL;
      }
      else
      {
          if (pszValue)
          {
              if (osURL[osURL.size()-1] != '&' && osURL[osURL.size()-1] != '?')
                  osURL += '&';
              osURL += osKey;
              osURL += pszValue;
          }
          return osURL;
      }
  }
  
  /************************************************************************/
  /*                            CPLOPrintf()                              */
  /************************************************************************/
  
  CPLString CPLOPrintf( const char *pszFormat, ... )
  
  {
      va_list args;
      va_start( args, pszFormat );
  
      CPLString osTarget;
      osTarget.vPrintf( pszFormat, args );
  
      va_end( args );
  
      return osTarget;
  }
  
  /************************************************************************/
  /*                            CPLOvPrintf()                             */
  /************************************************************************/
  
  CPLString CPLOvPrintf( const char *pszFormat, va_list args )
  
  {
      CPLString osTarget;
      osTarget.vPrintf( pszFormat, args);
      return osTarget;
  }
  