From bccaeab5077acbe60cd65b1b6a0e5b708672ae6d Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Tue, 3 Feb 2026 19:15:55 +0100 Subject: [PATCH] add strreplace.c --- application/Makefile | 4 +- make/Makefile.mk | 7 +- utils/Makefile | 49 ++++++++++ utils/strreplace.c | 228 +++++++++++++++++++++++++++++++++++++++++++ utils/strreplace.h | 133 +++++++++++++++++++++++++ 5 files changed, 417 insertions(+), 4 deletions(-) create mode 100644 utils/Makefile create mode 100644 utils/strreplace.c create mode 100644 utils/strreplace.h diff --git a/application/Makefile b/application/Makefile index 6ca1603..1beec5e 100644 --- a/application/Makefile +++ b/application/Makefile @@ -60,10 +60,10 @@ TEST_BIN = ../build/bin/notetest$(APP_EXT) all: $(APP_BIN) $(TEST_BIN) $(APP_BIN): $(MAIN_OBJ) $(OBJ) $(BUILD_ROOT)/build/lib/libuitk.a - $(LD) -o $(APP_BIN) $(MAIN_OBJ) $(OBJ) -L$(BUILD_ROOT)/build/lib $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(LIB_EXT) -lidav -ldbutils -lmd4c $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(DBU_LDFLAGS) + $(LD) -o $(APP_BIN) $(MAIN_OBJ) $(OBJ) -L$(BUILD_ROOT)/build/lib $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(LIB_EXT) -lidav -ldbutils -lmd4c -lnoteutils $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(DBU_LDFLAGS) $(TEST_BIN): $(OBJ) $(TEST_OBJ) $(BUILD_ROOT)/build/lib/libuitk.a - $(CC) -o $(TEST_BIN) $(TEST_OBJ) $(OBJ) -L$(BUILD_ROOT)/build/lib $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(LIB_EXT) -lidav -ldbutils -lmd4c $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(DBU_LDFLAGS) + $(CC) -o $(TEST_BIN) $(TEST_OBJ) $(OBJ) -L$(BUILD_ROOT)/build/lib $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(LIB_EXT) -lidav -ldbutils -lmd4c -lnoteutils $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(DBU_LDFLAGS) ../build/application/%$(OBJ_EXT): %.c $(CC) $(CFLAGS) $(TK_CFLAGS) $(DAV_CFLAGS) -o $@ -c $< diff --git a/make/Makefile.mk b/make/Makefile.mk index a870e95..492947e 100644 --- a/make/Makefile.mk +++ b/make/Makefile.mk @@ -32,7 +32,7 @@ BUILD_ROOT = ./ include config.mk BUILD_DIRS = build/bin build/lib -BUILD_DIRS += build/application build/application/tests build/ucx build/libidav build/dbutils build/md4c +BUILD_DIRS += build/application build/application/tests build/ucx build/libidav build/dbutils build/md4c build/utils BUILD_DIRS += build/ui/common build/ui/$(TOOLKIT) all: $(BUILD_DIRS) ucx dbutils ui libidav application @@ -50,13 +50,16 @@ ucx: FORCE md4c: FORCE cd md4c; $(MAKE) all +utils: FORCE + cd utils; $(MAKE) all + dbutils: ucx FORCE cd dbutils; $(MAKE) all libidav: ucx FORCE cd libidav; $(MAKE) all -application: ui libidav md4c FORCE +application: ui libidav md4c utils FORCE cd application; $(MAKE) FORCE: diff --git a/utils/Makefile b/utils/Makefile new file mode 100644 index 0000000..82976a5 --- /dev/null +++ b/utils/Makefile @@ -0,0 +1,49 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2018 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +include ../config.mk + +# list of source files +SRC = strreplace.c + +OBJ = $(SRC:%.c=../build/utils/%$(OBJ_EXT)) + +LIBNOTEUTILS = ../build/lib/libnoteutils$(LIB_EXT) + +all: ../build/ucx $(LIBNOTEUTILS) + +$(LIBNOTEUTILS): $(OBJ) + $(AR) $(ARFLAGS) $(AOFLAGS)$@ $(OBJ) + +../build/utils/%$(OBJ_EXT): %.c + $(CC) -I../ucx $(CFLAGS) $(DBU_CFLAGS) -c -o $@ $< + +../build/ucx: + test -d '$@' + + diff --git a/utils/strreplace.c b/utils/strreplace.c new file mode 100644 index 0000000..e842fce --- /dev/null +++ b/utils/strreplace.c @@ -0,0 +1,228 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "strreplace.h" + +#include +#include +#include +#include + +#include + + + +StringTemplate* string_template_compile(const CxAllocator *a, cxstring tpl) { + StringTemplateSegment *end = NULL; // segment list end + int var = false; + int error = false; + + CxBuffer buf; // tmp buffer + if(cxBufferInit(&buf, NULL, NULL, 128, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS)) { + return NULL; + } + + StringTemplate *t = cxMalloc(a, sizeof(StringTemplate)); + if(!t) { + cxBufferDestroy(&buf); + return NULL; + } + t->a = a ? a : cxDefaultAllocator; + t->segments = NULL; + + StringTemplateSegment *seg = NULL; + + for(size_t i=0;itype = var ? STRING_SEGMENT_VAR_PLACEHOLDER : STRING_SEGMENT_STR; + seg->str = (cxmutstr){NULL, 0}; + seg->num = 0; + seg->next = NULL; + // add segment to segment list + if(end) { + end->next = seg; + } else { + t->segments = seg; + } + end = seg; + } else { + error = true; + break; + } + } + + if(var) { + // current segment is a var + if(c == '}') { + var = false; + finish_seg = true; + } else if(c == '{') { + // noop + } else if(!isalnum(c)) { + var = false; + finish_seg = true; + i--; + } else { + add_char = true; + } + } else { + if(c == '$') { + if(i+1 $ + i++; + add_char = true; + } else { + var = true; + if(buf.pos == 0) { + // reuse current segment + seg->type = STRING_SEGMENT_VAR_PLACEHOLDER; + } else { + // create new segment + finish_seg = true; + } + } + } else { + add_char = true; + } + } + + if(add_char) { + if(cxBufferPut(&buf, c) != c) { + error = true; + break; + } + } else if(finish_seg) { + // copy buffer content + cxmutstr seg_str = cx_strdup_a(a, cx_strn(buf.space, buf.pos)); + if(!seg_str.ptr) { + error = true; + break; + } + seg->str = seg_str; + if(seg->type == STRING_SEGMENT_VAR_PLACEHOLDER) { + // is the var segment an integer reference? + if(!cx_strtoi(seg_str, &seg->num, 10)) { + seg->type = STRING_SEGMENT_NUM_PLACEHOLDER; + } + } + buf.pos = 0; + seg = NULL; + } + } + + // finish last segment + if(seg) { + cxmutstr seg_str = cx_strdup_a(a, cx_strn(buf.space, buf.pos)); + if(!seg_str.ptr) { + error = true; + } else { + seg->str = seg_str; + if(seg->type == STRING_SEGMENT_VAR_PLACEHOLDER) { + if(!cx_strtoi(seg_str, &seg->num, 10)) { + seg->type = STRING_SEGMENT_NUM_PLACEHOLDER; + } + } + } + } + + cxBufferDestroy(&buf); + if(error) { + string_template_free(t); + return NULL; + } + + return t; +} + +void string_template_free(StringTemplate *tpl) { + StringTemplateSegment *seg = tpl->segments; + while(seg) { + StringTemplateSegment *next = seg->next; + cxFree(tpl->a, seg->str.ptr); + cxFree(tpl->a, seg); + seg = next; + } + cxFree(tpl->a, tpl); +} + +ssize_t string_template_write_to(StringTemplate *tpl, const CxAllocator *a, strtpl_var_func varfunc, void *userdata, void *stream, cx_write_func writef) { + if(!tpl) { + return -1; + } + + // write each segment to the stream + StringTemplateSegment *seg = tpl->segments; + ssize_t w = 0; + while(seg) { + if(seg->type == STRING_SEGMENT_STR) { + // just write the segment string + if(seg->str.length > 0) { + size_t r = writef(seg->str.ptr, 1, seg->str.length, stream); + if(r != seg->str.length) { + return -1; + } + w += r; + } + } else if(varfunc) { + // convert var segment to value + bool free_str = false; + cxmutstr str = varfunc(a, seg, userdata, &free_str); + if(str.length > 0) { + size_t r = writef(str.ptr, 1, str.length, stream); + if(r != str.length) { + return -1; + } + w += r; + } + if(free_str) { + cxFree(a, str.ptr); + } + } + seg = seg->next; + } + return w; +} + +cxmutstr string_template_build_string(StringTemplate *tpl, const CxAllocator *a, strtpl_var_func varfunc, void *userdata) { + CxBuffer buf; + cxBufferInit(&buf, a, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); + + ssize_t w = string_template_write_to(tpl, a, varfunc, userdata, &buf, (cx_write_func)cxBufferWrite); + if(w < 0 || cxBufferTerminate(&buf)) { + cxBufferDestroy(&buf); + return (cxmutstr){ NULL, 0 }; + } + return (cxmutstr){ buf.space, buf.size }; +} diff --git a/utils/strreplace.h b/utils/strreplace.h new file mode 100644 index 0000000..8ebbb64 --- /dev/null +++ b/utils/strreplace.h @@ -0,0 +1,133 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STRINGREPLACE_H +#define STRINGREPLACE_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum StringSegmentType { + /* + * Static string segment + */ + STRING_SEGMENT_STR = 0, + /* + * Placeholder referenced by number, e.g., $1, $2, ... + */ + STRING_SEGMENT_NUM_PLACEHOLDER, + /* + * Placeholder referenced by name, e.g., ${varname} or $varname + */ + STRING_SEGMENT_VAR_PLACEHOLDER +} StringSegmentType; + +typedef struct StringTemplateSegment StringTemplateSegment; +typedef struct StringTemplate StringTemplate; + +struct StringTemplate { + const CxAllocator *a; + StringTemplateSegment *segments; +}; + +struct StringTemplateSegment { + /* + * STRING_SEGMENT_STR: static string segment + * STRING_SEGMENT_NUM_PLACEHOLDER: null + * STRING_SEGMENT_VAR_PLACEHOLDER: variable name + */ + cxmutstr str; + + /* + * Segment type + */ + StringSegmentType type; + + /* + * reference number if type is STRING_SEGMENT_NUM_PLACEHOLDER + */ + int num; + + /* + * Next segment + */ + StringTemplateSegment *next; +}; + +/* + * Callback for converting a var segment into a string + * + * a: The allocator passed to string_template_write_to + * seg: Placeholder segment + * userdata: The userdata pointer passed to string_template_write_to + * free_str: If set to true, the returned string pointer will be freed + * using the allocator's free function. + */ +typedef cxmutstr (*strtpl_var_func)(const CxAllocator *a, StringTemplateSegment *seg, void *userdata, bool *free_str); + +/* + * Compiles a string template + * + * a: The allocator to use for building the compiled template + * tpl: Semplate string + */ +StringTemplate* string_template_compile(const CxAllocator *a, cxstring tpl); + +/* + * Builds a string using the provided template and writes it to the stream. + * + * tpl: Template + * a: aThe allocator passed to the strtpl_var_func callback + * varfunc: The callback used for converting + * STRING_SEGMENT_NUM_PLACEHOLDER and STRING_SEGMENT_VAR_PLACEHOLDER segments + * userdata: The userdata pointer passed to the strtpl_var_func callback + * stream: The stream object to which the resulting string should be written + * writef: Stream write function + * + * returns the number of written bytes or -1 on error + */ +ssize_t string_template_write_to(StringTemplate *tpl, const CxAllocator *a, strtpl_var_func varfunc, void *userdata, void *stream, cx_write_func writef); + +/* + * Builds a string, using the provided template and allocator + */ +cxmutstr string_template_build_string(StringTemplate *tpl, const CxAllocator *a, strtpl_var_func varfunc, void *userdata); + +void string_template_free(StringTemplate *tpl); + +#ifdef __cplusplus +} +#endif + +#endif /* STRINGREPLACE_H */ + -- 2.47.3