src/server/util/strreplace.c

changeset 634
9728d3a2ac97
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/util/strreplace.c	Sat Nov 22 16:44:42 2025 +0100
@@ -0,0 +1,227 @@
+/*
+ * 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 <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, 128, NULL, 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
+            WSBool 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, NULL, 1024, a, 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 };
+}

mercurial