* str: added str_ichr, str_irchr, str_skip, str_split, str_[lr]trim, str_clean, str_t...
authorUrban Wallasch <urban.wallasch@freenet.de>
Mon, 28 Oct 2019 16:31:36 +0000 (17:31 +0100)
committerUrban Wallasch <urban.wallasch@freenet.de>
Mon, 28 Oct 2019 16:31:36 +0000 (17:31 +0100)
str/str.h
str/str_test.c

index db350ec9a4d59594b291307b5106e4468781e9fd..04ce8782f22c13d85681d41f0f24862d747ee1f3 100644 (file)
--- a/str/str.h
+++ b/str/str.h
@@ -9,7 +9,8 @@
  * str_icmp,
  * str_nicmp - compare two strings ignoring case
  *
- * str_delim - locate the first occurrence of a delimiter
+ * str_ichr,
+ * str_irchr - locate character in string ignoring case
  *
  * str_istr  - locate a substring ignoring case
  *
  * mem_str,
  * mem_istr  - locate a pattern in memory
  *
+ * str_delim - locate the first occurrence of a delimiter in a string
+
+ * str_skip  - skip initial sequence of specified characters in a string
+
+ * str_split - break down a string into separate parts
+ *
+ * str_ltrim,
+ * str_rtrim,
+ * str_trim  - remove sequences of characters from the ends of a string
+ *
+ * str_clean - remove unwanted characters from string
+ *
+ * str_trans - replace characters in string
+ *
+ * str_toupper,
+ * str_tolower - convert case of characters in string
+ *
  */
 
 #ifndef STR_H_
@@ -28,6 +46,7 @@ extern "C" {
 
 #include <string.h>
 #include <ctype.h>
+#include <stdbool.h>
 
 
 /*
@@ -67,19 +86,34 @@ static inline int str_nicmp(const char *s1, const char *s2, size_t n) {
 
 
 /*
- * str_delim - locate the first occurrence of a delimiter
+ * str_ichr, str_irchr - locate character in string ignoring case
  *
- * The str_delim() function finds the first occurrence in the string s
- * of one of the characters in the string delim.
+ * The str_ichr() function looks for the first occurrence of the character
+ * c in the string s, ignoring the case of both s and c.  The str_irchr()
+ * function is similar, but looks for the last occurrence of c in s.
  *
- * The function returns a pointer to the first occurrence of one of the
- * specified delimiters, or a pointer to the terminating null character
- * at the end of s, if no such occurrence was found.
+ * The str_ichr() and str_irchr() functions return a pointer to the
+ * matched character, or NULL if the character is not found.  The
+ * terminating null byte is considered part of the string, so that if c
+ * is specified as '\0', these function return a pointer to the terminator.
  */
-static inline char *str_delim(const char *s, const char *delim) {
-    while (!strchr(delim, *s))
-        ++s;
-    return (char *)s;
+static inline char *str_ichr(const char *s, int c) {
+    const unsigned char lc = tolower((unsigned char)c);
+    do {
+        if (tolower((unsigned char)*s) == lc)
+            return (char *)s;
+    } while ( *s++ );
+    return NULL;
+}
+
+static inline char *str_irchr(const char *s, int c) {
+    const unsigned char lc = tolower((unsigned char)c);
+    const char *last = NULL;
+    do {
+        if (tolower((unsigned char)*s) == lc)
+            last = s;
+    } while ( *s++ );
+    return (char *)last;
 }
 
 
@@ -153,6 +187,191 @@ static inline void *mem_istr(const void *haystack, size_t h, const char *needle)
     return NULL;
 }
 
+
+/*
+ * str_delim - locate the first occurrence of a delimiter
+ *
+ * The str_delim() function finds the first occurrence in the string s
+ * of one of the characters found in dset, or not found in dset, depending
+ * on the value of notin.
+ *
+ * The function returns a pointer to the first occurrence of one of the
+ * specified delimiters, or a pointer to the terminating null character
+ * at the end of s, if no such occurrence was found.
+ */
+static inline char *str_delim(const char *s, const char *dset, bool notin) {
+    while (*s && (!strchr(dset, *s) ^ notin))
+        ++s;
+    return (char *)s;
+}
+
+
+/*
+ * str_skip - skip initial sequence of characters
+ *
+ * The str_skip() function skips any initial sequence in s that consists
+ * entirely of characters found in skipset, or not found in skipset,
+ * depending on the value of notin.
+ *
+ * The function returns a pointer to the first occurrence of a character
+ * not in skipset, or a pointer to the terminating null character at the
+ * end of s, if no such character could be found.
+ */
+static inline char *str_skip(const char *s, const char *skipset, bool notin) {
+    while (*s && (!!strchr(skipset, *s) ^ notin))
+        ++s;
+    return (char *)s;
+}
+
+
+/*
+ * str_split - break down a string into separate parts
+ *
+ * The str_split() function looks for the first occurrence in the string
+ * s of one of the characters in the string sepset.  If len is not NULL,
+ * it is used to store the length of the initial sequence of characters
+ * leading up to this first separator.  If the STR_SPLIT_DESTR flag is
+ * set, this separator is overwritten with a zero byte.  If the
+ * STR_SPLIT_FOLD flag is set, sequences consisting of only separators
+ * are treated as a single separator.
+ *
+ * The str_split() function returns a pointer to the initial sequence, or
+ * a pointer to the terminating null character at the end of s.  If len
+ * is not NULL it is used to store the length of this sequence up to the
+ * first separator.  If next is not NULL, it is used to store a pointer
+ * to the next character after the first separator.  This pointer can be
+ * used in subsequent invocations of str_split() as the first argument
+ * to successively traverse the original input sting in its entirety.
+ *
+ * CAVEAT:
+ * When the STR_SPLIT_DESTR flag is set, the str_split function modifies
+ * the string pointed to by the first argument.
+ *
+ * EXAMPLE:
+ * A very simplistic word extractor might look like this:
+ *
+ *   char input[] = ">> Hello, world!  How are you? <<";
+ *   char *word, *next = input;
+ *   size_t len;
+ *   while (*next) {
+ *       word = str_split(next, " .,!?><", &len, &next,
+ *                        STR_SPLIT_FOLD | STR_SPLIT_DESTR);
+ *       printf("'%s' (%zu)\n", word, len);
+ *   }
+ *
+ */
+#define STR_SPLIT_FOLD      0x1U
+#define STR_SPLIT_DESTR     0x2U
+
+static inline char *str_split(const char *s, const char *sepset,
+                              size_t *len, char **next, unsigned flags) {
+    char *p;
+
+    if (flags & STR_SPLIT_FOLD)
+        s = str_skip(s, sepset, 0);
+    p = str_delim(s, sepset, 0);
+    if (len)
+        *len = (size_t)(p - s);
+    if (*p) {
+        if ((flags & STR_SPLIT_DESTR))
+            *p = '\0';
+        ++p;
+    }
+    if (next)
+        *next = p;
+    return (char *)s;
+}
+
+
+/*
+ * str_ltrim, str_rtrim, str_trim -
+ *   remove unwanted sequences of characters from the ends of a string
+ *
+ * The str_ltrim() function removes from the string s any initial sequence
+ * that consists entirely of characters found in trimset, or not found
+ * in trimset dset, depending on the value of notin.  The str_rtrim()
+ * function is similar, except it removes any such sequence from the end
+ * of the string.  The str_trim() function performs both operations back
+ * to back.
+ *
+ * The functions each return a pointer to the modified string.
+ */
+static inline char *str_ltrim(char *s, const char *trimset, bool notin) {
+    char *ls = str_skip(s, trimset, notin);
+    return memmove(s, ls, strlen(ls) + 1);
+}
+
+static inline char *str_rtrim(char *s, const char *trimset, bool notin) {
+    *(str_delim(s, trimset, notin)) = '\0';
+    return s;
+}
+
+static inline char *str_trim(char *s, const char *trimset, bool notin) {
+    return str_rtrim(str_ltrim(s, trimset, notin), trimset, notin);
+}
+
+
+/*
+ * str_clean - remove unwanted characters from string
+ *
+ * The str_clean() function removes from the string s any initial sequence
+ * that consists entirely of characters found in delset, or not found in
+ * delset, depending on the value of notin.
+ *
+ * The function returns a pointer to the modified string.
+ */
+static inline char *str_clean(char *s, const char *delset, bool notin) {
+    char *cs = s;
+    for (char *p = cs; *p; ++p )
+        if ( !strchr(delset, *p) ^ notin )
+            *cs++ = *p;
+    *cs = '\0';
+    return s;
+}
+
+
+/*
+ * str_trans - replace characters in string
+ *
+ * The str_trans() function replaces any occurrence in string s of a
+ * chracter that is present in set1 with the corresponding character
+ * from set2.
+ *
+ * CAVEAT:
+ * If set2 is shorter than set1, the behavior is undefined.
+ */
+static inline char *str_trans(char *s, const char *set1, const char *set2) {
+    for (char *cs = s; *cs; ++cs) {
+        char *match;
+        match = strchr(set1, *cs);
+        if (match)
+            *cs = set2[match - set1];
+    }
+    return s;
+}
+
+
+/*
+ * str_toupper, str_tolower - convert case of characters in string
+ *
+ * The str_toupper() function replaces any character in s for which
+ * isupper() is true with its lowercase counterpart.  The str_tolower()
+ * function replaces any character in s for which islower() is true with
+ * its lowercase counterpart.
+ */
+static inline char *str_toupper(char *s) {
+    for (unsigned char *cs = (unsigned char *)s; *cs; ++cs)
+        *cs = toupper(*cs);
+    return s;
+}
+
+static inline char *str_tolower(char *s) {
+    for (unsigned char *cs = (unsigned char *)s; *cs; ++cs)
+        *cs = tolower(*cs);
+    return s;
+}
+
+
 #ifdef cplusplus
 }
 #endif
index f58e6bcf287b9f0e4e9dbcdfb1a8bb0844f4ddbc..ca33ed57e86bc82f147d5e95df787146ffbae1a7 100644 (file)
@@ -12,6 +12,7 @@
 
 #include <assert.h>
 #include <inttypes.h>
+#include <stdbool.h>
 #include <stdio.h>
 
 #include "str.h"
@@ -20,7 +21,7 @@
 #define H(a_)   fprintf(stderr, "\n%s\n", a_)
 
 #define D(s_)    do{                                                   \
-        fprintf(stderr, ". %s = '", #s_);                              \
+        fprintf(stderr, ". %s = \"", #s_);                             \
         for (int i = 0; i < (int)sizeof(s_); ++i) {                    \
             if (s_[i] == 0)                                            \
                 fprintf(stderr, "\\0");                                \
@@ -29,7 +30,7 @@
             else                                                       \
                 fputc(s_[i], stderr);                                  \
         }                                                              \
-        fprintf(stderr, "'\n");                                        \
+        fprintf(stderr, "\"\n");                                       \
     }while(0)                                                          \
 
 
 
 int main(void) {
 
-    H("str_icmp");
-    T(str_icmp("asdf", "") > 0);
-    T(str_icmp("", "asdf") < 0);
-    T(str_icmp("asdf", "b") < 0);
-    T(str_icmp("b", "asdf") > 0);
-    T(str_icmp("asdfまほ", "asdfまほ") == 0);
-    T(str_icmp("Asdf", "asdf") == 0);
-    T(str_icmp("まほaSdf", "まほasdF") == 0);
-    T(str_icmp("asdf", "asdF") == 0);
-
-    H("str_nicmp");
-    T(str_nicmp(NULL, NULL, 0) == 0);
-    T(str_nicmp(NULL, "x", 0) == 0);
-    T(str_nicmp("x", NULL, 0) == 0);
-    T(str_nicmp("asdf", NULL, 0) == 0);
-    T(str_nicmp("asdf", NULL, 0) == 0);
-    T(str_nicmp("asdf", "", 0) == 0);
-    T(str_nicmp("asdf", "a", 1) == 0);
-    T(str_nicmp("a", "asdf", 1) == 0);
-    T(str_nicmp("asdf", "ab", 1) == 0);
-    T(str_nicmp("ab", "asdf", 1) == 0);
-    T(str_nicmp("asdf", "ab", 2) > 0);
-    T(str_nicmp("ab", "asdf", 2) < 0);
-    T(str_nicmp("asdfまほ", "asdまほ", 1) == 0);
-    T(str_nicmp("Asdf", "asdf", 1) == 0);
-    T(str_nicmp("まほaSdf", "まほasdF", 1) == 0);
-    T(str_nicmp("asdf", "asdF", 1) == 0);
-
-    H("str_delim");
-    char s1[] = "abc:def,\r\n\tghi";
-    D(s1);
-    T(str_delim(s1, "") - s1 == (int)strlen(s1));
-    T(str_delim(s1, "*") - s1 == (int)strlen(s1));
-    T(str_delim(s1, "i") - s1 == (int)strlen(s1)-1);
-    T(str_delim(s1, ":") - s1 == 3);
-    T(str_delim(s1, "-_:") - s1 == 3);
-    T(str_delim(s1, "e,") - s1 == 5);
-    T(str_delim(s1, ",#'") - s1 == 7);
-    T(str_delim(s1, "\n\r") - s1 == 8);
-
-    H("str_istr");
-    char h1[] = "asdfま\0ほyxc";
-    char h2[] = "";
-    D(h1);
-    D(h2);
-    T(str_istr(NULL, NULL) == NULL);
-    T(str_istr(h2, NULL) == h2);
-    T(str_istr(NULL, "") == NULL);
-    T(str_istr(h2, "") == h2);
-    T(str_istr(h1, "") == h1);
-    T(str_istr(h2, "\0\0") == h2);
-    T(str_istr("", "a") == NULL);
-    T(str_istr(h1, "xoxoxoxo") == NULL);
-    T(str_istr(h1, "S") == h1 + 1);
-    T(str_istr(h1, "as") == h1);
-    T(str_istr(h1, "df") == h1 + 2);
-    T(str_istr(h1, "As") == h1);
-    T(str_istr(h1, "dF") == h1 + 2);
-    T(str_istr(h1, "X") == NULL);
-    T(str_istr(h1, "yxc") == NULL);
-
-    H("mem_mem");
-    char m1[] = { 1,'s','d','f', 0,'y','x','c', -1 };
-    char m2[] = { 'a','b','c', 0 };
-    D(m1);
-    D(m2);
-    T(mem_mem(NULL, 0, NULL, 0) == NULL);
-    T(mem_mem(m2, 0, NULL, 0) == m2);
-    T(mem_mem(m2, 0, "", 0) == m2);
-    T(mem_mem(NULL, 0, m2, 0) == NULL);
-    T(mem_mem(NULL, 0, "", 0) == NULL);
-    T(mem_mem(m2, 3, "abc", 3) == m2);
-    T(mem_mem(m2, 4, "abc", 4) == m2);
-    T(mem_mem(m1, sizeof m1, "", 0) == m1);
-    T(mem_mem(m1, sizeof m1, NULL, 0) == m1);
-    T(mem_mem(m1, sizeof m1, "\x01s", 2) == m1);
-    T(mem_mem(m1, sizeof m1, "f\0y", 3) == m1 + 3);
-    T(mem_mem(m1, sizeof m1, "f\0y" + 1, 2) == m1 + 4);
-    T(mem_mem(m1, sizeof m1, "f\0Y" + 1, 2) == NULL);
-    T(mem_mem(m1, sizeof m1, (char[]){-1}, 1) == m1 + 8);
-    T(mem_mem(m1, sizeof m1, "xc", 3) == NULL);
-    T(mem_mem(m1, sizeof m1, "xc", 2) == m1 + 6);
-
-    H("mem_str");
-    D(m1);
-    T(mem_str(NULL, 0, NULL) == NULL);
-    T(mem_str(NULL, 0, "") == NULL);
-    T(mem_str(m1, 0, NULL) == m1);
-    T(mem_str(m1, 0, "") == m1);
-    T(mem_str(m1, sizeof m1, "") == m1);
-    T(mem_str(m1, sizeof m1, NULL) == m1);
-    T(mem_str(m1, sizeof m1, "sdf") == m1 + 1);
-    T(mem_str(m1, sizeof m1, "yxc") == m1 + 5);
-    T(mem_str(m1, sizeof m1, "yxc\0qwe") == m1 + 5);
-    T(mem_str(m1, sizeof m1, "yXc\0qwe") == NULL);
-
-    H("mem_istr");
-    D(m1);
-    T(mem_istr(NULL, 0, NULL) == NULL);
-    T(mem_istr(NULL, 0, "") == NULL);
-    T(mem_istr(m1, 0, NULL) == m1);
-    T(mem_istr(m1, 0, "") == m1);
-    T(mem_istr(m1, sizeof m1, "") == m1);
-    T(mem_istr(m1, sizeof m1, NULL) == m1);
-    T(mem_istr(m1, sizeof m1, "") == m1);
-    T(mem_istr(m1, sizeof m1, "sDf") == m1 + 1);
-    T(mem_istr(m1, sizeof m1, "Yxc") == m1 + 5);
-    T(mem_istr(m1, sizeof m1, "yxC\0qwe") == m1 + 5);
+    /*****************************************/
+
+    {
+        H("str_icmp");
+        T(str_icmp("asdf", "") > 0);
+        T(str_icmp("", "asdf") < 0);
+        T(str_icmp("asdf", "b") < 0);
+        T(str_icmp("b", "asdf") > 0);
+        T(str_icmp("asdfまほ", "asdfまほ") == 0);
+        T(str_icmp("Asdf", "asdf") == 0);
+        T(str_icmp("まほaSdf", "まほasdF") == 0);
+        T(str_icmp("asdf", "asdF") == 0);
+    }
+
+    /*****************************************/
+
+    {
+        H("str_nicmp");
+        T(str_nicmp(NULL, NULL, 0) == 0);
+        T(str_nicmp(NULL, "x", 0) == 0);
+        T(str_nicmp("x", NULL, 0) == 0);
+        T(str_nicmp("asdf", NULL, 0) == 0);
+        T(str_nicmp("asdf", NULL, 0) == 0);
+        T(str_nicmp("asdf", "", 0) == 0);
+        T(str_nicmp("asdf", "a", 1) == 0);
+        T(str_nicmp("a", "asdf", 1) == 0);
+        T(str_nicmp("asdf", "ab", 1) == 0);
+        T(str_nicmp("ab", "asdf", 1) == 0);
+        T(str_nicmp("asdf", "ab", 2) > 0);
+        T(str_nicmp("ab", "asdf", 2) < 0);
+        T(str_nicmp("asdfまほ", "asdまほ", 1) == 0);
+        T(str_nicmp("Asdf", "asdf", 1) == 0);
+        T(str_nicmp("まほaSdf", "まほasdF", 1) == 0);
+        T(str_nicmp("asdf", "asdF", 1) == 0);
+    }
+
+    /*****************************************/
+
+    {
+        H("str_ichr, str_irchr");
+        const char s[] = "abcdABCD";
+        D(s);
+        T(str_ichr(s, 'a') - s == 0);
+        T(str_ichr(s, 'A') - s == 0);
+        T(str_ichr(s, 'b') - s == 1);
+        T(str_ichr(s, 'B') - s == 1);
+        T(str_irchr(s, 'a') - s == 4);
+        T(str_irchr(s, 'A') - s == 4);
+        T(str_irchr(s, 'd') - s == 7);
+        T(str_irchr(s, 'D') - s == 7);
+    }
+
+    /*****************************************/
+
+    {
+        H("str_istr");
+        char h1[] = "asdfま\0ほyxc";
+        char h2[] = "";
+        D(h1);
+        D(h2);
+        T(str_istr(NULL, NULL) == NULL);
+        T(str_istr(h2, NULL) == h2);
+        T(str_istr(NULL, "") == NULL);
+        T(str_istr(h2, "") == h2);
+        T(str_istr(h1, "") == h1);
+        T(str_istr(h2, "\0\0") == h2);
+        T(str_istr("", "a") == NULL);
+        T(str_istr(h1, "xoxoxoxo") == NULL);
+        T(str_istr(h1, "S") == h1 + 1);
+        T(str_istr(h1, "as") == h1);
+        T(str_istr(h1, "df") == h1 + 2);
+        T(str_istr(h1, "As") == h1);
+        T(str_istr(h1, "dF") == h1 + 2);
+        T(str_istr(h1, "X") == NULL);
+        T(str_istr(h1, "yxc") == NULL);
+    }
+
+    /*****************************************/
+
+    {
+        H("mem_mem");
+        char m1[] = { 1,'s','d','f', 0,'y','x','c', -1 };
+        char m2[] = { 'a','b','c', 0 };
+        D(m1);
+        D(m2);
+        T(mem_mem(NULL, 0, NULL, 0) == NULL);
+        T(mem_mem(m2, 0, NULL, 0) == m2);
+        T(mem_mem(m2, 0, "", 0) == m2);
+        T(mem_mem(NULL, 0, m2, 0) == NULL);
+        T(mem_mem(NULL, 0, "", 0) == NULL);
+        T(mem_mem(m2, 3, "abc", 3) == m2);
+        T(mem_mem(m2, 4, "abc", 4) == m2);
+        T(mem_mem(m1, sizeof m1, "", 0) == m1);
+        T(mem_mem(m1, sizeof m1, NULL, 0) == m1);
+        T(mem_mem(m1, sizeof m1, "\x01s", 2) == m1);
+        T(mem_mem(m1, sizeof m1, "f\0y", 3) == m1 + 3);
+        T(mem_mem(m1, sizeof m1, "f\0y" + 1, 2) == m1 + 4);
+        T(mem_mem(m1, sizeof m1, "f\0Y" + 1, 2) == NULL);
+        T(mem_mem(m1, sizeof m1, (char[]){-1}, 1) == m1 + 8);
+        T(mem_mem(m1, sizeof m1, "xc", 3) == NULL);
+        T(mem_mem(m1, sizeof m1, "xc", 2) == m1 + 6);
+    }
+
+    /*****************************************/
+
+    {
+        H("mem_str");
+        char m1[] = { 1,'s','d','f', 0,'y','x','c', -1 };
+        D(m1);
+        T(mem_str(NULL, 0, NULL) == NULL);
+        T(mem_str(NULL, 0, "") == NULL);
+        T(mem_str(m1, 0, NULL) == m1);
+        T(mem_str(m1, 0, "") == m1);
+        T(mem_str(m1, sizeof m1, "") == m1);
+        T(mem_str(m1, sizeof m1, NULL) == m1);
+        T(mem_str(m1, sizeof m1, "sdf") == m1 + 1);
+        T(mem_str(m1, sizeof m1, "yxc") == m1 + 5);
+        T(mem_str(m1, sizeof m1, "yxc\0qwe") == m1 + 5);
+        T(mem_str(m1, sizeof m1, "yXc\0qwe") == NULL);
+    }
+
+    /*****************************************/
+
+    {
+        H("mem_istr");
+        char m1[] = { 1,'s','d','f', 0,'y','x','c', -1 };
+        D(m1);
+        T(mem_istr(NULL, 0, NULL) == NULL);
+        T(mem_istr(NULL, 0, "") == NULL);
+        T(mem_istr(m1, 0, NULL) == m1);
+        T(mem_istr(m1, 0, "") == m1);
+        T(mem_istr(m1, sizeof m1, "") == m1);
+        T(mem_istr(m1, sizeof m1, NULL) == m1);
+        T(mem_istr(m1, sizeof m1, "") == m1);
+        T(mem_istr(m1, sizeof m1, "sDf") == m1 + 1);
+        T(mem_istr(m1, sizeof m1, "Yxc") == m1 + 5);
+        T(mem_istr(m1, sizeof m1, "yxC\0qwe") == m1 + 5);
+    }
+
+    /*****************************************/
+
+    {
+        H("str_delim");
+        char s1[] = "abc:def,\r\n\tghi";
+        D(s1);
+        T(str_delim(s1, "", false) - s1 == (int)strlen(s1));
+        T(str_delim(s1, "*", false) - s1 == (int)strlen(s1));
+        T(str_delim(s1, "i", false) - s1 == (int)strlen(s1)-1);
+        T(str_delim(s1, "i", true) - s1 == 0);
+        T(str_delim(s1, ":", false) - s1 == 3);
+        T(str_delim(s1, "abc", true) - s1 == 3);
+        T(str_delim(s1, "-_:", false) - s1 == 3);
+        T(str_delim(s1, "e,", false) - s1 == 5);
+        T(str_delim(s1, ",#'", false) - s1 == 7);
+        T(str_delim(s1, "\n\r", false) - s1 == 8);
+    }
+
+    /*****************************************/
+
+    {
+        H("str_split");
+        char in1[] = ">> Hello, world!  How are you? <<";
+        char d1[] = " .,!?<>";
+        D(in1);
+        D(d1);
+        char *tok, *next = in1;
+        tok = str_split(next, d1, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        D(in1);
+        T(strcmp(tok, "Hello") == 0);
+        tok = str_split(next, d1, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        D(in1);
+        T(strcmp(tok, "world") == 0);
+        tok = str_split(next, d1, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        D(in1);
+        T(strcmp(tok, "How") == 0);
+        tok = str_split(next, d1, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        D(in1);
+        T(strcmp(tok, "are") == 0);
+        tok = str_split(next, d1, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        D(in1);
+        T(strcmp(tok, "you") == 0);
+        tok = str_split(next, d1, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        D(in1);
+        T(strcmp(tok, "") == 0);
+
+        char in2[] = ">> Hello, world!  How are you? <<";
+        char d2[] = "";
+        D(in2);
+        D(d2);
+        next = in2;
+        tok = str_split(next, d2, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        T(strcmp(tok, in2) == 0);
+
+        char in3[] = ">> Hello, world!  How are you? <<";
+        char d3[] = " ";
+        D(in3);
+        D(d3);
+        next = in3;
+        tok = str_split(next, d3, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        T(strcmp(tok, ">>") == 0);
+        tok = str_split(next, d3, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        T(strcmp(tok, "Hello,") == 0);
+        tok = str_split(next, d3, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        T(strcmp(tok, "world!") == 0);
+        tok = str_split(next, d3, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        T(strcmp(tok, "How") == 0);
+        tok = str_split(next, d3, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        T(strcmp(tok, "are") == 0);
+        tok = str_split(next, d3, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        T(strcmp(tok, "you?") == 0);
+        tok = str_split(next, d3, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        T(strcmp(tok, "<<") == 0);
+        tok = str_split(next, d3, NULL, &next, STR_SPLIT_FOLD|STR_SPLIT_DESTR);
+        T(strcmp(tok, "") == 0);
+
+        char in4[] = ",,,0,1,,pi,";
+        char d4[] = ",";
+        D(in4);
+        D(d4);
+        next = in4;
+        tok = str_split(next, d4, NULL, &next, STR_SPLIT_DESTR);
+        T(strcmp(tok, "") == 0);
+        tok = str_split(next, d4, NULL, &next, STR_SPLIT_DESTR);
+        T(strcmp(tok, "") == 0);
+        tok = str_split(next, d4, NULL, &next, STR_SPLIT_DESTR);
+        T(strcmp(tok, "") == 0);
+        tok = str_split(next, d4, NULL, &next, STR_SPLIT_DESTR);
+        T(strcmp(tok, "0") == 0);
+        tok = str_split(next, d4, NULL, &next, STR_SPLIT_DESTR);
+        T(strcmp(tok, "1") == 0);
+        tok = str_split(next, d4, NULL, &next, STR_SPLIT_DESTR);
+        T(strcmp(tok, "") == 0);
+        tok = str_split(next, d4, NULL, &next, STR_SPLIT_DESTR);
+        T(strcmp(tok, "pi") == 0);
+        T(tok != next);
+        tok = str_split(next, d4, NULL, &next, STR_SPLIT_DESTR);
+        T(strcmp(tok, "") == 0);
+        T(tok == next);
+
+        size_t len;
+        char in5[] = "fourty;;two";
+        char d5[] = ";";
+        D(in5);
+        D(d5);
+        next = in5;
+        tok = str_split(next, d5, &len, &next, 0);
+        T(strncmp(tok, "fourty", len) == 0 && len == 6);
+        tok = str_split(next, d5, &len, &next, 0);
+        T(strncmp(tok, "", len) == 0 && len == 0);
+        tok = str_split(next, d5, &len, &next, 0);
+        T(strncmp(tok, "two", len) == 0 && len == 3);
+        tok = str_split(next, d5, &len, &next, 0);
+        T(strncmp(tok, "", len) == 0 && len == 0);
+        T(tok == next && len == 0);
+    }
+
+    /*****************************************/
+
+    {
+        H("str_ltrim, str_rtrim, str_trim");
+        char s1[] = ",.-XXX,.-";
+        D(s1);
+        T(strcmp(str_ltrim(s1, "-,.", false), "XXX,.-") == 0);
+        D(s1);
+        T(strcmp(str_rtrim(s1, "-,.", false), "XXX") == 0);
+        D(s1);
+        T(strcmp(str_rtrim(s1, "-,.", true), "") == 0);
+        char s2[] = "***XXX***";
+        D(s2);
+        T(strcmp(str_trim(s2, "*", false), "XXX") == 0);
+        char s3[] = "XXX***XXX";
+        D(s3);
+        T(strcmp(str_trim(s3, "*", true), "***") == 0);
+    }
+
+    /*****************************************/
+
+    {
+        H("str_clean");
+        char s1[] = "a1b2c3 45def6";
+        D(s1);
+        T(strcmp(str_clean(s1, "0123456789", false), "abc def") == 0);
+        D(s1);
+        T(strcmp(str_clean(s1, "0123456789", true), "") == 0);
+        char s2[] = "a1b2c3 45def6";
+        D(s2);
+        T(strcmp(str_clean(s2, "0123456789", true), "123456") == 0);
+        D(s2);
+        T(strcmp(str_clean(s2, "0123456789", false), "") == 0);
+        char s3[] = "123abc4d";
+        char s4[] = "123abc4d";
+        D(s3);
+        D(s4);
+        T(strcmp(str_clean(s3, "1234", false), str_clean(s4, "abcd", true)) == 0);
+    }
+
+    /*****************************************/
+
+    {
+        H("str_trans");
+        char s1[] = "a,b,c,43";
+        D(s1);
+        T(strcmp(str_trans(s1, ",3", ";2"), "a;b;c;42") == 0);
+        T(strcmp(str_trans(s1, "", ""), "a;b;c;42") == 0);
+        T(strcmp(str_trans("", "x", "y"), "") == 0);
+    }
+
+    /*****************************************/
+
+    {
+        H("str_toupper, str_tolower");
+        char s1[] = "MiXed CaSe 101";
+        char s2[] = "202 MiXed CaSe";
+        D(s1);
+        T(strcmp(str_toupper(s1), "MIXED CASE 101") == 0);
+        T(strcmp(str_tolower(s1), "mixed case 101") == 0);
+        D(s2);
+        T(strcmp(str_tolower(s2), "202 mixed case") == 0);
+        T(strcmp(str_toupper(s2), "202 MIXED CASE") == 0);
+        T(strcmp(str_toupper(""), "") == 0);
+        T(strcmp(str_tolower(""), "") == 0);
+    }
+
+    /*****************************************/
 
     H("All tests passed.");
     return 0;