Originally Posted By: Khaled
Anyone interested in coming up with a C function that does that? :-)

There really isn't much more to it than what you described. This is how I would do it, I guess..
Code:
#include <windows.h>

#define LINE_MAX 4149 // NOTE: this is excluding the terminating null character!

typedef enum {
  OK,
  INVALID_PARAMS_ERROR,
  LINE_TOO_LONG_ERROR
} myerr_t; // whatever

static __inline int char_equal(TCHAR c1, TCHAR c2, int cs)
{
  if (cs) return c1 == c2;

  return CharLower((LPTSTR) c1) == CharLower((LPTSTR) c2);
}

static ssize_t find_sub(TCHAR *buf, size_t buflen, char *map, const TCHAR *str, size_t len, size_t start, int cs)
{
  size_t i;
  ssize_t j;

  // go through the rest of the buffer, one position at a time
  for (i = start; i + len <= buflen; i++) {
    // see if we can match the substring here
    for (j = len - 1; j >= 0; j--) {
      // if any part is not original, skip to the start of the next original part
      // we're looping back-to-front so as to skip as much as possible in that case
      if (map[i + j]) {
        i += j;
        break;
      }
      // actually compare characters
      if (!char_equal(buf[i + j], str[j], cs)) break;
    }
    if (j < 0) return i; // a match; return the starting position
  }

  return -1; // no match
}

static ssize_t replacex_one(TCHAR *buf, size_t buflen, char *map, const TCHAR *substr, const TCHAR *newstr, int cs)
{
  size_t sublen, newlen;
  ssize_t pos, tail;

  sublen = lstrlen(substr);
  newlen = lstrlen(newstr);

  if (!sublen) return buflen; // can't replace nothing

  pos = 0;
  // repeatedly find the next all-original occurrence of the substring to replace
  while ((pos = find_sub(buf, buflen, map, substr, sublen, pos, cs)) >= 0) {
    // and replace it - at least, if the buffer is big enough
    if (buflen - sublen + newlen >= LINE_MAX)
     return -1;

    // move everything after the substring we found, if necessary
    tail = buflen - sublen - pos;
    if (sublen != newlen && tail > 0) {
      memmove(&buf[pos + newlen], &buf[pos + sublen], sizeof(buf[0]) * tail);
      memmove(&map[pos + newlen], &map[pos + sublen], sizeof(map[0]) * tail);
    }

    // put the new string in the gap, marking it as changed in the map
    memcpy(&buf[pos], newstr, sizeof(buf[0]) * newlen);
    memset(&map[pos],      1, sizeof(map[0]) * newlen);

    buflen += newlen - sublen;
    pos += newlen; // the new substring will obviously never match
  }

  return buflen;
}

/*
 * replacex() - implements mIRC's $replacex[cs](buf,substr,newstr,..)
 *
 * The caller must provide the (null-terminated) input string in 'buf'.
 * 'params' must be an array of strings, where the first string is the first
 * substr to match, the second string is the first newstr to replace matches
 * with, the third string is the second substr, the fourth string is the
 * second newstr, etcetera. 'nparams' must be the number of strings in the
 * array; it must obviously be a multiple of two, and at least one pair must
 * be given. 'cs' indicates case sensitivity (0 = case-insensitive, 1 =
 * case-sensitive).
 *
 * Upon success, OK is returned, and 'buf' contains the (null-terminated)
 * result string. Upon failure, an error is returned, and the contents of
 * 'buf' are undefined.
 */
myerr_t replacex(TCHAR *buf, const TCHAR *param[], int nparams, int cs)
{
  char map[LINE_MAX];
  ssize_t buflen;
  int i;

  // must be given one or more substr/newstr pairs
  if (!nparams || nparams % 2) return INVALID_PARAMS_ERROR;

  // track which characters have already been replaced before
  // initially, everything is still original, so the map is zeroed
  buflen = lstrlen(buf);
  memset(map, 0, sizeof(map[0]) * buflen);

  // replace each pair in the parameter array
  for (i = 0; i < nparams; i += 2) {
    buflen = replacex_one(buf, buflen, map, param[i], param[i + 1], cs);

    if (buflen < 0) return LINE_TOO_LONG_ERROR;
  }

  // the resulting buffer may not be properly null-terminated, so do that now
  buf[buflen] = 0;

  return OK;
}

void example(void)
{
  TCHAR buf[LINE_MAX+1] = TEXT("1234");
  TCHAR *param[] = { TEXT("234"), TEXT("z"), TEXT("123"), TEXT("u") };

  if (replacex(buf, param, sizeof(param)/sizeof(param[0]), FALSE) == OK)
    printf(TEXT("result string: %s"), buf); // will be "1z"
}

Not very thoroughly tested. Public domain.


Saturn, QuakeNet staff