view th_string.c @ 240:5e781dba6136

Some preparation for %f support .. if it ever happens.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 16 Feb 2016 16:10:41 +0200
parents 10f596441e75
children d0bf513f2118
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 inline 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_vput_itoa(void *ctx, int (*vputch)(void *ctx, const char ch),
    int val, const int radix, const char padMode, const int width,
    const BOOL unsig, const BOOL upcase, const BOOL sign)
{
    char buf[64];
    size_t pos = 0;
    BOOL neg = FALSE;
    int ret = 0;

    if (radix > 16)
        return 0;

    // Check for negative value
    if (val < 0)
    {
        neg = TRUE;
        val = -val;
    }

    // Render the value to a string in buf (reversed)
    do
    {
        int digit = val % radix;
        if (digit < 10)
            buf[pos] = '0' + digit;
        else
            buf[pos] = (upcase ? 'A' : 'a') + digit - 10;
        val /= radix;
        pos++;
    }
    while (val > 0 && pos < sizeof(buf) - 1);
    buf[pos] = 0;

    // Oops, the value did not fit in the buffer!
    if (val > 0)
        return -1;

    // Do we want a sign prefix? Not for unsigned values
    if (!unsig)
    {
        char ch = sign ? (neg ? '-' : '+') : (neg ? '-' : 0);
        if (ch && (ret = vputch(ctx, ch)) == EOF)
            goto out;
    }

    // Calculate necessary padding, if any
    int nwidth = width - pos;

    // Suffix padding?
    if (padMode != '-' && nwidth > 0)
    {
        while (nwidth--)
        {
            if ((ret = vputch(ctx, padMode)) == EOF)
                goto out;
        }
    }

    // Output the value
    while (pos--)
    {
        if ((ret = vputch(ctx, buf[pos])) == EOF)
            goto out;
    }

    // Postfix padding?
    if (padMode == '-' && nwidth > 0)
    {
        while (nwidth--)
        {
            if ((ret = vputch(ctx, ' ')) == EOF)
                goto out;
        }
    }

out:
    return ret;
}


static int th_vput_str(void *ctx, int (*vputch)(void *ctx, const char ch),
    const char *str, const char padMode, const int width)
{
    int nwidth = width - strlen(str);
    int ret = 0;

    if (padMode != '-' && nwidth > 0)
    {
        while (nwidth--)
        {
            if ((ret = vputch(ctx, padMode)) == EOF)
                goto out;
        }
    }

    while (*str)
    {
        if ((ret = vputch(ctx, *str++)) == EOF)
            goto out;
    }

    if (padMode == '-' && nwidth > 0)
    {
        while (nwidth--)
        {
            if ((ret = vputch(ctx, ' ')) == EOF)
                goto out;
        }
    }

out:
    return ret;
}


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

    while (*fmt)
    {
        if (*fmt != '%')
        {
            if ((ret = vputch(ctx, *fmt)) == EOF)
                return ret;
        }
        else
        {
            char padMode = ' ', padChar = 0;
            BOOL sign = FALSE;
            int width = 0, prec = 0;

            fmt++;

            if (*fmt == '+')
            {
                sign = TRUE;
                fmt++;
            }

            if (*fmt == '0' || *fmt == '-' || *fmt == '\'')
            {
                padMode = *fmt++;
                if (padMode == '\'')
                {
                    padChar = *fmt++;
                    if (*fmt != '\'')
                        goto out;
                    fmt++;
                }

                if (*fmt == 0)
                    goto out;
            }

            while (th_isdigit(*fmt))
                width = width * 10 + (*fmt++ - '0');

            if (*fmt == '.')
            {
                fmt++;
                if (!th_isdigit(*fmt))
                    goto out;

                while (th_isdigit(*fmt))
                    prec = prec * 10 + (*fmt++ - '0');
            }

            switch (*fmt)
            {
                case '%':
                    vputch(ctx, *fmt);
                    break;

                case 0:
                    goto out;

                case 'u':
                case 'd':
                    if (padMode != '0' && padMode != '-' && padMode != ' ')
                        goto out;

                    if ((ret = th_vput_itoa(ctx, vputch, va_arg(ap, unsigned int), 10, padMode, width, *fmt == 'u', FALSE, sign)) == EOF)
                        goto out;
                    break;

                case 'x':
                case 'X':
                    if (padMode != '0' && padMode != '-' && padMode != ' ')
                        goto out;

                    if ((ret = th_vput_itoa(ctx, vputch, va_arg(ap, unsigned int), 16, padMode, width, TRUE, *fmt == 'X', FALSE)) == EOF)
                        goto out;
                    break;

                case 'f':
                    goto out;
                    break;

                case 's':
                    if ((padMode != '-' && padMode != ' ') || sign)
                        goto out;

                    if ((ret = th_vput_str(ctx, vputch, va_arg(ap, char *), padMode, width)) == EOF)
                        goto out;
                    break;

                default:
                    vputch(ctx, *fmt);
                    break;
            }
        }
        fmt++;
    }
out:
    return ret;
}


#ifdef TH_USE_INTERNAL_SPRINTF
typedef struct
{
    char *buf;
    size_t size, pos;
} th_pbuf_ctx;


static int th_pbuf_vputch(void *pctx, const char ch)
{
    th_pbuf_ctx *ctx = (th_pbuf_ctx *) pctx;
    if (ctx->pos < ctx->size)
        ctx->buf[ctx->pos] = ch;
    ctx->pos++;
    return ch;
}


static int th_stdio_vputch(void *ctx, const char ch)
{
    return fputc(ch, (FILE *) ctx);
}
#endif


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

    return th_vprintf_do((void *) &ctx, th_pbuf_vputch, fmt, ap);
#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
    return th_vprintf_do((void *) fh, th_stdio_vputch, fmt, ap);
#else
    return vfprintf(fh, fmt, ap);
#endif
}


int th_fprintf(FILE *fh, const char *fmt, ...)
{
    int ret;
    va_list ap;
    va_start(ap, fmt);
#ifdef TH_USE_INTERNAL_SPRINTF
    ret = th_vprintf_do((void *) fh, 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 ((buf = th_malloc(size)) == NULL)
        return NULL;

    while (1)
    {
        int n;
        va_list ap;
        va_copy(ap, args);
#ifdef TH_USE_INTERNAL_SPRINTF
        n = th_vsnprintf(buf, size, fmt, ap);
#else
        n = vsnprintf(buf, size, fmt, ap);
#endif
        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 && th_tolower(*s1) == th_tolower(*s2))
    {
        s1++;
        s2++;
    }

    return th_tolower(*s1) - th_tolower(*s2);
}


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 && th_tolower(*s1) == th_tolower(*s2))
    {
        s1++;
        s2++;
        n--;
    }

    return n > 0 ? (th_tolower(*s1) - th_tolower(*s2)) : 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");
    }
}