/******************************************************************************
 * $Id$
 *
 * Project:  MapServer
 * Purpose:  Various string handling functions.
 * Author:   Steve Lime and the MapServer team.
 *
 * Notes: A couple of string handling functions (strrstr, strlcat) were taken from
 * other sources. Copyright notices accompany those functions below.
 *
 ******************************************************************************
 * Copyright (c) 1996-2005 Regents of the University of Minnesota.
 * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
 *
 * 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 of this Software or works derived from this 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 "mapserver.h"
#include "mapthread.h"

#include "cpl_vsi.h"

#include <ctype.h>
#include <string.h>
#include <errno.h>

#include <algorithm>
#include <cctype>

/*
 * Find the first occurrence of find in s, ignore case.
 */

#ifdef USE_FRIBIDI
#if (defined(_WIN32) && !defined(__CYGWIN__)) || defined(HAVE_FRIBIDI2)
#include "fribidi.h"
#else
#include <fribidi/fribidi.h>
#endif
#define MAX_STR_LEN 65000
#endif

#ifdef USE_ICONV
#include "mapiconv.h"
#include <iconv.h>
#include <wchar.h>
#endif

#include "mapentities.h"

#ifndef HAVE_STRRSTR
/*
** Copyright (c) 2000-2004  University of Illinois Board of Trustees
** Copyright (c) 2000-2005  Mark D. Roth
** All rights reserved.
**
** Developed by: Campus Information Technologies and Educational Services,
** University of Illinois at Urbana-Champaign
**
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** ``Software''), to deal with 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:
**
** * Redistributions of source code must retain the above copyright
**   notice, this list of conditions and the following disclaimers.
**
** * Redistributions in binary form must reproduce the above copyright
**   notice, this list of conditions and the following disclaimers in the
**   documentation and/or other materials provided with the distribution.
**
** * Neither the names of Campus Information Technologies and Educational
**   Services, University of Illinois at Urbana-Champaign, nor the names
**   of its contributors may be used to endorse or promote products derived
**   from this Software without specific prior written permission.
**
** 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 CONTRIBUTORS 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 WITH THE SOFTWARE.
*/
char *strrstr(const char *string, const char *find)
{
  size_t stringlen, findlen;
  const char *cp;

  findlen = strlen(find);
  stringlen = strlen(string);
  if (findlen > stringlen)
    return NULL;

  for (cp = string + stringlen - findlen; cp >= string; cp--)
    if (strncmp(cp, find, findlen) == 0)
      return (char*) cp;

  return NULL;
}
#endif

#ifndef HAVE_STRLCAT
/*
 * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MS_MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
size_t strlcat(char *dst, const char *src, size_t siz)
{
  char *d = dst;
  const char *s = src;
  size_t n = siz;
  size_t dlen;

  /* Find the end of dst and adjust bytes left but don't go past end */
  while (n-- != 0 && *d != '\0')
    d++;
  dlen = d - dst;
  n = siz - dlen;

  if (n == 0)
    return(dlen + strlen(s));
  while (*s != '\0') {
    if (n != 1) {
      *d++ = *s;
      n--;
    }
    s++;
  }
  *d = '\0';

  return(dlen + (s - src));/* count does not include NUL */
}
#endif

#ifndef HAVE_STRLCPY
/*
 * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Copy src to string dst of size siz.  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz == 0).
 * Returns strlen(src); if retval >= siz, truncation occurred.
 */
size_t
strlcpy(char *dst, const char *src, size_t siz)
{
  char *d = dst;
  const char *s = src;
  size_t n = siz;

  /* Copy as many bytes as will fit */
  if (n != 0 && --n != 0) {
    do {
      if ((*d++ = *s++) == 0)
        break;
    } while (--n != 0);
  }

  /* Not enough room in dst, add NUL and traverse rest of src */
  if (n == 0) {
    if (siz != 0)
      *d = '\0';              /* NUL-terminate dst */
    while (*s++)
      ;
  }

  return(s - src - 1);    /* count does not include NUL */
}
#endif

#ifndef HAVE_STRCASESTR
/*-
 * Copyright (c) 1990, 1993
 *  The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Chris Torek.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
char *strcasestr(const char *s, const char *find)
{
  char c, sc;
  size_t len;

  if ((c = *find++) != 0) {
    c = tolower((unsigned char)c);
    len = strlen(find);
    do {
      do {
        if ((sc = *s++) == 0)
          return (NULL);
      } while ((char)tolower((unsigned char)sc) != c);
    } while (strncasecmp(s, find, len) != 0);
    s--;
  }
  return ((char *)s);
}
#endif

#ifndef HAVE_STRNCASECMP
int strncasecmp(const char *s1, const char *s2, size_t len)
{
  const char *cp1, *cp2;

  cp1 = s1;
  cp2 = s2;

  if(len == 0)
    return(0);

  if (!*cp1)
    return -1;
  else if (!*cp2)
    return 1;

  while(*cp1 && *cp2 && len) {
    int cmp;
    if((cmp = (toupper(*cp1) - toupper(*cp2))) != 0)
      return(cmp);
    cp1++;
    cp2++;
    len--;
  }

  if(len == 0) {
    return(0);
  }
  if(*cp1 || *cp2) {
    if (*cp1)
      return(1);
    else
      return (-1);
  }
  return(0);
}
#endif

#ifndef HAVE_STRCASECMP
int strcasecmp(const char *s1, const char *s2)
{
  const char *cp1, *cp2;

  cp1 = s1;
  cp2 = s2;
  if ((!cp1) || (!cp2 )) {
    return (0);
  }
  while(*cp1 && *cp2) {
    int cmp;
    if((cmp = (toupper(*cp1) - toupper(*cp2))) != 0)
      return(cmp);
    cp1++;
    cp2++;
  }
  if(*cp1 || *cp2) {
    if (*cp1)
      return(1);
    else
      return (-1);
  }

  return(0);
}
#endif

/*
** msStringToInt() and msStringToDouble() are helper functions to convert strings to numbers. They
** return MS_FAILURE if the input string is NULL or if the entire string did not convert successfully.
*/
int msStringToInt(const char *str, int *value, int base)
{
  char *parse_check = NULL;

  if(!str) return MS_FAILURE;

  *value = (int)strtol(str, &parse_check, base);
  if(*parse_check != '\0') return MS_FAILURE;

  return MS_SUCCESS;
}

int msStringToDouble(const char *str, double *value)
{
  char *parse_check = NULL;

  if(!str) return MS_FAILURE;

  *value = strtod(str, &parse_check);
  if(*parse_check != '\0') return MS_FAILURE;

  return MS_SUCCESS;
}

char *msLongToString(long value)
{
  size_t bufferSize = 256;
  char *buffer = (char*)msSmallMalloc(bufferSize);

  snprintf(buffer, bufferSize, "%ld", value);
  return(buffer);
}

char *msDoubleToString(double value, int force_f)
{
  size_t bufferSize = 256;
  char *buffer = (char*)msSmallMalloc(bufferSize);

  if (force_f == MS_TRUE)
    snprintf(buffer, bufferSize, "%f", value);
  else
    snprintf(buffer, bufferSize, "%g", value);
  return(buffer);
}

char *msIntToString(int value)
{
  size_t bufferSize = 256;
  char *buffer = (char*)msSmallMalloc(bufferSize);

  snprintf(buffer, bufferSize, "%i", value);
  return(buffer);
}

void msStringToUpper(char *string)
{
  int i;

  if (string != NULL) {
    for (i = 0; string[i]; i++) {
      string[i] = toupper(string[i]);
    }
    return;
  }
}

void msStringToLower(char *string)
{
  int i;

  if (string != NULL) {
    for (i = 0; string[i]; i++) {
      string[i] = tolower(string[i]);
    }
    return;
  }
}

std::string msStringToLower(const std::string& s) {
  std::string ret(s);
  std::transform(ret.begin(), ret.end(), ret.begin(), 
                 [](unsigned char c){ return std::tolower(c); });
  return ret;
}

/**
 * Force the first character to uppercase and the rest of the characters to
 * lower case for EACH word in the string.
 */
void msStringInitCap(char *string)
{
  int i;
  int start = 1; 
  if (string != NULL) {
    for (i = 0; i < (int)strlen(string); i++) {
      if (string[i] == ' ')
        start = 1;
      else if (start) {
        string[i] = toupper(string[i]);
        start = 0;
      }
      else {
        string[i] = tolower(string[i]);
      }
    }
  }
}

/**
 * Force the first character to uppercase for the FIRST word in the string
 * and the rest of the characters to lower case.
 */
void msStringFirstCap(char *string)
{
  int i;
  int start = 1; 
  if (string != NULL) {
    for (i = 0; i < (int)strlen(string); i++) {
      if (string[i] != ' ') {
        if (start) {
          string[i] = toupper(string[i]);
          start = 0;
        }
        else
          string[i] = tolower(string[i]);
      }
    }
  }
}

char *msStringChop(char *string)
{
  int n;

  n = strlen(string);
  if(n>0)
    string[n-1] = '\0';

  return(string);
}

/*
** Trim leading and trailing white space.
*/
void msStringTrim(char *str)
{
  int i;

  /* Send nulls home without supper. */
  if( ! str ) return;

  /* Move non-white string to the front. */
  i = strspn(str, " ");
  if(i) {
    memmove(str, str + i, strlen(str) - i + 1);
  }
  /* Nothing left? Exit. */
  if(strlen(str) == 0) {
    return;
  }
  /* Null-terminate end of non-white string. */
  for(i=strlen(str)-1; i>=0; i--) { /* step backwards from end */
    if(str[i] != ' ') {
      str[i+1] = '\0';
      return;
    }
  }
  return;
}

void msStringTrim(std::string& string)
{
  const size_t npos = string.find_first_not_of(' ');
  if( npos != std::string::npos )
    string.erase(0, npos);
  msStringTrimBlanks(string);
}


/*
** Remove leading white spaces and shift everything to the left.
*/
char *msStringTrimLeft(char *string)
{
  char *read, *write;
  int i, length;

  if (string && strlen(string) > 0) {
    length = strlen(string);
    read = string;
    write = string;

    for (i=0; i<length; i++) {
      if (isspace(string[i]))
        read++;
      else
        break;
    }

    if (read > write) {
      while (*read) {
        *write = *read;
        read++;
        write++;
      }
      *write = '\0';
    }
  }
  return string;
}

void msStringTrimLeft(std::string& string)
{
  const size_t length = string.length();
  for( size_t i = 0; i < length; i++ ) {
    if( !isspace(string[i]) ) {
        if( i > 0 ) {
          string.erase(0, i-1);
        }
        return;
    }
  }
  string.clear();
}

/* ------------------------------------------------------------------------------- */
/*       Trims trailing blanks from a string                                        */
/* ------------------------------------------------------------------------------- */
void msStringTrimBlanks(char *string)
{
  int i,n;

  n = strlen(string);
  for(i=n-1; i>=0; i--) { /* step backwards through the string */
    if(string[i] != ' ') {
      string[i+1] = '\0';
      return;
    }
  }
}

void msStringTrimBlanks(std::string& string)
{
  const size_t npos = string.find_last_not_of(' ');
  if( npos != std::string::npos )
    string.resize(npos+1);
}

/* ------------------------------------------------------------------------------- */
/*       Trims end-of-line marker from a string                                    */
/*       Usefull in conjunction with fgets() calls                                 */
/* ------------------------------------------------------------------------------- */
void msStringTrimEOL(char *string)
{
  int i;

  for(i=0 ; string[i] != '\0'; i++) {
    if(string[i] == '\n') {
      string[i] = '\0'; /* Terminate the string at the newline */
      return;
    }
  }
}

/* ------------------------------------------------------------------------------- */
/*       Replace all occurances of old with new in str.                            */
/*       It is assumed that str was dynamically created using malloc.              */
/* ------------------------------------------------------------------------------- */
char *msReplaceSubstring(char *str, const char *old, const char *newstr)
{
  size_t str_len, old_len, new_len, tmp_offset;
  char *tmp_ptr;

  if(newstr == NULL)
    newstr = "";

  /*
  ** If old is not found then leave str alone
  */
  if( (tmp_ptr = strstr(str, old)) == NULL)
    return(str);

  /*
  ** Grab some info about incoming strings
  */
  str_len = strlen(str);
  old_len = strlen(old);
  new_len = strlen(newstr);

  /*
  ** Now loop until old is NOT found in new
  */
  while( tmp_ptr != NULL ) {

    /*
    ** re-allocate memory for buf assuming 1 replacement of old with new
          ** don't bother reallocating if old is larger than new)
    */
    if (old_len < new_len) {
      tmp_offset = tmp_ptr - str;
      str_len = str_len - old_len + new_len;
      str = (char *)msSmallRealloc(str, (str_len + 1)); /* make new space for a copy */
      tmp_ptr = str + tmp_offset;
    }

    /*
    ** Move the trailing part of str to make some room unless old_len == new_len
    */
    if (old_len != new_len) {
      memmove(tmp_ptr+new_len, tmp_ptr+old_len, strlen(tmp_ptr)-old_len+1);
    }

    /*
    ** Now copy new over old
    */
    memcpy(tmp_ptr, newstr, new_len);

    /*
    ** And look for more matches in the rest of the string
    */
    tmp_ptr = strstr(tmp_ptr + new_len, old);
  }

  return(str);
}

/*
 * same goal as msReplaceSubstring, but for the known case
 * when we won't have to do reallocs etc
 * used to replace the wrap characetr by a newline for labels
 */
void msReplaceChar(char *str, char old, char newstr)
{
  while(*(str++))
    if(*str==old)
      *str=newstr;
}

/*
** how many times does ch occur in str
*/
int msCountChars(char *str, char ch)
{
  int i, l, n=0;

  l = strlen(str);
  for(i=0; i<l; i++)
    if(str[i] == ch) n++;

  return(n);
}

/* ------------------------------------------------------------------------------- */
/*       Strip filename from a full path                                           */
/* ------------------------------------------------------------------------------- */
char *msStripPath(char *fn)
{
  char *pSlash;
  char *pBackslash;

  /* try to locate both, the last slash or backslash */
  pSlash = strrchr(fn,'/');
  pBackslash = strrchr(fn,'\\');

  if( pSlash != NULL && pBackslash != NULL ) {
    if( pSlash < pBackslash )
      return ++pBackslash;
    else
      return ++pSlash;
  } else if ( pSlash != NULL )
    return ++pSlash; /* skip past the "slash" */
  else if ( pBackslash != NULL )
    return ++pBackslash; /* skip past the "backslash" */
  else
    return(fn);
}

/*
** Returns the *path* portion of the filename fn. Memory is allocated using malloc.
*/
char *msGetPath(const char *fn)
{
  char *str;
  int i, length;

  length = strlen(fn);
  if((str = msStrdup(fn)) == NULL)
    return(NULL);

  for(i=length-1; i>=0; i--) { /* step backwards through the string */
    if((str[i] == '/') || (str[i] == '\\')) {
      str[i+1] = '\0';
      break;
    }
  }

  if(strcmp(str, fn) == 0) {
    msFree(str);
#if defined(_WIN32) && !defined(__CYGWIN__)
    str = msStrdup(".\\");
#else
    str= msStrdup("./");
#endif
  }

  return(str);
}

/*
** Returns a *path* built from abs_path and path.
** The pszReturnPath must be declared by the caller function as an array
** of MS_MAXPATHLEN char
*/
char *msBuildPath(char *pszReturnPath, const char *abs_path, const char *path)
{
  int   abslen = 0;
  int   pathlen = 0;


  if(path == NULL) {
    msSetError(MS_IOERR, NULL, "msBuildPath");
    return NULL;
  }

  pathlen = strlen(path);
  if (abs_path)
    abslen = strlen(abs_path);

  if((pathlen + abslen + 2) > MS_MAXPATHLEN) {
    msSetError(MS_IOERR, "Path is too long.  Check server logs.",
               "msBuildPath()");
    msDebug("msBuildPath(): (%s%s): path is too long.\n", abs_path, path);
    return NULL;
  }

  /* Check if path is absolute */
  if((abs_path == NULL) || (abslen == 0) ||
      (path[0] == '\\') || (path[0] == '/') ||
      (pathlen > 1 && (path[1] == ':'))) {
    strlcpy(pszReturnPath, path, MS_MAXPATHLEN);
    return(pszReturnPath);
  }

  /* else return abs_path/path */
  if((abs_path[abslen-1] == '/') || (abs_path[abslen-1] == '\\')) {
    snprintf(pszReturnPath, MS_MAXPATHLEN, "%s%s", abs_path, path);
  } else {
    snprintf(pszReturnPath, MS_MAXPATHLEN, "%s/%s", abs_path, path);
  }

  return(pszReturnPath);
}

/*
** Returns a *path* built from abs_path, path1 and path2.
** abs_path/path1/path2
** The pszReturnPath must be declared by the caller function as an array
** of MS_MAXPATHLEN char
*/
char *msBuildPath3(char *pszReturnPath, const char *abs_path, const char *path1,const char *path2)
{
  char szPath[MS_MAXPATHLEN];

  return msBuildPath(pszReturnPath, abs_path,
                     msBuildPath(szPath, path1, path2));
}

/*
** Similar to msBuildPath(), but the input path is only qualified by the
** absolute path if this will result in it pointing to a readable file.
**
** Returns NULL if the resulting path doesn't point to a readable file.
*/

char *msTryBuildPath(char *szReturnPath, const char *abs_path, const char *path)

{
  VSILFILE  *fp;

  if( msBuildPath( szReturnPath, abs_path, path ) == NULL )
    return NULL;

  fp = VSIFOpenL( szReturnPath, "r" );
  if( fp == NULL ) {
    strlcpy( szReturnPath, path, MS_MAXPATHLEN);
    return NULL;
  } else
    VSIFCloseL( fp );

  return szReturnPath;
}

/*
** Similar to msBuildPath3(), but the input path is only qualified by the
** absolute path if this will result in it pointing to a readable file.
**
** Returns NULL if the resulting path doesn't point to a readable file.
*/

char *msTryBuildPath3(char *szReturnPath, const char *abs_path, const char *path1, const char *path2)

{
  VSILFILE  *fp;

  if( msBuildPath3( szReturnPath, abs_path, path1, path2 ) == NULL )
    return NULL;

  fp = VSIFOpenL( szReturnPath, "r" );
  if( fp == NULL ) {
    strlcpy( szReturnPath, path2, MS_MAXPATHLEN);
    return NULL;
  } else
    VSIFCloseL( fp );

  return szReturnPath;
}

/*
** Splits a string into multiple strings based on ch. Consecutive ch's are ignored.
*/
char **msStringSplit(const char *string, char ch, int *num_tokens)
{
  int i,j,k;
  int length,n;
  char **token;
  char last_ch='\0';

  n = 1; /* always at least 1 token, the string itself */
  length = strlen(string);
  for(i=0; i<length; i++) {
    if(string[i] == ch && last_ch != ch)
      n++;
    last_ch = string[i];
  }

  token = (char **) msSmallMalloc(sizeof(char *)*n);

  k = 0;
  token[k] = (char *)msSmallMalloc(sizeof(char)*(length+1));

  j = 0;
  last_ch='\0';
  for(i=0; i<length; i++) {
    if(string[i] == ch) {

      if(last_ch == ch)
        continue;

      token[k][j] = '\0'; /* terminate current token */

      k++;
      token[k] = (char *)msSmallMalloc(sizeof(char)*(length+1));

      j = 0;
    } else {
      token[k][j] = string[i];
      j++;
    }

    last_ch = string[i];
  }

  token[k][j] = '\0'; /* terminate last token */

  *num_tokens = n;

  return(token);
}

std::vector<std::string> msStringSplit(const char *string, char ch)
{
    int num_tokens = 0;
    char** tmp = msStringSplit(string, ch, &num_tokens);
    std::vector<std::string> res;
    res.reserve(num_tokens);
    for( int i = 0; i < num_tokens; i++ )
        res.push_back(tmp[i]);
    msFreeCharArray(tmp, num_tokens);
    return res;
}

/*
 This function is a copy of CSLTokenizeString2() function of the CPL component.
 See the port/cpl_string.cpp file in gdal source for the complete documentation.
 Available Flags:
 * - MS_ALLOWEMPTYTOKENS: allow the return of empty tokens when two
 * delimiters in a row occur with no other text between them.  If not set,
 * empty tokens will be discarded;
 * - MS_STRIPLEADSPACES: strip leading space characters from the token (as
 * reported by isspace());
 * - MS_STRIPENDSPACES: strip ending space characters from the token (as
 * reported by isspace());
 * - MS_HONOURSTRINGS: double quotes can be used to hold values that should
 * not be broken into multiple tokens;
 * - MS_PRESERVEQUOTES: string quotes are carried into the tokens when this
 * is set, otherwise they are removed;
 * - MS_PRESERVEESCAPES: if set backslash escapes (for backslash itself,
 * and for literal double quotes) will be preserved in the tokens, otherwise
 * the backslashes will be removed in processing.
 */
char ** msStringSplitComplex( const char * pszString,
                              const char * pszDelimiters,
                              int *num_tokens,
                              int nFlags )

{
  char        **papszRetList = NULL;
  int         nRetMax = 0, nRetLen = 0;
  char        *pszToken;
  int         nTokenMax, nTokenLen;
  int         bHonourStrings = (nFlags & MS_HONOURSTRINGS);
  int         bAllowEmptyTokens = (nFlags & MS_ALLOWEMPTYTOKENS);
  int         bStripLeadSpaces = (nFlags & MS_STRIPLEADSPACES);
  int         bStripEndSpaces = (nFlags & MS_STRIPENDSPACES);

  pszToken = (char *) msSmallMalloc(sizeof(char)*10);;
  nTokenMax = 10;

  while( pszString != NULL && *pszString != '\0' ) {
    int     bInString = MS_FALSE;
    int     bStartString = MS_TRUE;

    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( nFlags & MS_PRESERVEQUOTES ) {
          pszToken[nTokenLen] = *pszString;
          nTokenLen++;
        }

        if( bInString ) {
          bInString = MS_FALSE;
          continue;
        } else {
          bInString = MS_TRUE;
          continue;
        }
      }

      /*
       * Within string constants we allow for escaped quotes, but in
       * processing them we will unescape the quotes and \\ sequence
       * reduces to \
       */
      if( bInString && pszString[0] == '\\' ) {
        if ( pszString[1] == '"' || pszString[1] == '\\' ) {
          if( nFlags & MS_PRESERVEESCAPES ) {
            pszToken[nTokenLen] = *pszString;
            nTokenLen++;
          }

          pszString++;
        }
      }

      /*
       * Strip spaces at the token start if requested.
       */
      if ( !bInString && bStripLeadSpaces
           && bStartString && isspace((unsigned char)*pszString) )
        continue;

      bStartString = MS_FALSE;

      /*
       * Extend token buffer if we are running close to its end.
       */
      if( nTokenLen >= nTokenMax-3 ) {
        nTokenMax = nTokenMax * 2 + 10;
        pszToken = (char *) msSmallRealloc(pszToken, sizeof(char)*nTokenMax);
      }

      pszToken[nTokenLen] = *pszString;
      nTokenLen++;
    }

    /*
     * Strip spaces at the token end if requested.
     */
    if ( !bInString && bStripEndSpaces ) {
      while ( nTokenLen && isspace((unsigned char)pszToken[nTokenLen - 1]) )
        nTokenLen--;
    }

    pszToken[nTokenLen] = '\0';

    /*
     * Add the token.
     */
    if( pszToken[0] != '\0' || bAllowEmptyTokens ) {
      if( nRetLen >= nRetMax - 1 ) {
        nRetMax = nRetMax * 2 + 10;
        papszRetList = (char **) msSmallRealloc(papszRetList, sizeof(char*)*nRetMax);
      }

      papszRetList[nRetLen++] = msStrdup( pszToken );
      papszRetList[nRetLen] = NULL;
    }
  }

  /*
   * If the last token was empty, then we need to capture
   * it now, as the loop would skip it.
   */
  if( pszString != NULL && *pszString == '\0' && bAllowEmptyTokens && nRetLen > 0
      && strchr(pszDelimiters,*(pszString-1)) != NULL ) {
    if( nRetLen >= nRetMax - 1 ) {
      nRetMax = nRetMax * 2 + 10;
      papszRetList = (char **) msSmallRealloc(papszRetList, sizeof(char*)*nRetMax);
    }

    papszRetList[nRetLen++] = msStrdup("");
    papszRetList[nRetLen] = NULL;
  }

  if( papszRetList == NULL )
    papszRetList = (char **) msSmallMalloc(sizeof(char *)*1);

  *num_tokens = nRetLen;
  free(pszToken);

  return papszRetList;
}

/* This method is similar to msStringSplit but support quoted strings.
   It also support multi-characters delimiter and allows to preserve quotes */
char **msStringTokenize( const char *pszLine, const char *pszDelim,
                         int *num_tokens, int preserve_quote )
{
  char **papszResult = NULL;
  int n = 1, iChar, nLength = strlen(pszLine), iTokenChar = 0, bInQuotes = MS_FALSE;
  char *pszToken = (char *) msSmallMalloc(sizeof(char)*(nLength+1));
  int nDelimLen = strlen(pszDelim);

  /* Compute the number of tokens */
  for( iChar = 0; pszLine[iChar] != '\0'; iChar++ ) {
    if( bInQuotes && pszLine[iChar] == '"' && pszLine[iChar+1] == '"' ) {
      iChar++;
    } else if( pszLine[iChar] == '"' ) {
      bInQuotes = !bInQuotes;
    } else if ( !bInQuotes && strncmp(pszLine+iChar,pszDelim,nDelimLen) == 0 ) {
      iChar += nDelimLen - 1;
      n++;
    }
  }

  papszResult = (char **) msSmallMalloc(sizeof(char *)*n);
  n = iTokenChar = bInQuotes = 0;
  for( iChar = 0; pszLine[iChar] != '\0'; iChar++ ) {
    if( bInQuotes && pszLine[iChar] == '"' && pszLine[iChar+1] == '"' ) {
      if (preserve_quote == MS_TRUE)
        pszToken[iTokenChar++] = '"';
      pszToken[iTokenChar++] = '"';
      iChar++;
    } else if( pszLine[iChar] == '"' ) {
      if (preserve_quote == MS_TRUE)
        pszToken[iTokenChar++] = '"';
      bInQuotes = !bInQuotes;
    } else if( !bInQuotes && strncmp(pszLine+iChar,pszDelim,nDelimLen) == 0 ) {
      pszToken[iTokenChar++] = '\0';
      papszResult[n] = pszToken;
      pszToken = (char *) msSmallMalloc(sizeof(char)*(nLength+1));
      iChar += nDelimLen - 1;
      iTokenChar = 0;
      n++;
    } else {
      pszToken[iTokenChar++] = pszLine[iChar];
    }
  }

  pszToken[iTokenChar++] = '\0';
  papszResult[n] = pszToken;

  *num_tokens = n+1;

  return papszResult;
}

/**********************************************************************
 *                       msEncodeChar()
 *
 * Return 1 if the character argument should be encoded for safety
 * in URL use and 0 otherwise. Specific character map taken from
 * http://www.ietf.org/rfc/rfc2396.txt
 *
 **********************************************************************/

int msEncodeChar(const char c)
{
  if (
    (c >= 0x61 && c <= 0x7A ) ||   /* Letters a-z */
    (c >= 0x41 && c <= 0x5A ) ||   /* Letters A-Z */
    (c >= 0x30 && c <= 0x39 ) ||   /* Numbers 0-9 */
    (c >= 0x27 && c <= 0x2A ) ||   /* * ' ( )     */
    (c >= 0x2D && c <= 0x2E ) ||   /* - .         */
    (c == 0x5F ) ||                /* _           */
    (c == 0x21 ) ||                /* !           */
    (c == 0x7E ) ) {               /* ~           */
    return(0);
  } else {
    return(1);
  }
}

char *msEncodeUrl(const char *data)
{
  /*
   * Delegate to msEncodeUrlExcept, with a null second argument
   * to render the except handling moot.
   */
  return(msEncodeUrlExcept(data, '\0'));
}

/**********************************************************************
 *                       msEncodeCharExcept()
 *
 * URL encoding, applies RFP2396 encoding to all characters
 * except the one exception character. An exception character
 * of '\0' implies no exception handling.
 *
 **********************************************************************/

char *msEncodeUrlExcept(const char *data, const char except)
{
  static const char *hex = "0123456789ABCDEF";
  const char *i;
  char  *j, *code;
  int   inc;
  unsigned char ch;

  for (inc=0, i=data; *i!='\0'; i++)
    if (msEncodeChar(*i))
      inc += 2;

  code = (char*)msSmallMalloc(strlen(data)+inc+1);

  for (j=code, i=data; *i!='\0'; i++, j++) {
    if ( except != '\0' && *i == except ) {
      *j = except;
    } else if (msEncodeChar(*i)) {
      ch = *i;
      *j++ = '%';
      *j++ = hex[ch/16];
      *j   = hex[ch%16];
    } else
      *j = *i;
  }
  *j = '\0';

  return code;
}

/************************************************************************/
/*                            msEscapeJSonString()                      */
/************************************************************************/

/* The input (and output) string are not supposed to start/end with double */
/* quote characters. It is the responsibility of the caller to do that. */
char* msEscapeJSonString(const char* pszJSonString)
{
    /* Worst case is one character to become \uABCD so 6 characters */
    char* pszRet;
    int i = 0, j = 0;
    static const char* pszHex = "0123456789ABCDEF";
    
    pszRet = (char*) msSmallMalloc(strlen(pszJSonString) * 6 + 1);
    /* From http://www.json.org/ */
    for(i = 0; pszJSonString[i] != '\0'; i++)
    {
        unsigned char ch = pszJSonString[i];
        if( ch == '\b' )
        {
            pszRet[j++] = '\\';
            pszRet[j++] = 'b';
        }
        else if( ch == '\f' )
        {
            pszRet[j++] = '\\';
            pszRet[j++] = 'f';
        }
        else if( ch == '\n' )
        {
            pszRet[j++] = '\\';
            pszRet[j++] = 'n';
        }
        else if( ch == '\r' )
        {
            pszRet[j++] = '\\';
            pszRet[j++] = 'r';
        }
        else if( ch == '\t' )
        {
            pszRet[j++] = '\\';
            pszRet[j++] = 't';
        }
        else if( ch < 32 )
        {
            pszRet[j++] = '\\';
            pszRet[j++] = 'u';
            pszRet[j++] = '0';
            pszRet[j++] = '0';
            pszRet[j++] = pszHex[ch / 16];
            pszRet[j++] = pszHex[ch % 16];
        }
        else if( ch == '"' )
        {
            pszRet[j++] = '\\';
            pszRet[j++] = '"';
        }
        else if( ch == '\\' )
        {
            pszRet[j++] = '\\';
            pszRet[j++] = '\\';
        }
        else
        {
            pszRet[j++] = ch;
        }
    }
    pszRet[j] = '\0';
    return pszRet;
}

/* msEncodeHTMLEntities()
**
** Return a copy of string after replacing some problematic chars with their
** HTML entity equivalents.
**
** The replacements performed are:
**  '&' -> "&amp;", '"' -> "&quot;", '<' -> "&lt;" and '>' -> "&gt;"
**/
char *msEncodeHTMLEntities(const char *string)
{
  int buflen, i;
  char *newstring;
  const char *c;

  if(string == NULL)
    return NULL;

  /* Start with 100 extra chars for replacements...  */
  /* should be good enough for most cases */
  buflen = strlen(string) + 100;
  newstring = (char*)malloc(buflen+1);
  MS_CHECK_ALLOC(newstring, buflen+1, NULL);

  for(i=0, c=string; *c != '\0'; c++) {
    /* Need to realloc buffer? */
    if (i+6 > buflen) {
      /* If we had to realloc then this string must contain several */
      /* entities... so let's go with twice the previous buffer size */
      buflen *= 2;
      /* cppcheck-suppress memleakOnRealloc */
      newstring = (char*)realloc(newstring, buflen+1);
      MS_CHECK_ALLOC(newstring, buflen+1, NULL);
    }

    switch(*c) {
      case '&':
        strcpy(newstring+i, "&amp;");
        i += 5;
        break;
      case '<':
        strcpy(newstring+i, "&lt;");
        i += 4;
        break;
      case '>':
        strcpy(newstring+i, "&gt;");
        i += 4;
        break;
      case '"':
        strcpy(newstring+i, "&quot;");
        i += 6;
        break;
      case '\'':
        strcpy(newstring+i, "&#39;"); /* changed from &apos; and i += 6 (bug 1040) */
        i += 5;
        break;
      default:
        newstring[i++] = *c;
    }
  }

  newstring[i++] = '\0';

  return newstring;
}


/* msDecodeHTMLEntities()
**
** Modify the string to replace encoded characters by their true value
**
** The replacements performed are:
**  "&amp;" -> '&', "&quot;" -> '"', "&lt;" -> '<' and "&gt;" -> '>'
**/
void msDecodeHTMLEntities(const char *string)
{
  char *pszAmp=NULL, *pszSemiColon=NULL, *pszReplace=NULL, *pszEnd=NULL;
  char *pszBuffer=NULL;
  size_t bufferSize = 0;

  if(string == NULL)
    return;
  else
    pszBuffer = (char*)string;

  bufferSize = strlen(pszBuffer);
  pszReplace = (char*) msSmallMalloc(bufferSize+1);
  pszEnd = (char*) msSmallMalloc(bufferSize+1);

  while((pszAmp = strchr(pszBuffer, '&')) != NULL) {
    /* Get the &...; */
    strlcpy(pszReplace, pszAmp, bufferSize);
    pszSemiColon = strchr(pszReplace, ';');
    if(pszSemiColon == NULL)
      break;
    else
      pszSemiColon++;

    /* Get everything after the &...; */
    strlcpy(pszEnd, pszSemiColon, bufferSize);

    pszReplace[pszSemiColon-pszReplace] = '\0';

    /* Replace the &...; */
    if(strcasecmp(pszReplace, "&amp;") == 0) {
      pszBuffer[pszAmp - pszBuffer] = '&';
      pszBuffer[pszAmp - pszBuffer + 1] = '\0';
      strcat(pszBuffer, pszEnd);
    } else if(strcasecmp(pszReplace, "&lt;") == 0) {
      pszBuffer[pszAmp - pszBuffer] = '<';
      pszBuffer[pszAmp - pszBuffer + 1] = '\0';
      strcat(pszBuffer, pszEnd);
    } else if(strcasecmp(pszReplace, "&gt;") == 0) {
      pszBuffer[pszAmp - pszBuffer] = '>';
      pszBuffer[pszAmp - pszBuffer + 1] = '\0';
      strcat(pszBuffer, pszEnd);
    } else if(strcasecmp(pszReplace, "&quot;") == 0) {
      pszBuffer[pszAmp - pszBuffer] = '"';
      pszBuffer[pszAmp - pszBuffer + 1] = '\0';
      strcat(pszBuffer, pszEnd);
    } else if(strcasecmp(pszReplace, "&apos;") == 0) {
      pszBuffer[pszAmp - pszBuffer] = '\'';
      pszBuffer[pszAmp - pszBuffer + 1] = '\0';
      strcat(pszBuffer, pszEnd);
    }

    pszBuffer = pszAmp + 1;
  }

  free(pszReplace);
  free(pszEnd);

  return;
}

/*
** msIsXMLValid
**
** Check if the string is an XML valid string. It should contains only
** A-Z, a-z, 0-9, '_', '-', '.', and ':'
** Return MS_TRUE or MS_FALSE
*/
int msIsXMLTagValid(const char *string)
{
  int i, nLen;

  nLen = strlen(string);

  for(i=0; i<nLen; i++) {
    if( !( string[i] >= 'A' && string[i] <= 'Z' ) &&
        !( string[i] >= 'a' && string[i] <= 'z' ) &&
        !( string[i] >= '0' && string[i] <= '9' ) &&
        string[i] != '-' && string[i] != '.' &&
        string[i] != ':' && string[i] != '_' )
      return MS_FALSE;
  }

  return MS_TRUE;
}


/*
 * Concatenate pszSrc to pszDest and reallocate memory if necessary.
*/
char *msStringConcatenate(char *pszDest, const char *pszSrc)
{
  int nLen;

  if (pszSrc == NULL)
    return pszDest;

  /* if destination is null, allocate memory */
  if (pszDest == NULL) {
    pszDest = msStrdup(pszSrc);
  } else { /* if dest is not null, reallocate memory */
    char *pszTemp;

    nLen = strlen(pszDest) + strlen(pszSrc);

    pszTemp = (char*)realloc(pszDest, nLen + 1);
    if (pszTemp) {
      pszDest = pszTemp;
      strcat(pszDest, pszSrc);
      pszDest[nLen] = '\0';
    } else {
      msSetError(MS_MEMERR, "Error while reallocating memory.", "msStringConcatenate()");
      return NULL;
    }
  }

  return pszDest;
}

char *msJoinStrings(char **array, int arrayLength, const char *delimeter)
{
  char *string;
  int stringLength=0;
  int delimeterLength;
  int i;

  if(!array || arrayLength <= 0 || !delimeter) return NULL;

  delimeterLength = strlen(delimeter);

  for(i=0; i<arrayLength; i++)
    stringLength += strlen(array[i]) + delimeterLength;

  string = (char *)calloc(stringLength+1, sizeof(char));
  MS_CHECK_ALLOC(string, (stringLength+1)* sizeof(char), NULL);
  string[0] = '\0';

  for(i=0; i<arrayLength-1; i++) {
    strlcat(string, array[i], stringLength);
    strlcat(string, delimeter, stringLength);
  }
  strlcat(string, array[i], stringLength); /* add last element, no delimiter */

  return string;
}

#define HASH_SIZE  16
/*
 * Return a hashed string for a given input string.
 * The caller should free the return value.
*/
char *msHashString(const char *pszStr)
{
  unsigned char sums[HASH_SIZE] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  char *pszOutBuf = NULL;
  size_t bufferSize = 0;
  int i=0;

  bufferSize = HASH_SIZE*2+1;
  pszOutBuf = (char*)msSmallMalloc(bufferSize);

  for(i=0; pszStr && pszStr[i]; i++) {
    sums[i%HASH_SIZE] += (unsigned char)(pszStr[i]);
  }

  for(i=0; i<HASH_SIZE; i++) {
    snprintf(pszOutBuf + i*2, bufferSize-(i*2), "%02x", sums[i]);
  }

  return pszOutBuf;
}

char *msCommifyString(char *str)
{
  int i, j, old_length, new_length;
  int num_commas=0, num_decimal_points=0;
  int add_commas;

  char comma=',', decimal_point='.';

  if(!str) return NULL;

  num_decimal_points = msCountChars(str, decimal_point);
  if(num_decimal_points > 1) return str;

  old_length = strlen(str);
  if(num_decimal_points == 0) {
    num_commas =  floor((old_length - 1)/3);
    add_commas=1; /* add commas right away */
  } else {
    num_commas = floor(((old_length - strlen(strchr(str, decimal_point))) - 1)/3);
    add_commas=0; /* wait until after the decimal point */
  }

  if(num_commas < 1) return str; /* nothing to add */

  new_length = old_length + num_commas;
  str = (char *) msSmallRealloc(str, new_length+1);
  str[new_length] = '\0';

  j = 0;
  for(i=new_length-1; i>=0; i--) { /* step backwards through the string */

    if(num_decimal_points == 1 &&  add_commas == 0) { /* to the right of the decimal point, no commas */
      str[i] = str[i-num_commas];
      if(str[i] == decimal_point) add_commas = 1;
    } else if(add_commas == 1 && j>2) { /* need a comma */
      str[i] = comma;
      num_commas--; /* need one fewer now */
      j = 0; /* reset */
    } else {
      str[i] = str[i-num_commas]; /* shift to the right */
      j++;
    }

    if(num_commas == 0) break; /* done, rest of string is ok "as is" */
  }

  return str;
}


/* ------------------------------------------------------------------------------- */
/*       Replace all occurrences of old with new in str.                           */
/*       It is assumed that str was dynamically created using malloc.              */
/*       Same function as msReplaceSubstring but this is case insensitive          */
/* ------------------------------------------------------------------------------- */
char *msCaseReplaceSubstring(char *str, const char *old, const char *newstr)
{
  size_t str_len, old_len, new_len, tmp_offset;
  char *tmp_ptr;

  /*
  ** If old is not found then leave str alone
  */
  if( (tmp_ptr = (char *) strcasestr(str, old)) == NULL)
    return(str);
  
  if(newstr == NULL)
    newstr = "";


  /*
  ** Grab some info about incoming strings
  */
  str_len = strlen(str);
  old_len = strlen(old);
  new_len = strlen(newstr);

  /*
  ** Now loop until old is NOT found in new
  */
  while( tmp_ptr != NULL ) {

    /*
    ** re-allocate memory for buf assuming 1 replacement of old with new
          ** don't bother reallocating if old is larger than new)
    */
    if (old_len < new_len) {
      tmp_offset = tmp_ptr - str;
      str_len = str_len - old_len + new_len;
      str = (char *)msSmallRealloc(str, (str_len + 1)); /* make new space for a copy */
      tmp_ptr = str + tmp_offset;
    }

    /*
    ** Move the trailing part of str to make some room unless old_len == new_len
    */
    if (old_len != new_len) {
      memmove(tmp_ptr+new_len, tmp_ptr+old_len, strlen(tmp_ptr)-old_len+1);
    }

    /*
    ** Now copy new over old
    */
    memcpy(tmp_ptr, newstr, new_len);

    /*
    ** And look for more matches in the rest of the string
    */
    tmp_ptr = (char *) strcasestr(tmp_ptr + new_len, old);
  }

  return(str);
}

/*
** Converts a 2 character hexidecimal string to an integer.
*/
int msHexToInt(char *hex)
{
  int number;

  number = (hex[0] >= 'A' ? ((hex[0] & 0xdf) - 'A')+10 : (hex[0] - '0'));
  number *= 16;
  number += (hex[1] >= 'A' ? ((hex[1] & 0xdf) - 'A')+10 : (hex[1] - '0'));

  return(number);
}


/*
** Use FRIBIDI to encode the string.
** The return value must be freed by the caller.
*/
#ifdef USE_FRIBIDI
char *msGetFriBidiEncodedString(const char *string, const char *encoding)
{
  FriBidiChar logical[MAX_STR_LEN];
  FriBidiParType base;
  size_t len;

#ifdef FRIBIDI_NO_CHARSETS
  iconv_t to_ucs4, from_ucs4;
#else
  FriBidiCharSet to_char_set_num;
  FriBidiCharSet from_char_set_num;
#endif

  len = strlen(string);

#ifdef FRIBIDI_NO_CHARSETS
  to_ucs4 = iconv_open ("WCHAR_T", encoding);
  from_ucs4 = iconv_open ("UTF-8", "WCHAR_T");
#else
  to_char_set_num = fribidi_parse_charset ((char*)encoding);
  from_char_set_num = fribidi_parse_charset ("UTF-8");
#endif

#ifdef FRIBIDI_NO_CHARSETS
  if (to_ucs4 == (iconv_t) (-1) || from_ucs4 == (iconv_t) (-1))
#else
  if (!to_char_set_num || !from_char_set_num)
#endif
  {
    msSetError(MS_IDENTERR, "Encoding not supported (%s).",
               "msGetFriBidiEncodedString()", encoding);
    return NULL;
  }

#ifdef FRIBIDI_NO_CHARSETS
  {
    char *st = string, *ust = (char *) logical;
    int in_len = (int) len;
    len = sizeof logical;
    iconv (to_ucs4, &st, &in_len, &ust, (int *) &len);
    len = (FriBidiChar *) ust - logical;
  }
#else
  len = fribidi_charset_to_unicode (to_char_set_num, (char*)string, len, logical);
#endif

  {
    FriBidiChar *visual;
    char outstring[MAX_STR_LEN];
    FriBidiStrIndex *ltov, *vtol;
    FriBidiLevel *levels;
    FriBidiStrIndex new_len;
    fribidi_boolean log2vis;

    visual = (FriBidiChar *) msSmallMalloc (sizeof (FriBidiChar) * (len + 1));
    ltov = NULL;
    vtol = NULL;
    levels = NULL;

    // fribidi_log2vis() doesn't support multi-line paragraphs.
    // See: https://lists.freedesktop.org/archives/fribidi/2008-January/000515.html
    for( size_t i = 0; i < len; i++ )
    {
        if (logical[i] == '\n') {
          msSetError(MS_IDENTERR,
                     "Input string is a multi-line paragraph, which is not supported.",
                     "msGetFriBidiEncodedString()");
          msFree(visual);
          return NULL;
        }
    }

    /* Create a bidi string. */
    log2vis = fribidi_log2vis (logical, len, &base,
                               /* output */
                               visual, ltov, vtol, levels);

    if (!log2vis) {
      msSetError(MS_IDENTERR, "Failed to create bidi string.",
                 "msGetFriBidiEncodedString()");
      msFree(visual);
      return NULL;
    }

    /* Convert it to utf-8 for display. */
#ifdef FRIBIDI_NO_CHARSETS
    {
      char *str = outstring, *ust = (char *) visual;
      int in_len = len * sizeof visual[0];
      new_len = sizeof outstring;
      iconv (from_ucs4, &ust, &in_len, &str, (int *) &new_len);
      *str = '\0';
      new_len = str - outstring;
    }
#else
    new_len =
      fribidi_unicode_to_charset (from_char_set_num,
                                  visual, len, outstring);

    /* scan str and compress out FRIBIDI_CHAR_FILL UTF8 characters */

    int j=0;
    for (int i=0; i<new_len; i++, j++) {
      if (outstring[i] == '\xef' && outstring[i+1] == '\xbb' && outstring[i+2] == '\xbf') {
        i += 3;
      }
      if (i != j) {
        outstring[j] = outstring[i];
      }
    }
    outstring[j] = '\0';

#endif

    msFree(visual);
    return msStrdup(outstring);
  }
}
#endif

/*
** Simple charset converter. Converts string from specified encoding to UTF-8.
** The return value must be freed by the caller.
*/
char *msGetEncodedString(const char *string, const char *encoding)
{
#ifdef USE_ICONV
  iconv_t cd = NULL;
  const char *inp;
  char *outp, *out = NULL;
  size_t len, bufsize, bufleft;
  assert(encoding);

#ifdef USE_FRIBIDI
  msAcquireLock(TLOCK_FRIBIDI);
  if(fribidi_parse_charset ((char*)encoding)) {
    char *ret = msGetFriBidiEncodedString(string, encoding);
    msReleaseLock(TLOCK_FRIBIDI);
    return ret;
  }
  msReleaseLock(TLOCK_FRIBIDI);
#endif
  len = strlen(string);

  if (len == 0 || strcasecmp(encoding, "UTF-8")==0)
    return msStrdup(string);    /* Nothing to do: string already in UTF-8 */

  cd = iconv_open("UTF-8", encoding);
  if(cd == (iconv_t)-1) {
    msSetError(MS_IDENTERR, "Encoding not supported by libiconv (%s).",
               "msGetEncodedString()", encoding);
    return NULL;
  }

  bufsize = len * 6 + 1; /* Each UTF-8 char can be up to 6 bytes */
  inp = string;
  out = (char*) malloc(bufsize);
  if(out == NULL) {
    msSetError(MS_MEMERR, NULL, "msGetEncodedString()");
    iconv_close(cd);
    return NULL;
  }
  strlcpy(out, string, bufsize);
  outp = out;

  bufleft = bufsize;

  while (len > 0) {
    const size_t iconv_status = msIconv(cd, (char**)&inp, &len, &outp, &bufleft);
    if(iconv_status == static_cast<size_t>(-1)) {
      msFree(out);
      iconv_close(cd);
      return msStrdup(string);
    }
  }
  out[bufsize - bufleft] = '\0';

  iconv_close(cd);

  return out;
#else
  if (*string == '\0' || (encoding && strcasecmp(encoding, "UTF-8")==0))
    return msStrdup(string);    /* Nothing to do: string already in UTF-8 */

  msSetError(MS_MISCERR, "Not implemeted since Iconv is not enabled.", "msGetEncodedString()");
  return NULL;
#endif
}


char* msConvertWideStringToUTF8 (const wchar_t* string, const char* encoding)
{
#ifdef USE_ICONV

  char* output = NULL;
  const char* errormessage = NULL;
  iconv_t cd = NULL;
  size_t nStr;
  size_t nInSize;
  size_t nOutSize;
  size_t nBufferSize;

  char* pszUTF8 = NULL;
  const wchar_t* pwszWide = NULL;

  if (string != NULL) {
    nStr = wcslen (string);
    nBufferSize = ((nStr * 6) + 1);
    output = (char*) msSmallMalloc (nBufferSize);

    if (nStr == 0) {
      /* return an empty 8 byte string */
      output[0] = '\0';
      return output;
    }

    cd = iconv_open("UTF-8", encoding);

    nOutSize = nBufferSize;
    if ((iconv_t)-1 != cd) {
      nInSize = sizeof (wchar_t)*nStr;
      pszUTF8 = output;
      pwszWide = string;
      size_t iconv_status = msIconv(cd, (char **)&pwszWide, &nInSize, &pszUTF8, &nOutSize);
      if ((size_t)-1 == iconv_status) {
        switch (errno) {
          case E2BIG:
            errormessage = "There is not sufficient room in buffer";
            break;
          case EILSEQ:
            errormessage = "An invalid multibyte sequence has been encountered in the input";
            break;
          case EINVAL:
            errormessage = "An incomplete multibyte sequence has been encountered in the input";
            break;
          default:
            errormessage = "Unknown";
            break;
        }
        msSetError(MS_MISCERR, "Unable to convert string in encoding '%s' to UTF8 %s",
                   "msConvertWideStringToUTF8()",
                   encoding,errormessage);
        iconv_close(cd);
        msFree(output);
        return NULL;
      }
      iconv_close(cd);
    } else {
      msSetError(MS_MISCERR, "Encoding not supported by libiconv (%s).",
                 "msConvertWideStringToUTF8()",
                 encoding);
      msFree(output);
      return NULL;
    }

  } else {
    /* we were given a NULL wide string, nothing we can do here */
    return NULL;
  }

  /* NULL-terminate the output string */
  output[nBufferSize - nOutSize] = '\0';
  return output;
#else
  msSetError(MS_MISCERR, "Not implemented since Iconv is not enabled.", "msConvertWideStringToUTF8()");
  return NULL;
#endif
}

wchar_t* msConvertWideStringFromUTF8 (const char* string, const char* encoding)
{
#ifdef USE_ICONV
  wchar_t* output = NULL;
  const char* errormessage = NULL;
  iconv_t cd = NULL;
  size_t nStr;
  size_t nInSize;
  size_t nOutSize;
  size_t nBufferSize;

  const char* pszUTF8 = NULL;
  wchar_t* pwszWide = NULL;

  if (string != NULL) {
    nStr = strlen (string);
    nBufferSize = ((nStr * 6) + 1);
    output = (wchar_t*) msSmallMalloc (nBufferSize);

    if (nStr == 0) {
      /* return an empty 8 byte string */
      output[0] = '\0';
      return output;
    }

    cd = iconv_open(encoding, "UTF-8");

    nOutSize = nBufferSize;
    if ((iconv_t)-1 != cd) {
      nInSize = sizeof (char)*nStr;
      pszUTF8 = string;
      pwszWide = output;
      size_t iconv_status = msIconv(cd, (char **)&pszUTF8, &nInSize, (char **)&pwszWide, &nOutSize);
      if ((size_t)-1 == iconv_status) {
        switch (errno) {
          case E2BIG:
            errormessage = "There is not sufficient room in buffer";
            break;
          case EILSEQ:
            errormessage = "An invalid multibyte sequence has been encountered in the input";
            break;
          case EINVAL:
            errormessage = "An incomplete multibyte sequence has been encountered in the input";
            break;
          default:
            errormessage = "Unknown";
            break;
        }
        msSetError(MS_MISCERR, "Unable to convert string in UTF8 to encoding '%s' %s",
                   "msConvertWideStringFromUTF8()",
                   encoding, errormessage);
        iconv_close(cd);
        msFree(output);
        return NULL;
      }
      iconv_close(cd);
    } else {
      msSetError(MS_MISCERR, "Encoding not supported by libiconv (%s).",
                 "msConvertWideStringFromUTF8()",
                 encoding);
      msFree(output);
      return NULL;
    }
  } else {
    /* we were given a NULL wide string, nothing we can do here */
    return NULL;
  }

  /* NULL-terminate the output string */
  if (nOutSize >= sizeof (wchar_t))
    *((wchar_t *) pwszWide) = L'\0';

  return output;
#else
  msSetError(MS_MISCERR, "Not implemented since Iconv is not enabled.", "msConvertWideStringFromUTF8()");
  return NULL;
#endif
}

/*
** Returns the next glyph in string and advances *in_ptr to the next
** character.
**
** If out_string is not NULL then the character (bytes) is copied to this
** buffer and null-terminated. out_string must be a pre-allocated buffer of
** at least 11 bytes.
**
** The function returns the number of bytes in this glyph.
**
** This function treats 3 types of glyph encodings:
*   - as an html entity, for example &#123; , &#x1af; , or &eacute;
*   - as an utf8 encoded character
*   - if utf8 decoding fails, as a raw character
*
** This function mimics the character decoding function used in gdft.c of
* libGD. It is necessary to have the same behaviour, as input strings must be
* split into the same glyphs as what gd does.
**
** In UTF-8, the number of leading 1 bits in the first byte specifies the
** number of bytes in the entire sequence.
** Source: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
**
** U-00000000 U-0000007F: 0xxxxxxx
** U-00000080 U-000007FF: 110xxxxx 10xxxxxx
** U-00000800 U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
** U-00010000 U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
** U-00200000 U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
** U-04000000 U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
*/
int msGetNextGlyph(const char **in_ptr, char *out_string)
{
  unsigned char in;
  int numbytes=0;
  unsigned int unicode;
  int i;

  in = (unsigned char)**in_ptr;

  if (in == 0)
    return -1;  /* Empty string */
  if((numbytes=msGetUnicodeEntity(*in_ptr,&unicode))>0) {
    if(out_string) {
      for(i=0; i<numbytes; i++) {
        out_string[i]=(*in_ptr)[i];
      }
      out_string[numbytes]='\0';
    }
    *in_ptr+=numbytes;
    return numbytes;
  }
  if (in < 0xC0) {
    /*
    * Handles properly formed UTF-8 characters between
    * 0x01 and 0x7F.  Also treats \0 and naked trail
    * bytes 0x80 to 0xBF as valid characters representing
    * themselves.
    */
    /*goto end of loop to return just the char*/
  } else if (in < 0xE0) {
    if (((*in_ptr)[1]& 0xC0) == 0x80) {
      if(out_string) {
        out_string[0]=in;
        out_string[1]=(*in_ptr)[1];
        out_string[2]='\0';
      }
      *in_ptr+=2;
      return 2; /*110xxxxx 10xxxxxx*/
    }
  } else if (in < 0xF0) {
    if (((*in_ptr)[1]& 0xC0) == 0x80 && ((*in_ptr)[2]& 0xC0) == 0x80) {
      if(out_string) {
        out_string[0]=in;
        *in_ptr+=numbytes;
        out_string[1]=(*in_ptr)[1];
        out_string[2]=(*in_ptr)[2];
        out_string[3]='\0';
      }
      *in_ptr+=3;
      return 3;   /* 1110xxxx 10xxxxxx 10xxxxxx */
    }
  } else if (in < 0xF8) {
    if (((*in_ptr)[1]& 0xC0) == 0x80 && ((*in_ptr)[2]& 0xC0) == 0x80
        && ((*in_ptr)[3]& 0xC0) == 0x80) {
      if(out_string) {
        out_string[0]=in;
        out_string[1]=(*in_ptr)[1];
        out_string[2]=(*in_ptr)[2];
        out_string[3]=(*in_ptr)[3];
        out_string[4]='\0';
      }
      *in_ptr+=4;
      return 4;   /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
    }
  } else if (in < 0xFC) {
    if (((*in_ptr)[1]& 0xC0) == 0x80 && ((*in_ptr)[2]& 0xC0) == 0x80
        && ((*in_ptr)[3]& 0xC0) == 0x80 && ((*in_ptr)[4]& 0xC0) == 0x80) {
      if(out_string) {
        out_string[0]=in;
        out_string[1]=(*in_ptr)[1];
        out_string[2]=(*in_ptr)[2];
        out_string[3]=(*in_ptr)[3];
        out_string[4]=(*in_ptr)[4];
        out_string[5]='\0';
      }
      *in_ptr+=5;
      return 5;   /* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */
    }
  } else if (in < 0xFE) {
    if (((*in_ptr)[1]& 0xC0) == 0x80 && ((*in_ptr)[2]& 0xC0) == 0x80
        && ((*in_ptr)[3]& 0xC0) == 0x80 && ((*in_ptr)[4]& 0xC0) == 0x80
        && ((*in_ptr)[5]& 0xC0) == 0x80) {
      if(out_string) {
        out_string[0]=in;
        out_string[1]=(*in_ptr)[1];
        out_string[2]=(*in_ptr)[2];
        out_string[3]=(*in_ptr)[3];
        out_string[4]=(*in_ptr)[4];
        out_string[5]=(*in_ptr)[5];
        out_string[6]='\0';
      }
      *in_ptr+=6;
      return 6;   /* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */
    }
  }

  if (out_string) {
    out_string[0]=in;
    out_string[1] = '\0';   /* 0xxxxxxx */
  }
  (*in_ptr)++;
  return 1;
}

/*
** Returns the number of glyphs in string
*/
int msGetNumGlyphs(const char *in_ptr)
{
  int numchars=0;

  while( msGetNextGlyph(&in_ptr, NULL) != -1 )
    numchars++;

  return numchars;
}

static int cmp_entities(const void *e1, const void *e2)
{
  struct mapentities_s *en1 = (struct mapentities_s *) e1;
  struct mapentities_s *en2 = (struct mapentities_s *) e2;
  return strcmp(en1->name, en2->name);
}
/*
 * this function tests if the string pointed by inptr represents
 * an HTML entity, in decimal form ( e.g. &#197;), in hexadecimal
 * form ( e.g. &#x6C34; ), or from html 4.0 spec ( e.g. &eacute; )
 * - returns returns 0 if the string doesn't represent such an entity.
 * - if the string does start with such entity,it returns the number of
 * bytes occupied by said entity, and stores the unicode value in *unicode
 */
int msGetUnicodeEntity(const char *inptr, unsigned int *unicode)
{
  unsigned char *in = (unsigned char*)inptr;
  int l,val=0;
  if(*in=='&') {
    in++;
    if(*in=='#') {
      in++;
      if(*in=='x'||*in=='X') {
        in++;
        for(l=3; l<8; l++) {
          char byte;
          if(*in>='0'&&*in<='9')
            byte = *in - '0';
          else if(*in>='a'&&*in<='f')
            byte = *in - 'a' + 10;
          else if(*in>='A'&&*in<='F')
            byte = *in - 'A' + 10;
          else
            break;
          in++;
          val = (val * 16) + byte;
        }
        if(*in==';' && l>3 ) {
          *unicode=val;
          return ++l;
        }
      } else {
        for(l=2; l<8; l++) {
          if(*in>='0'&&*in<='9') {
            val = val*10+*in-'0';
            in++;
          } else
            break;
        }
        if(*in==';' && l>2 ) {
          *unicode=val;
          return ++l;
        }
      }
    } else {
      char entity_name_buf[MAP_ENTITY_NAME_LENGTH_MAX+1];
      char *p;
      struct mapentities_s key;
      key.name = p = entity_name_buf;
      for (l = 1; l <=  MAP_ENTITY_NAME_LENGTH_MAX+1; l++) {
        if (*in == '\0') /*end of string before possible entity: return*/
          break;
        if (*in == ';') { /*possible end of entity: do a lookup*/
          *p++ = '\0';
          const struct mapentities_s* res =
            static_cast<const struct mapentities_s*>(bsearch(
                &key,
                mapentities,
                MAP_NR_OF_ENTITIES,
                sizeof(mapentities[0]), cmp_entities));
          if (res) {
            *unicode = res->value;
            return ++l;
          }
          break; /*the string was of the form of an entity but didn't correspond to an existing one: return*/
        }
        *p++ = *in;
        in++;
      }
    }
  }
  return 0;
}

/**
 * msStringIsInteger()
 *
 * determines whether a given string is an integer
 *
 * @param string the string to be tested
 *
 * @return MS_SUCCESS or MS_FAILURE
 */

int msStringIsInteger(const char *string)
{
  int length, i;

  length = strlen(string);

  if (length == 0)
    return MS_FAILURE;

  for(i=0; i<length; i++) {
    if (!isdigit(string[i]))
      return MS_FAILURE;
  }

  return MS_SUCCESS;
}

/************************************************************************/
/*                             msStrdup()                               */
/************************************************************************/

/* Safe version of msStrdup(). This function is taken from gdal/cpl. */

char *msStrdup(const char * pszString)
{
    size_t nStringLength; 
    char *pszReturn;

    if (pszString == NULL)
        pszString = "";

    nStringLength = strlen(pszString) + 1; /* null terminated byte */
    pszReturn = static_cast<char*>(malloc(nStringLength));

    if (pszReturn == NULL) {
        fprintf(stderr, "msSmallMalloc(): Out of memory allocating %ld bytes.\n",
            (long)strlen(pszString));
        exit(1);
    }

    memcpy(pszReturn, pszString, nStringLength);

    return pszReturn;
}


/************************************************************************/
/*                             msStringEscape()                         */
/************************************************************************/

/* Checks if a string contains single or double quotes and escape them.
   NOTE: the user must free the returned char* if it is different than the
   one passed in */

char* msStringEscape( const char * pszString )
{
  char *string_tmp, *string_ptr;
  int i,ncharstoescape=0;

  if (pszString ==  NULL || strlen(pszString) == 0)
    return msStrdup("");

  for (i=0; pszString[i]; i++)
    ncharstoescape += ((pszString[i] == '\"')||(pszString[i] == '\''));
  
  if(!ncharstoescape) {
    return (char*)pszString;
  }

  string_tmp = (char*)msSmallMalloc(strlen(pszString)+ncharstoescape+1);
  for (string_ptr=(char*)pszString,i=0; *string_ptr!='\0'; ++string_ptr,++i) {
    if ( (*string_ptr == '\"') || (*string_ptr == '\'') ) {
      string_tmp[i] = '\\';
      ++i;
    }
    string_tmp[i] = *string_ptr;
  }

  string_tmp[i] = '\0';
  return string_tmp;
}

std::string msStdStringEscape( const char * pszString )
{
  char* tmp = msStringEscape(pszString);
  std::string ret(tmp);
  if( tmp != pszString )
      msFree(tmp);
  return ret;
}

/************************************************************************/
/*                             msStringInArray()                        */
/************************************************************************/

/* Check if a string is in a array */
int msStringInArray( const char * pszString, char **array, int numelements)
{
  int i;
  for (i=0; i<numelements; ++i) {
    if (strcasecmp(pszString, array[i])==0)
      return MS_TRUE;
  }
  return MS_FALSE;
}

bool msStringInArray( const char * pszString, const std::vector<std::string>& array)
{
  for( const auto& str: array ) {
    if (strcasecmp(pszString, str.c_str())==0)
      return true;
  }
  return false;
}

int msLayerEncodeShapeAttributes( layerObj *layer, shapeObj *shape) {

#ifdef USE_ICONV
  iconv_t cd = NULL;
  const char *inp;
  char *outp, *out = NULL;
  size_t len, bufsize, bufleft;
  int i;

  if( !layer->encoding || !*layer->encoding || !strcasecmp(layer->encoding, "UTF-8"))
    return MS_SUCCESS;

  cd = iconv_open("UTF-8", layer->encoding);
  if(cd == (iconv_t)-1) {
    msSetError(MS_IDENTERR, "Encoding not supported by libiconv (%s).",
               "msGetEncodedString()", layer->encoding);
    return MS_FAILURE;
  }

  for(i=0;i <shape->numvalues; i++) {
    if(!shape->values[i] || (len = strlen(shape->values[i]))==0) {
      continue;    /* Nothing to do */
    }

    bufsize = len * 6 + 1; /* Each UTF-8 char can be up to 6 bytes */
    inp = shape->values[i];
    out = (char*) msSmallMalloc(bufsize);

    strlcpy(out, shape->values[i], bufsize);
    outp = out;

    bufleft = bufsize;

    bool failedIconv = false;
    while (len > 0) {
      const size_t iconv_status = msIconv(cd, (char**)&inp, &len, &outp, &bufleft);
      if(iconv_status == static_cast<size_t>(-1)) {
        failedIconv = true;
        break;
      }
    }
    if( failedIconv ) {
      msFree(out);
      continue; /* silently ignore failed conversions */
    }
    out[bufsize - bufleft] = '\0';
    msFree(shape->values[i]);
    shape->values[i] = out;
  }
  iconv_close(cd);

  return MS_SUCCESS;
#else
  if( !layer->encoding || !*layer->encoding || !strcasecmp(layer->encoding, "UTF-8"))
    return MS_SUCCESS;
  msSetError(MS_MISCERR, "Not implemented since Iconv is not enabled.", "msGetEncodedString()");
  return MS_FAILURE;
#endif
}

/************************************************************************/
/*                             msStringBuffer                           */
/************************************************************************/

struct msStringBuffer
{
    size_t alloc_size;
    size_t length;
    char  *str;
};

/************************************************************************/
/*                         msStringBufferAlloc()                        */
/************************************************************************/

msStringBuffer* msStringBufferAlloc(void)
{
    return (msStringBuffer*)msSmallCalloc(sizeof(msStringBuffer), 1);
}

/************************************************************************/
/*                         msStringBufferFree()                         */
/************************************************************************/

void msStringBufferFree(msStringBuffer* sb)
{
    if( sb )
        msFree(sb->str);
    msFree(sb);
}

/************************************************************************/
/*                       msStringBufferGetString()                      */
/************************************************************************/

const char* msStringBufferGetString(msStringBuffer* sb)
{
    return sb->str;
}

/************************************************************************/
/*                   msStringBufferReleaseStringAndFree()               */
/************************************************************************/

char* msStringBufferReleaseStringAndFree(msStringBuffer* sb)
{
    char* str = sb->str;
    sb->str = NULL;
    sb->alloc_size = 0;
    sb->length = 0;
    msStringBufferFree(sb);
    return str;
}

/************************************************************************/
/*                        msStringBufferAppend()                        */
/************************************************************************/

int msStringBufferAppend(msStringBuffer* sb, const char* pszAppendedString)
{
    size_t nAppendLen = strlen(pszAppendedString);
    if( sb->length + nAppendLen >= sb->alloc_size )
    {
        size_t newAllocSize1 = sb->alloc_size + sb->alloc_size / 3;
        size_t newAllocSize2 = sb->length + nAppendLen + 1;
        size_t newAllocSize = MAX(newAllocSize1, newAllocSize2);
        void* newStr = realloc(sb->str, newAllocSize);
        if( newStr == NULL ) {
            msSetError(MS_MEMERR, "Not enough memory", "msStringBufferAppend()");
            return MS_FAILURE;
        }
        sb->alloc_size = newAllocSize;
        sb->str = (char*) newStr;
    }
    memcpy(sb->str + sb->length, pszAppendedString, nAppendLen + 1);
    sb->length += nAppendLen;
    return MS_SUCCESS;
}
