1042 lines
23 KiB
C
1042 lines
23 KiB
C
/*
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
|
|
* All rights reserved.
|
|
*
|
|
* The Original Code is: all of this file.
|
|
*
|
|
* Contributor(s): none yet.
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*
|
|
*/
|
|
|
|
/** \file blender/blenlib/intern/string.c
|
|
* \ingroup bli
|
|
*/
|
|
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <ctype.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_dynstr.h"
|
|
#include "BLI_string.h"
|
|
|
|
#include "BLI_utildefines.h"
|
|
|
|
#ifdef __GNUC__
|
|
# pragma GCC diagnostic error "-Wsign-conversion"
|
|
#endif
|
|
|
|
// #define DEBUG_STRSIZE
|
|
|
|
/**
|
|
* Duplicates the first \a len bytes of cstring \a str
|
|
* into a newly mallocN'd string and returns it. \a str
|
|
* is assumed to be at least len bytes long.
|
|
*
|
|
* \param str The string to be duplicated
|
|
* \param len The number of bytes to duplicate
|
|
* \retval Returns the duplicated string
|
|
*/
|
|
char *BLI_strdupn(const char *str, const size_t len)
|
|
{
|
|
char *n = MEM_mallocN(len + 1, "strdup");
|
|
memcpy(n, str, len);
|
|
n[len] = '\0';
|
|
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* Duplicates the cstring \a str into a newly mallocN'd
|
|
* string and returns it.
|
|
*
|
|
* \param str The string to be duplicated
|
|
* \retval Returns the duplicated string
|
|
*/
|
|
char *BLI_strdup(const char *str)
|
|
{
|
|
return BLI_strdupn(str, strlen(str));
|
|
}
|
|
|
|
/**
|
|
* Appends the two strings, and returns new mallocN'ed string
|
|
* \param str1 first string for copy
|
|
* \param str2 second string for append
|
|
* \retval Returns dst
|
|
*/
|
|
char *BLI_strdupcat(const char *__restrict str1, const char *__restrict str2)
|
|
{
|
|
/* include the NULL terminator of str2 only */
|
|
const size_t str1_len = strlen(str1);
|
|
const size_t str2_len = strlen(str2) + 1;
|
|
char *str, *s;
|
|
|
|
str = MEM_mallocN(str1_len + str2_len, "strdupcat");
|
|
s = str;
|
|
|
|
memcpy(s, str1, str1_len); s += str1_len;
|
|
memcpy(s, str2, str2_len);
|
|
|
|
return str;
|
|
}
|
|
|
|
/**
|
|
* Like strncpy but ensures dst is always
|
|
* '\0' terminated.
|
|
*
|
|
* \param dst Destination for copy
|
|
* \param src Source string to copy
|
|
* \param maxncpy Maximum number of characters to copy (generally
|
|
* the size of dst)
|
|
* \retval Returns dst
|
|
*/
|
|
char *BLI_strncpy(char *__restrict dst, const char *__restrict src, const size_t maxncpy)
|
|
{
|
|
size_t srclen = BLI_strnlen(src, maxncpy - 1);
|
|
BLI_assert(maxncpy != 0);
|
|
|
|
#ifdef DEBUG_STRSIZE
|
|
memset(dst, 0xff, sizeof(*dst) * maxncpy);
|
|
#endif
|
|
|
|
memcpy(dst, src, srclen);
|
|
dst[srclen] = '\0';
|
|
return dst;
|
|
}
|
|
|
|
/**
|
|
* Like BLI_strncpy but ensures dst is always padded by given char, on both sides (unless src is empty).
|
|
*
|
|
* \param dst Destination for copy
|
|
* \param src Source string to copy
|
|
* \param pad the char to use for padding
|
|
* \param maxncpy Maximum number of characters to copy (generally the size of dst)
|
|
* \retval Returns dst
|
|
*/
|
|
char *BLI_strncpy_ensure_pad(char *__restrict dst, const char *__restrict src, const char pad, size_t maxncpy)
|
|
{
|
|
BLI_assert(maxncpy != 0);
|
|
|
|
#ifdef DEBUG_STRSIZE
|
|
memset(dst, 0xff, sizeof(*dst) * maxncpy);
|
|
#endif
|
|
|
|
if (src[0] == '\0') {
|
|
dst[0] = '\0';
|
|
}
|
|
else {
|
|
/* Add heading/trailing wildcards if needed. */
|
|
size_t idx = 0;
|
|
size_t srclen;
|
|
|
|
if (src[idx] != pad) {
|
|
dst[idx++] = pad;
|
|
maxncpy--;
|
|
}
|
|
maxncpy--; /* trailing '\0' */
|
|
|
|
srclen = BLI_strnlen(src, maxncpy);
|
|
if ((src[srclen - 1] != pad) && (srclen == maxncpy)) {
|
|
srclen--;
|
|
}
|
|
|
|
memcpy(&dst[idx], src, srclen);
|
|
idx += srclen;
|
|
|
|
if (dst[idx - 1] != pad) {
|
|
dst[idx++] = pad;
|
|
}
|
|
dst[idx] = '\0';
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
/**
|
|
* Like strncpy but ensures dst is always
|
|
* '\0' terminated.
|
|
*
|
|
* \note This is a duplicate of #BLI_strncpy that returns bytes copied.
|
|
* And is a drop in replacement for 'snprintf(str, sizeof(str), "%s", arg);'
|
|
*
|
|
* \param dst Destination for copy
|
|
* \param src Source string to copy
|
|
* \param maxncpy Maximum number of characters to copy (generally
|
|
* the size of dst)
|
|
* \retval The number of bytes copied (The only difference from BLI_strncpy).
|
|
*/
|
|
size_t BLI_strncpy_rlen(char *__restrict dst, const char *__restrict src, const size_t maxncpy)
|
|
{
|
|
size_t srclen = BLI_strnlen(src, maxncpy - 1);
|
|
BLI_assert(maxncpy != 0);
|
|
|
|
#ifdef DEBUG_STRSIZE
|
|
memset(dst, 0xff, sizeof(*dst) * maxncpy);
|
|
#endif
|
|
|
|
memcpy(dst, src, srclen);
|
|
dst[srclen] = '\0';
|
|
return srclen;
|
|
}
|
|
|
|
size_t BLI_strcpy_rlen(char *__restrict dst, const char *__restrict src)
|
|
{
|
|
size_t srclen = strlen(src);
|
|
memcpy(dst, src, srclen + 1);
|
|
return srclen;
|
|
}
|
|
|
|
/**
|
|
* Portable replacement for #vsnprintf
|
|
*/
|
|
size_t BLI_vsnprintf(char *__restrict buffer, size_t maxncpy, const char *__restrict format, va_list arg)
|
|
{
|
|
size_t n;
|
|
|
|
BLI_assert(buffer != NULL);
|
|
BLI_assert(maxncpy > 0);
|
|
BLI_assert(format != NULL);
|
|
|
|
n = (size_t)vsnprintf(buffer, maxncpy, format, arg);
|
|
|
|
if (n != -1 && n < maxncpy) {
|
|
buffer[n] = '\0';
|
|
}
|
|
else {
|
|
buffer[maxncpy - 1] = '\0';
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* A version of #BLI_vsnprintf that returns ``strlen(buffer)``
|
|
*/
|
|
size_t BLI_vsnprintf_rlen(char *__restrict buffer, size_t maxncpy, const char *__restrict format, va_list arg)
|
|
{
|
|
size_t n;
|
|
|
|
BLI_assert(buffer != NULL);
|
|
BLI_assert(maxncpy > 0);
|
|
BLI_assert(format != NULL);
|
|
|
|
n = (size_t)vsnprintf(buffer, maxncpy, format, arg);
|
|
|
|
if (n != -1 && n < maxncpy) {
|
|
/* pass */
|
|
}
|
|
else {
|
|
n = maxncpy - 1;
|
|
}
|
|
buffer[n] = '\0';
|
|
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* Portable replacement for #snprintf
|
|
*/
|
|
size_t BLI_snprintf(char *__restrict dst, size_t maxncpy, const char *__restrict format, ...)
|
|
{
|
|
size_t n;
|
|
va_list arg;
|
|
|
|
#ifdef DEBUG_STRSIZE
|
|
memset(dst, 0xff, sizeof(*dst) * maxncpy);
|
|
#endif
|
|
|
|
va_start(arg, format);
|
|
n = BLI_vsnprintf(dst, maxncpy, format, arg);
|
|
va_end(arg);
|
|
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* A version of #BLI_snprintf that returns ``strlen(dst)``
|
|
*/
|
|
size_t BLI_snprintf_rlen(char *__restrict dst, size_t maxncpy, const char *__restrict format, ...)
|
|
{
|
|
size_t n;
|
|
va_list arg;
|
|
|
|
#ifdef DEBUG_STRSIZE
|
|
memset(dst, 0xff, sizeof(*dst) * maxncpy);
|
|
#endif
|
|
|
|
va_start(arg, format);
|
|
n = BLI_vsnprintf_rlen(dst, maxncpy, format, arg);
|
|
va_end(arg);
|
|
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* Print formatted string into a newly #MEM_mallocN'd string
|
|
* and return it.
|
|
*/
|
|
char *BLI_sprintfN(const char *__restrict format, ...)
|
|
{
|
|
DynStr *ds;
|
|
va_list arg;
|
|
char *n;
|
|
|
|
va_start(arg, format);
|
|
|
|
ds = BLI_dynstr_new();
|
|
BLI_dynstr_vappendf(ds, format, arg);
|
|
n = BLI_dynstr_get_cstring(ds);
|
|
BLI_dynstr_free(ds);
|
|
|
|
va_end(arg);
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
/* match pythons string escaping, assume double quotes - (")
|
|
* TODO: should be used to create RNA animation paths.
|
|
* TODO: support more fancy string escaping. current code is primitive
|
|
* this basically is an ascii version of PyUnicode_EncodeUnicodeEscape()
|
|
* which is a useful reference. */
|
|
size_t BLI_strescape(char *__restrict dst, const char *__restrict src, const size_t maxncpy)
|
|
{
|
|
size_t len = 0;
|
|
|
|
BLI_assert(maxncpy != 0);
|
|
|
|
while (len < maxncpy) {
|
|
switch (*src) {
|
|
case '\0':
|
|
goto escape_finish;
|
|
case '\\':
|
|
case '"':
|
|
/* fall-through */
|
|
|
|
/* less common but should also be support */
|
|
case '\t':
|
|
case '\n':
|
|
case '\r':
|
|
if (len + 1 < maxncpy) {
|
|
*dst++ = '\\';
|
|
len++;
|
|
}
|
|
else {
|
|
/* not enough space to escape */
|
|
break;
|
|
}
|
|
/* fall-through */
|
|
default:
|
|
*dst = *src;
|
|
break;
|
|
}
|
|
dst++;
|
|
src++;
|
|
len++;
|
|
}
|
|
|
|
escape_finish:
|
|
|
|
*dst = '\0';
|
|
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* Makes a copy of the text within the "" that appear after some text 'blahblah'
|
|
* i.e. for string 'pose["apples"]' with prefix 'pose[', it should grab "apples"
|
|
*
|
|
* - str: is the entire string to chop
|
|
* - prefix: is the part of the string to leave out
|
|
*
|
|
* Assume that the strings returned must be freed afterwards, and that the inputs will contain
|
|
* data we want...
|
|
*
|
|
* \return the offset and a length so as to avoid doing an allocation.
|
|
*/
|
|
char *BLI_str_quoted_substrN(const char *__restrict str, const char *__restrict prefix)
|
|
{
|
|
const char *startMatch, *endMatch;
|
|
|
|
/* get the starting point (i.e. where prefix starts, and add prefixLen+1 to it to get be after the first " */
|
|
startMatch = strstr(str, prefix);
|
|
if (startMatch) {
|
|
const size_t prefixLen = strlen(prefix);
|
|
startMatch += prefixLen + 1;
|
|
/* get the end point (i.e. where the next occurance of " is after the starting point) */
|
|
|
|
endMatch = startMatch;
|
|
while ((endMatch = strchr(endMatch, '"'))) {
|
|
if (LIKELY(*(endMatch - 1) != '\\')) {
|
|
break;
|
|
}
|
|
else {
|
|
endMatch++;
|
|
}
|
|
}
|
|
|
|
if (endMatch) {
|
|
/* return the slice indicated */
|
|
return BLI_strdupn(startMatch, (size_t)(endMatch - startMatch));
|
|
}
|
|
}
|
|
return BLI_strdupn("", 0);
|
|
}
|
|
|
|
/**
|
|
* string with all instances of substr_old replaced with substr_new,
|
|
* Returns a copy of the cstring \a str into a newly mallocN'd
|
|
* and returns it.
|
|
*
|
|
* \note A rather wasteful string-replacement utility, though this shall do for now...
|
|
* Feel free to replace this with an even safe + nicer alternative
|
|
*
|
|
* \param str The string to replace occurrences of substr_old in
|
|
* \param substr_old The text in the string to find and replace
|
|
* \param substr_new The text in the string to find and replace
|
|
* \retval Returns the duplicated string
|
|
*/
|
|
char *BLI_str_replaceN(const char *__restrict str, const char *__restrict substr_old, const char *__restrict substr_new)
|
|
{
|
|
DynStr *ds = NULL;
|
|
size_t len_old = strlen(substr_old);
|
|
const char *match;
|
|
|
|
BLI_assert(substr_old[0] != '\0');
|
|
|
|
/* while we can still find a match for the old substring that we're searching for,
|
|
* keep dicing and replacing
|
|
*/
|
|
while ((match = strstr(str, substr_old))) {
|
|
/* the assembly buffer only gets created when we actually need to rebuild the string */
|
|
if (ds == NULL)
|
|
ds = BLI_dynstr_new();
|
|
|
|
/* if the match position does not match the current position in the string,
|
|
* copy the text up to this position and advance the current position in the string
|
|
*/
|
|
if (str != match) {
|
|
/* add the segment of the string from str to match to the buffer, then restore the value at match
|
|
*/
|
|
BLI_dynstr_nappend(ds, str, (match - str));
|
|
|
|
/* now our current position should be set on the start of the match */
|
|
str = match;
|
|
}
|
|
|
|
/* add the replacement text to the accumulation buffer */
|
|
BLI_dynstr_append(ds, substr_new);
|
|
|
|
/* advance the current position of the string up to the end of the replaced segment */
|
|
str += len_old;
|
|
}
|
|
|
|
/* finish off and return a new string that has had all occurrences of */
|
|
if (ds) {
|
|
char *str_new;
|
|
|
|
/* add what's left of the string to the assembly buffer
|
|
* - we've been adjusting str to point at the end of the replaced segments
|
|
*/
|
|
BLI_dynstr_append(ds, str);
|
|
|
|
/* convert to new c-string (MEM_malloc'd), and free the buffer */
|
|
str_new = BLI_dynstr_get_cstring(ds);
|
|
BLI_dynstr_free(ds);
|
|
|
|
return str_new;
|
|
}
|
|
else {
|
|
/* just create a new copy of the entire string - we avoid going through the assembly buffer
|
|
* for what should be a bit more efficiency...
|
|
*/
|
|
return BLI_strdup(str);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* In-place replace every \a src to \a dst in \a str.
|
|
*
|
|
* \param str: The string to operate on.
|
|
* \param src: The character to replace.
|
|
* \param dst: The character to replace with.
|
|
*/
|
|
void BLI_str_replace_char(char *str, char src, char dst)
|
|
{
|
|
while (*str) {
|
|
if (*str == src) {
|
|
*str = dst;
|
|
}
|
|
str++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compare two strings without regard to case.
|
|
*
|
|
* \retval True if the strings are equal, false otherwise.
|
|
*/
|
|
int BLI_strcaseeq(const char *a, const char *b)
|
|
{
|
|
return (BLI_strcasecmp(a, b) == 0);
|
|
}
|
|
|
|
/**
|
|
* Portable replacement for #strcasestr (not available in MSVC)
|
|
*/
|
|
char *BLI_strcasestr(const char *s, const char *find)
|
|
{
|
|
register char c, sc;
|
|
register size_t len;
|
|
|
|
if ((c = *find++) != 0) {
|
|
c = tolower(c);
|
|
len = strlen(find);
|
|
do {
|
|
do {
|
|
if ((sc = *s++) == 0)
|
|
return (NULL);
|
|
sc = tolower(sc);
|
|
} while (sc != c);
|
|
} while (BLI_strncasecmp(s, find, len) != 0);
|
|
s--;
|
|
}
|
|
return ((char *) s);
|
|
}
|
|
|
|
/**
|
|
* Variation of #BLI_strcasestr with string length limited to \a len
|
|
*/
|
|
char *BLI_strncasestr(const char *s, const char *find, size_t len)
|
|
{
|
|
register char c, sc;
|
|
|
|
if ((c = *find++) != 0) {
|
|
c = tolower(c);
|
|
if (len > 1) {
|
|
do {
|
|
do {
|
|
if ((sc = *s++) == 0)
|
|
return NULL;
|
|
sc = tolower(sc);
|
|
} while (sc != c);
|
|
} while (BLI_strncasecmp(s, find, len - 1) != 0);
|
|
}
|
|
else {
|
|
{
|
|
do {
|
|
if ((sc = *s++) == 0)
|
|
return NULL;
|
|
sc = tolower(sc);
|
|
} while (sc != c);
|
|
}
|
|
}
|
|
s--;
|
|
}
|
|
return ((char *)s);
|
|
}
|
|
|
|
int BLI_strcasecmp(const char *s1, const char *s2)
|
|
{
|
|
register int i;
|
|
register char c1, c2;
|
|
|
|
for (i = 0;; i++) {
|
|
c1 = tolower(s1[i]);
|
|
c2 = tolower(s2[i]);
|
|
|
|
if (c1 < c2) {
|
|
return -1;
|
|
}
|
|
else if (c1 > c2) {
|
|
return 1;
|
|
}
|
|
else if (c1 == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int BLI_strncasecmp(const char *s1, const char *s2, size_t len)
|
|
{
|
|
register size_t i;
|
|
register char c1, c2;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
c1 = tolower(s1[i]);
|
|
c2 = tolower(s2[i]);
|
|
|
|
if (c1 < c2) {
|
|
return -1;
|
|
}
|
|
else if (c1 > c2) {
|
|
return 1;
|
|
}
|
|
else if (c1 == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* compare number on the left size of the string */
|
|
static int left_number_strcmp(const char *s1, const char *s2, int *tiebreaker)
|
|
{
|
|
const char *p1 = s1, *p2 = s2;
|
|
int numdigit, numzero1, numzero2;
|
|
|
|
/* count and skip leading zeros */
|
|
for (numzero1 = 0; *p1 && (*p1 == '0'); numzero1++)
|
|
p1++;
|
|
for (numzero2 = 0; *p2 && (*p2 == '0'); numzero2++)
|
|
p2++;
|
|
|
|
/* find number of consecutive digits */
|
|
for (numdigit = 0; ; numdigit++) {
|
|
if (isdigit(*(p1 + numdigit)) && isdigit(*(p2 + numdigit)))
|
|
continue;
|
|
else if (isdigit(*(p1 + numdigit)))
|
|
return 1; /* s2 is bigger */
|
|
else if (isdigit(*(p2 + numdigit)))
|
|
return -1; /* s1 is bigger */
|
|
else
|
|
break;
|
|
}
|
|
|
|
/* same number of digits, compare size of number */
|
|
if (numdigit > 0) {
|
|
int compare = (int)strncmp(p1, p2, (size_t)numdigit);
|
|
|
|
if (compare != 0)
|
|
return compare;
|
|
}
|
|
|
|
/* use number of leading zeros as tie breaker if still equal */
|
|
if (*tiebreaker == 0) {
|
|
if (numzero1 > numzero2)
|
|
*tiebreaker = 1;
|
|
else if (numzero1 < numzero2)
|
|
*tiebreaker = -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* natural string compare, keeping numbers in order */
|
|
int BLI_natstrcmp(const char *s1, const char *s2)
|
|
{
|
|
register int d1 = 0, d2 = 0;
|
|
register char c1, c2;
|
|
int tiebreaker = 0;
|
|
|
|
/* if both chars are numeric, to a left_number_strcmp().
|
|
* then increase string deltas as long they are
|
|
* numeric, else do a tolower and char compare */
|
|
|
|
while (1) {
|
|
c1 = tolower(s1[d1]);
|
|
c2 = tolower(s2[d2]);
|
|
|
|
if (isdigit(c1) && isdigit(c2)) {
|
|
int numcompare = left_number_strcmp(s1 + d1, s2 + d2, &tiebreaker);
|
|
|
|
if (numcompare != 0)
|
|
return numcompare;
|
|
|
|
d1++;
|
|
while (isdigit(s1[d1]))
|
|
d1++;
|
|
d2++;
|
|
while (isdigit(s2[d2]))
|
|
d2++;
|
|
|
|
c1 = tolower(s1[d1]);
|
|
c2 = tolower(s2[d2]);
|
|
}
|
|
|
|
/* first check for '.' so "foo.bar" comes before "foo 1.bar" */
|
|
if (c1 == '.' && c2 != '.')
|
|
return -1;
|
|
if (c1 != '.' && c2 == '.')
|
|
return 1;
|
|
else if (c1 < c2) {
|
|
return -1;
|
|
}
|
|
else if (c1 > c2) {
|
|
return 1;
|
|
}
|
|
else if (c1 == 0) {
|
|
break;
|
|
}
|
|
d1++;
|
|
d2++;
|
|
}
|
|
|
|
if (tiebreaker)
|
|
return tiebreaker;
|
|
|
|
/* we might still have a different string because of lower/upper case, in
|
|
* that case fall back to regular string comparison */
|
|
return strcmp(s1, s2);
|
|
}
|
|
|
|
/**
|
|
* Like strcmp, but will ignore any heading/trailing pad char for comparison.
|
|
* So e.g. if pad is '*', '*world' and 'world*' will compare equal.
|
|
*/
|
|
int BLI_strcmp_ignore_pad(const char *str1, const char *str2, const char pad)
|
|
{
|
|
size_t str1_len, str2_len;
|
|
|
|
while (*str1 == pad) {
|
|
str1++;
|
|
}
|
|
while (*str2 == pad) {
|
|
str2++;
|
|
}
|
|
|
|
str1_len = strlen(str1);
|
|
str2_len = strlen(str2);
|
|
|
|
while (str1_len && (str1[str1_len - 1] == pad)) {
|
|
str1_len--;
|
|
}
|
|
while (str2_len && (str2[str2_len - 1] == pad)) {
|
|
str2_len--;
|
|
}
|
|
|
|
if (str1_len == str2_len) {
|
|
return strncmp(str1, str2, str2_len);
|
|
}
|
|
else if (str1_len > str2_len) {
|
|
int ret = strncmp(str1, str2, str2_len);
|
|
if (ret == 0) {
|
|
ret = 1;
|
|
}
|
|
return ret;
|
|
}
|
|
else {
|
|
int ret = strncmp(str1, str2, str1_len);
|
|
if (ret == 0) {
|
|
ret = -1;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* determine the length of a fixed-size string */
|
|
size_t BLI_strnlen(const char *s, const size_t maxlen)
|
|
{
|
|
size_t len;
|
|
|
|
for (len = 0; len < maxlen; len++, s++) {
|
|
if (!*s)
|
|
break;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
void BLI_str_tolower_ascii(char *str, const size_t len)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; (i < len) && str[i]; i++)
|
|
if (str[i] >= 'A' && str[i] <= 'Z')
|
|
str[i] += 'a' - 'A';
|
|
}
|
|
|
|
void BLI_str_toupper_ascii(char *str, const size_t len)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; (i < len) && str[i]; i++)
|
|
if (str[i] >= 'a' && str[i] <= 'z')
|
|
str[i] -= 'a' - 'A';
|
|
}
|
|
|
|
/**
|
|
* Strip trailing zeros from a float, eg:
|
|
* 0.0000 -> 0.0
|
|
* 2.0010 -> 2.001
|
|
*
|
|
* \param str
|
|
* \param pad
|
|
* \return The number of zeros stripped.
|
|
*/
|
|
int BLI_str_rstrip_float_zero(char *str, const char pad)
|
|
{
|
|
char *p = strchr(str, '.');
|
|
int totstrip = 0;
|
|
if (p) {
|
|
char *end_p;
|
|
p++; /* position at first decimal place */
|
|
end_p = p + (strlen(p) - 1); /* position at last character */
|
|
if (end_p > p) {
|
|
while (end_p != p && *end_p == '0') {
|
|
*end_p = pad;
|
|
end_p--;
|
|
}
|
|
}
|
|
}
|
|
|
|
return totstrip;
|
|
}
|
|
|
|
/**
|
|
* Return index of a string in a string array.
|
|
*
|
|
* \param str The string to find.
|
|
* \param str_array Array of strings.
|
|
* \param str_array_len The length of the array, or -1 for a NULL-terminated array.
|
|
* \return The index of str in str_array or -1.
|
|
*/
|
|
int BLI_str_index_in_array_n(const char *__restrict str, const char **__restrict str_array, const int str_array_len)
|
|
{
|
|
int index;
|
|
const char **str_iter = str_array;
|
|
|
|
for (index = 0; index < str_array_len; str_iter++, index++) {
|
|
if (STREQ(str, *str_iter)) {
|
|
return index;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Return index of a string in a string array.
|
|
*
|
|
* \param str The string to find.
|
|
* \param str_array Array of strings, (must be NULL-terminated).
|
|
* \return The index of str in str_array or -1.
|
|
*/
|
|
int BLI_str_index_in_array(const char *__restrict str, const char **__restrict str_array)
|
|
{
|
|
int index;
|
|
const char **str_iter = str_array;
|
|
|
|
for (index = 0; *str_iter; str_iter++, index++) {
|
|
if (STREQ(str, *str_iter)) {
|
|
return index;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool BLI_strn_endswith(const char *__restrict str, const char *__restrict end, size_t slength)
|
|
{
|
|
size_t elength = strlen(end);
|
|
|
|
if (elength < slength) {
|
|
const char *iter = &str[slength - elength];
|
|
while (*iter) {
|
|
if (*iter++ != *end++) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Find if a string ends with another string.
|
|
*
|
|
* \param str The string to search within.
|
|
* \param end The string we look for at the end.
|
|
* \return If str ends with end.
|
|
*/
|
|
bool BLI_str_endswith(const char *__restrict str, const char * __restrict end)
|
|
{
|
|
const size_t slength = strlen(str);
|
|
return BLI_strn_endswith(str, end, slength);
|
|
}
|
|
|
|
/**
|
|
* Find the first char matching one of the chars in \a delim, from left.
|
|
*
|
|
* \param str The string to search within.
|
|
* \param delim The set of delimiters to search for, as unicode values.
|
|
* \param sep Return value, set to the first delimiter found (or NULL if none found).
|
|
* \param suf Return value, set to next char after the first delimiter found (or NULL if none found).
|
|
* \return The length of the prefix (i.e. *sep - str).
|
|
*/
|
|
size_t BLI_str_partition(const char *str, const char delim[], const char **sep, const char **suf)
|
|
{
|
|
return BLI_str_partition_ex(str, NULL, delim, sep, suf, false);
|
|
}
|
|
|
|
/**
|
|
* Find the first char matching one of the chars in \a delim, from right.
|
|
*
|
|
* \param str The string to search within.
|
|
* \param delim The set of delimiters to search for, as unicode values.
|
|
* \param sep Return value, set to the first delimiter found (or NULL if none found).
|
|
* \param suf Return value, set to next char after the first delimiter found (or NULL if none found).
|
|
* \return The length of the prefix (i.e. *sep - str).
|
|
*/
|
|
size_t BLI_str_rpartition(const char *str, const char delim[], const char **sep, const char **suf)
|
|
{
|
|
return BLI_str_partition_ex(str, NULL, delim, sep, suf, true);
|
|
}
|
|
|
|
/**
|
|
* Find the first char matching one of the chars in \a delim, either from left or right.
|
|
*
|
|
* \param str The string to search within.
|
|
* \param end If non-NULL, the right delimiter of the string.
|
|
* \param delim The set of delimiters to search for, as unicode values.
|
|
* \param sep Return value, set to the first delimiter found (or NULL if none found).
|
|
* \param suf Return value, set to next char after the first delimiter found (or NULL if none found).
|
|
* \param from_right If %true, search from the right of \a str, else, search from its left.
|
|
* \return The length of the prefix (i.e. *sep - str).
|
|
*/
|
|
size_t BLI_str_partition_ex(
|
|
const char *str, const char *end, const char delim[], const char **sep, const char **suf, const bool from_right)
|
|
{
|
|
const char *d;
|
|
char *(*func)(const char *str, int c) = from_right ? strrchr : strchr;
|
|
|
|
BLI_assert(end == NULL || end > str);
|
|
|
|
*sep = *suf = NULL;
|
|
|
|
for (d = delim; *d != '\0'; ++d) {
|
|
const char *tmp;
|
|
|
|
if (end) {
|
|
if (from_right) {
|
|
for (tmp = end - 1; (tmp >= str) && (*tmp != *d); tmp--);
|
|
if (tmp < str) {
|
|
tmp = NULL;
|
|
}
|
|
}
|
|
else {
|
|
tmp = func(str, *d);
|
|
if (tmp >= end) {
|
|
tmp = NULL;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
tmp = func(str, *d);
|
|
}
|
|
|
|
if (tmp && (from_right ? (*sep < tmp) : (!*sep || *sep > tmp))) {
|
|
*sep = tmp;
|
|
}
|
|
}
|
|
|
|
if (*sep) {
|
|
*suf = *sep + 1;
|
|
return (size_t)(*sep - str);
|
|
}
|
|
|
|
return end ? (size_t)(end - str) : strlen(str);
|
|
}
|
|
|
|
/**
|
|
* Format ints with decimal grouping.
|
|
* 1000 -> 1,000
|
|
*
|
|
* \param dst The resulting string
|
|
* \param num Number to format
|
|
* \return The length of \a dst
|
|
*/
|
|
size_t BLI_str_format_int_grouped(char dst[16], int num)
|
|
{
|
|
char src[16];
|
|
char *p_src = src;
|
|
char *p_dst = dst;
|
|
|
|
const char separator = ',';
|
|
int num_len, commas;
|
|
|
|
num_len = sprintf(src, "%d", num);
|
|
|
|
if (*p_src == '-') {
|
|
*p_dst++ = *p_src++;
|
|
num_len--;
|
|
}
|
|
|
|
for (commas = 2 - num_len % 3; *p_src; commas = (commas + 1) % 3) {
|
|
*p_dst++ = *p_src++;
|
|
if (commas == 1) {
|
|
*p_dst++ = separator;
|
|
}
|
|
}
|
|
*--p_dst = '\0';
|
|
|
|
return (size_t)(p_dst - dst);
|
|
}
|
|
|
|
/**
|
|
* Find the ranges needed to split \a str into its individual words.
|
|
*
|
|
* \param str: The string to search for words.
|
|
* \param len: Size of the string to search.
|
|
* \param delim: Character to use as a delimiter.
|
|
* \param r_words: Info about the words found. Set to [index, len] pairs.
|
|
* \param words_max: Max number of words to find
|
|
* \return The number of words found in \a str
|
|
*/
|
|
int BLI_string_find_split_words(
|
|
const char *str, const size_t len,
|
|
const char delim, int r_words[][2], int words_max)
|
|
{
|
|
int n = 0, i;
|
|
bool charsearch = true;
|
|
|
|
/* Skip leading spaces */
|
|
for (i = 0; (i < len) && (str[i] != '\0'); i++) {
|
|
if (str[i] != delim) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (; (i < len) && (str[i] != '\0') && (n < words_max); i++) {
|
|
if ((str[i] != delim) && (charsearch == true)) {
|
|
r_words[n][0] = i;
|
|
charsearch = false;
|
|
}
|
|
else {
|
|
if ((str[i] == delim) && (charsearch == false)) {
|
|
r_words[n][1] = i - r_words[n][0];
|
|
n++;
|
|
charsearch = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (charsearch == false) {
|
|
r_words[n][1] = i - r_words[n][0];
|
|
n++;
|
|
}
|
|
|
|
return n;
|
|
}
|