pennmush-1.8.3p3/game/data/
pennmush-1.8.3p3/game/log/
pennmush-1.8.3p3/game/save/
pennmush-1.8.3p3/game/txt/evt/
pennmush-1.8.3p3/game/txt/nws/
pennmush-1.8.3p3/po/
pennmush-1.8.3p3/win32/msvc.net/
pennmush-1.8.3p3/win32/msvc6/
/**
 * \file strutil.c
 *
 * \brief String utilities for PennMUSH.
 *
 *
 */

#include "config.h"

#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <limits.h>
#ifdef WIN32
#define snprintf(s1,n,s2,s3) sprintf_s((s1), (n), (s2), (s3))
#endif
#include "copyrite.h"
#include "conf.h"
#include "case.h"
#include "pueblo.h"
#include "parse.h"
#include "externs.h"
#include "ansi.h"
#include "mymalloc.h"
#include "log.h"
#include "game.h"
#include "confmagic.h"

#define ANSI_BLACK_V    (30)
#define ANSI_RED_V      (31)
#define ANSI_GREEN_V    (32)
#define ANSI_YELLOW_V   (33)
#define ANSI_BLUE_V     (34)
#define ANSI_MAGENTA_V  (35)
#define ANSI_CYAN_V     (36)
#define ANSI_WHITE_V    (37)

#define ANSI_BEGIN   "\x1B["
#define ANSI_FINISH  "m"

#define COL_NORMAL   "\x1B[0m"

/* COL_* and VAL_* defines */

#define CBIT_FLASH       (1)     /**< ANSI flash attribute bit */
#define CBIT_HILITE      (2)     /**< ANSI hilite attribute bit */
#define CBIT_INVERT      (4)     /**< ANSI inverse attribute bit */
#define CBIT_UNDERSCORE  (8)     /**< ANSI underscore attribute bit */

#define COL_HILITE      (1)     /**< ANSI hilite attribute value */
#define COL_UNDERSCORE  (4)     /**< ANSI underscore attribute value */
#define COL_FLASH       (5)     /**< ANSI flag attribute value */
#define COL_INVERT      (7)     /**< ANSI inverse attribute value */

#define COL_BLACK       (30)    /**< ANSI color black */
#define COL_RED         (31)    /**< ANSI color red */
#define COL_GREEN       (32)    /**< ANSI color green */
#define COL_YELLOW      (33)    /**< ANSI color yellow */
#define COL_BLUE        (34)    /**< ANSI color blue */
#define COL_MAGENTA     (35)    /**< ANSI color magenta */
#define COL_CYAN        (36)    /**< ANSI color cyan */
#define COL_WHITE       (37)    /**< ANSI color white */

/* Now the code */

HASHTAB htab_tag;  /**< Hash table of safe html tags */

static int write_ansi_close(char *buff, char **bp);
static int is_ansi_oldstyle(const char *str);
static int safe_markup(char const *a_tag, char *buf, char **bp, char type);
static int
 safe_markup_cancel(char const *a_tag, char *buf, char **bp, char type);
static int
 compare_starts(const void *a, const void *b);


/* ARGSUSED */
FUNCTION(fun_stripansi)
{
  /* Strips ANSI codes away from a given string of text. Starts by
   * finding the '\x' character and stripping until it hits an 'm'.
   */

  char *cp;

  cp = remove_markup(args[0], NULL);
  safe_str(cp, buff, bp);
}

#ifdef ANSI_DEBUG
/* ARGSUSED */
void inspect_ansi_string(ansi_string *as, dbref who);
FUNCTION(fun_ansiinspect)
{
  char *ptr;
  ansi_string *as;
  char choice;
  if (nargs < 2 || !args[1] || !*args[1])
    choice = 'r';
  else
    choice = *args[1];
  switch (choice) {
  case 'i':
    as = parse_ansi_string(args[0]);
    inspect_ansi_string(as, executor);
    free_ansi_string(as);
    break;
  case 'r':
    for (ptr = args[0]; *ptr; ptr++) {
      if (*ptr == TAG_START)
        *ptr = '<';
      if (*ptr == TAG_END)
        *ptr = '>';
    }
    safe_str(args[0], buff, bp);
    break;
  case 'l':
    safe_integer(arglens[0], buff, bp);
    break;
  default:
    safe_str("i: inspect r: raw l: length", buff, bp);
    break;
  }
}
#endif

/* ARGSUSED */
FUNCTION(fun_ansi)
{
  struct ansi_data colors;
  char *save = *bp;
  char *p;
  int i;

  /* Populate the colors struct */
  define_ansi_data(&colors, args[0]);
  if (!(colors.bits || colors.offbits || colors.fore || colors.back)) {
    if (!safe_strl(args[1], arglens[1], buff, bp))
      write_ansi_close(buff, bp);
    return;
  }

  /* Write the colors to buff */
  if (write_ansi_data(&colors, buff, bp)) {
    *bp = save;
    return;
  }

  /* If the contents overrun the buffer, we
   * place an ANSI_ENDALL tag at the end */
  if (safe_str(args[1], buff, bp) || write_ansi_close(buff, bp)) {
    p = buff + BUFFER_LEN - 6;  /* <c/a> */
    for (i = 10; i > 0 && *p != TAG_START; i--, p--) ;
    if (i > 0) {
      /* There's an extant tag, let's just replace that. */
      *bp = p;
      safe_str(ANSI_ENDALL, buff, bp);
    } else {
      *bp = buff + BUFFER_LEN - 6;
      safe_str(ANSI_ENDALL, buff, bp);
    }
  }
}

/* ARGSUSED */
FUNCTION(fun_html)
{
  if (!Wizard(executor))
    safe_str(T(e_perm), buff, bp);
  else
    safe_tag(args[0], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_tag)
{
  int i;
  if (!Wizard(executor) && !hash_find(&htab_tag, strupper(args[0]))) {
    safe_str("#-1", buff, bp);
    return;
  }
  safe_chr(TAG_START, buff, bp);
  safe_chr(MARKUP_HTML, buff, bp);
  safe_strl(args[0], arglens[0], buff, bp);
  for (i = 1; i < nargs; i++) {
    if (ok_tag_attribute(executor, args[i])) {
      safe_chr(' ', buff, bp);
      safe_strl(args[i], arglens[i], buff, bp);
    }
  }
  safe_chr(TAG_END, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_endtag)
{
  if (!Wizard(executor) && !hash_find(&htab_tag, strupper(args[0])))
    safe_str("#-1", buff, bp);
  else
    safe_tag_cancel(args[0], buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_tagwrap)
{
  if (!Wizard(executor) && !hash_find(&htab_tag, strupper(args[0])))
    safe_str("#-1", buff, bp);
  else {
    if (nargs == 2)
      safe_tag_wrap(args[0], NULL, args[1], buff, bp, executor);
    else
      safe_tag_wrap(args[0], args[1], args[2], buff, bp, executor);
  }
}

/** A version of strlen that ignores ansi and HTML sequences.
 * \param p string to get length of.
 * \return length of string p, not including ansi/html sequences.
 */
int
ansi_strlen(const char *p)
{
  int i = 0;

  if (!p)
    return 0;

  while (*p) {
    if (*p == ESC_CHAR) {
      while ((*p) && (*p != 'm'))
        p++;
    } else if (*p == TAG_START) {
      while ((*p) && (*p != TAG_END))
        p++;
    } else {
      i++;
    }
    p++;
  }
  return i;
}

/** Compare two strings, ignoring  all ansi and html markup from a string.
 *  Is *NOT* locale safe (a la strcoll)
 * \param a string to compare to
 * \param b Other string
 * \return int - 0 is identical, -1 or 1 for difference.
 */
int
ansi_strcmp(const char *astr, const char *bstr)
{
  const char *a, *b;

  for (a = astr, b = bstr; *a && *b;) {
    a = skip_leading_ansi(a);
    b = skip_leading_ansi(b);
    if (*a != *b)
      return (*a - *b);
    b++;
    a++;
  }
  if (*a)
    a = skip_leading_ansi(a);
  if (*b)
    b = skip_leading_ansi(b);
  return (*a - *b);
}

/** Returns the apparent length of a string, up to numchars visible
 * characters. The apparent length skips over nonprinting ansi and
 * tags.
 * \param p string.
 * \param numchars maximum size to report.
 * \return apparent length of string.
 */
int
ansi_strnlen(const char *p, size_t numchars)
{
  size_t i = 0;

  if (!p)
    return 0;
  while (*p && numchars > 0) {
    if (*p == ESC_CHAR) {
      while ((*p) && (*p != 'm')) {
        p++;
        i++;
      }
    } else if (*p == TAG_START) {
      while ((*p) && (*p != TAG_END)) {
        p++;
        i++;
      }
    } else
      numchars--;
    i++;
    p++;
  }
  return i;
}

/** Strip all ansi and html markup from a string. As a side effect,
 * stores the length of the stripped string in a provided address.
 * NOTE! Length returned is length *including* the terminating NULL,
 * because we usually memcpy the result.
 * \param orig string to strip.
 * \param s_len address to store length of stripped string, if provided.
 * \return pointer to static buffer containing stripped string.
 */
char *
remove_markup(const char *orig, size_t * s_len)
{
  static char buff[BUFFER_LEN];
  char *bp = buff;
  const char *q;
  size_t len = 0;

  if (!orig) {
    if (s_len)
      *s_len = 0;
    return NULL;
  }

  for (q = orig; *q;) {
    switch (*q) {
    case ESC_CHAR:
      /* Skip over ansi */
      while (*q && *q++ != 'm') ;
      break;
    case TAG_START:
      /* Skip over HTML */
      while (*q && *q++ != TAG_END) ;
      break;
    default:
      safe_chr(*q++, buff, &bp);
      len++;
    }
  }
  *bp = '\0';
  if (s_len)
    *s_len = len + 1;
  return buff;
}

static char ansi_chars[50];

static int ansi_codes[255];

struct {
  char flag;
  int num;
} build_ansi_codes[] = {
  {
   'n', 0}, {
             'f', COL_FLASH}, {
                               'h', COL_HILITE}, {
                                                  'i', COL_INVERT}, {
                                                                     'u',
                                                                     COL_UNDERSCORE},
  {
   'x', COL_BLACK}, {
                     'X', COL_BLACK + 10}, {
                                            'r', COL_RED}, {
                                                            'R', COL_RED + 10}, {
                                                                                 'g',
                                                                                 COL_GREEN},
  {
   'G', COL_GREEN + 10}, {
                          'y', COL_YELLOW}, {
                                             'Y', COL_YELLOW + 10}, {
                                                                     'b',
                                                                     COL_BLUE},
  {
   'B', COL_BLUE + 10}, {
                         'm', COL_MAGENTA}, {
                                             'M', COL_MAGENTA + 10}, {
                                                                      'c',
                                                                      COL_CYAN},
  {
   'C', COL_CYAN + 10}, {
                         'w', COL_WHITE}, {
                                           'W', COL_WHITE + 10}, {
                                                                  '\0', 0}
};

void
init_ansi_codes(void)
{
  int i;

  memset(ansi_chars, 0, sizeof(ansi_chars));
  memset(ansi_codes, 0, sizeof(ansi_codes));

  for (i = 0; build_ansi_codes[i].flag; i++) {
    ansi_chars[build_ansi_codes[i].num] = build_ansi_codes[i].flag;
    ansi_codes[(unsigned char) build_ansi_codes[i].flag] =
      build_ansi_codes[i].num;
  }
}

int
read_raw_ansi_data(struct ansi_data *store, const char *codes)
{
  int curnum;
  if (codes == NULL || store == NULL)
    return 0;
  store->bits = 0;
  store->offbits = 0;
  store->fore = 0;
  store->back = 0;

  /* codes can point at either the ESC_CHAR or one
   * following after. */

  /* Skip to the first ansi number */
  while (*codes && !isdigit((unsigned char) *codes) && *codes != 'm')
    codes++;

  memset(store, 0, sizeof(struct ansi_data));

  while (*codes && (*codes != 'm')) {
    curnum = atoi(codes);
    if (curnum < 10) {
      switch (curnum) {
      case COL_HILITE:
        store->bits ^= CBIT_HILITE;
        break;
      case COL_UNDERSCORE:
        store->bits ^= CBIT_UNDERSCORE;
        break;
      case COL_FLASH:
        store->bits ^= CBIT_FLASH;
        break;
      case COL_INVERT:
        store->bits ^= CBIT_INVERT;
        break;
      case 0:
        store->bits = 0;
        store->offbits = 0;
        store->fore = 'n';
        store->back = 'n';
        break;
      }
    } else if (curnum < 40) {
      store->fore = ansi_chars[curnum];
    } else if (curnum < 50) {
      store->back = ansi_chars[curnum];
    }
    /* Skip current and find the nxt ansi number */
    while (*codes && isdigit((unsigned char) *codes))
      codes++;
    while (*codes && !isdigit((unsigned char) *codes) && (*codes != 'm'))
      codes++;
  }
  return 1;
}

static int
write_ansi_close(char *buff, char **bp)
{
  int retval = 0;
  retval += safe_chr(TAG_START, buff, bp);
  retval += safe_chr(MARKUP_COLOR, buff, bp);
  retval += safe_chr('/', buff, bp);
  retval += safe_chr(TAG_END, buff, bp);
  return retval;
}


int
write_ansi_data(struct ansi_data *cur, char *buff, char **bp)
{
  int retval = 0;
  retval += safe_chr(TAG_START, buff, bp);
  retval += safe_chr(MARKUP_COLOR, buff, bp);
  if (cur->fore == 'n') {
    retval += safe_chr(cur->fore, buff, bp);
  } else {
#define CBIT_SET(x,y) (x->bits & y)
    if (CBIT_SET(cur, CBIT_FLASH))
      retval += safe_chr('f', buff, bp);
    if (CBIT_SET(cur, CBIT_HILITE))
      retval += safe_chr('h', buff, bp);
    if (CBIT_SET(cur, CBIT_INVERT))
      retval += safe_chr('i', buff, bp);
    if (CBIT_SET(cur, CBIT_UNDERSCORE))
      retval += safe_chr('u', buff, bp);
#undef CBIT_SET
#define CBIT_SET(x,y) (x->offbits & y)
    if (CBIT_SET(cur, CBIT_FLASH))
      retval += safe_chr('F', buff, bp);
    if (CBIT_SET(cur, CBIT_HILITE))
      retval += safe_chr('H', buff, bp);
    if (CBIT_SET(cur, CBIT_INVERT))
      retval += safe_chr('I', buff, bp);
    if (CBIT_SET(cur, CBIT_UNDERSCORE))
      retval += safe_chr('U', buff, bp);
#undef CBIT_SET

    if (cur->fore)
      retval += safe_chr(cur->fore, buff, bp);
    if (cur->back)
      retval += safe_chr(cur->back, buff, bp);
  }

  retval += safe_chr(TAG_END, buff, bp);
  return retval;
}

/* We need EDGE_UP to return 1 if:
 * x has bit set and y's offbit does.
 */
#define EDGE_UP(x,y,z) ((x->bits & z) != (y->bits & z))

static struct ansi_data ansi_normal = { 0, 0xFF, 'n', 0 };

void
nest_ansi_data(struct ansi_data *old, struct ansi_data *cur)
{
  if (cur->fore != 'n') {
    cur->bits |= old->bits;
    cur->bits &= ~cur->offbits;
    if (!cur->fore)
      cur->fore = old->fore;
    if (!cur->back)
      cur->back = old->back;
  }
}

int
write_raw_ansi_data(struct ansi_data *old, struct ansi_data *cur,
                    char *buff, char **bp)
{
  int f = 0;

  if (cur->fore == 'n') {
    if (old->bits || (old->fore != 'n') || old->back) {
      return safe_str(COL_NORMAL, buff, bp);
    }
  }
  if (cur->fore == 'd')
    cur->fore = 0;
  if (cur->back == 'D')
    cur->back = 0;

  /* Do we *unset* anything in cur? */
  if ((old->bits & ~(cur->bits)) ||
      (old->fore && !cur->fore) || (old->back && !cur->back)) {
    safe_str(COL_NORMAL, buff, bp);
    old = &ansi_normal;
  }

  cur->bits |= old->bits;
  cur->bits &= ~cur->offbits;

  if (old->fore == cur->fore &&
      old->back == cur->back && old->bits == cur->bits)
    return 0;

  if (!(cur->fore || cur->back || cur->bits || cur->offbits)) {
    return safe_str(COL_NORMAL, buff, bp);
  }

  safe_str(ANSI_BEGIN, buff, bp);
  if (EDGE_UP(old, cur, CBIT_FLASH)) {
    if (f++)
      safe_chr(';', buff, bp);
    safe_integer(ansi_codes['f'], buff, bp);
  }
  if (EDGE_UP(old, cur, CBIT_HILITE)) {
    if (f++)
      safe_chr(';', buff, bp);
    safe_integer(ansi_codes['h'], buff, bp);
  }
  if (EDGE_UP(old, cur, CBIT_INVERT)) {
    if (f++)
      safe_chr(';', buff, bp);
    safe_integer(ansi_codes['i'], buff, bp);
  }
  if (EDGE_UP(old, cur, CBIT_UNDERSCORE)) {
    if (f++)
      safe_chr(';', buff, bp);
    safe_integer(ansi_codes['u'], buff, bp);
  }

  if (cur->fore && cur->fore != old->fore) {
    if (f++)
      safe_chr(';', buff, bp);
    safe_integer(ansi_codes[(unsigned char) cur->fore], buff, bp);
  }
  if (cur->back && cur->back != old->back) {
    if (f++)
      safe_chr(';', buff, bp);
    safe_integer(ansi_codes[(unsigned char) cur->back], buff, bp);
  }

  return safe_str(ANSI_FINISH, buff, bp);
}

void
define_ansi_data(struct ansi_data *cur, const char *str)
{
  cur->bits = 0;
  cur->offbits = 0;
  cur->fore = 0;
  cur->back = 0;

  for (; str && *str && (*str != TAG_END); str++) {
    switch (*str) {
    case 'n':                  /* normal */
      /* This is explicitly normal, it'll never be
       * clored */
      cur->bits = 0;
      cur->fore = 'n';
      cur->back = 0;
      break;
    case 'f':                  /* flash */
      cur->bits |= CBIT_FLASH;
      break;
    case 'h':                  /* hilite */
      cur->bits |= CBIT_HILITE;
      break;
    case 'i':                  /* inverse */
      cur->bits |= CBIT_INVERT;
      break;
    case 'u':                  /* underscore */
      cur->bits |= CBIT_UNDERSCORE;
      break;
    case 'F':                  /* flash */
      cur->offbits |= CBIT_FLASH;
      break;
    case 'H':                  /* hilite */
      cur->offbits |= CBIT_HILITE;
      break;
    case 'I':                  /* inverse */
      cur->offbits |= CBIT_INVERT;
      break;
    case 'U':                  /* underscore */
      cur->offbits |= CBIT_UNDERSCORE;
      break;
    case 'b':                  /* blue fg */
    case 'c':                  /* cyan fg */
    case 'g':                  /* green fg */
    case 'm':                  /* magenta fg */
    case 'r':                  /* red fg */
    case 'w':                  /* white fg */
    case 'x':                  /* black fg */
    case 'y':                  /* yellow fg */
    case 'd':                  /* default fg */
      cur->fore = *str;
      break;
    case 'B':                  /* blue bg */
    case 'C':                  /* cyan bg */
    case 'G':                  /* green bg */
    case 'M':                  /* magenta bg */
    case 'R':                  /* red bg */
    case 'W':                  /* white bg */
    case 'X':                  /* black bg */
    case 'Y':                  /* yellow bg */
    case 'D':                  /* default fg */
      cur->back = *str;
      break;
    }
  }
}

/** Return a string pointer past any ansi/html markup at the start.
 * \param p a string.
 * \return pointer to string after any initial ansi/html markup.
 */

char *
skip_leading_ansi(const char *p)
{
  if (!p)
    return NULL;
  while (*p == ESC_CHAR || *p == TAG_START) {
    if (*p == ESC_CHAR) {
      while (*p && *p != 'm')
        p++;
    } else {                    /* TAG_START */
      while (*p && *p != TAG_END)
        p++;
    }
    if (*p)
      p++;
  }
  return (char *) p;

}

static char *
parse_tagname(const char *ptr)
{
  static char tagname[BUFFER_LEN];
  char *tag = tagname;
  if (!ptr || !*ptr)
    return NULL;
  while (*ptr && !isspace((unsigned char) *ptr) && *ptr != TAG_END) {
    *(tag++) = *(ptr++);
  }
  *tag = '\0';
  return tagname;
}

static void
free_markup_info(markup_information *info)
{
  if (info) {
    if (info->start_code) {
      mush_free(info->start_code, "markup_code");
      info->start_code = NULL;
    }
    if (info->stop_code) {
      mush_free(info->stop_code, "markup_code");
      info->stop_code = NULL;
    }
  }
}

/* Is this string an old style format? */
static int
is_ansi_oldstyle(const char *source)
{
  const char *ptr;
  /* First test: ESC_CHAR. If there's one this is old style. */
  if (strchr(source, ESC_CHAR) != NULL)
    return 1;
  /* This usually means a TAG_START appears, but no ESC_CHAR. */
  if (strstr(source, MARKUP_START MARKUP_HTML_STR "/") ||
      strstr(source, MARKUP_START MARKUP_COLOR_STR "/")) {
    /* There's a <p/ or <c/ - which is newstyle */
    return 0;
  }
  /* Check for those that don't begin with p or c */
  for (ptr = strchr(source, TAG_START); ptr; ptr = strchr(ptr + 1, TAG_START)) {
    if ((*(ptr + 1) != MARKUP_HTML) && (*(ptr + 1) != MARKUP_COLOR)) {
      return 1;
    }
  }
  for (ptr = strchr(source, TAG_START); ptr; ptr = strchr(ptr + 1, TAG_START)) {
    /* Check for <pre ...> */
    if (strncasecmp(ptr + 1, "PRE", 3) == 0)
      return 1;
    /* And <p> or <p ...> */
    if (!isalnum((unsigned char) *(ptr + 2)))
      return 1;
  }
  return 0;
}

/** Convert a string into an ansi_string.
 * This takes a string that may contain ansi/html markup codes and
 * converts it to an ansi_string structure that separately stores
 * the plain string and the markup codes for each character.
 * \param source string to parse.
 * \return pointer to an ansi_string structure representing the src string.
 */
ansi_string *
parse_ansi_string(const char *source)
{
  return parse_ansi_string_real(source, 0);
}

/** Convert a string into an ansi_string.
 * This takes a string that may contain ansi/html markup codes and
 * converts it to an ansi_string structure that separately stores
 * the plain string and the markup codes for each character.
 * \param source string to parse.
 * \param oldstyle If true, treats it as an old style ansi string.
 * \return pointer to an ansi_string structure representing the src string.
 */
ansi_string *
parse_ansi_string_real(const char *source, int oldstyle)
{
  ansi_string *data = NULL;
  char src[BUFFER_LEN], *sptr;
  char tagbuff[BUFFER_LEN];
  char *ptr, *txt;
  char *tmp;
  char type;
  int i, j;
  int priority = 0;
  markup_information *info;

  if (!source)
    return NULL;

  if (oldstyle == 2)
    oldstyle = is_ansi_oldstyle(source);
  info = NULL;

  sptr = src;
  safe_str(source, src, &sptr);
  *sptr = '\0';


  data = mush_malloc(sizeof(ansi_string), "ansi_string");
  if (!data)
    return NULL;

  /* Set it to zero */
  memset(data, 0, sizeof(ansi_string));

  txt = data->text;
  i = 0;
  for (ptr = src; *ptr;) {
    /* Is this an ansi sequence? */
    switch (*ptr) {
    case TAG_START:
      /* In modern Penn, this is both Pueblo/HTML and color defining code */
      /* Find the end. */
      for (tmp = ptr; *tmp && *tmp != TAG_END; tmp++) ;
      if (*tmp) {
        *(tmp) = '\0';
      } else {
        /* Point tmp at the end */
        tmp--;
      }
      ptr++;
      type = oldstyle ? MARKUP_HTML : *(ptr++);
      switch (type) {
      case MARKUP_HTML:
        if (*ptr && *ptr != '/') {
          /* We're at the start tag. */
          info = &(data->markup[data->nmarkups++]);
          info->start_code = mush_strdup(ptr, "markup_code");
          snprintf(tagbuff, BUFFER_LEN, "/%s", parse_tagname(ptr));
          info->stop_code = mush_strdup(tagbuff, "markup_code");
          info->type = MARKUP_HTML;
          info->start = i;
          info->end = -1;
          info->priority = priority++;
        } else if (*ptr) {
          /* Closing tag */
          for (j = data->nmarkups - 1; j >= 0; j--) {
            if (data->markup[j].end < 0 && data->markup[j].stop_code &&
                strcasecmp(data->markup[j].stop_code, ptr) == 0) {
              break;
            }
          }
          if (j >= 0) {
            data->markup[j].end = i;
          } else {
            /* This is greviously wrong, we can't find the begin tag?
             * Consider this a standalone tag with no close tag. */
            info = &(data->markup[data->nmarkups++]);
            /* Start code is where we are */
            info->stop_code = mush_strdup(ptr, "markup_code");
            info->type = MARKUP_HTML;
            info->priority = priority++;
            info->start = -1;
            info->end = i;
          }
        }
        break;
      case MARKUP_COLOR:
        if (*ptr && *ptr != '/') {
          /* We're at the start tag. */
          j = data->nmarkups++;
          data->markup[j].start_code = NULL;
          data->markup[j].stop_code = NULL;
          data->markup[j].type = MARKUP_COLOR;
          data->markup[j].priority = priority++;
          define_ansi_data(&(data->markup[j].ansi), ptr);
          data->markup[j].start = i;
          data->markup[j].end = -1;
        } else if (*ptr) {
          int endall = (*(ptr + 1) == 'a');
          /* Closing tag. For markup color, this means we
           * close the last opened tag. */
          for (j = (data->nmarkups) - 1; j >= 0; j--) {
            if (data->markup[j].end < 0 && data->markup[j].type == MARKUP_COLOR) {
              data->markup[j].end = i;
              /* If it's not ENDALL, break */
              if (!endall)
                break;
            }
          }
        }
        break;
      default:
        /* This is a broken string. Are we near or at buffer_len? */
        if (ptr - source < BUFFER_LEN - 4) {
          /* If we're not, this is broken in more ways than I can think */
          goto broken_string;
        }
        break;
      }
      ptr = tmp;
      ptr++;
      break;
    case ESC_CHAR:
      /* I'm getting rid of ESC_CHAR. Still, pretend it's
       * a proper "opening" one, unless it's normal,
       * in which case we ether close all extant, or
       * open a 'normal'.
       *
       * If we open a new one, find all old open ones that
       * it overwrites (rather than modifies) */
      for (tmp = ptr; *tmp && *tmp != 'm'; tmp++) ;
      if (strcmp(ptr, COL_NORMAL) != 0) {
        struct ansi_data cur;
        read_raw_ansi_data(&cur, ptr);
        /* Close any we can */
        for (j = (data->nmarkups) - 1; j >= 0; j--) {
          if (data->markup[j].type == MARKUP_COLOR_OLD) {
            cur.bits |= data->markup[j].ansi.bits;
            data->markup[j].type = MARKUP_COLOR;
            data->markup[j].end = i;
          }
        }
        /* We're at a start tag. */
        j = data->nmarkups++;
        data->markup[j].start_code = NULL;
        data->markup[j].stop_code = NULL;
        data->markup[j].type = MARKUP_COLOR_OLD;
        data->markup[j].priority = priority++;
        data->markup[j].ansi = cur;
        data->markup[j].start = i;
        data->markup[j].end = -1;
      } else {
        int found = 0;
        /* Closing tag. For markup color, this means we
         * close the last opened tag. */
        for (j = (data->nmarkups) - 1; j >= 0; j--) {
          if (data->markup[j].type == MARKUP_COLOR_OLD) {
            data->markup[j].type = MARKUP_COLOR;
            data->markup[j].end = i;
            found = 1;
          }
        }
        /* Is it an "opening" ansi_normal tag? */
        if (!found) {
          j = data->nmarkups++;
          data->markup[j].start_code = NULL;
          data->markup[j].stop_code = NULL;
          data->markup[j].type = MARKUP_COLOR_OLD;
          data->markup[j].priority = priority++;
          data->markup[j].ansi.bits = 0;
          data->markup[j].ansi.offbits = 0;
          data->markup[j].ansi.fore = 'n';
          data->markup[j].ansi.back = 0;
          data->markup[j].start = i;
          data->markup[j].end = -1;
        }
      }
      ptr = tmp;
      if (*tmp)
        ptr++;
      break;
    default:
      txt[i++] = *(ptr++);
    }
  }

  txt[i] = '\0';
  data->len = i;

  /* For everything left on the stack:
   * If it's an ANSI code, close it with COL_NORMAL and i.
   * If it's an HTML code, assume it's a standalone, and leave
   *   its stop point where it is. */
  for (j = 0; j < data->nmarkups; j++) {
    info = &(data->markup[j]);
    switch (info->type) {
    case MARKUP_COLOR_OLD:
      info->type = MARKUP_COLOR;
    case MARKUP_COLOR:
      /* If it's ANSI, we assume it affects the whole string */
      /* Sucks, but ... */
      if (info->end < 0)
        info->end = i;
      if (info->end == info->start) {
        info->end = info->start = -1;
      }
      break;
    case MARKUP_HTML:
      /* If it's HTML, we treat it as standalone (<IMG>, <BR>, etc)
       * This is ugly - it's not a "start" but a "stop" */
      if (info->end < 0) {
        mush_free(info->stop_code, "markup_code");
        info->stop_code = info->start_code;
        info->start_code = NULL;
        info->end = info->start;
        info->start = -1;
      }
      break;
    }
  }
  return data;
broken_string:
  /* This stinks. We treat this as if it's not ansi safe */
  if (data == NULL)
    return NULL;
  strncpy(data->text, source, BUFFER_LEN);
  data->len = strlen(data->text);
  for (i = data->nmarkups - 1; i >= 0; i--) {
    free_markup_info(&(data->markup[i]));
  }
  data->nmarkups = 0;
  return data;
}

/** Reverse an ansi string, preserving its ansification.
 * This function destructively modifies the ansi_string passed.
 * \param as pointer to an ansi string.
 */
void
flip_ansi_string(ansi_string *as)
{
  int i, j;
  markup_information *info;
  char tmp;
  int mid;
  int len = as->len;

  /* Reverse the text */
  mid = len / 2;                /* Midpoint */
  for (i = len - 1, j = 0; i >= mid; j++, i--) {
    tmp = as->text[i];
    as->text[i] = as->text[j];
    as->text[j] = tmp;
  }

  /* Now reverse the markup. */
  for (i = as->nmarkups - 1; i >= 0; i--) {
    int start, end;
    info = &(as->markup[i]);
    if (info->start == -1) {
      /* Standalones */
      info->end = len - info->end;
    } else {
      end = len - info->start;
      start = len - info->end;
      info->start = start;
      info->end = end;
    }
  }
}

/** Free an ansi_string.
 * \param as pointer to ansi_string to free.
 */
void
free_ansi_string(ansi_string *as)
{
  int i;

  if (!as)
    return;

  for (i = as->nmarkups - 1; i >= 0; i--) {
    free_markup_info(&(as->markup[i]));
  }
  mush_free(as, "ansi_string");
}

/* Compress the markup information in an ansi_string.
 *
 * This combines adjacent identical markup.
 */

static int
compare_starts(const void *a, const void *b)
{
  markup_information *ai, *bi;

  ai = (markup_information *) a;
  bi = (markup_information *) b;

  // if (ai->start == bi->start) return ai->priority - bi->priority;
  return ai->start - bi->start;
}

void
optimize_ansi_string(ansi_string *as)
{
  int i, j;

  /* Nothing to optimize if we've only got 1 or none. */
  if (as->nmarkups > 1) {
    /* Sort the markup codes by their start position */
    qsort(as->markup, as->nmarkups, sizeof(markup_information), compare_starts);

    for (i = 0; i < as->nmarkups; i++) {
      /* If start and end are negative, it's a standalone (img) */
      if (as->markup[i].start == -1 && as->markup[i].end == -1)
        continue;
      for (j = i + 1; j < as->nmarkups; j++) {
        /* Already removed? */
        if (as->markup[j].start == -1 && as->markup[j].end == -1)
          continue;
        if (as->markup[j].start > as->markup[i].end)
          break;                /* Far apart */
        if (as->markup[i].type == MARKUP_COLOR &&
            as->markup[j].type == MARKUP_COLOR &&
            (as->markup[i].ansi.bits == as->markup[j].ansi.bits &&
             as->markup[i].ansi.offbits == as->markup[j].ansi.offbits &&
             as->markup[i].ansi.fore == as->markup[j].ansi.fore &&
             as->markup[i].ansi.back == as->markup[j].ansi.back)
          ) {
          if (as->markup[j].end > as->markup[i].end)
            as->markup[i].end = as->markup[j].end;
          if (as->markup[j].start < as->markup[i].start)
            as->markup[i].start = as->markup[j].start;
          if (as->markup[j].end > as->markup[i].end)
            as->markup[j].start = -1;
          as->markup[j].end = -1;
        } else if ((as->markup[i].start_code && as->markup[j].start_code) &&
                   strcmp(as->markup[j].start_code,
                          as->markup[i].start_code) == 0) {
          /* i and j are adjacent and identical */
          if (as->markup[j].end > as->markup[i].end)
            as->markup[i].end = as->markup[j].end;
          if (as->markup[j].start < as->markup[i].start)
            as->markup[i].start = as->markup[j].start;
          as->markup[j].start = -1;
          as->markup[j].end = -1;
        }
      }
    }
  }

  /* Get rid of all removed markups */
  for (i = 0, j = 0; i < as->nmarkups; i++) {
    if ((as->markup[i].end >= 0) && (as->markup[i].start != as->markup[i].end)) {
      if (i != j) {
        memmove(&(as->markup[j]), &(as->markup[i]), sizeof(markup_information));
      }
      j++;
    } else {
      free_markup_info(&(as->markup[i]));
    }
  }

  as->nmarkups = j;
}

/* Copy the start code for a particular markup_info
 * For HTML/Pueblo, this inserts TAG_START and TAG_END
 * Otherwise it's just a plain copy */
static int
copy_start_code(markup_information *info, char *buff, char **bp)
{
  int retval = 0;
  char *save;
  save = *bp;
  if (info->start_code) {
    retval += safe_chr(TAG_START, buff, bp);
    retval += safe_chr(info->type, buff, bp);
    retval += safe_str(info->start_code, buff, bp);
    retval += safe_chr(TAG_END, buff, bp);
  } else if (info->type == MARKUP_COLOR) {
    retval += write_ansi_data(&(info->ansi), buff, bp);
  }
  if (retval)
    *bp = save;
  return retval;
}

/* Copy the stop code for a particular markup_info
 * For HTML/Pueblo, this inserts TAG_START and TAG_END
 * Otherwise it's just a plain copy */
static int
copy_stop_code(markup_information *info, char *buff, char **bp)
{
  int retval = 0;
  char *save;
  save = *bp;
  if (info->type == MARKUP_HTML && (info->stop_code != NULL)) {
    retval += safe_chr(TAG_START, buff, bp);
    retval += safe_chr(MARKUP_HTML, buff, bp);
    retval += safe_str(info->stop_code, buff, bp);
    retval += safe_chr(TAG_END, buff, bp);
  } else if (info->type == MARKUP_COLOR) {
    retval += safe_chr(TAG_START, buff, bp);
    retval += safe_chr(MARKUP_COLOR, buff, bp);
    retval += safe_chr('/', buff, bp);
    retval += safe_chr(TAG_END, buff, bp);
  }
  if (retval)
    *bp = save;
  return retval;
}

#ifdef ANSI_DEBUG
void
inspect_ansi_string(ansi_string *as, dbref who)
{
  markup_information *info;
  int count = 0;
  int j;
  notify_format(who, "Inspecting ansi string");
  notify_format(who, "  Text: %s", as->text);
  notify_format(who, "  Nmarkups: %d", as->nmarkups);
  for (j = 0; j < as->nmarkups; j++) {
    info = &(as->markup[j]);
    if (info->type == MARKUP_HTML) {
      notify_format(who,
                    "    %d (%s): (start: %d end: %d) start_code: %s stop_code: %s",
                    count++, (info->type == MARKUP_HTML ? "html" : "ansi"),
                    info->start, info->end, info->start_code, info->stop_code);
    } else {
      notify_format(who,
                    "    %d (%s): (start: %d end: %d) bits: %d fore: %c back: %c",
                    count++, (info->type == MARKUP_HTML ? "html" : "ansi"),
                    info->start, info->end, info->ansi.bits,
                    (info->ansi.fore) ? info->ansi.fore : '-',
                    (info->ansi.back) ? info->ansi.back : '-');
    }
  }
  notify_format(who, "Inspecting ansi string complete");
}
#endif

/** Delete a portion of an ansi string.
 * \param as ansi_string to delete from
 * \param start start point to remove
 * \param size length of string to remove
 * \retval 0 success
 * \reval 1 failure.
 */

int
ansi_string_delete(ansi_string *as, int start, int count)
{
  int i;
  int end;
  markup_information *dm;

  if (start >= as->len)
    return 0;
  if (count <= 0)
    return 0;
  if ((start + count) > as->len)
    count = as->len - start;

  end = start + count;

  as->optimized = 0;

  dm = as->markup;

  /* Remove or shrink the markup on dst */
  for (i = 0; i < as->nmarkups; i++) {
    if (dm[i].start >= start && dm[i].end <= end) {
      dm[i].start = -1;
      dm[i].end = -1;
    }
    if (dm[i].start >= start) {
      dm[i].start -= count;
      if (dm[i].start < start)
        dm[i].start = start;
    }
    if (dm[i].end > start) {
      dm[i].end -= count;
      if (dm[i].end < start)
        dm[i].end = start;
    }
  }

  /* Shift text over */
  memmove(as->text + start, as->text + end, as->len - end);
  as->len -= count;
  as->text[as->len] = '\0';
  return 0;
}

#define copyto(x,y) \
do { \
  x.type = y.type; \
  x.priority = y.priority; \
  x.start_code = NULL; \
  x.stop_code = NULL; \
  if (y.start_code) x.start_code = mush_strdup(y.start_code,"markup_code"); \
  else (x.start_code = NULL); \
  if (y.stop_code) x.stop_code = mush_strdup(y.stop_code,"markup_code"); \
  else (x.stop_code = NULL); \
  x.ansi.bits    = y.ansi.bits; \
  x.ansi.offbits = y.ansi.offbits; \
  x.ansi.fore    = y.ansi.fore; \
  x.ansi.back    = y.ansi.back; \
} while (0)

/** Insert an ansi string into another ansi_string
 * with markups kept as straight as possible.
 * \param dst ansi_string to insert into.
 * \param loc Location to  insert into, 0-indexed
 * \param src ansi_string to insert
 * \param start start point in src
 * \param size length of string from src
 * \retval 0 success
 * \reval 1 failure.
 */

int
ansi_string_insert(ansi_string *dst, int loc,
                   ansi_string *src, int start, int count)
{
  int i, j;
  int len;
  int end, m_end;
  int rval = 0;
  markup_information *dm, *sm;

  if (loc >= dst->len)
    loc = dst->len;
  if (start >= src->len)
    return 0;
  if (count <= 0)
    return 0;
  if ((start + count) > src->len)
    count = src->len - start;

  dst->optimized = 0;

  dm = dst->markup;
  sm = src->markup;

  /* End in src */
  end = start + count;

  /* Starting location */
  if (loc <= 0)
    loc = 0;
  if (loc >= dst->len)
    loc = dst->len;

  /* End of src's insert location in dst */
  m_end = loc + count;

  /* shift or widen the markup on dst */
  for (i = 0; i < dst->nmarkups; i++) {
    if (dm[i].start >= loc)
      dm[i].start += count;
    if (dm[i].end > loc)
      dm[i].end += count;
  }
  /* Copy markup */
  for (j = 0; j < src->nmarkups; j++) {
    /* It's possible, but not at all easy, to get this much ansi markup */
    if (i >= BUFFER_LEN)
      break;
    if (((sm[j].start <= end && sm[j].end >= start) && sm[j].start >= 0) ||
        (sm[j].end > start && sm[j].end <= end)) {
      copyto(dm[i], sm[j]);
      if (sm[j].start < 0) {
        dm[i].start = -1;
      } else {
        dm[i].start = loc + sm[j].start - start;
        if (dm[i].start < loc)
          dm[i].start = loc;
      }
      dm[i].end = loc + sm[j].end - start;
      if (dm[i].end >= m_end)
        dm[i].end = m_end;
      i++;
    }
  }
  dst->nmarkups = i;

  dst->len += count;
  if (dst->len >= BUFFER_LEN) {
    rval = 1;
    dst->len = BUFFER_LEN - 1;
  }
  len = dst->len - m_end;

  /* Shift text over */
  if (len > 0) {
    if (m_end + len >= BUFFER_LEN) {
      len = (BUFFER_LEN - m_end - loc - 1);
      memmove(dst->text + m_end, dst->text + loc, len);
    } else {
      memmove(dst->text + m_end, dst->text + loc, len);
    }
  }

  /* Copy text from src */
  if (loc + count >= BUFFER_LEN)
    count = BUFFER_LEN - 1 - loc;
  memcpy(dst->text + loc, src->text + start, count);
  dst->text[dst->len] = '\0';
  return rval;
}

/** Replace a portion of an ansi string with
 *  another ansi string, keeping markups as
 *  straight as possible.
 * \param dst ansi_string to insert into.
 * \param loc Location to  insert into, 0-indexed
 * \param len Length of string inside dst to replace
 * \param src ansi_string to insert
 * \param start start point in src
 * \param size length of string from src
 * \retval 0 success
 * \reval 1 failure.
 */
int
ansi_string_replace(ansi_string *dst, int loc, int len,
                    ansi_string *src, int start, int count)
{
  int i, j;
  int end, m_end, s_end;
  int diff;
  int rval = 0;
  markup_information *dm, *sm;

  /* Is it really an insert? */
  if (loc >= dst->len || len == 0) {
    return ansi_string_insert(dst, loc, src, start, count);
  }
  /* Boundaries */
  if (start <= 0)
    start = 0;
  if ((start + count) > src->len)
    count = src->len - start;
  if ((len + loc) > dst->len)
    len = dst->len - loc;
  /* Is it really a delete? */
  if ((start >= src->len) || (count <= 0)) {
    return ansi_string_delete(dst, loc, len);
  }

  /* Starting location */
  if (loc <= 0)
    loc = 0;
  if (loc >= dst->len)
    loc = dst->len;

  end = loc + len;
  diff = count - len;
  m_end = loc + count;

  dst->optimized = 0;

  dm = dst->markup;
  sm = src->markup;

  /* Modify, remove, stretch, and mangle markup */
  for (i = 0; i < dst->nmarkups; i++) {
    /* If it doesn't cross into the replaced part, leave as is */
    if (dm[i].end <= loc)
      continue;
    if (dm[i].start == loc && dm[i].end == end) {
      /* Debatable: If it surrounds the replaced part exactly,
       * keep it, stretching it to wrap around the replacement */
      dm[i].end = m_end;
    } else if (dm[i].start <= loc && dm[i].end >= end) {
      dm[i].end += diff;
      if (dm[i].end > BUFFER_LEN)
        dm[i].end = BUFFER_LEN;
    } else if (dm[i].start >= loc && dm[i].end <= end) {
      /* If it's completely inside the removed area, remove it */
      dm[i].start = -1;
      dm[i].end = -1;
    } else if (dm[i].start < loc && dm[i].end < end) {
      /* If it ends inside, but begins to the left, push end left. */
      if (dm[i].start >= 0)
        dm[i].end = loc;
      else                      /* Standalone is inside */
        dm[i].end = -1;
    } else if (dm[i].end > end && dm[i].start < end) {
      /* If it begins inside, but ends to the right, push start right. */
      if (dm[i].start >= 0)
        dm[i].start = m_end;
      dm[i].end += diff;
    } else if (dm[i].start > end) {
      /* It's to the right */
      dm[i].start += diff;
      dm[i].end += diff;
    } else {
      /* Shift */
      if (dm[i].start > loc)
        dm[i].start += diff;
      dm[i].end += diff;
    }
  }

  s_end = start + count;
  /* Copy markup */
  for (j = 0; j < src->nmarkups; j++) {
    /* It's possible, but not at all easy, to get this much ansi markup */
    if (i >= BUFFER_LEN)
      break;
    if (((sm[j].start <= s_end && sm[j].end >= start) && sm[j].start >= 0) ||
        (sm[j].end > start && sm[j].end <= s_end)) {
      copyto(dm[i], sm[j]);
      if (sm[j].start < 0) {
        dm[i].start = -1;
      } else {
        dm[i].start = loc + sm[j].start - start;
        if (dm[i].start < loc)
          dm[i].start = loc;
      }
      dm[i].end = loc + sm[j].end - start;
      if (dm[i].end >= m_end)
        dm[i].end = m_end;
      i++;
    }
  }
  if (i >= BUFFER_LEN)
    i = BUFFER_LEN - 1;
  dst->nmarkups = i;

  /* length of original string after replace bits */
  len = dst->len - end;

  dst->len += diff;
  if (dst->len >= BUFFER_LEN) {
    rval = 1;
    dst->len = BUFFER_LEN - 1;
  }

  /* Shift text over */
  if (diff != 0) {
    if (m_end + len >= BUFFER_LEN) {
      len = BUFFER_LEN - (1 + m_end);
      if (len > 0) {
        memmove(dst->text + m_end, dst->text + end, len);
      }
    } else {
      memmove(dst->text + m_end, dst->text + end, len);
    }
  }

  /* Copy text from src */
  if (loc + count >= BUFFER_LEN)
    count = BUFFER_LEN - (1 + loc);
  memcpy(dst->text + loc, src->text + start, count);
  dst->text[dst->len] = '\0';
  return rval;
}

#undef copyto

/** Safely append an ansi_string into a buffer as a real string,
 * \param as pointer to ansi_string to append.
 * \param start position in as to start copying from.
 * \param len length in characters to copy from as.
 * \param buff buffer to insert into.
 * \param bp pointer to pointer to insertion point of buff.
 * \retval 0 success.
 * \retval 1 failure.
 */

int
safe_ansi_string(ansi_string *as, int start, int len, char *buff, char **bp)
{
  int i, j;
  markup_information *info;
  int nextstart, nextend, next;
  int end = start + len;
  int retval = 0;

  if (as->optimized == 0) {
    optimize_ansi_string(as);
    as->optimized = 1;
  }

  if (len <= 0)
    return 0;

  i = start;
  if (start >= as->len)
    return 0;
  if (end > as->len)
    end = as->len;

  /* Standalones (Stop codes with -1 for start) */
  for (j = 0; j < as->nmarkups; j++) {
    info = &(as->markup[j]);
    if (info->start == -1 && info->end == i) {
      /* This is a standalone tag. YUCK! */
      if (info->stop_code)
        retval += safe_str(info->stop_code, buff, bp);
    }
  }

  /* Now, start codes of everything that impacts us. */
  for (j = 0; j < as->nmarkups; j++) {
    info = &(as->markup[j]);
    if (info->start >= 0) {
      if (info->start <= i && info->end > i) {
        retval += copy_start_code(info, buff, bp);
      }
    }
  }


  /* Find  the next changes */
  nextstart = BUFFER_LEN + 1;
  nextend = BUFFER_LEN + 1;

  for (j = 0; j < as->nmarkups; j++) {
    info = &(as->markup[j]);
    if (info->start > i && info->start < nextstart)
      nextstart = info->start;
    if (info->end > i && info->end < nextend)
      nextend = info->end;
  }

  next = (nextend < nextstart) ? nextend : nextstart;
  if (end < next)
    next = end;

  for (; i < next && i < as->len; i++) {
    if (as->text[i])
      safe_chr(as->text[i], buff, bp);
  }
  i = next;

  while (i < end) {
    if (i >= nextend) {
      nextend = BUFFER_LEN + 2;
      for (j = 0; j < as->nmarkups; j++) {
        info = &(as->markup[j]);
        if (info->end == i) {
          retval += copy_stop_code(info, buff, bp);
        }
        if (info->end > i && info->end < nextend)
          nextend = info->end;
      }
    }
    if (i >= nextstart) {
      nextstart = BUFFER_LEN + 2;
      for (j = 0; j < as->nmarkups; j++) {
        info = &(as->markup[j]);
        if (info->start == i) {
          retval += copy_start_code(info, buff, bp);
        } else if (info->start > i && info->start < nextstart) {
          nextstart = info->start;
        }
      }
    }
    next = (nextend < nextstart) ? nextend : nextstart;
    if (end < next)
      next = end;

    for (; i < next && i < as->len; i++) {
      if (as->text[i])
        safe_chr(as->text[i], buff, bp);
    }
  }

  /* Now, find all things that end for us */
  for (j = 0; j < as->nmarkups; j++) {
    info = &(as->markup[j]);
    if (info->start < i && info->end >= i)
      retval += copy_stop_code(info, buff, bp);
  }

  return retval;
}

/* Following functions are used for
 * decompose_str()
 */

extern char escaped_chars[UCHAR_MAX + 1];

static int escape_strn(char *s, int start, int count, char *buff, char **bp);

static int
escape_str(char *in, char *buff, char **bp)
{
  return escape_strn(in, 0, strlen(in), buff, bp);
}

static int
escape_strn(char *s, int start, int count, char *buff, char **bp)
{
  unsigned char *in;
  int retval = 0;
  int dospace = 1;
  int spaces = 0;
  int i, j;

  in = (unsigned char *) s;

  if (*in) {
    count += start;
    for (i = start; in[i] && i < count; i++) {
      if (in[i] == ' ') {
        spaces++;
      } else {
        if (spaces) {
          if (spaces >= 5) {
            retval += safe_str("[space(", buff, bp);
            retval += safe_number(spaces, buff, bp);
            retval += safe_str(")]", buff, bp);
          } else {
            if (dospace) {
              spaces--;
              retval += safe_str("%b", buff, bp);
            }
            while (spaces) {
              retval += safe_chr(' ', buff, bp);
              if (--spaces) {
                --spaces;
                retval += safe_str("%b", buff, bp);
              }
            }
          }
        }
        spaces = 0;
        dospace = 0;
        if (in[i] == '\n') {
          retval += safe_str("%r", buff, bp);
        } else if (in[i] == '\t') {
          retval += safe_str("%t", buff, bp);
        } else if (in[i] == BEEP_CHAR) {
          for (j = i; in[i + 1] == BEEP_CHAR && (i - j) < 4; i++) ;
          retval += safe_format(buff, bp, "[beep(%d)]", (i - j) + 1);
        } else if (escaped_chars[in[i]]) {
          retval += safe_chr('\\', buff, bp);
          retval += safe_chr(in[i], buff, bp);
        } else {
          retval += safe_chr(in[i], buff, bp);
        }
      }
    }
    if (spaces) {
      if (spaces >= 5) {
        retval += safe_str("[space(", buff, bp);
        retval += safe_number(spaces, buff, bp);
        retval += safe_str(")]", buff, bp);
      } else {
        spaces--;               /* This is for the final %b space */
        if (spaces && dospace) {
          spaces--;
          retval += safe_str("%b", buff, bp);
        }
        while (spaces) {
          safe_chr(' ', buff, bp);
          if (--spaces) {
            --spaces;
            retval += safe_str("%b", buff, bp);
          }
        }
        retval += safe_str("%b", buff, bp);
      }
    }
  }
  return retval;
}


static int
dump_start_code(markup_information *info, char *buff, char **bp)
{
  int retval = 0;
  char *save;
  save = *bp;
  if (info->type == MARKUP_HTML) {
    if (info->stop_code != NULL) {
      char *ptr;
      retval += safe_str("[tagwrap(", buff, bp);
      if ((ptr = strchr(info->start_code, ' ')) != NULL) {
        *(ptr++) = '\0';
        retval += escape_str(info->start_code, buff, bp);
        if (*ptr) {
          retval += safe_chr(',', buff, bp);
          retval += escape_str(ptr, buff, bp);
        }
        ptr--;
        *ptr = ' ';
      } else {
        retval += escape_str(info->start_code, buff, bp);
      }
      retval += safe_chr(',', buff, bp);
    } else {
      retval += safe_str("[tag(", buff, bp);
      retval += escape_str(info->start_code, buff, bp);
      retval += safe_str(")]", buff, bp);
    }
  } else {
    /* Find the digits */
    retval += safe_str("[ansi(", buff, bp);
    if (info->ansi.fore == 'n') {
      retval += safe_chr('n', buff, bp);
    } else {
#define CBIT_SET(x,y) (x.bits & y)
      if (CBIT_SET(info->ansi, CBIT_FLASH))
        retval += safe_chr('f', buff, bp);
      if (CBIT_SET(info->ansi, CBIT_HILITE))
        retval += safe_chr('h', buff, bp);
      if (CBIT_SET(info->ansi, CBIT_INVERT))
        retval += safe_chr('i', buff, bp);
      if (CBIT_SET(info->ansi, CBIT_UNDERSCORE))
        retval += safe_chr('u', buff, bp);
#undef CBIT_SET
#define CBIT_SET(x,y) (x.offbits & y)
      if (CBIT_SET(info->ansi, CBIT_FLASH))
        retval += safe_chr('F', buff, bp);
      if (CBIT_SET(info->ansi, CBIT_HILITE))
        retval += safe_chr('H', buff, bp);
      if (CBIT_SET(info->ansi, CBIT_INVERT))
        retval += safe_chr('I', buff, bp);
      if (CBIT_SET(info->ansi, CBIT_UNDERSCORE))
        retval += safe_chr('U', buff, bp);
#undef CBIT_SET

      if (info->ansi.fore)
        retval += safe_chr(info->ansi.fore, buff, bp);
      if (info->ansi.back)
        retval += safe_chr(info->ansi.back, buff, bp);
      retval += safe_chr(',', buff, bp);
    }
  }
  if (retval)
    *bp = save;
  return retval;
}

static int
dump_stop_code(markup_information *info
               __attribute__ ((__unused__)), char *buff, char **bp)
{
  char *save = *bp;
  if (safe_str(")]", buff, bp)) {
    *bp = save;
  }
  return 1;
}

int
dump_ansi_string(ansi_string *as, char *buff, char **bp)
{
  int i, j;
  markup_information *info;
  int nextstart, nextend, next;
  int end;
  int start = 0;
  int retval = 0;

  end = as->len;

  if (as->optimized == 0) {
    optimize_ansi_string(as);
    as->optimized = 1;
  }

  i = start;
  if (start > as->len)
    return 0;
  if (end > as->len)
    end = as->len;

  /* Standalones (Stop codes with -1 for start) */
  for (j = 0; j < as->nmarkups; j++) {
    info = &(as->markup[j]);
    if (info->start == -1 && info->end == i) {
      /* This is a standalone tag. YUCK! */
      if (info->stop_code)
        retval += safe_str(info->stop_code, buff, bp);
    }
  }

  /* Now, start codes of everything that impacts us. */
  for (j = 0; j < as->nmarkups; j++) {
    info = &(as->markup[j]);
    if (info->start >= 0) {
      if (info->start <= i && info->end > i) {
        retval += dump_start_code(info, buff, bp);
      }
    }
  }


  /* Find  the next changes */
  nextstart = BUFFER_LEN + 1;
  nextend = BUFFER_LEN + 1;

  for (j = 0; j < as->nmarkups; j++) {
    info = &(as->markup[j]);
    if (info->start > i && info->start < nextstart)
      nextstart = info->start;
    if (info->end > i && info->end < nextend)
      nextend = info->end;
  }

  next = (nextend < nextstart) ? nextend : nextstart;
  if (end < next)
    next = end;

  if (next > as->len)
    next = as->len;
  escape_strn(as->text, i, next - i, buff, bp);
  i = next;

  while (i < end) {
    if (i >= nextend) {
      nextend = BUFFER_LEN + 2;
      for (j = 0; j < as->nmarkups; j++) {
        info = &(as->markup[j]);
        if (info->end == i) {
          retval += dump_stop_code(info, buff, bp);
        }
        if (info->end > i && info->end < nextend)
          nextend = info->end;
      }
    }
    if (i >= nextstart) {
      nextstart = BUFFER_LEN + 2;
      for (j = 0; j < as->nmarkups; j++) {
        info = &(as->markup[j]);
        if (info->start == i) {
          retval += dump_start_code(info, buff, bp);
        } else if (info->start > i && info->start < nextstart) {
          nextstart = info->start;
        }
      }
    }
    next = (nextend < nextstart) ? nextend : nextstart;
    if (end < next)
      next = end;

    escape_strn(as->text, i, next - i, buff, bp);
    i = next;
  }

  /* Now, find all things that end for us */
  for (j = 0; j < as->nmarkups; j++) {
    info = &(as->markup[j]);
    if (info->start < i && info->end >= i)
      retval += dump_stop_code(info, buff, bp);
  }

  return retval;
}

/** Our version of pcre_copy_substring, with ansi-safeness.
 * \param as the ansi_string whose .text value was matched against.
 * \param ovector the offset vectors
 * \param stringcount the number of subpatterns
 * \param stringnumber the number of the desired subpattern
 * \param buff buffer to copy the subpattern to
 * \param bp pointer to the end of buffer
 * \return size of subpattern, or -1 if unknown pattern
 */
int
ansi_pcre_copy_substring(ansi_string *as, int *ovector, int stringcount,
                         int stringnumber, int nonempty, char *buff, char **bp)
{
  int yield;
  if (stringnumber < 0 || stringnumber >= stringcount)
    return -1;
  stringnumber *= 2;
  yield = ovector[stringnumber + 1] - ovector[stringnumber];
  if (!nonempty || yield) {
    safe_ansi_string(as, ovector[stringnumber], yield, buff, bp);
    **bp = '\0';
  }
  return yield;
}


/** Our version of pcre_copy_named_substring, with ansi-safeness.
 * \param code the pcre compiled code
 * \param as the ansi_string whose .text value was matched against.
 * \param ovector the offset vectors
 * \param stringcount the number of subpatterns
 * \param stringname the name of the desired subpattern
 * \param buff buffer to copy the subpattern to
 * \param bp pointer to the end of buffer
 * \return size of subpattern, or -1 if unknown pattern
 */
int
ansi_pcre_copy_named_substring(const pcre * code, ansi_string *as, int *ovector,
                               int stringcount, const char *stringname, int ne,
                               char *buff, char **bp)
{
  int n = pcre_get_stringnumber(code, stringname);
  if (n <= 0)
    return -1;
  return ansi_pcre_copy_substring(as, ovector, stringcount, n, ne, buff, bp);
}

/** Safely add a tag into a buffer.
 * If we support pueblo, this function adds the tag start token,
 * the tag, and the tag end token. If not, it does nothing.
 * If we can't fit the tag in, we don't put any of it in.
 * \param a_tag the html tag to add.
 * \param buf the buffer to append to.
 * \param bp pointer to address in buf to insert.
 * \retval 0, successfully added.
 * \retval 1, tag wouldn't fit in buffer.
 */
static int
safe_markup(char const *a_tag, char *buf, char **bp, char type)
{
  int result = 0;
  char *save = buf;

  result = safe_chr(TAG_START, buf, bp);
  result = safe_chr(type, buf, bp);
  result = safe_str(a_tag, buf, bp);
  result = safe_chr(TAG_END, buf, bp);
  /* If it didn't all fit, rewind. */
  if (result)
    *bp = save;

  return result;
}

int
safe_tag(char const *a_tag, char *buff, char **bp)
{
  if (SUPPORT_PUEBLO)
    return safe_markup(a_tag, buff, bp, MARKUP_HTML);
  return 0;
}

/** Safely add a closing tag into a buffer.
 * If we support pueblo, this function adds the tag start token,
 * a slash, the tag, and the tag end token. If not, it does nothing.
 * If we can't fit the tag in, we don't put any of it in.
 * \param a_tag the html tag to add.
 * \param buf the buffer to append to.
 * \param bp pointer to address in buf to insert.
 * \retval 0, successfully added.
 * \retval 1, tag wouldn't fit in buffer.
 */
static int
safe_markup_cancel(char const *a_tag, char *buf, char **bp, char type)
{
  int result = 0;
  char *save = buf;

  result = safe_chr(TAG_START, buf, bp);
  result = safe_chr(type, buf, bp);
  result = safe_chr('/', buf, bp);
  result = safe_str(a_tag, buf, bp);
  result = safe_chr(TAG_END, buf, bp);
  /* If it didn't all fit, rewind. */
  if (result)
    *bp = save;

  return result;
}

int
safe_tag_cancel(char const *a_tag, char *buf, char **bp)
{
  if (SUPPORT_PUEBLO)
    return safe_markup_cancel(a_tag, buf, bp, MARKUP_HTML);
  return 0;
}

/** Safely add a tag, some text, and a matching closing tag into a buffer.
 * If we can't fit the stuff, we don't put any of it in.
 * \param a_tag the html tag to add.
 * \param params tag parameters.
 * \param data the text to wrap the tag around.
 * \param buf the buffer to append to.
 * \param bp pointer to address in buf to insert.
 * \param player the player involved in all this, or NOTHING if internal.
 * \retval 0, successfully added.
 * \retval 1, tagged text wouldn't fit in buffer.
 */
int
safe_tag_wrap(char const *a_tag, char const *params, char const *data,
              char *buf, char **bp, dbref player)
{
  int result = 0;
  char *save = buf;

  if (SUPPORT_PUEBLO) {
    result = safe_chr(TAG_START, buf, bp);
    result = safe_chr(MARKUP_HTML, buf, bp);
    result = safe_str(a_tag, buf, bp);
    if (params && *params && ok_tag_attribute(player, params)) {
      result = safe_chr(' ', buf, bp);
      result = safe_str(params, buf, bp);
    }
    result = safe_chr(TAG_END, buf, bp);
  }
  result = safe_str(data, buf, bp);
  if (SUPPORT_PUEBLO) {
    result = safe_tag_cancel(a_tag, buf, bp);
  }
  /* If it didn't all fit, rewind. */
  if (result)
    *bp = save;
  return result;
}

/** Initialize the html tag hash table with all the safe tags from HTML 4.0 */
void
init_tag_hashtab(void)
{
  static char dummy = 1;
  int i = 0;
  static const char *safetags[] = { "A", "B", "I", "U", "STRONG", "EM",
    "ADDRESS", "BLOCKQUOTE", "CENTER", "DEL", "DIV",
    "H1", "H2", "H3", "H4", "H5", "H6", "HR", "INS",
    "P", "PRE", "DIR", "DL", "DT", "DD", "LI", "MENU", "OL", "UL",
    "TABLE", "CAPTION", "COLGROUP", "COL", "THEAD", "TFOOT",
    "TBODY", "TR", "TD", "TH",
    "BR", "FONT", "IMG", "SPAN", "SUB", "SUP",
    "ABBR", "ACRONYM", "CITE", "CODE", "DFN", "KBD", "SAMP", "VAR",
    "BIG", "S", "SMALL", "STRIKE", "TT",
    NULL
  };
  hashinit(&htab_tag, 64, 1);
  while (safetags[i]) {
    hashadd(safetags[i], (void *) &dummy, &htab_tag);
    i++;
  }
}