194 lines
5.8 KiB
C
194 lines
5.8 KiB
C
#include <ctype.h>
|
|||
|
|
#include <c_StrUtil.h>
|
||
|
|
#include <c_Memory.h>
|
||
|
|
|
||
|
|
#include "c_ArrayList.h"
|
||
|
|
|
||
|
|
// Reverses a string in-place
|
||
|
|
void c_StrUtil_Reverse(char* str) {
|
||
|
|
if (!str) return;
|
||
|
|
const c_size_t len = strlen(str);
|
||
|
|
if (len <= 1) return;
|
||
|
|
|
||
|
|
c_size_t i = 0;
|
||
|
|
c_size_t j = len - 1;
|
||
|
|
while (i < j) {
|
||
|
|
char temp = str[i];
|
||
|
|
str[i] = str[j];
|
||
|
|
str[j] = temp;
|
||
|
|
i++;
|
||
|
|
j--;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Converts a string to lowercase into a destination buffer
|
||
|
|
c_err_t c_StrUtil_ToLower(char* out_dest, const char* src, c_size_t dest_capacity) {
|
||
|
|
if (!out_dest || !src || dest_capacity == 0) return C_ERR_PARAM;
|
||
|
|
|
||
|
|
c_size_t i = 0;
|
||
|
|
while (src[i] != '\0') {
|
||
|
|
if (i >= dest_capacity - 1) {
|
||
|
|
out_dest[i] = '\0';
|
||
|
|
return C_ERR_INDEX;
|
||
|
|
}
|
||
|
|
out_dest[i] = (char)tolower((unsigned char)src[i]);
|
||
|
|
i++;
|
||
|
|
}
|
||
|
|
out_dest[i] = '\0';
|
||
|
|
return C_ERR_SUCCESS;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Converts a string to uppercase into a destination buffer
|
||
|
|
c_err_t c_StrUtil_ToUpper(char* out_dest, const char* src, c_size_t dest_capacity) {
|
||
|
|
if (!out_dest || !src || dest_capacity == 0) return C_ERR_PARAM;
|
||
|
|
|
||
|
|
c_size_t i = 0;
|
||
|
|
while (src[i] != '\0') {
|
||
|
|
if (i >= dest_capacity - 1) {
|
||
|
|
out_dest[i] = '\0';
|
||
|
|
return C_ERR_INDEX;
|
||
|
|
}
|
||
|
|
out_dest[i] = (char)toupper((unsigned char)src[i]);
|
||
|
|
i++;
|
||
|
|
}
|
||
|
|
out_dest[i] = '\0';
|
||
|
|
return C_ERR_SUCCESS;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Removes leading and trailing whitespaces into a destination buffer
|
||
|
|
c_err_t c_StrUtil_Trim(char* out_dest, const char* src, c_size_t dest_capacity) {
|
||
|
|
if (!out_dest || !src || dest_capacity == 0) return C_ERR_PARAM;
|
||
|
|
|
||
|
|
// Find first non-whitespace character
|
||
|
|
while (*src && isspace((unsigned char)*src)) {
|
||
|
|
src++;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Find the end of the string
|
||
|
|
c_size_t len = strlen(src);
|
||
|
|
while (len > 0 && isspace((unsigned char)src[len - 1])) {
|
||
|
|
len--;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (len >= dest_capacity) {
|
||
|
|
return C_ERR_INDEX;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Copy trimmed substring
|
||
|
|
memmove(out_dest, src, len);
|
||
|
|
out_dest[len] = '\0';
|
||
|
|
return C_ERR_SUCCESS;
|
||
|
|
}
|
||
|
|
|
||
|
|
c_bool_t c_StrUtil_StartsWith(const char* str, const char* prefix) {
|
||
|
|
if (!str || !prefix) return C_FALSE;
|
||
|
|
return strncmp(str, prefix, strlen(prefix)) == 0 ? C_TRUE : C_FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
c_bool_t c_StrUtil_EndsWith(const char* str, const char* suffix) {
|
||
|
|
if (!str || !suffix) return C_FALSE;
|
||
|
|
c_size_t str_len = strlen(str);
|
||
|
|
c_size_t suf_len = strlen(suffix);
|
||
|
|
if (suf_len > str_len) return C_FALSE;
|
||
|
|
return strcmp(str + str_len - suf_len, suffix) == 0 ? C_TRUE : C_FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Safely extracts a substring with full bounds verification
|
||
|
|
c_err_t c_StrUtil_Substring(char* out_dest, const char* src, c_size_t start, c_size_t len, c_size_t dest_capacity) {
|
||
|
|
if (!out_dest || !src || dest_capacity == 0) return C_ERR_PARAM;
|
||
|
|
|
||
|
|
c_size_t src_len = strlen(src);
|
||
|
|
if (start > src_len) return C_ERR_INDEX;
|
||
|
|
|
||
|
|
// Adjust requested length if it overflows source string boundaries
|
||
|
|
if (start + len > src_len) {
|
||
|
|
len = src_len - start;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (len >= dest_capacity) {
|
||
|
|
return C_ERR_INDEX;
|
||
|
|
}
|
||
|
|
|
||
|
|
memcpy(out_dest, src + start, len);
|
||
|
|
out_dest[len] = '\0';
|
||
|
|
return C_ERR_SUCCESS;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Replaces occurrences of a substring inside a buffer safely
|
||
|
|
c_err_t c_StrUtil_Replace(char* out_dest, const char* src, const char* find, const char* replace_with, c_size_t dest_capacity) {
|
||
|
|
if (!out_dest || !src || !find || !replace_with || dest_capacity == 0) return C_ERR_PARAM;
|
||
|
|
|
||
|
|
c_size_t find_len = strlen(find);
|
||
|
|
c_size_t replace_len = strlen(replace_with);
|
||
|
|
c_size_t dest_len = 0;
|
||
|
|
|
||
|
|
if (find_len == 0) return C_ERR_PARAM;
|
||
|
|
|
||
|
|
while (*src) {
|
||
|
|
// If match found, inject replacement string
|
||
|
|
if (strncmp(src, find, find_len) == 0) {
|
||
|
|
if (dest_len + replace_len >= dest_capacity) {
|
||
|
|
out_dest[dest_len] = '\0';
|
||
|
|
return C_ERR_INDEX;
|
||
|
|
}
|
||
|
|
strcpy(out_dest + dest_len, replace_with);
|
||
|
|
dest_len += replace_len;
|
||
|
|
src += find_len;
|
||
|
|
} else {
|
||
|
|
// Otherwise, inject individual source character
|
||
|
|
if (dest_len + 1 >= dest_capacity) {
|
||
|
|
out_dest[dest_len] = '\0';
|
||
|
|
return C_ERR_INDEX;
|
||
|
|
}
|
||
|
|
out_dest[dest_len] = *src;
|
||
|
|
dest_len++;
|
||
|
|
src++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
out_dest[dest_len] = '\0';
|
||
|
|
return C_ERR_SUCCESS;
|
||
|
|
}
|
||
|
|
|
||
|
|
c_err_t c_StrUtil_Split(c_ArrayList_t* out_list, const char* src, const char* delimiter) {
|
||
|
|
if (!out_list || !src || !delimiter) return C_ERR_PARAM;
|
||
|
|
if (out_list->obj_size != sizeof(c_StrToken_t)) return C_ERR_PARAM; // Type safety assert
|
||
|
|
|
||
|
|
const c_size_t delim_len = strlen(delimiter);
|
||
|
|
if (delim_len == 0) return C_ERR_PARAM;
|
||
|
|
|
||
|
|
const char* current = src;
|
||
|
|
const char* next_match;
|
||
|
|
|
||
|
|
while ((next_match = strstr(current, delimiter)) != NULL) {
|
||
|
|
size_t token_len = next_match - current;
|
||
|
|
|
||
|
|
if (token_len > 0) {
|
||
|
|
c_StrToken_t new_token;
|
||
|
|
// Bound checking to ensure long strings don't cause buffer overflows
|
||
|
|
size_t copy_len = (token_len >= C_STR_TOKEN_MAX_LEN) ? (C_STR_TOKEN_MAX_LEN - 1) : token_len;
|
||
|
|
|
||
|
|
memcpy(new_token.text, current, copy_len);
|
||
|
|
new_token.text[copy_len] = '\0';
|
||
|
|
|
||
|
|
// Deep-copy token structure payload straight into the dynamic ArrayList
|
||
|
|
c_ArrayList_Add(out_list, &new_token);
|
||
|
|
}
|
||
|
|
current = next_match + delim_len;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capture the final token remaining after the last delimiter match
|
||
|
|
if (*current != '\0') {
|
||
|
|
c_StrToken_t new_token;
|
||
|
|
size_t token_len = strlen(current);
|
||
|
|
size_t copy_len = (token_len >= C_STR_TOKEN_MAX_LEN) ? (C_STR_TOKEN_MAX_LEN - 1) : token_len;
|
||
|
|
|
||
|
|
memcpy(new_token.text, current, copy_len);
|
||
|
|
new_token.text[copy_len] = '\0';
|
||
|
|
|
||
|
|
c_ArrayList_Add(out_list, &new_token);
|
||
|
|
}
|
||
|
|
|
||
|
|
return C_ERR_SUCCESS;
|
||
|
|
}
|
||
|
|
|