view th_string.c @ 374:1d8ae82304ec

Rename s/th_printf_ctx/th_vprintf_cts/g.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 29 Feb 2016 16:12:10 +0200
parents 22fb23dc8c9b
children 04a273cce4fa
line wrap: on
line source

/*
 * Miscellaneous string-handling related utility-functions
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2002-2016 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include "th_util.h"
#include "th_string.h"


/* Implementation of strdup() with a NULL check
 */
char *th_strdup(const char *src)
{
    char *res;
    if (src == NULL)
        return NULL;

    if ((res = th_malloc(strlen(src) + 1)) == NULL)
        return NULL;

    strcpy(res, src);
    return res;
}


/* Implementation of strndup() with NULL check
 */
char *th_strndup(const char *src, const size_t n)
{
    char *res;
    size_t len;

    if (src == NULL)
        return NULL;

    len = strlen(src);
    if (len > n)
        len = n;

    if ((res = th_malloc(len + 1)) == NULL)
        return NULL;

    memcpy(res, src, len);
    res[len] = 0;

    return res;
}


/* Like strdup, but trims whitespace from the string according to specified flags.
 * See TH_TRIM_* in th_string.h. If the resulting string would be empty (length 0),
 * NULL is returned.
 */
static char * th_strdup_trim_do(const char *src, size_t len, const int flags)
{
    char *res;
    size_t start, end;

    if (len == 0)
        return NULL;

    // Trim start: find first non-whitespace character
    if (flags & TH_TRIM_START)
        for (start = 0; start < len && th_isspace(src[start]); start++);
    else
        start = 0;

    // Trim end: find last non-whitespace character
    if (flags & TH_TRIM_END)
        for (end = len - 1; end > start && th_isspace(src[end]); end--);
    else
        end = len;

    // Allocate memory for result
    if (src[end] == 0 || th_isspace(src[end]))
        return NULL;

    len = end - start + 1;
    if ((res = th_malloc(len + 1)) == NULL)
        return NULL;

    memcpy(res, src + start, len);
    res[len] = 0;
    return res;
}


char *th_strdup_trim(const char *src, const int flags)
{
    if (src == NULL)
        return NULL;

    return th_strdup_trim_do(src, strlen(src), flags);
}


char *th_strndup_trim(const char *src, const size_t n, const int flags)
{
    size_t len;
    if (src == NULL || n == 0)
        return NULL;

    for (len = 0; len < n && src[len]; len++);

    return th_strdup_trim_do(src, len, flags);
}


//
// Simple implementations of printf() type functions
//
static int th_vprintf_put_pstr(th_vprintf_ctx *ctx, th_vprintf_putch vputch, const char *str)
{
    while (*str)
    {
        int ret;
        if ((ret = vputch(ctx, *str++)) == EOF)
            return ret;
    }
    return 0;
}


static int th_vprintf_put_repch(th_vprintf_ctx *ctx, th_vprintf_putch vputch, int count, const char ch)
{
    while (count-- > 0)
    {
        int ret;
        if ((ret = vputch(ctx, ch)) == EOF)
            return ret;
    }
    return 0;
}


static int th_printf_pad_pre(th_vprintf_ctx *ctx, th_vprintf_putch vputch,
    int f_width, const int f_flags)
{
    if (f_width > 0 && (f_flags & TH_PF_LEFT) == 0)
        return th_vprintf_put_repch(ctx, vputch, f_width, ' ');
    else
        return 0;
}


static int th_printf_pad_post(th_vprintf_ctx *ctx, th_vprintf_putch vputch,
    int f_width, const int f_flags)
{
    if (f_width > 0 && (f_flags & TH_PF_LEFT))
        return th_vprintf_put_repch(ctx, vputch, f_width, ' ');
    else
        return 0;
}


#define TH_PFUNC_NAME th_printf_vbuf_int
#define TH_PFUNC_TYPE_S int
#define TH_PFUNC_TYPE_U unsigned int
#include "th_printf1.c"


#define TH_PFUNC_NAME th_printf_vbuf_int64
#define TH_PFUNC_TYPE_S int64_t
#define TH_PFUNC_TYPE_U uint64_t
#include "th_printf1.c"


static int th_vprintf_put_int(th_vprintf_ctx *ctx, th_vprintf_putch vputch,
    va_list ap, const int f_radix, int f_flags, int f_width, int f_prec,
    const BOOL f_unsig, char *(f_alt)(const int flags))
{
    char buf[64];
    int f_len = 0, ret = 0, vret, nwidth;
    char f_sign, *f_altstr;
    BOOL f_neg = FALSE;

    if (f_flags & TH_PF_LONGLONG)
    {
        vret = th_printf_vbuf_int64(buf, sizeof(buf), &f_len, va_arg(ap, int64_t),
            f_radix, f_flags & TH_PF_UPCASE, f_unsig, &f_neg);
    }
    else
    {
       vret = th_printf_vbuf_int(buf, sizeof(buf), &f_len, va_arg(ap, unsigned int),
            f_radix, f_flags & TH_PF_UPCASE, f_unsig, &f_neg);
    }

    if (vret == EOF)
        return ret;

    // Special case for value of 0
    if (vret == 0)
    {
        if (f_flags & TH_PF_POINTER)
        {
            strcpy(buf, ")lin(");
            f_len = 5;
            f_flags &= ~TH_PF_ZERO;
        }
        else
        if (f_prec != 0)
        {
            buf[f_len++] = '0';
            buf[f_len] = 0;
        }
    }

    f_altstr = vret != 0 && (f_flags & TH_PF_ALT) && f_alt != NULL ? f_alt(f_flags) : NULL;

    // Are we using a sign prefix?
    f_sign = f_unsig ? 0 : ((f_flags & TH_PF_SIGN) ?
        (f_neg ? '-' : '+') :
        (f_neg ? '-' : ((f_flags & TH_PF_SPACE) ? ' ' : 0)));

    // Calculate necessary padding, etc
    if (f_sign)
        f_width--;

    if ((f_flags & TH_PF_ZERO) && f_prec < 0 && (f_flags & TH_PF_LEFT) == 0)
        f_prec = f_width;

    f_prec = (f_prec > f_len) ? f_prec - f_len : 0;
    nwidth = f_width - f_len - f_prec - (f_altstr ? strlen(f_altstr) : 0);

    // Prefix padding?
    if ((ret = th_printf_pad_pre(ctx, vputch, nwidth, f_flags)) == EOF)
        return ret;

    // Do we want a sign prefix? Not for unsigned values
    if (f_sign && (ret = vputch(ctx, f_sign)) == EOF)
        return ret;

    if (f_altstr && (ret = th_vprintf_put_pstr(ctx, vputch, f_altstr)) == EOF)
        return ret;

    if (f_prec > 0 && (ret = th_vprintf_put_repch(ctx, vputch, f_prec, '0')) == EOF)
        return ret;

    // Output the value
    while (f_len-- > 0)
    {
        if ((ret = vputch(ctx, buf[f_len])) == EOF)
            return ret;
    }

    // Postfix padding?
    return th_printf_pad_post(ctx, vputch, nwidth, f_flags);
}


static int th_vprintf_put_str(th_vprintf_ctx *ctx, th_vprintf_putch vputch,
    const char *str, int f_flags, const int f_width, const int f_prec)
{
    int nwidth, f_len, ret = 0;

    // Check for null strings
    if (str == NULL)
        str = "(null)";

    f_len = strlen(str);
    if (f_prec >= 0 && f_len > f_prec)
        f_len = f_prec;

    nwidth = f_width - f_len;

    // Prefix padding?
    if ((ret = th_printf_pad_pre(ctx, vputch, nwidth, f_flags)) == EOF)
        return ret;

    while (*str && f_len--)
    {
        if ((ret = vputch(ctx, *str++)) == EOF)
            return ret;
    }

    // Postfix padding?
    return th_printf_pad_post(ctx, vputch, nwidth, f_flags);
}


static char * th_printf_altfmt_oct(const int flags)
{
    (void) flags;
    return "0";
}


static char * th_printf_altfmt_hex(const int flags)
{
    return (flags & TH_PF_UPCASE) ? "0X" : "0x";
}


int th_vprintf_do(th_vprintf_ctx *ctx, th_vprintf_putch vputch, const char *fmt, va_list ap)
{
    int ret = 0;

    while (*fmt)
    {
        if (*fmt != '%')
        {
            if ((ret = vputch(ctx, *fmt)) == EOF)
                goto out;
        }
        else
        {
            int f_width = 0, f_prec = -1, f_flags = 0;
            BOOL end = FALSE;

            fmt++;

            // Check for flags
            while (!end)
            {
                switch (*fmt)
                {
                    case '#':
                        f_flags |= TH_PF_ALT;
                        break;

                    case '+':
                        f_flags |= TH_PF_SIGN;
                        break;

                    case '0':
                        f_flags |= TH_PF_ZERO;
                        break;

                    case '-':
                        f_flags |= TH_PF_LEFT;
                        break;

                    case ' ':
                        f_flags |= TH_PF_SPACE;
                        break;

                    case '\'':
                        f_flags |= TH_PF_GROUP;
                        break;

                    default:
                        end = TRUE;
                        break;
                }
                if (!end) fmt++;
            }

            // Get field width
            while (th_isdigit(*fmt))
                f_width = f_width * 10 + (*fmt++ - '0');

            // Check for field precision
            if (*fmt == '.')
            {
                fmt++;
                // If no digit after '.', precision is to be 0
                f_prec = 0;
                while (th_isdigit(*fmt))
                    f_prec = f_prec * 10 + (*fmt++ - '0');
            }

            // Check for length modifiers (NOT SUPPORTED CURRENTLY)
            switch (*fmt)
            {
                case 'l':
                    if (*++fmt == 'l')
                    {
                        f_flags |= TH_PF_LONGLONG;
                        fmt++;
                    }
                    else
                        f_flags |= TH_PF_LONG;
                    break;

                case 'L':
                    fmt++;
                    f_flags |= TH_PF_LONGLONG;
                    break;

                case 'h':
                case 'j':
                case 'z':
                case 't':
                    return -202;
            }

            switch (*fmt)
            {
                case 0:
                    return -104;

                case 'c':
                    if ((ret = th_printf_pad_pre(ctx, vputch, f_width - 1, f_flags)) == EOF)
                        goto out;
                    if ((ret = vputch(ctx, va_arg(ap, int))) == EOF)
                        goto out;
                    if ((ret = th_printf_pad_post(ctx, vputch, f_width - 1, f_flags)) == EOF)
                        goto out;
                    break;

                case 'o':
                    if ((ret = th_vprintf_put_int(ctx, vputch, ap, 8, f_flags, f_width, f_prec, TRUE, th_printf_altfmt_oct)) == EOF)
                        goto out;
                    break;

                case 'u':
                case 'i':
                case 'd':
                    if ((ret = th_vprintf_put_int(ctx, vputch, ap, 10, f_flags, f_width, f_prec, *fmt == 'u', NULL)) == EOF)
                        goto out;
                    break;

                case 'x':
                case 'X':
                    if (*fmt == 'X')
                        f_flags |= TH_PF_UPCASE;
                    if ((ret = th_vprintf_put_int(ctx, vputch, ap, 16, f_flags, f_width, f_prec, TRUE, th_printf_altfmt_hex)) == EOF)
                        goto out;
                    break;

                case 'p':
                    if (f_flags & (TH_PF_LONG | TH_PF_LONGLONG))
                        return -120;

#if (TH_PTRSIZE == 32)
                    f_flags |= TH_PF_LONG;
#elif (TH_PTRSIZE == 64)
                    f_flags |= TH_PF_LONGLONG;
#endif
                    f_flags |= TH_PF_ALT | TH_PF_POINTER;
                    if ((ret = th_vprintf_put_int(ctx, vputch, ap, 16, f_flags, f_width, f_prec, TRUE, th_printf_altfmt_hex)) == EOF)
                        goto out;
                    break;

                case 'f':
                case 'F':
                    return -112;
/*
                    if ((ret = th_vprintf_put_float(ctx, vputch, ap,
                        f_flags, f_width, f_prec)) == EOF)
                        goto out;
                    break;
*/

                case 's':
                    if ((ret = th_vprintf_put_str(ctx, vputch, va_arg(ap, char *),
                        f_flags, f_width, f_prec)) == EOF)
                        goto out;
                    break;

                //case '%':
                default:
                    if ((ret = vputch(ctx, *fmt)) == EOF)
                        goto out;
                    break;
            }
        }
        fmt++;
    }

out:
    return ret == EOF ? ret : ctx->ipos;
}


#ifdef TH_USE_INTERNAL_SPRINTF
static int th_pbuf_vputch(th_vprintf_ctx *ctx, const char ch)
{
    if (ctx->pos < ctx->size)
        ctx->buf[ctx->pos] = ch;

    ctx->pos++;
    ctx->ipos++;
    return ch;
}


static int th_stdio_vputch(th_vprintf_ctx *ctx, const char ch)
{
    ctx->pos++;
    ctx->ipos++;
    return fputc(ch, (FILE *) ctx->data);
}
#endif


int th_vsnprintf(char *buf, size_t size, const char *fmt, va_list ap)
{
#ifdef TH_USE_INTERNAL_SPRINTF
    int ret;
    th_vprintf_ctx ctx;
    ctx.buf = buf;
    ctx.size = size;
    ctx.pos = 0;
    ctx.ipos = 0;

    ret = th_vprintf_do(&ctx, th_pbuf_vputch, fmt, ap);

    if (ctx.pos < size)
        buf[ctx.pos] = 0;
    else
    if (size > 0)
        buf[size - 1] = 0;

    return ret;
#else
    return vsnprintf(buf, size, fmt, ap);
#endif
}


int th_snprintf(char *buf, size_t size, const char *fmt, ...)
{
    int n;
    va_list ap;
    va_start(ap, fmt);
#ifdef TH_USE_INTERNAL_SPRINTF
    n = th_vsnprintf(buf, size, fmt, ap);
#else
    n = vsnprintf(buf, size, fmt, ap);
#endif
    va_end(ap);
    return n;
}


int th_vfprintf(FILE *fh, const char *fmt, va_list ap)
{
#ifdef TH_USE_INTERNAL_SPRINTF
    th_vprintf_ctx ctx;
    ctx.data = (void *) fh;
    ctx.pos = 0;
    ctx.ipos = 0;

    return th_vprintf_do(&ctx, th_stdio_vputch, fmt, ap);
#else
    return vfprintf(fh, fmt, ap);
#endif
}


int th_fprintf(FILE *fh, const char *fmt, ...)
{
    int ret;
#ifdef TH_USE_INTERNAL_SPRINTF
    th_vprintf_ctx ctx;
#endif
    va_list ap;
    va_start(ap, fmt);
#ifdef TH_USE_INTERNAL_SPRINTF
    ctx.data = (void *) fh;
    ctx.pos = 0;
    ctx.ipos = 0;

    ret = th_vprintf_do(&ctx, th_stdio_vputch, fmt, ap);
#else
    ret = fprintf(fh, fmt, ap);
#endif
    va_end(ap);
    return ret;
}


/* Simulate a sprintf() that allocates memory
 */
char *th_strdup_vprintf(const char *fmt, va_list args)
{
    int size = 64;
    char *buf, *tmp;

    if (fmt == NULL)
        return NULL;

    if ((buf = th_malloc(size)) == NULL)
        return NULL;

    while (1)
    {
        int n;
        va_list ap;
        va_copy(ap, args);
        n = vsnprintf(buf, size, fmt, ap);
        va_end(ap);

        if (n > -1 && n < size)
            return buf;
        if (n > -1)
            size = n + 1;
        else
            size *= 2;

        if ((tmp = th_realloc(buf, size)) == NULL)
        {
            th_free(buf);
            return NULL;
        }
        else
            buf = tmp;
    }
}


char *th_strdup_printf(const char *fmt, ...)
{
    char *res;
    va_list ap;

    va_start(ap, fmt);
    res = th_strdup_vprintf(fmt, ap);
    va_end(ap);

    return res;
}


void th_pstr_vprintf(char **buf, const char *fmt, va_list ap)
{
    char *tmp = th_strdup_vprintf(fmt, ap);
    th_free(*buf);
    *buf = tmp;
}


void th_pstr_printf(char **buf, const char *fmt, ...)
{
    char *tmp;
    va_list ap;

    va_start(ap, fmt);
    tmp = th_strdup_vprintf(fmt, ap);
    va_end(ap);

    th_free(*buf);
    *buf = tmp;
}


/* Compare two strings ignoring case [strcasecmp, strncasecmp]
 */
int th_strcasecmp(const char *haystack, const char *needle)
{
    const char *s1 = haystack, *s2 = needle;
    assert(haystack != NULL);
    assert(needle != NULL);

    if (haystack == needle)
        return 0;

    while (*s1 && *s2)
    {
        int k = th_tolower(*s1) - th_tolower(*s2);
        if (k != 0)
            return k;
        s1++;
        s2++;
    }

    return 0;
}


int th_strncasecmp(const char *haystack, const char *needle, size_t n)
{
    const char *s1 = haystack, *s2 = needle;
    assert(haystack != NULL);
    assert(needle != NULL);

    if (haystack == needle)
        return 0;

    while (n > 0 && *s1 && *s2)
    {
        int k = th_tolower(*s1) - th_tolower(*s2);
        if (k != 0)
            return k;
        s1++;
        s2++;
        n--;
    }

    return 0;
}


/* Check if end of the given string str matches needle
 * case-insensitively, return pointer to start of the match,
 * if found, NULL otherwise.
 */
char *th_strrcasecmp(char *str, const char *needle)
{
    if (str == NULL || needle == NULL)
        return NULL;

    const size_t
        slen = strlen(str),
        nlen = strlen(needle);

    if (slen < nlen)
        return NULL;

    if (th_strcasecmp(str - nlen - 1, needle) == 0)
        return str - nlen - 1;
    else
        return NULL;
}


/* Remove all occurences of control characters, in-place.
 * Resulting string is always shorter or same length than original.
 */
void th_strip_ctrlchars(char *str)
{
    char *i, *j;
    assert(str != NULL);

    i = str;
    j = str;
    while (*i)
    {
        if (!th_iscntrl(*i))
            *(j++) = *i;
        i++;
    }

    *j = 0;
}


/* Copy a given string over in *pdst.
 */
int th_pstrcpy(char **pdst, const char *src)
{
    assert(pdst != NULL);

    if (src == NULL)
        return -1;

    th_free(*pdst);
    if ((*pdst = th_malloc(strlen(src) + 1)) == NULL)
        return -2;

    strcpy(*pdst, src);
    return 0;
}


/* Concatenates a given string into string pointed by *pdst.
 */
int th_pstrcat(char **pdst, const char *src)
{
    assert(pdst != NULL);

    if (src == NULL)
        return -1;

    if (*pdst != NULL)
    {
        *pdst = th_realloc(*pdst, strlen(*pdst) + strlen(src) + 1);
        if (*pdst == NULL)
            return -1;

        strcat(*pdst, src);
    }
    else
    {
        *pdst = th_malloc(strlen(src) + 1);
        if (*pdst == NULL)
            return -1;

        strcpy(*pdst, src);
    }

    return 0;
}


/* Find next non-whitespace character in string.
 * Updates iPos into the position of such character and
 * returns pointer to the string.
 */
const char *th_findnext(const char *str, size_t *pos)
{
    assert(str != NULL);

    // Terminating NULL-character is not whitespace!
    while (th_isspace(str[*pos]))
        (*pos)++;

    return &str[*pos];
}


/* Find next sep-character from string
 */
const char *th_findsep(const char *str, size_t *pos, char sep)
{
    assert(str != NULL);

    while (str[*pos] && str[*pos] != sep)
        (*pos)++;

    return &str[*pos];
}


/* Find next sep- or whitespace from string
 */
const char *th_findseporspace(const char *str, size_t *pos, char sep)
{
    assert(str != NULL);

    while (!th_isspace(str[*pos]) && str[*pos] != sep)
        (*pos)++;

    return &str[*pos];
}


/* Compare a string to a pattern. Case-SENSITIVE version.
 * The matching pattern can consist of any normal characters plus
 * wildcards ? and *. "?" matches any character and "*" matches
 * any number of characters.
 */
BOOL th_strmatch(const char *haystack, const char *pattern)
{
    BOOL matched = TRUE, any = FALSE, end = FALSE;
    const char *tmp = NULL;

    // Check given pattern and string
    if (haystack == NULL || pattern == NULL)
        return FALSE;

    // Start comparision
    do {
        matched = FALSE;
        switch (*pattern)
        {
        case '?':
            // Any single character matches
            if (*haystack)
            {
                matched = TRUE;
                pattern++;
                haystack++;
            }
            break;

        case '*':
            matched = TRUE;
            pattern++;
            if (!*pattern)
                end = TRUE;
            any = TRUE;
            tmp = pattern;
            break;

        case 0:
            if (any)
            {
                if (*haystack)
                    haystack++;
                else
                    end = TRUE;
            }
            else
            {
                if (*haystack)
                {
                    if (tmp)
                    {
                        any = TRUE;
                        pattern = tmp;
                    }
                    else
                        matched = FALSE;
                }
                else
                    end = TRUE;
            }
            break;

        default:
            if (any)
            {
                if (*pattern == *haystack)
                {
                    any = FALSE;
                    matched = TRUE;
                }
                else
                {
                    if (*haystack)
                    {
                        matched = TRUE;
                        haystack++;
                    }
                }
            }
            else
            {
                if (*pattern == *haystack)
                {
                    matched = TRUE;
                    if (*pattern)
                        pattern++;
                    if (*haystack)
                        haystack++;
                }
                else
                {
                    if (tmp)
                    {
                        matched = TRUE;
                        any = TRUE;
                        pattern = tmp;
                    }
                }
            }

            if (!*haystack && !*pattern)
                end = TRUE;
            break;

        }        // switch
    } while (matched && !end);

    return matched;
}


/* Compare a string to a pattern. Case-INSENSITIVE version.
 */
BOOL th_strcasematch(const char *haystack, const char *pattern)
{
    BOOL matched = TRUE, any = FALSE, end = FALSE;
    const char *tmp = NULL;

    // Check given pattern and string
    if (haystack == NULL || pattern == NULL)
        return FALSE;

    // Start comparision
    do {
        switch (*pattern) {
        case '?':
            // Any single character matches
            if (*haystack)
            {
                pattern++;
                haystack++;
            }
            else
                matched = FALSE;
            break;

        case '*':
            pattern++;
            if (!*pattern || *pattern == '?')
                end = TRUE;
            any = TRUE;
            tmp = pattern;
            break;

        case 0:
            if (any)
            {
                if (*haystack)
                    haystack++;
                else
                    end = TRUE;
            }
            else
            {
                if (*haystack)
                {
                    if (tmp)
                    {
                        any = TRUE;
                        pattern = tmp;
                    }
                    else
                        matched = FALSE;
                }
                else
                    end = TRUE;
            }
            break;

        default:
            if (any)
            {
                if (th_tolower(*pattern) == th_tolower(*haystack))
                {
                    any = FALSE;
                }
                else
                {
                    if (*haystack)
                        haystack++;
                    else
                        matched = FALSE;
                }
            }
            else
            {
                if (th_tolower(*pattern) == th_tolower(*haystack))
                {
                    if (*pattern)
                        pattern++;
                    if (*haystack)
                        haystack++;
                }
                else
                {
                    if (tmp)
                    {
                        any = TRUE;
                        pattern = tmp;
                    }
                    else
                        matched = FALSE;
                }
            }

            if (!*haystack && !*pattern)
                end = TRUE;

            break;

        }        // switch
    } while (matched && !end);

    return matched;
}


int th_get_hex_triplet(const char *str)
{
    const char *p = str;
    int len, val = 0;

    for (len = 0; *p && len < 6; p++, len++)
    {
        if (*p >= '0' && *p <= '9')
        {
            val *= 16;
            val += (*p - '0');
        }
        else
        if (*p >= 'A' && *p <= 'F')
        {
            val *= 16;
            val += (*p - 'A') + 10;
        }
        else
        if (*p >= 'a' && *p <= 'f')
        {
            val *= 16;
            val += (*p - 'a') + 10;
        }
        else
            return -1;
    }

    return (len == 6) ? val : -1;
}


BOOL th_get_boolean(const char *str, BOOL *value)
{
    if (!th_strcasecmp(str, "yes") ||
        !th_strcasecmp(str, "on") ||
        !th_strcasecmp(str, "true") ||
        !th_strcasecmp(str, "1"))
    {
        *value = TRUE;
        return TRUE;
    }
    else
    if (!th_strcasecmp(str, "no") ||
        !th_strcasecmp(str, "off") ||
        !th_strcasecmp(str, "false") ||
        !th_strcasecmp(str, "0"))
    {
        *value = FALSE;
        return TRUE;
    }
    else
        return FALSE;
}


static void th_pad(FILE *outFile, int count)
{
    while (count--)
        fputc(' ', outFile);
}


void th_print_wrap(FILE *fh, const char *str, int spad, int rpad, int width)
{
    size_t pos = 0;
    BOOL first = TRUE;

    while (str[pos])
    {
        // Pre-pad line
        int linelen = first ? spad : rpad;
        th_pad(fh, first ? 0 : rpad);
        first = FALSE;

        // Skip whitespace at line start
        while (th_isspace(str[pos]) || str[pos] == '\n') pos++;

        // Handle each word
        while (str[pos] && str[pos] != '\n')
        {
            size_t next;
            int wlen;

            // Find word length and next break
            for (wlen = 0, next = pos; str[next] && !th_isspace(str[next]) && str[next] != '\n'; next++, wlen++);

            // Check if we have too much of text?
            if (linelen + wlen >= width)
                break;

            // Print what we have
            for (;pos < next; pos++, linelen++)
                fputc(str[pos], fh);

            // Check if we are at end of input or hard linefeed
            if (str[next] == '\n' || str[next] == 0)
                break;
            else
            {
                fputc(str[pos], fh);
                pos++;
                linelen++;
            }
        }
        fprintf(fh, "\n");
    }
}