]> uap-core.de Git - note.git/commitdiff
add strreplace.c
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Tue, 3 Feb 2026 18:15:55 +0000 (19:15 +0100)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Tue, 3 Feb 2026 18:15:55 +0000 (19:15 +0100)
application/Makefile
make/Makefile.mk
utils/Makefile [new file with mode: 0644]
utils/strreplace.c [new file with mode: 0644]
utils/strreplace.h [new file with mode: 0644]

index 6ca16039dbb3494360eca6b27c3bf997e246c7f4..1beec5e384bbd65704168d160cece9eda0a3fca6 100644 (file)
@@ -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 $<
index a870e959c44c5f285428d5b5ef099e1b14895a57..492947e2b3f6ea634c03ef7150b2ac3c899bf610 100644 (file)
@@ -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 (file)
index 0000000..82976a5
--- /dev/null
@@ -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 (file)
index 0000000..e842fce
--- /dev/null
@@ -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 <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include <cx/buffer.h>
+
+
+
+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;i<tpl.length;i++) {
+        char c = tpl.ptr[i];
+        int add_char = false; // add current char to the buffer
+        int finish_seg = false; // copy buffer to segment string and start new segment
+        
+        if(!seg) {
+            // start new segment
+            seg = cxMalloc(a, sizeof(StringTemplateSegment));
+            if(seg) {
+                seg->type = 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<tpl.length && tpl.ptr[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 (file)
index 0000000..8ebbb64
--- /dev/null
@@ -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 <stdbool.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
+
+#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 */
+