1312 lines
32 KiB
C
1312 lines
32 KiB
C
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bli
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <inttypes.h>
|
|
#include <math.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_string.h"
|
|
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BLI_strict_flags.h" /* Keep last. */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Duplicate/Copy
|
|
* \{ */
|
|
|
|
char *BLI_strdupn(const char *str, const size_t len)
|
|
{
|
|
BLI_assert_msg(BLI_strnlen(str, len) == len, "strlen(str) must be greater or equal to 'len'!");
|
|
|
|
char *n = MEM_mallocN(len + 1, "strdup");
|
|
memcpy(n, str, len);
|
|
n[len] = '\0';
|
|
|
|
return n;
|
|
}
|
|
|
|
char *BLI_strdup(const char *str)
|
|
{
|
|
return BLI_strdupn(str, strlen(str));
|
|
}
|
|
|
|
char *BLI_strdup_null(const char *str)
|
|
{
|
|
return (str != NULL) ? BLI_strdupn(str, strlen(str)) : NULL;
|
|
}
|
|
|
|
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); /* NOLINT: bugprone-not-null-terminated-result */
|
|
s += str1_len;
|
|
memcpy(s, str2, str2_len);
|
|
|
|
return str;
|
|
}
|
|
|
|
char *BLI_strncpy(char *__restrict dst, const char *__restrict src, const size_t dst_maxncpy)
|
|
{
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
|
|
BLI_assert(dst_maxncpy != 0);
|
|
size_t srclen = BLI_strnlen(src, dst_maxncpy - 1);
|
|
|
|
memcpy(dst, src, srclen);
|
|
dst[srclen] = '\0';
|
|
return dst;
|
|
}
|
|
|
|
char *BLI_strncpy_ensure_pad(char *__restrict dst,
|
|
const char *__restrict src,
|
|
const char pad,
|
|
size_t dst_maxncpy)
|
|
{
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
BLI_assert(dst_maxncpy != 0);
|
|
|
|
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;
|
|
dst_maxncpy--;
|
|
}
|
|
dst_maxncpy--; /* trailing '\0' */
|
|
|
|
srclen = BLI_strnlen(src, dst_maxncpy);
|
|
if ((src[srclen - 1] != pad) && (srclen == dst_maxncpy)) {
|
|
srclen--;
|
|
}
|
|
|
|
memcpy(&dst[idx], src, srclen);
|
|
idx += srclen;
|
|
|
|
if (dst[idx - 1] != pad) {
|
|
dst[idx++] = pad;
|
|
}
|
|
dst[idx] = '\0';
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
size_t BLI_strncpy_rlen(char *__restrict dst, const char *__restrict src, const size_t dst_maxncpy)
|
|
{
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
|
|
size_t srclen = BLI_strnlen(src, dst_maxncpy - 1);
|
|
BLI_assert(dst_maxncpy != 0);
|
|
|
|
memcpy(dst, src, srclen);
|
|
dst[srclen] = '\0';
|
|
return srclen;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Append
|
|
* \{ */
|
|
|
|
char *BLI_strncat(char *__restrict dst, const char *__restrict src, const size_t dst_maxncpy)
|
|
{
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
|
|
size_t len = BLI_strnlen(dst, dst_maxncpy);
|
|
if (len < dst_maxncpy) {
|
|
BLI_strncpy(dst + len, src, dst_maxncpy - len);
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Printing
|
|
* \{ */
|
|
|
|
size_t BLI_vsnprintf(char *__restrict dst,
|
|
size_t dst_maxncpy,
|
|
const char *__restrict format,
|
|
va_list arg)
|
|
{
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
|
|
size_t n;
|
|
|
|
BLI_assert(dst != NULL);
|
|
BLI_assert(dst_maxncpy > 0);
|
|
BLI_assert(format != NULL);
|
|
|
|
n = (size_t)vsnprintf(dst, dst_maxncpy, format, arg);
|
|
|
|
if (n != (size_t)-1 && n < dst_maxncpy) {
|
|
dst[n] = '\0';
|
|
}
|
|
else {
|
|
dst[dst_maxncpy - 1] = '\0';
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
size_t BLI_vsnprintf_rlen(char *__restrict dst,
|
|
size_t dst_maxncpy,
|
|
const char *__restrict format,
|
|
va_list arg)
|
|
{
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
|
|
size_t n;
|
|
|
|
BLI_assert(dst != NULL);
|
|
BLI_assert(dst_maxncpy > 0);
|
|
BLI_assert(format != NULL);
|
|
|
|
n = (size_t)vsnprintf(dst, dst_maxncpy, format, arg);
|
|
|
|
if (n != (size_t)-1 && n < dst_maxncpy) {
|
|
/* pass */
|
|
}
|
|
else {
|
|
n = dst_maxncpy - 1;
|
|
}
|
|
dst[n] = '\0';
|
|
|
|
return n;
|
|
}
|
|
|
|
size_t BLI_snprintf(char *__restrict dst, size_t dst_maxncpy, const char *__restrict format, ...)
|
|
{
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
|
|
size_t n;
|
|
va_list arg;
|
|
|
|
va_start(arg, format);
|
|
n = BLI_vsnprintf(dst, dst_maxncpy, format, arg);
|
|
va_end(arg);
|
|
|
|
return n;
|
|
}
|
|
|
|
size_t BLI_snprintf_rlen(char *__restrict dst,
|
|
size_t dst_maxncpy,
|
|
const char *__restrict format,
|
|
...)
|
|
{
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
|
|
size_t n;
|
|
va_list arg;
|
|
|
|
va_start(arg, format);
|
|
n = BLI_vsnprintf_rlen(dst, dst_maxncpy, format, arg);
|
|
va_end(arg);
|
|
|
|
return n;
|
|
}
|
|
|
|
char *BLI_sprintfN_with_buffer(
|
|
char *fixed_buf, size_t fixed_buf_size, size_t *result_len, const char *__restrict format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
int retval = vsnprintf(fixed_buf, fixed_buf_size, format, args);
|
|
va_end(args);
|
|
if (UNLIKELY(retval < 0)) {
|
|
/* Return an empty string as there was an error there is no valid output. */
|
|
*result_len = 0;
|
|
if (UNLIKELY(fixed_buf_size == 0)) {
|
|
return MEM_callocN(sizeof(char), __func__);
|
|
}
|
|
*fixed_buf = '\0';
|
|
return fixed_buf;
|
|
}
|
|
*result_len = (size_t)retval;
|
|
if ((size_t)retval < fixed_buf_size) {
|
|
return fixed_buf;
|
|
}
|
|
|
|
/* `retval` doesn't include null terminator. */
|
|
const size_t size = (size_t)retval + 1;
|
|
char *result = MEM_mallocN(sizeof(char) * size, __func__);
|
|
va_start(args, format);
|
|
retval = vsnprintf(result, size, format, args);
|
|
va_end(args);
|
|
BLI_assert((size_t)(retval + 1) == size);
|
|
UNUSED_VARS_NDEBUG(retval);
|
|
return result;
|
|
}
|
|
|
|
char *BLI_vsprintfN_with_buffer(char *fixed_buf,
|
|
size_t fixed_buf_size,
|
|
size_t *result_len,
|
|
const char *__restrict format,
|
|
va_list args)
|
|
{
|
|
va_list args_copy;
|
|
va_copy(args_copy, args);
|
|
int retval = vsnprintf(fixed_buf, fixed_buf_size, format, args_copy);
|
|
va_end(args_copy);
|
|
if (UNLIKELY(retval < 0)) {
|
|
/* Return an empty string as there was an error there is no valid output. */
|
|
*result_len = 0;
|
|
if (UNLIKELY(fixed_buf_size == 0)) {
|
|
return MEM_callocN(sizeof(char), __func__);
|
|
}
|
|
*fixed_buf = '\0';
|
|
return fixed_buf;
|
|
}
|
|
*result_len = (size_t)retval;
|
|
if ((size_t)retval < fixed_buf_size) {
|
|
return fixed_buf;
|
|
}
|
|
|
|
/* `retval` doesn't include null terminator. */
|
|
const size_t size = (size_t)retval + 1;
|
|
char *result = MEM_mallocN(sizeof(char) * size, __func__);
|
|
retval = vsnprintf(result, size, format, args);
|
|
BLI_assert((size_t)(retval + 1) == size);
|
|
return result;
|
|
}
|
|
|
|
char *BLI_sprintfN(const char *__restrict format, ...)
|
|
{
|
|
char fixed_buf[256];
|
|
size_t result_len;
|
|
va_list args;
|
|
va_start(args, format);
|
|
char *result = BLI_vsprintfN_with_buffer(
|
|
fixed_buf, sizeof(fixed_buf), &result_len, format, args);
|
|
va_end(args);
|
|
if (result != fixed_buf) {
|
|
return result;
|
|
}
|
|
size_t size = result_len + 1;
|
|
result = MEM_mallocN(sizeof(char) * size, __func__);
|
|
memcpy(result, fixed_buf, size);
|
|
return result;
|
|
}
|
|
|
|
char *BLI_vsprintfN(const char *__restrict format, va_list args)
|
|
{
|
|
char fixed_buf[256];
|
|
size_t result_len;
|
|
char *result = BLI_vsprintfN_with_buffer(
|
|
fixed_buf, sizeof(fixed_buf), &result_len, format, args);
|
|
if (result != fixed_buf) {
|
|
return result;
|
|
}
|
|
size_t size = result_len + 1;
|
|
result = MEM_mallocN(sizeof(char) * size, __func__);
|
|
memcpy(result, fixed_buf, size);
|
|
return result;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Escape/Un-Escape
|
|
* \{ */
|
|
|
|
size_t BLI_str_escape(char *__restrict dst, const char *__restrict src, const size_t dst_maxncpy)
|
|
{
|
|
BLI_assert(dst_maxncpy != 0);
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
|
|
size_t len = 0;
|
|
for (; (len < dst_maxncpy) && (*src != '\0'); dst++, src++, len++) {
|
|
char c = *src;
|
|
if (ELEM(c, '\\', '"') || /* Use as-is. */
|
|
((c == '\t') && ((void)(c = 't'), true)) || /* Tab. */
|
|
((c == '\n') && ((void)(c = 'n'), true)) || /* Newline. */
|
|
((c == '\r') && ((void)(c = 'r'), true)) || /* Carriage return. */
|
|
((c == '\a') && ((void)(c = 'a'), true)) || /* Bell. */
|
|
((c == '\b') && ((void)(c = 'b'), true)) || /* Backspace. */
|
|
((c == '\f') && ((void)(c = 'f'), true))) /* Form-feed. */
|
|
{
|
|
if (UNLIKELY(len + 1 >= dst_maxncpy)) {
|
|
/* Not enough space to escape. */
|
|
break;
|
|
}
|
|
*dst++ = '\\';
|
|
len++;
|
|
}
|
|
*dst = c;
|
|
}
|
|
*dst = '\0';
|
|
|
|
return len;
|
|
}
|
|
|
|
BLI_INLINE bool str_unescape_pair(char c_next, char *r_out)
|
|
{
|
|
#define CASE_PAIR(value_src, value_dst) \
|
|
case value_src: { \
|
|
*r_out = value_dst; \
|
|
return true; \
|
|
}
|
|
switch (c_next) {
|
|
CASE_PAIR('"', '"'); /* Quote. */
|
|
CASE_PAIR('\\', '\\'); /* Backslash. */
|
|
CASE_PAIR('t', '\t'); /* Tab. */
|
|
CASE_PAIR('n', '\n'); /* Newline. */
|
|
CASE_PAIR('r', '\r'); /* Carriage return. */
|
|
CASE_PAIR('a', '\a'); /* Bell. */
|
|
CASE_PAIR('b', '\b'); /* Backspace. */
|
|
CASE_PAIR('f', '\f'); /* Form-feed. */
|
|
}
|
|
#undef CASE_PAIR
|
|
return false;
|
|
}
|
|
|
|
size_t BLI_str_unescape_ex(char *__restrict dst,
|
|
const char *__restrict src,
|
|
const size_t src_maxncpy,
|
|
/* Additional arguments to #BLI_str_unescape */
|
|
const size_t dst_maxncpy,
|
|
bool *r_is_complete)
|
|
{
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
|
|
size_t len = 0;
|
|
bool is_complete = true;
|
|
const size_t max_strlen = dst_maxncpy - 1; /* Account for trailing zero byte. */
|
|
for (const char *src_end = src + src_maxncpy; (src < src_end) && *src; src++) {
|
|
if (UNLIKELY(len == max_strlen)) {
|
|
is_complete = false;
|
|
break;
|
|
}
|
|
char c = *src;
|
|
if (UNLIKELY(c == '\\') && str_unescape_pair(*(src + 1), &c)) {
|
|
src++;
|
|
}
|
|
dst[len++] = c;
|
|
}
|
|
dst[len] = 0;
|
|
*r_is_complete = is_complete;
|
|
return len;
|
|
}
|
|
|
|
size_t BLI_str_unescape(char *__restrict dst, const char *__restrict src, const size_t src_maxncpy)
|
|
{
|
|
BLI_string_debug_size(dst, src_maxncpy); /* `dst` must be at least as big as `src`. */
|
|
|
|
size_t len = 0;
|
|
for (const char *src_end = src + src_maxncpy; (src < src_end) && *src; src++) {
|
|
char c = *src;
|
|
if (UNLIKELY(c == '\\') && str_unescape_pair(*(src + 1), &c)) {
|
|
src++;
|
|
}
|
|
dst[len++] = c;
|
|
}
|
|
dst[len] = 0;
|
|
return len;
|
|
}
|
|
|
|
const char *BLI_str_escape_find_quote(const char *str)
|
|
{
|
|
bool escape = false;
|
|
while (*str && (*str != '"' || escape)) {
|
|
/* A pair of back-slashes represents a single back-slash,
|
|
* only use a single back-slash for escaping. */
|
|
escape = (escape == false) && (*str == '\\');
|
|
str++;
|
|
}
|
|
return (*str == '"') ? str : NULL;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Quote/Un-Quote
|
|
* \{ */
|
|
|
|
bool BLI_str_quoted_substr_range(const char *__restrict str,
|
|
const char *__restrict prefix,
|
|
int *__restrict r_start,
|
|
int *__restrict r_end)
|
|
{
|
|
const char *str_start = strstr(str, prefix);
|
|
if (str_start == NULL) {
|
|
return false;
|
|
}
|
|
const size_t prefix_len = strlen(prefix);
|
|
if (UNLIKELY(prefix_len == 0)) {
|
|
BLI_assert_msg(0,
|
|
"Zero length prefix passed in, "
|
|
"caller must prevent this from happening!");
|
|
return false;
|
|
}
|
|
BLI_assert_msg(prefix[prefix_len - 1] != '"',
|
|
"Prefix includes trailing quote, "
|
|
"caller must prevent this from happening!");
|
|
|
|
str_start += prefix_len;
|
|
if (UNLIKELY(*str_start != '\"')) {
|
|
return false;
|
|
}
|
|
str_start += 1;
|
|
const char *str_end = BLI_str_escape_find_quote(str_start);
|
|
if (UNLIKELY(str_end == NULL)) {
|
|
return false;
|
|
}
|
|
|
|
*r_start = (int)(str_start - str);
|
|
*r_end = (int)(str_end - str);
|
|
return true;
|
|
}
|
|
|
|
/* NOTE(@ideasman42): in principal it should be possible to access a quoted string
|
|
* with an arbitrary size, currently all callers for this functionality
|
|
* happened to use a fixed size buffer, so only #BLI_str_quoted_substr is needed. */
|
|
#if 0
|
|
/**
|
|
* Makes a copy of the text within the "" that appear after the contents of \a prefix.
|
|
* i.e. for string `pose["apples"]` with prefix `pose[`, it will return `apples`.
|
|
*
|
|
* \param str: is the entire string to chop.
|
|
* \param prefix: is the part of the string to step over.
|
|
*
|
|
* Assume that the strings returned must be freed afterwards,
|
|
* and that the inputs will contain data we want.
|
|
*/
|
|
char *BLI_str_quoted_substrN(const char *__restrict str, const char *__restrict prefix)
|
|
{
|
|
int start_match_ofs, end_match_ofs;
|
|
if (!BLI_str_quoted_substr_range(str, prefix, &start_match_ofs, &end_match_ofs)) {
|
|
return NULL;
|
|
}
|
|
const size_t escaped_len = (size_t)(end_match_ofs - start_match_ofs);
|
|
char *result = MEM_mallocN(sizeof(char) * (escaped_len + 1), __func__);
|
|
const size_t unescaped_len = BLI_str_unescape(result, str + start_match_ofs, escaped_len);
|
|
if (unescaped_len != escaped_len) {
|
|
result = MEM_reallocN(result, sizeof(char) * (unescaped_len + 1));
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
bool BLI_str_quoted_substr(const char *__restrict str,
|
|
const char *__restrict prefix,
|
|
char *result,
|
|
size_t result_maxncpy)
|
|
{
|
|
BLI_string_debug_size(result, result_maxncpy);
|
|
|
|
int start_match_ofs, end_match_ofs;
|
|
if (!BLI_str_quoted_substr_range(str, prefix, &start_match_ofs, &end_match_ofs)) {
|
|
return false;
|
|
}
|
|
const size_t escaped_len = (size_t)(end_match_ofs - start_match_ofs);
|
|
bool is_complete;
|
|
BLI_str_unescape_ex(result, str + start_match_ofs, escaped_len, result_maxncpy, &is_complete);
|
|
if (is_complete == false) {
|
|
*result = '\0';
|
|
}
|
|
return is_complete;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Comparison/Matching
|
|
* \{ */
|
|
|
|
int BLI_strcaseeq(const char *a, const char *b)
|
|
{
|
|
return (BLI_strcasecmp(a, b) == 0);
|
|
}
|
|
|
|
char *BLI_strcasestr(const char *s, const char *find)
|
|
{
|
|
char c, sc;
|
|
size_t len;
|
|
|
|
if ((c = *find++) != 0) {
|
|
c = (char)tolower(c);
|
|
len = strlen(find);
|
|
do {
|
|
do {
|
|
if ((sc = *s++) == 0) {
|
|
return NULL;
|
|
}
|
|
sc = (char)tolower(sc);
|
|
} while (sc != c);
|
|
} while (BLI_strncasecmp(s, find, len) != 0);
|
|
s--;
|
|
}
|
|
return ((char *)s);
|
|
}
|
|
|
|
int BLI_string_max_possible_word_count(const int str_len)
|
|
{
|
|
return (str_len / 2) + 1;
|
|
}
|
|
|
|
bool BLI_string_has_word_prefix(const char *haystack, const char *needle, size_t needle_len)
|
|
{
|
|
const char *match = BLI_strncasestr(haystack, needle, needle_len);
|
|
if (match) {
|
|
if ((match == haystack) || (*(match - 1) == ' ') || ispunct(*(match - 1))) {
|
|
return true;
|
|
}
|
|
return BLI_string_has_word_prefix(match + 1, needle, needle_len);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BLI_string_all_words_matched(const char *name,
|
|
const char *str,
|
|
int (*words)[2],
|
|
const int words_len)
|
|
{
|
|
int index;
|
|
for (index = 0; index < words_len; index++) {
|
|
if (!BLI_string_has_word_prefix(name, str + words[index][0], (size_t)words[index][1])) {
|
|
break;
|
|
}
|
|
}
|
|
const bool all_words_matched = (index == words_len);
|
|
|
|
return all_words_matched;
|
|
}
|
|
|
|
char *BLI_strncasestr(const char *s, const char *find, size_t len)
|
|
{
|
|
char c, sc;
|
|
|
|
if ((c = *find++) != 0) {
|
|
c = (char)tolower(c);
|
|
if (len > 1) {
|
|
do {
|
|
do {
|
|
if ((sc = *s++) == 0) {
|
|
return NULL;
|
|
}
|
|
sc = (char)tolower(sc);
|
|
} while (sc != c);
|
|
} while (BLI_strncasecmp(s, find, len - 1) != 0);
|
|
}
|
|
else {
|
|
{
|
|
do {
|
|
if ((sc = *s++) == 0) {
|
|
return NULL;
|
|
}
|
|
sc = (char)tolower(sc);
|
|
} while (sc != c);
|
|
}
|
|
}
|
|
s--;
|
|
}
|
|
return ((char *)s);
|
|
}
|
|
|
|
int BLI_strcasecmp(const char *s1, const char *s2)
|
|
{
|
|
int i;
|
|
char c1, c2;
|
|
|
|
for (i = 0;; i++) {
|
|
c1 = (char)tolower(s1[i]);
|
|
c2 = (char)tolower(s2[i]);
|
|
|
|
if (c1 < c2) {
|
|
return -1;
|
|
}
|
|
if (c1 > c2) {
|
|
return 1;
|
|
}
|
|
if (c1 == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int BLI_strncasecmp(const char *s1, const char *s2, size_t len)
|
|
{
|
|
size_t i;
|
|
char c1, c2;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
c1 = (char)tolower(s1[i]);
|
|
c2 = (char)tolower(s2[i]);
|
|
|
|
if (c1 < c2) {
|
|
return -1;
|
|
}
|
|
if (c1 > c2) {
|
|
return 1;
|
|
}
|
|
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 == '0'; numzero1++) {
|
|
p1++;
|
|
}
|
|
for (numzero2 = 0; *p2 == '0'; numzero2++) {
|
|
p2++;
|
|
}
|
|
|
|
/* find number of consecutive digits */
|
|
for (numdigit = 0;; numdigit++) {
|
|
if (isdigit(*(p1 + numdigit)) && isdigit(*(p2 + numdigit))) {
|
|
continue;
|
|
}
|
|
if (isdigit(*(p1 + numdigit))) {
|
|
return 1; /* s2 is bigger */
|
|
}
|
|
if (isdigit(*(p2 + numdigit))) {
|
|
return -1; /* s1 is bigger */
|
|
}
|
|
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;
|
|
}
|
|
|
|
int BLI_strcasecmp_natural(const char *s1, const char *s2)
|
|
{
|
|
int d1 = 0, d2 = 0;
|
|
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) {
|
|
if (isdigit(s1[d1]) && isdigit(s2[d2])) {
|
|
int numcompare = left_number_strcmp(s1 + d1, s2 + d2, &tiebreaker);
|
|
|
|
if (numcompare != 0) {
|
|
return numcompare;
|
|
}
|
|
|
|
/* Some wasted work here, left_number_strcmp already consumes at least some digits. */
|
|
d1++;
|
|
while (isdigit(s1[d1])) {
|
|
d1++;
|
|
}
|
|
d2++;
|
|
while (isdigit(s2[d2])) {
|
|
d2++;
|
|
}
|
|
}
|
|
|
|
/* Test for end of strings first so that shorter strings are ordered in front. */
|
|
if (ELEM(0, s1[d1], s2[d2])) {
|
|
break;
|
|
}
|
|
|
|
c1 = (char)tolower(s1[d1]);
|
|
c2 = (char)tolower(s2[d2]);
|
|
|
|
if (c1 == c2) {
|
|
/* Continue iteration */
|
|
}
|
|
/* Check for '.' so "foo.bar" comes before "foo 1.bar". */
|
|
else if (c1 == '.') {
|
|
return -1;
|
|
}
|
|
else if (c2 == '.') {
|
|
return 1;
|
|
}
|
|
else if (c1 < c2) {
|
|
return -1;
|
|
}
|
|
else if (c1 > c2) {
|
|
return 1;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
if (str1_len > str2_len) {
|
|
int ret = strncmp(str1, str2, str2_len);
|
|
if (ret == 0) {
|
|
ret = 1;
|
|
}
|
|
return ret;
|
|
}
|
|
{
|
|
int ret = strncmp(str1, str2, str1_len);
|
|
if (ret == 0) {
|
|
ret = -1;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Comparison at Start/End
|
|
* \{ */
|
|
|
|
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;
|
|
}
|
|
|
|
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_str_startswith(const char *__restrict str, const char *__restrict start)
|
|
{
|
|
for (; *str && *start; str++, start++) {
|
|
if (*str != *start) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return (*start == '\0');
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Length
|
|
* \{ */
|
|
|
|
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;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Scanning
|
|
* \{ */
|
|
|
|
const char *BLI_strchr_or_end(const char *str, const char ch)
|
|
{
|
|
const char *p = str;
|
|
while (!ELEM(*p, ch, '\0')) {
|
|
p++;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Case Conversion
|
|
* \{ */
|
|
|
|
char BLI_tolower_ascii(const char c)
|
|
{
|
|
return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c;
|
|
}
|
|
|
|
char BLI_toupper_ascii(const char c)
|
|
{
|
|
return (c >= 'a' && c <= 'z') ? c - ('a' - 'A') : c;
|
|
}
|
|
|
|
void BLI_str_tolower_ascii(char *str, const size_t len)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; (i < len) && str[i]; i++) {
|
|
str[i] = BLI_tolower_ascii(str[i]);
|
|
}
|
|
}
|
|
|
|
void BLI_str_toupper_ascii(char *str, const size_t len)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; (i < len) && str[i]; i++) {
|
|
str[i] = BLI_toupper_ascii(str[i]);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Stripping
|
|
* \{ */
|
|
|
|
void BLI_str_rstrip(char *str)
|
|
{
|
|
for (int i = (int)strlen(str) - 1; i >= 0; i--) {
|
|
if (isspace(str[i])) {
|
|
str[i] = '\0';
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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--;
|
|
totstrip++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return totstrip;
|
|
}
|
|
|
|
int BLI_str_rstrip_digits(char *str)
|
|
{
|
|
int totstrip = 0;
|
|
int str_len = (int)strlen(str);
|
|
while (str_len > 0 && isdigit(str[--str_len])) {
|
|
str[str_len] = '\0';
|
|
totstrip++;
|
|
}
|
|
return totstrip;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Split (Partition)
|
|
* \{ */
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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--) {
|
|
/* pass */
|
|
}
|
|
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);
|
|
}
|
|
|
|
int BLI_string_find_split_words(
|
|
const char *str, const size_t str_maxlen, const char delim, int r_words[][2], int words_max)
|
|
{
|
|
int n = 0, i;
|
|
bool charsearch = true;
|
|
|
|
/* Skip leading spaces */
|
|
for (i = 0; (i < (int)str_maxlen) && (str[i] != '\0'); i++) {
|
|
if (str[i] != delim) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (; (i < (int)str_maxlen) && (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;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Formatting (Numeric)
|
|
* \{ */
|
|
|
|
static size_t BLI_str_format_int_grouped_ex(char *src, char *dst, int num_len)
|
|
{
|
|
char *p_src = src;
|
|
char *p_dst = dst;
|
|
|
|
const char separator = ',';
|
|
int commas;
|
|
|
|
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);
|
|
}
|
|
|
|
size_t BLI_str_format_int_grouped(char dst[BLI_STR_FORMAT_INT32_GROUPED_SIZE], int num)
|
|
{
|
|
const size_t dst_maxncpy = BLI_STR_FORMAT_INT32_GROUPED_SIZE;
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
UNUSED_VARS_NDEBUG(dst_maxncpy);
|
|
|
|
char src[BLI_STR_FORMAT_INT32_GROUPED_SIZE];
|
|
const int num_len = (int)SNPRINTF(src, "%d", num);
|
|
|
|
return BLI_str_format_int_grouped_ex(src, dst, num_len);
|
|
}
|
|
|
|
size_t BLI_str_format_uint64_grouped(char dst[BLI_STR_FORMAT_UINT64_GROUPED_SIZE], uint64_t num)
|
|
{
|
|
const size_t dst_maxncpy = BLI_STR_FORMAT_UINT64_GROUPED_SIZE;
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
UNUSED_VARS_NDEBUG(dst_maxncpy);
|
|
|
|
char src[BLI_STR_FORMAT_UINT64_GROUPED_SIZE];
|
|
const int num_len = (int)SNPRINTF(src, "%" PRIu64 "", num);
|
|
|
|
return BLI_str_format_int_grouped_ex(src, dst, num_len);
|
|
}
|
|
|
|
void BLI_str_format_byte_unit(char dst[BLI_STR_FORMAT_INT64_BYTE_UNIT_SIZE],
|
|
long long int bytes,
|
|
const bool base_10)
|
|
{
|
|
const size_t dst_maxncpy = BLI_STR_FORMAT_INT64_BYTE_UNIT_SIZE;
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
|
|
double bytes_converted = (double)bytes;
|
|
int order = 0;
|
|
int decimals;
|
|
const int base = base_10 ? 1000 : 1024;
|
|
const char *units_base_10[] = {"B", "KB", "MB", "GB", "TB", "PB"};
|
|
const char *units_base_2[] = {"B", "KiB", "MiB", "GiB", "TiB", "PiB"};
|
|
const int units_num = ARRAY_SIZE(units_base_2);
|
|
|
|
BLI_STATIC_ASSERT(ARRAY_SIZE(units_base_2) == ARRAY_SIZE(units_base_10), "array size mismatch");
|
|
|
|
while ((fabs(bytes_converted) >= base) && ((order + 1) < units_num)) {
|
|
bytes_converted /= base;
|
|
order++;
|
|
}
|
|
decimals = MAX2(order - 1, 0);
|
|
|
|
/* Format value first, stripping away floating zeroes. */
|
|
size_t len = BLI_snprintf_rlen(dst, dst_maxncpy, "%.*f", decimals, bytes_converted);
|
|
len -= (size_t)BLI_str_rstrip_float_zero(dst, '\0');
|
|
dst[len++] = ' ';
|
|
BLI_strncpy(dst + len, base_10 ? units_base_10[order] : units_base_2[order], dst_maxncpy - len);
|
|
}
|
|
|
|
void BLI_str_format_byte_unit_compact(char dst[BLI_STR_FORMAT_INT64_BYTE_UNIT_COMPACT_SIZE],
|
|
long long int bytes,
|
|
const bool base_10)
|
|
{
|
|
const size_t dst_maxncpy = BLI_STR_FORMAT_INT64_BYTE_UNIT_COMPACT_SIZE;
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
|
|
float number_to_format_converted = (float)bytes;
|
|
int order = 0;
|
|
const int base = base_10 ? 1000 : 1024;
|
|
const char *units[] = {"B", "K", "M", "G", "T", "P"};
|
|
const int units_num = ARRAY_SIZE(units);
|
|
|
|
while ((fabsf(number_to_format_converted) >= base) && ((order + 1) < units_num)) {
|
|
number_to_format_converted /= (float)base;
|
|
order++;
|
|
}
|
|
|
|
const bool add_dot = (llabs(bytes) > 99999) && fabsf(number_to_format_converted) > 99;
|
|
|
|
if (add_dot) {
|
|
number_to_format_converted /= 100.0f;
|
|
order++;
|
|
}
|
|
|
|
BLI_snprintf(dst,
|
|
dst_maxncpy,
|
|
"%s%d%s",
|
|
add_dot ? "." : "",
|
|
(int)floorf(fabsf(number_to_format_converted)),
|
|
units[order]);
|
|
}
|
|
|
|
void BLI_str_format_decimal_unit(char dst[BLI_STR_FORMAT_INT32_DECIMAL_UNIT_SIZE],
|
|
int number_to_format)
|
|
{
|
|
BLI_string_debug_size(dst, BLI_STR_FORMAT_INT32_DECIMAL_UNIT_SIZE);
|
|
|
|
float number_to_format_converted = (float)number_to_format;
|
|
int order = 0;
|
|
const float base = 1000;
|
|
const char *units[] = {"", "K", "M", "B"};
|
|
const int units_num = ARRAY_SIZE(units);
|
|
|
|
while ((fabsf(number_to_format_converted) >= base) && ((order + 1) < units_num)) {
|
|
number_to_format_converted /= base;
|
|
order++;
|
|
}
|
|
|
|
const size_t dst_maxncpy = BLI_STR_FORMAT_INT32_DECIMAL_UNIT_SIZE;
|
|
int decimals = 0;
|
|
if ((order > 0) && fabsf(number_to_format_converted) < 100.0f) {
|
|
decimals = 1;
|
|
}
|
|
BLI_snprintf(dst, dst_maxncpy, "%.*f%s", decimals, number_to_format_converted, units[order]);
|
|
}
|
|
|
|
void BLI_str_format_integer_unit(char dst[BLI_STR_FORMAT_INT32_INTEGER_UNIT_SIZE],
|
|
const int number_to_format)
|
|
{
|
|
const size_t dst_maxncpy = BLI_STR_FORMAT_INT32_INTEGER_UNIT_SIZE;
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
|
|
float number_to_format_converted = (float)number_to_format;
|
|
int order = 0;
|
|
const float base = 1000;
|
|
const char *units[] = {"", "K", "M", "B"};
|
|
const int units_num = ARRAY_SIZE(units);
|
|
|
|
while ((fabsf(number_to_format_converted) >= base) && ((order + 1) < units_num)) {
|
|
number_to_format_converted /= base;
|
|
order++;
|
|
}
|
|
|
|
const bool add_dot = (abs(number_to_format) > 99999) && fabsf(number_to_format_converted) > 99;
|
|
|
|
if (add_dot) {
|
|
number_to_format_converted /= 100;
|
|
order++;
|
|
}
|
|
|
|
BLI_snprintf(dst,
|
|
dst_maxncpy,
|
|
"%s%s%d%s",
|
|
number_to_format < 0 ? "-" : "",
|
|
add_dot ? "." : "",
|
|
(int)floorf(fabsf(number_to_format_converted)),
|
|
units[order]);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name String Debugging
|
|
* \{ */
|
|
|
|
#ifdef WITH_STRSIZE_DEBUG
|
|
void BLI_string_debug_size_after_nil(char *str, size_t str_maxncpy)
|
|
{
|
|
/* Step over the nil, into the character afterwards. */
|
|
size_t str_tail = BLI_strnlen(str, str_maxncpy) + 2;
|
|
if (str_tail < str_maxncpy) {
|
|
BLI_string_debug_size(str + str_tail, str_maxncpy - str_tail);
|
|
}
|
|
}
|
|
|
|
#endif /* WITH_STRSIZE_DEBUG */
|
|
|
|
/** \} */
|