src/server/util/strreplace.c

Sat, 22 Nov 2025 16:44:42 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 22 Nov 2025 16:44:42 +0100
changeset 634
9728d3a2ac97
permissions
-rw-r--r--

add simple string template function

/*
 * 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