Blender V5.0
blenlib/intern/string_utils.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2017 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <cctype>
10#include <cstdlib>
11#include <cstring>
12
13#include <array>
14
15#include <stdexcept>
16
17#include "MEM_guardedalloc.h"
18
19#include "BLI_array.hh"
20#include "BLI_listbase.h"
21#include "BLI_string.h"
22#include "BLI_string_utf8.h"
23#include "BLI_string_utils.hh"
24#include "BLI_utildefines.h"
25
26#include "BLI_dynstr.h"
27
28#include "DNA_listBase.h"
29
30#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
31
32/* -------------------------------------------------------------------- */
35
36char *BLI_string_replaceN(const char *__restrict str,
37 const char *__restrict substr_old,
38 const char *__restrict substr_new)
39{
40 DynStr *ds = nullptr;
41 size_t len_old = strlen(substr_old);
42 const char *match;
43
44 BLI_assert(substr_old[0] != '\0');
45
46 /* While we can still find a match for the old sub-string that we're searching for,
47 * keep dicing and replacing. */
48 while ((match = strstr(str, substr_old))) {
49 /* the assembly buffer only gets created when we actually need to rebuild the string */
50 if (ds == nullptr) {
51 ds = BLI_dynstr_new();
52 }
53
54 /* If the match position does not match the current position in the string,
55 * copy the text up to this position and advance the current position in the string. */
56 if (str != match) {
57 /* Add the segment of the string from `str` to match to the buffer,
58 * then restore the value at match. */
59 BLI_dynstr_nappend(ds, str, int(match - str));
60
61 /* now our current position should be set on the start of the match */
62 str = match;
63 }
64
65 /* Add the replacement text to the accumulation buffer. */
66 BLI_dynstr_append(ds, substr_new);
67
68 /* Advance the current position of the string up to the end of the replaced segment. */
69 str += len_old;
70 }
71
72 /* Finish off and return a new string that has had all occurrences of. */
73 if (ds) {
74 char *str_new;
75
76 /* Add what's left of the string to the assembly buffer
77 * - we've been adjusting `str` to point at the end of the replaced segments. */
79
80 /* Convert to new c-string (MEM_malloc'd), and free the buffer. */
81 str_new = BLI_dynstr_get_cstring(ds);
83
84 return str_new;
85 }
86 /* Just create a new copy of the entire string - we avoid going through the assembly buffer
87 * for what should be a bit more efficiency. */
88 return BLI_strdup(str);
89}
90
91void BLI_string_replace(std::string &haystack,
92 const blender::StringRef needle,
93 const blender::StringRef other)
94{
95 size_t i = 0;
96 size_t index;
97 while ((index = haystack.find(needle, i)) != std::string::npos) {
98 haystack.replace(index, size_t(needle.size()), other);
99 i = index + size_t(other.size());
100 }
101}
102
103void BLI_string_replace_char(char *str, char src, char dst)
104{
105 while (*str) {
106 if (*str == src) {
107 *str = dst;
108 }
109 str++;
110 }
111}
112
114 const size_t string_maxncpy,
115 const char *replace_table[][2],
116 int replace_table_len)
117{
118 BLI_string_debug_size_after_nil(string, string_maxncpy);
119
120 for (int i = 0; i < replace_table_len; i++) {
121 if (STREQ(string, replace_table[i][0])) {
122 BLI_strncpy(string, replace_table[i][1], string_maxncpy);
123 return true;
124 }
125 }
126 return false;
127}
128
130 char *string, size_t string_maxncpy, int src_beg, int src_end, const char *dst)
131{
132 int string_len = int(strlen(string));
133 BLI_assert(src_beg <= src_end);
134 BLI_assert(src_end <= string_len);
135 const int src_len = src_end - src_beg;
136 int dst_len = int(strlen(dst));
137
138 if (src_len < dst_len) {
139 /* Grow, first handle special cases. */
140
141 /* Special case, the src_end is entirely clipped. */
142 if (UNLIKELY(int(string_maxncpy) <= src_beg + dst_len)) {
143 /* There is only room for the destination. */
144 dst_len = (int(string_maxncpy) - src_beg) - 1;
145 string_len = src_end;
146 string[string_len] = '\0';
147 }
148
149 const int ofs = dst_len - src_len;
150 /* Clip the string when inserting the destination string exceeds `string_maxncpy`. */
151 if (string_len + ofs >= int(string_maxncpy)) {
152 string_len = (int(string_maxncpy) - ofs) - 1;
153 string[string_len] = '\0';
154 BLI_assert(src_end <= string_len);
155 }
156
157 /* Grow. */
158 memmove(string + (src_end + ofs), string + src_end, size_t(string_len - src_end) + 1);
159 string_len += ofs;
160 }
161 else if (src_len > dst_len) {
162 /* Shrink. */
163 const int ofs = src_len - dst_len;
164 memmove(string + (src_end - ofs), string + src_end, size_t(string_len - src_end) + 1);
165 string_len -= ofs;
166 }
167 else { /* Simple case, no resizing. */
168 BLI_assert(src_len == dst_len);
169 }
170
171 if (dst_len > 0) {
172 memcpy(string + src_beg, dst, size_t(dst_len));
173 }
174 BLI_assert(string[string_len] == '\0');
175 return size_t(string_len);
176}
177
179
181 const char delim,
182 int &r_number)
183{
184 const int64_t delim_index = name_full.rfind(delim);
185 r_number = 0;
186 if (delim_index == blender::StringRef::not_found) {
187 return name_full;
188 }
189
190 blender::StringRef name_base = name_full.substr(0, delim_index);
191
192 if (delim_index < name_full.size() - 1) {
193 const blender::StringRef num_str = name_full.substr(delim_index + 1);
194 if (!std::all_of(num_str.begin(), num_str.end(), ::isdigit)) {
195 return name_full;
196 }
197 /* Converting numerical suffix to an int, can overflow for large numbers. */
198 try {
199 r_number = std::stoi(num_str);
200 return name_base;
201 }
202 catch (std::out_of_range const & /*ex*/) {
203 r_number = 0;
204 }
205 }
206
207 return name_full;
208}
209
211 const char delim,
212 char *r_name_left,
213 int *r_number)
214{
215 const std::string name_base = BLI_string_split_name_number(name, delim, *r_number);
216 BLI_strncpy(r_name_left, name_base.c_str(), name_base.size() + 1);
217 return name_base.size();
218}
219
220bool BLI_string_is_decimal(const char *string)
221{
222 if (*string == '\0') {
223 return false;
224 }
225
226 /* Keep iterating over the string until a non-digit is found. */
227 while (isdigit(*string)) {
228 string++;
229 }
230
231 /* If the non-digit we found is the terminating \0, everything was digits. */
232 return *string == '\0';
233}
234
235static bool is_char_sep(const char c)
236{
237 return ELEM(c, '.', ' ', '-', '_');
238}
239
240void BLI_string_split_suffix(const char *string,
241 const size_t string_maxlen,
242 char *r_body,
243 char *r_suf)
244{
245 BLI_string_debug_size(r_body, string_maxlen);
246 BLI_string_debug_size(r_suf, string_maxlen);
247
248 size_t len = BLI_strnlen(string, string_maxlen);
249 size_t i;
250
251 r_body[0] = r_suf[0] = '\0';
252
253 for (i = len; i > 0; i--) {
254 if (is_char_sep(string[i])) {
255 BLI_strncpy(r_body, string, i + 1);
256 BLI_strncpy(r_suf, string + i, (len + 1) - i);
257 return;
258 }
259 }
260
261 memcpy(r_body, string, len + 1);
262}
263
264void BLI_string_split_prefix(const char *string,
265 const size_t string_maxlen,
266 char *r_pre,
267 char *r_body)
268{
269 BLI_string_debug_size(r_pre, string_maxlen);
270 BLI_string_debug_size(r_body, string_maxlen);
271
272 size_t len = BLI_strnlen(string, string_maxlen);
273 size_t i;
274
275 r_body[0] = r_pre[0] = '\0';
276
277 for (i = 1; i < len; i++) {
278 if (is_char_sep(string[i])) {
279 i++;
280 BLI_strncpy(r_pre, string, i + 1);
281 BLI_strncpy(r_body, string + i, (len + 1) - i);
282 return;
283 }
284 }
285
286 BLI_strncpy(r_body, string, len);
287}
288
289size_t BLI_string_flip_side_name(char *name_dst,
290 const char *name_src,
291 const bool strip_number,
292 const size_t name_dst_maxncpy)
293{
294 BLI_string_debug_size(name_dst, name_dst_maxncpy);
295
296 size_t len;
297 char *prefix = static_cast<char *>(alloca(name_dst_maxncpy)); /* The part before the facing */
298 char *suffix = static_cast<char *>(alloca(name_dst_maxncpy)); /* The part after the facing */
299 char *number = static_cast<char *>(alloca(name_dst_maxncpy)); /* The number extension string */
300 const char *replace = nullptr;
301 char *index = nullptr;
302 bool is_set = false;
303
304 *prefix = *suffix = *number = '\0';
305
306 /* always copy the name, since this can be called with an uninitialized string */
307 len = BLI_strncpy_utf8_rlen(name_dst, name_src, name_dst_maxncpy);
308 if (len < 3) {
309 /* We don't support names such as `.R` or `.L`. */
310 return len;
311 }
312
313 /* We first check the case with a .### extension, let's find the last period */
314 if (isdigit(name_dst[len - 1])) {
315 index = strrchr(name_dst, '.'); /* Last occurrence. */
316 if (index && isdigit(index[1])) { /* Doesn't handle case `bone.1abc2` correct..., whatever! */
317 if (strip_number == false) {
318 BLI_strncpy_utf8(number, index, name_dst_maxncpy);
319 }
320 *index = '\0';
321 len = size_t(index - name_dst); /* Same as `strlen(name_dst)`. */
322 }
323 }
324
325 BLI_strncpy_utf8(prefix, name_dst, name_dst_maxncpy);
326
327 /* First case; separator (`.` or `_`) with extensions in `r R l L`. */
328 if ((len > 1) && is_char_sep(name_dst[len - 2])) {
329 is_set = true;
330 switch (name_dst[len - 1]) {
331 case 'l':
332 prefix[len - 1] = 0;
333 replace = "r";
334 break;
335 case 'r':
336 prefix[len - 1] = 0;
337 replace = "l";
338 break;
339 case 'L':
340 prefix[len - 1] = 0;
341 replace = "R";
342 break;
343 case 'R':
344 prefix[len - 1] = 0;
345 replace = "L";
346 break;
347 default:
348 is_set = false;
349 }
350 }
351
352 /* case; beginning with r R l L, with separator after it */
353 if (!is_set && is_char_sep(name_dst[1])) {
354 is_set = true;
355 switch (name_dst[0]) {
356 case 'l':
357 replace = "r";
358 BLI_strncpy_utf8(suffix, name_dst + 1, name_dst_maxncpy);
359 prefix[0] = 0;
360 break;
361 case 'r':
362 replace = "l";
363 BLI_strncpy_utf8(suffix, name_dst + 1, name_dst_maxncpy);
364 prefix[0] = 0;
365 break;
366 case 'L':
367 replace = "R";
368 BLI_strncpy_utf8(suffix, name_dst + 1, name_dst_maxncpy);
369 prefix[0] = 0;
370 break;
371 case 'R':
372 replace = "L";
373 BLI_strncpy_utf8(suffix, name_dst + 1, name_dst_maxncpy);
374 prefix[0] = 0;
375 break;
376 default:
377 is_set = false;
378 }
379 }
380
381 if (!is_set && len > 5) {
382 /* Test for a separator to apply the rule: ultimate left or right. */
383 if (((index = BLI_strcasestr(prefix, "right")) == prefix) || (index == prefix + len - 5)) {
384 is_set = true;
385 if (index[0] == 'r') {
386 replace = "left";
387 }
388 else {
389 replace = (index[1] == 'I' ? "LEFT" : "Left");
390 }
391 *index = 0;
392 BLI_strncpy_utf8(suffix, index + 5, name_dst_maxncpy);
393 }
394 else if (((index = BLI_strcasestr(prefix, "left")) == prefix) || (index == prefix + len - 4)) {
395 is_set = true;
396 if (index[0] == 'l') {
397 replace = "right";
398 }
399 else {
400 replace = (index[1] == 'E' ? "RIGHT" : "Right");
401 }
402 *index = 0;
403 BLI_strncpy_utf8(suffix, index + 4, name_dst_maxncpy);
404 }
405 }
406
408 name_dst, name_dst_maxncpy, "%s%s%s%s", prefix, replace ? replace : "", suffix, number);
409}
410
411/* Unique name utils. */
412
414 const char *defname,
415 char delim,
416 char *name,
417 size_t name_maxncpy)
418{
420
421 if (name[0] == '\0') {
422 BLI_strncpy_utf8(name, defname, name_maxncpy);
423 }
424
425 if (unique_check(name)) {
426 char numstr[16];
427 char *tempname = static_cast<char *>(alloca(name_maxncpy));
428 char *left = static_cast<char *>(alloca(name_maxncpy));
429 int number;
430 size_t len = BLI_string_split_name_number(name, delim, left, &number);
431 do {
432 const size_t numlen = SNPRINTF_UTF8(numstr, "%c%03d", delim, ++number);
433
434 /* highly unlikely the string only has enough room for the number
435 * but support anyway */
436 if (UNLIKELY((len == 0) || (numlen + 1 >= name_maxncpy))) {
437 /* Number is known not to be UTF8. */
438 BLI_strncpy(tempname, numstr, name_maxncpy);
439 }
440 else {
441 char *tempname_buf;
442 tempname_buf = tempname + BLI_strncpy_utf8_rlen(tempname, left, name_maxncpy - numlen);
443 memcpy(tempname_buf, numstr, numlen + 1);
444 }
445 } while (unique_check(tempname));
446 /* There will always be enough room for this string. */
447 BLI_strncpy_utf8(name, tempname, name_maxncpy);
448 }
449}
450
452 const char delim,
454{
455 std::string new_name = name;
456
457 if (!unique_check(new_name)) {
458 return new_name;
459 }
460
461 int number;
462 blender::Array<char> left_buffer(int64_t(new_name.size()) + 1);
463 const size_t len = BLI_string_split_name_number(
464 new_name.c_str(), delim, left_buffer.data(), &number);
465
466 const std::string left = left_buffer.data();
467
468 do {
469 std::array<char, 16> num_str;
470 BLI_snprintf(num_str.data(), num_str.size(), "%c%03d", delim, ++number);
471
472 if (len == 0) {
473 new_name = num_str.data();
474 }
475 else {
476 new_name = left + num_str.data();
477 }
478 } while (unique_check(new_name));
479
480 return new_name;
481}
482
483void BLI_uniquename(const ListBase *list,
484 void *vlink,
485 const char *defname,
486 char delim,
487 int name_offset,
488 size_t name_maxncpy)
489{
490 BLI_assert(name_maxncpy > 1);
491
492 /* See if we are given an empty string */
493 if (ELEM(nullptr, vlink)) {
494 return;
495 }
496
498 [&](const blender::StringRefNull name) {
499 LISTBASE_FOREACH (Link *, link, list) {
500 if (link != vlink) {
501 const char *link_name = POINTER_OFFSET((const char *)link, name_offset);
502 if (name == link_name) {
503 return true;
504 }
505 }
506 }
507 return false;
508 },
509 defname,
510 delim,
511 static_cast<char *>(POINTER_OFFSET(vlink, name_offset)),
512 name_maxncpy);
513}
514
515size_t BLI_string_len_array(const char *strings[], uint strings_num)
516{
517 size_t total_len = 0;
518 for (uint i = 0; i < strings_num; i++) {
519 total_len += strlen(strings[i]);
520 }
521 return total_len;
522}
523
524/* ------------------------------------------------------------------------- */
534
536 size_t result_maxncpy,
537 const char *strings[],
538 uint strings_num)
539{
540 BLI_string_debug_size(result, result_maxncpy);
541
542 char *c = result;
543 char *c_end = &result[result_maxncpy - 1];
544 for (uint i = 0; i < strings_num; i++) {
545 const char *p = strings[i];
546 while (*p) {
547 if (UNLIKELY(!(c < c_end))) {
548 i = strings_num; /* Break outer loop. */
549 break;
550 }
551 *c++ = *p++;
552 }
553 }
554 *c = '\0';
555 return size_t(c - result);
556}
557
559 char *result, size_t result_maxncpy, char sep, const char *strings[], uint strings_num)
560{
561 BLI_string_debug_size(result, result_maxncpy);
562
563 char *c = result;
564 char *c_end = &result[result_maxncpy - 1];
565 for (uint i = 0; i < strings_num; i++) {
566 if (i != 0) {
567 if (UNLIKELY(!(c < c_end))) {
568 break;
569 }
570 *c++ = sep;
571 }
572 const char *p = strings[i];
573 while (*p) {
574 if (UNLIKELY(!(c < c_end))) {
575 i = strings_num; /* Break outer loop. */
576 break;
577 }
578 *c++ = *p++;
579 }
580 }
581 *c = '\0';
582 return size_t(c - result);
583}
584
585char *BLI_string_join_arrayN(const char *strings[], uint strings_num)
586{
587 const size_t result_size = BLI_string_len_array(strings, strings_num) + 1;
588 char *result = MEM_calloc_arrayN<char>(result_size, __func__);
589 char *c = result;
590 for (uint i = 0; i < strings_num; i++) {
591 const size_t string_len = strlen(strings[i]);
592 memcpy(c, strings[i], string_len);
593 c += string_len;
594 }
595 /* Only needed when `strings_num == 0`. */
596 *c = '\0';
597 BLI_assert(result + result_size == c + 1);
598 return result;
599}
600
601char *BLI_string_join_array_by_sep_charN(char sep, const char *strings[], uint strings_num)
602{
603 const size_t result_size = BLI_string_len_array(strings, strings_num) +
604 (strings_num ? strings_num - 1 : 0) + 1;
605 char *result = MEM_calloc_arrayN<char>(result_size, __func__);
606 char *c = result;
607 if (strings_num != 0) {
608 for (uint i = 0; i < strings_num; i++) {
609 const size_t string_len = strlen(strings[i]);
610 memcpy(c, strings[i], string_len);
611 c += string_len;
612 *c = sep;
613 c++;
614 }
615 c--;
616 }
617 *c = '\0';
618 BLI_assert(result + result_size == c + 1);
619 return result;
620}
621
623 char *table[],
624 const char *strings[],
625 uint strings_num)
626{
627 size_t result_size = 0;
628 for (uint i = 0; i < strings_num; i++) {
629 result_size += strlen(strings[i]) + 1;
630 }
631 if (result_size == 0) {
632 result_size = 1;
633 }
634
635 char *result = MEM_calloc_arrayN<char>(result_size, __func__);
636 char *c = result;
637 if (strings_num != 0) {
638 for (uint i = 0; i < strings_num; i++) {
639 const size_t string_len = strlen(strings[i]);
640 memcpy(c, strings[i], string_len);
641 table[i] = c; /* <-- only difference to BLI_string_join_array_by_sep_charN. */
642 memcpy(c, strings[i], string_len);
643 c += string_len;
644 *c = sep;
645 c++;
646 }
647 c--;
648 }
649 *c = '\0';
650 BLI_assert(result + result_size == c + 1);
651 return result;
652}
653
#define BLI_assert(a)
Definition BLI_assert.h:46
A dynamically sized string ADT.
char * BLI_dynstr_get_cstring(const DynStr *ds) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
void BLI_dynstr_nappend(DynStr *__restrict ds, const char *cstr, int len) ATTR_NONNULL()
Definition BLI_dynstr.cc:75
DynStr * BLI_dynstr_new(void) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
Definition BLI_dynstr.cc:37
void BLI_dynstr_free(DynStr *ds) ATTR_NONNULL()
void BLI_dynstr_append(DynStr *__restrict ds, const char *cstr) ATTR_NONNULL()
Definition BLI_dynstr.cc:56
#define LISTBASE_FOREACH(type, var, list)
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.cc:41
int char char int int int int size_t BLI_strnlen(const char *str, size_t maxlen) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition string.cc:913
#define BLI_string_debug_size_after_nil(str, str_maxncpy)
Definition BLI_string.h:681
#define BLI_string_debug_size(str, str_maxncpy)
Definition BLI_string.h:676
size_t BLI_snprintf(char *__restrict dst, size_t dst_maxncpy, const char *__restrict format,...) ATTR_NONNULL(1
int char * BLI_strcasestr(const char *s, const char *find) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
#define SNPRINTF_UTF8(dst, format,...)
char * BLI_strncpy_utf8(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
char size_t BLI_strncpy_utf8_rlen(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
size_t BLI_snprintf_utf8_rlen(char *__restrict dst, size_t dst_maxncpy, const char *__restrict format,...) ATTR_NONNULL(1
unsigned int uint
#define UNLIKELY(x)
#define ELEM(...)
#define POINTER_OFFSET(v, ofs)
#define STREQ(a, b)
These structs are the foundation for all linked lists in the library system.
Read Guarded memory(de)allocation.
char * BLI_string_replaceN(const char *__restrict str, const char *__restrict substr_old, const char *__restrict substr_new)
static bool is_char_sep(const char c)
blender::StringRef BLI_string_split_name_number(const blender::StringRef name_full, const char delim, int &r_number)
void BLI_string_split_suffix(const char *string, const size_t string_maxlen, char *r_body, char *r_suf)
char * BLI_string_join_array_by_sep_charN(char sep, const char *strings[], uint strings_num)
bool BLI_string_replace_table_exact(char *string, const size_t string_maxncpy, const char *replace_table[][2], int replace_table_len)
char * BLI_string_join_array_by_sep_char_with_tableN(char sep, char *table[], const char *strings[], uint strings_num)
void BLI_uniquename(const ListBase *list, void *vlink, const char *defname, char delim, int name_offset, size_t name_maxncpy)
size_t BLI_string_join_array_by_sep_char(char *result, size_t result_maxncpy, char sep, const char *strings[], uint strings_num)
void BLI_string_split_prefix(const char *string, const size_t string_maxlen, char *r_pre, char *r_body)
void BLI_string_replace_char(char *str, char src, char dst)
size_t BLI_string_join_array(char *result, size_t result_maxncpy, const char *strings[], uint strings_num)
void BLI_string_replace(std::string &haystack, const blender::StringRef needle, const blender::StringRef other)
size_t BLI_string_replace_range(char *string, size_t string_maxncpy, int src_beg, int src_end, const char *dst)
void BLI_uniquename_cb(blender::FunctionRef< bool(blender::StringRefNull)> unique_check, const char *defname, char delim, char *name, size_t name_maxncpy)
char * BLI_string_join_arrayN(const char *strings[], uint strings_num)
size_t BLI_string_len_array(const char *strings[], uint strings_num)
size_t BLI_string_flip_side_name(char *name_dst, const char *name_src, const bool strip_number, const size_t name_dst_maxncpy)
bool BLI_string_is_decimal(const char *string)
long long int int64_t
const T * data() const
Definition BLI_array.hh:312
static constexpr int64_t not_found
constexpr int64_t rfind(char c, int64_t pos=INT64_MAX) const
constexpr const char * begin() const
constexpr const char * end() const
constexpr StringRef substr(int64_t start, int64_t size) const
constexpr int64_t size() const
#define str(s)
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
static int left
const char * name
i
Definition text_draw.cc:230
uint len