diff options
| author | frosty <frosty@illegalfirearms.store> | 2025-12-28 03:26:05 -0500 |
|---|---|---|
| committer | frosty <frosty@illegalfirearms.store> | 2025-12-28 03:26:05 -0500 |
| commit | 4af132cf6adeeeeb5d6764c378bec2d05cad042f (patch) | |
| tree | e422cff2831424775ba5c20196064f94cbe1e5c3 /src/template.c | |
Migrated from GitHub
Diffstat (limited to 'src/template.c')
| -rw-r--r-- | src/template.c | 961 |
1 files changed, 961 insertions, 0 deletions
diff --git a/src/template.c b/src/template.c new file mode 100644 index 0000000..494b011 --- /dev/null +++ b/src/template.c @@ -0,0 +1,961 @@ +#include "../beaker.h" +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static char *render_template_segment(const char *template_segment, + TemplateContext *ctx); + +static ContextVar *find_context_var(TemplateContext *ctx, const char *key) { + if (ctx == NULL || key == NULL) { + fprintf(stderr, + "[ERROR] find_context_var: Invalid NULL input (ctx or key).\n"); + return NULL; + } + for (int i = 0; i < ctx->count; i++) { + if (strcmp(ctx->vars[i].key, key) == 0) { + return &ctx->vars[i]; + } + } + return NULL; +} + +static void free_string_array_var(ContextVar *var) { + if (var == NULL || var->value.string_array_data.values == NULL) { + return; + } + for (int i = 0; i < var->value.string_array_data.count; i++) { + if (var->value.string_array_data.values[i] != NULL) { + free(var->value.string_array_data.values[i]); + var->value.string_array_data.values[i] = NULL; + } + } + free(var->value.string_array_data.values); + var->value.string_array_data.values = NULL; + var->value.string_array_data.count = 0; +} + +static void free_string_2d_array_var(ContextVar *var) { + if (var == NULL || var->value.string_2d_array_data.values == NULL) { + return; + } + for (int i = 0; i < var->value.string_2d_array_data.outer_count; i++) { + if (var->value.string_2d_array_data.values[i] != NULL) { + for (int j = 0; j < var->value.string_2d_array_data.inner_counts[i]; + j++) { + if (var->value.string_2d_array_data.values[i][j] != NULL) { + free(var->value.string_2d_array_data.values[i][j]); + var->value.string_2d_array_data.values[i][j] = NULL; + } + } + free(var->value.string_2d_array_data.values[i]); + var->value.string_2d_array_data.values[i] = NULL; + } + } + free(var->value.string_2d_array_data.values); + var->value.string_2d_array_data.values = NULL; + + if (var->value.string_2d_array_data.inner_counts != NULL) { + free(var->value.string_2d_array_data.inner_counts); + var->value.string_2d_array_data.inner_counts = NULL; + } + var->value.string_2d_array_data.outer_count = 0; +} + +TemplateContext new_context() { + TemplateContext ctx; + ctx.count = 0; + return ctx; +} + +void context_set(TemplateContext *ctx, const char *key, const char *value) { + if (ctx == NULL || key == NULL || value == NULL) { + fprintf(stderr, + "[ERROR] context_set: Invalid NULL input (ctx, key, or value).\n"); + return; + } + + ContextVar *var = find_context_var(ctx, key); + if (var != NULL) { + + if (var->type == CONTEXT_TYPE_STRING_ARRAY) { + free_string_array_var(var); + } else if (var->type == CONTEXT_TYPE_STRING_2D_ARRAY) { + free_string_2d_array_var(var); + } + + strncpy(var->value.string_val, value, MAX_VALUE_LEN - 1); + var->value.string_val[MAX_VALUE_LEN - 1] = '\0'; + var->type = CONTEXT_TYPE_STRING; + return; + } + + if (ctx->count < MAX_CONTEXT_VARS) { + ContextVar *new_var = &ctx->vars[ctx->count]; + strncpy(new_var->key, key, MAX_KEY_LEN - 1); + new_var->key[MAX_KEY_LEN - 1] = '\0'; + strncpy(new_var->value.string_val, value, MAX_VALUE_LEN - 1); + new_var->value.string_val[MAX_VALUE_LEN - 1] = '\0'; + new_var->type = CONTEXT_TYPE_STRING; + ctx->count++; + } else { + fprintf(stderr, + "[WARNING] context_set: TemplateContext is full. Cannot add key " + "'%s'.\n", + key); + } +} + +void context_set_string_array(TemplateContext *ctx, const char *key, + char *values[], int count) { + if (ctx == NULL || key == NULL || values == NULL) { + fprintf(stderr, "[ERROR] context_set_string_array: Invalid NULL input " + "(ctx, key, or values).\n"); + return; + } + if (count < 0 || count > MAX_OUTER_ARRAY_ITEMS) { + fprintf(stderr, + "[ERROR] context_set_string_array: Invalid count %d for string " + "array context '%s'. Max %d allowed.\n", + count, key, MAX_OUTER_ARRAY_ITEMS); + return; + } + + ContextVar *var = find_context_var(ctx, key); + if (var != NULL) { + + if (var->type == CONTEXT_TYPE_STRING_ARRAY) { + free_string_array_var(var); + } else if (var->type == CONTEXT_TYPE_STRING_2D_ARRAY) { + free_string_2d_array_var(var); + } + } else { + + if (ctx->count < MAX_CONTEXT_VARS) { + var = &ctx->vars[ctx->count]; + strncpy(var->key, key, MAX_KEY_LEN - 1); + var->key[MAX_KEY_LEN - 1] = '\0'; + ctx->count++; + } else { + fprintf(stderr, + "[WARNING] context_set_string_array: TemplateContext is full. " + "Cannot add string array key '%s'.\n", + key); + return; + } + } + + var->type = CONTEXT_TYPE_STRING_ARRAY; + var->value.string_array_data.values = (char **)malloc(sizeof(char *) * count); + if (var->value.string_array_data.values == NULL) { + perror("Failed to allocate memory for string array pointers"); + fprintf(stderr, + "[ERROR] context_set_string_array: Allocation failed for values " + "for key '%s'.\n", + key); + return; + } + var->value.string_array_data.count = count; + + for (int i = 0; i < count; i++) { + if (values[i] == NULL) { + fprintf(stderr, + "[ERROR] context_set_string_array: NULL value at index %d for " + "key '%s'. Skipping.\n", + i, key); + var->value.string_array_data.values[i] = NULL; + continue; + } + var->value.string_array_data.values[i] = strdup(values[i]); + if (var->value.string_array_data.values[i] == NULL) { + perror("Failed to duplicate string for string array context"); + fprintf(stderr, + "[ERROR] context_set_string_array: Failed to duplicate value for " + "item %d of key '%s'.\n", + i, key); + + for (int j = 0; j < i; j++) { + if (var->value.string_array_data.values[j] != NULL) { + free(var->value.string_array_data.values[j]); + } + } + free(var->value.string_array_data.values); + var->value.string_array_data.values = NULL; + var->value.string_array_data.count = 0; + return; + } + } +} + +void context_set_array_of_arrays(TemplateContext *ctx, const char *key, + char **values_2d[], int outer_count, + int inner_counts[]) { + if (ctx == NULL || key == NULL || values_2d == NULL || inner_counts == NULL) { + fprintf(stderr, "[ERROR] context_set_array_of_arrays: Invalid NULL input " + "(ctx, key, values_2d, or inner_counts).\n"); + return; + } + if (outer_count < 0 || outer_count > MAX_OUTER_ARRAY_ITEMS) { + fprintf(stderr, + "[ERROR] context_set_array_of_arrays: Invalid outer count %d for " + "array of arrays context '%s'. Max %d allowed.\n", + outer_count, key, MAX_OUTER_ARRAY_ITEMS); + return; + } + + ContextVar *var = find_context_var(ctx, key); + if (var != NULL) { + + if (var->type == CONTEXT_TYPE_STRING_ARRAY) { + free_string_array_var(var); + } else if (var->type == CONTEXT_TYPE_STRING_2D_ARRAY) { + free_string_2d_array_var(var); + } + } else { + + if (ctx->count < MAX_CONTEXT_VARS) { + var = &ctx->vars[ctx->count]; + strncpy(var->key, key, MAX_KEY_LEN - 1); + var->key[MAX_KEY_LEN - 1] = '\0'; + ctx->count++; + } else { + fprintf(stderr, + "[WARNING] context_set_array_of_arrays: TemplateContext is full. " + "Cannot add array of arrays key '%s'.\n", + key); + return; + } + } + + var->type = CONTEXT_TYPE_STRING_2D_ARRAY; + var->value.string_2d_array_data.values = + (char ***)malloc(sizeof(char **) * outer_count); + var->value.string_2d_array_data.inner_counts = + (int *)malloc(sizeof(int) * outer_count); + + if (var->value.string_2d_array_data.values == NULL || + var->value.string_2d_array_data.inner_counts == NULL) { + perror("Failed to allocate memory for 2D string array pointers or counts"); + fprintf(stderr, + "[ERROR] context_set_array_of_arrays: Allocation failed for key " + "'%s'.\n", + key); + free(var->value.string_2d_array_data.values); + free(var->value.string_2d_array_data.inner_counts); + var->value.string_2d_array_data.values = NULL; + var->value.string_2d_array_data.inner_counts = NULL; + return; + } + var->value.string_2d_array_data.outer_count = outer_count; + + for (int i = 0; i < outer_count; i++) { + int current_inner_count = inner_counts[i]; + if (current_inner_count < 0 || + current_inner_count > MAX_INNER_ARRAY_ITEMS) { + fprintf(stderr, + "[ERROR] context_set_array_of_arrays: Invalid inner count %d for " + "item %d in 2D array '%s'. Max %d allowed.\n", + current_inner_count, i, key, MAX_INNER_ARRAY_ITEMS); + + for (int k = 0; k < i; k++) { + if (var->value.string_2d_array_data.values[k] != NULL) { + for (int l = 0; l < var->value.string_2d_array_data.inner_counts[k]; + l++) { + if (var->value.string_2d_array_data.values[k][l] != NULL) { + free(var->value.string_2d_array_data.values[k][l]); + } + } + free(var->value.string_2d_array_data.values[k]); + } + } + free(var->value.string_2d_array_data.values); + free(var->value.string_2d_array_data.inner_counts); + var->value.string_2d_array_data.values = NULL; + var->value.string_2d_array_data.inner_counts = NULL; + return; + } + + var->value.string_2d_array_data.values[i] = + (char **)malloc(sizeof(char *) * current_inner_count); + if (var->value.string_2d_array_data.values[i] == NULL) { + perror("Failed to allocate memory for inner string array pointers"); + fprintf(stderr, + "[ERROR] context_set_array_of_arrays: Allocation failed for " + "inner array %d of key '%s'.\n", + i, key); + + for (int k = 0; k < i; k++) { + if (var->value.string_2d_array_data.values[k] != NULL) { + for (int l = 0; l < var->value.string_2d_array_data.inner_counts[k]; + l++) { + if (var->value.string_2d_array_data.values[k][l] != NULL) { + free(var->value.string_2d_array_data.values[k][l]); + } + } + free(var->value.string_2d_array_data.values[k]); + } + } + free(var->value.string_2d_array_data.values); + free(var->value.string_2d_array_data.inner_counts); + var->value.string_2d_array_data.values = NULL; + var->value.string_2d_array_data.inner_counts = NULL; + return; + } + var->value.string_2d_array_data.inner_counts[i] = current_inner_count; + + for (int j = 0; j < current_inner_count; j++) { + if (values_2d[i][j] == NULL) { + fprintf(stderr, + "[ERROR] context_set_array_of_arrays: NULL value at index " + "[%d][%d] for key '%s'. Skipping.\n", + i, j, key); + var->value.string_2d_array_data.values[i][j] = NULL; + continue; + } + var->value.string_2d_array_data.values[i][j] = strdup(values_2d[i][j]); + if (var->value.string_2d_array_data.values[i][j] == NULL) { + perror("Failed to duplicate string for inner array context"); + fprintf(stderr, + "[ERROR] context_set_array_of_arrays: Failed to duplicate " + "value for item [%d][%d] of key '%s'.\n", + i, j, key); + + for (int k = 0; k <= i; k++) { + if (var->value.string_2d_array_data.values[k] != NULL) { + for (int l = 0; + l < + (k == i ? j : var->value.string_2d_array_data.inner_counts[k]); + l++) { + if (var->value.string_2d_array_data.values[k][l] != NULL) { + free(var->value.string_2d_array_data.values[k][l]); + } + } + free(var->value.string_2d_array_data.values[k]); + } + } + free(var->value.string_2d_array_data.values); + free(var->value.string_2d_array_data.inner_counts); + var->value.string_2d_array_data.values = NULL; + var->value.string_2d_array_data.inner_counts = NULL; + return; + } + } + } +} + +void free_context(TemplateContext *ctx) { + if (ctx == NULL) { + return; + } + for (int i = 0; i < ctx->count; i++) { + if (ctx->vars[i].type == CONTEXT_TYPE_STRING_ARRAY) { + free_string_array_var(&ctx->vars[i]); + } else if (ctx->vars[i].type == CONTEXT_TYPE_STRING_2D_ARRAY) { + free_string_2d_array_var(&ctx->vars[i]); + } + } + ctx->count = 0; +} + +static char *html_escape(const char *input) { + if (input == NULL) { + return strdup(""); + } + + size_t input_len = strlen(input); + + size_t estimated_len = input_len * 5 + 1; + char *output = (char *)malloc(estimated_len); + if (output == NULL) { + perror("Failed to allocate memory for HTML escape output"); + return NULL; + } + output[0] = '\0'; + size_t current_output_len = 0; + + for (size_t i = 0; i < input_len; i++) { + const char *replacement = NULL; + + switch (input[i]) { + case '&': + replacement = "&"; + break; + case '<': + replacement = "<"; + break; + case '>': + replacement = ">"; + break; + case '"': + replacement = """; + break; + case '\'': + replacement = "'"; + break; + default: + break; + } + + if (replacement) { + size_t repl_len = strlen(replacement); + + if (current_output_len + repl_len + 1 > estimated_len) { + estimated_len = (current_output_len + repl_len + 1) * 2; + char *new_output = (char *)realloc(output, estimated_len); + if (new_output == NULL) { + perror("Failed to reallocate memory for HTML escape output"); + free(output); + return NULL; + } + output = new_output; + } + strcat(output, replacement); + current_output_len += repl_len; + } else { + + if (current_output_len + 1 + 1 > estimated_len) { + estimated_len = (current_output_len + 1 + 1) * 2; + char *new_output = (char *)realloc(output, estimated_len); + if (new_output == NULL) { + perror("Failed to reallocate memory for HTML escape output"); + free(output); + return NULL; + } + output = new_output; + } + output[current_output_len++] = input[i]; + output[current_output_len] = '\0'; + } + } + return output; +} + +static void append_to_buffer(char **buffer, size_t *current_len, + size_t *max_len, const char *str_to_add) { + if (str_to_add == NULL) { + fprintf(stderr, "[WARNING] append_to_buffer: Attempted to append NULL " + "string. Skipping.\n"); + return; + } + + size_t add_len = strlen(str_to_add); + + if (*current_len + add_len + 1 > *max_len) { + *max_len = (*current_len + add_len + 1) * 2; + + if (*max_len < BUFFER_SIZE * 2) { + *max_len = BUFFER_SIZE * 2; + } + + char *new_buffer = (char *)realloc(*buffer, *max_len); + if (new_buffer == NULL) { + perror("Failed to reallocate buffer for template rendering"); + fprintf(stderr, + "[ERROR] append_to_buffer: Reallocation failed (requested %zu " + "bytes).\n", + *max_len); + free(*buffer); + *buffer = NULL; + + exit(EXIT_FAILURE); + } + *buffer = new_buffer; + } + + strcat(*buffer, str_to_add); + *current_len += add_len; +} + +static char *parse_indexed_tag(const char *tag_content, int *index_val) { + *index_val = -1; + + char *open_bracket = strchr(tag_content, '['); + if (open_bracket == NULL) { + + char *key_name = strdup(tag_content); + if (key_name == NULL) { + perror("Failed to duplicate tag content for simple key"); + fprintf(stderr, "[ERROR] parse_indexed_tag: strdup failed for '%s'.\n", + tag_content); + } + return key_name; + } + + char *close_bracket = strchr(open_bracket, ']'); + if (close_bracket == NULL) { + fprintf(stderr, + "[ERROR] parse_indexed_tag: Unclosed bracket in tag '%s'. " + "Returning raw tag content.\n", + tag_content); + + char *key_name = strdup(tag_content); + if (key_name == NULL) { + perror("Failed to duplicate malformed tag content"); + } + return key_name; + } + + size_t key_len = open_bracket - tag_content; + char *key_name = (char *)malloc(key_len + 1); + if (key_name == NULL) { + perror("Failed to allocate memory for key_name in parse_indexed_tag"); + fprintf(stderr, + "[ERROR] parse_indexed_tag: Allocation failed for key_name.\n"); + return NULL; + } + strncpy(key_name, tag_content, key_len); + key_name[key_len] = '\0'; + + size_t index_str_len = close_bracket - (open_bracket + 1); + char *index_str = (char *)malloc(index_str_len + 1); + if (index_str == NULL) { + perror("Failed to allocate memory for index_str in parse_indexed_tag"); + fprintf(stderr, + "[ERROR] parse_indexed_tag: Allocation failed for index_str.\n"); + free(key_name); + return NULL; + } + strncpy(index_str, open_bracket + 1, index_str_len); + index_str[index_str_len] = '\0'; + + *index_val = atoi(index_str); + free(index_str); + + return key_name; +} + +static char *render_template_segment(const char *template_segment, + TemplateContext *ctx) { + size_t initial_max_len = BUFFER_SIZE; + char *rendered_buffer = (char *)malloc(initial_max_len); + if (rendered_buffer == NULL) { + perror("Failed to allocate initial buffer for template segment rendering"); + fprintf(stderr, + "[ERROR] render_template_segment: Failed to allocate initial %zu " + "bytes for rendered_buffer.\n", + initial_max_len); + return NULL; + } + rendered_buffer[0] = '\0'; + size_t current_len = 0; + size_t max_len = initial_max_len; + + const char *current_pos = template_segment; + const char *start_tag; + + while ((start_tag = strstr(current_pos, "{{")) != NULL) { + + size_t text_len = start_tag - current_pos; + if (text_len > 0) { + char *text_before_tag = (char *)malloc(text_len + 1); + if (text_before_tag == NULL) { + perror("Failed to allocate memory for text_before_tag"); + fprintf(stderr, "[ERROR] render_template_segment: Allocation failed " + "for text_before_tag.\n"); + free(rendered_buffer); + return NULL; + } + strncpy(text_before_tag, current_pos, text_len); + text_before_tag[text_len] = '\0'; + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + text_before_tag); + free(text_before_tag); + } + + const char *end_tag = strstr(start_tag, "}}"); + if (end_tag == NULL) { + fprintf(stderr, "[ERROR] render_template_segment: Unclosed '{{' tag. " + "Appending remaining template content as-is.\n"); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, start_tag); + return rendered_buffer; + } + + size_t tag_content_len = end_tag - (start_tag + 2); + char *tag_content_raw = (char *)malloc(tag_content_len + 1); + if (tag_content_raw == NULL) { + perror("Failed to allocate memory for tag_content_raw"); + fprintf(stderr, "[ERROR] render_template_segment: Allocation failed for " + "tag_content_raw.\n"); + free(rendered_buffer); + return NULL; + } + strncpy(tag_content_raw, start_tag + 2, tag_content_len); + tag_content_raw[tag_content_len] = '\0'; + + char *trimmed_tag_content = tag_content_raw; + while (*trimmed_tag_content != '\0' && + (*trimmed_tag_content == ' ' || *trimmed_tag_content == '\t' || + *trimmed_tag_content == '\n' || *trimmed_tag_content == '\r')) { + trimmed_tag_content++; + } + + if (*trimmed_tag_content == '\0') { + free(tag_content_raw); + current_pos = end_tag + 2; + continue; + } else { + char *end_trimmed = trimmed_tag_content + strlen(trimmed_tag_content) - 1; + while (end_trimmed >= trimmed_tag_content && + (*end_trimmed == ' ' || *end_trimmed == '\t' || + *end_trimmed == '\n' || *end_trimmed == '\r')) { + } + *(end_trimmed + 1) = '\0'; + } + + if (strncmp(trimmed_tag_content, "for ", 4) == 0) { + char loop_var[MAX_KEY_LEN]; + char list_var[MAX_KEY_LEN]; + if (sscanf(trimmed_tag_content, "for %s in %s", loop_var, list_var) == + 2) { + ContextVar *list_ctx_var = find_context_var(ctx, list_var); + + const char *loop_end_tag = strstr(end_tag + 2, "{{endfor}}"); + if (loop_end_tag == NULL) { + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + trimmed_tag_content); + free(tag_content_raw); + current_pos = end_tag + 2; + continue; + } + + if (list_ctx_var != NULL) { + + bool is_empty = false; + if (list_ctx_var->type == CONTEXT_TYPE_STRING_2D_ARRAY) { + is_empty = + (list_ctx_var->value.string_2d_array_data.outer_count == 0); + } else if (list_ctx_var->type == CONTEXT_TYPE_STRING_ARRAY) { + is_empty = (list_ctx_var->value.string_array_data.count == 0); + } else { + + is_empty = true; + } + + if (is_empty) { + current_pos = loop_end_tag + strlen("{{endfor}}"); + free(tag_content_raw); + continue; + } + + const char *loop_inner_start = end_tag + 2; + size_t loop_inner_len = loop_end_tag - loop_inner_start; + char *loop_inner_template = (char *)malloc(loop_inner_len + 1); + if (loop_inner_template == NULL) { + perror("Failed to allocate memory for loop_inner_template"); + fprintf(stderr, "[ERROR] render_template_segment: Allocation " + "failed for loop_inner_template.\n"); + free(rendered_buffer); + free(tag_content_raw); + return NULL; + } + strncpy(loop_inner_template, loop_inner_start, loop_inner_len); + loop_inner_template[loop_inner_len] = '\0'; + + if (list_ctx_var->type == CONTEXT_TYPE_STRING_2D_ARRAY) { + for (int i = 0; + i < list_ctx_var->value.string_2d_array_data.outer_count; + i++) { + TemplateContext loop_ctx = new_context(); + + for (int j = 0; j < ctx->count; j++) { + if (ctx->vars[j].type == CONTEXT_TYPE_STRING) { + context_set(&loop_ctx, ctx->vars[j].key, + ctx->vars[j].value.string_val); + } + } + + context_set_string_array( + &loop_ctx, loop_var, + list_ctx_var->value.string_2d_array_data.values[i], + list_ctx_var->value.string_2d_array_data.inner_counts[i]); + + char *rendered_loop_item = + render_template_segment(loop_inner_template, &loop_ctx); + if (rendered_loop_item) { + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + rendered_loop_item); + free(rendered_loop_item); + } + free_context(&loop_ctx); + } + } else if (list_ctx_var->type == CONTEXT_TYPE_STRING_ARRAY) { + for (int i = 0; i < list_ctx_var->value.string_array_data.count; + i++) { + TemplateContext loop_ctx = new_context(); + + for (int j = 0; j < ctx->count; j++) { + if (ctx->vars[j].type == CONTEXT_TYPE_STRING) { + context_set(&loop_ctx, ctx->vars[j].key, + ctx->vars[j].value.string_val); + } + } + + context_set(&loop_ctx, loop_var, + list_ctx_var->value.string_array_data.values[i]); + + char *rendered_loop_item = + render_template_segment(loop_inner_template, &loop_ctx); + if (rendered_loop_item) { + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + rendered_loop_item); + free(rendered_loop_item); + } + free_context(&loop_ctx); + } + } else { + fprintf( + stderr, + "[IGNORE] [ERROR] render_template_segment: List variable '%s' " + "(type %d) is not an iterable array type for 'for' loop.\n", + list_var, list_ctx_var->type); + } + free(loop_inner_template); + current_pos = loop_end_tag + strlen("{{endfor}}"); + free(tag_content_raw); + continue; + } else { + + fprintf(stderr, + "[IGNORE] [ERROR] render_template_segment: List variable " + "'%s' not found for 'for' loop. Skipping loop block.\n", + list_var); + current_pos = loop_end_tag + strlen("{{endfor}}"); + free(tag_content_raw); + continue; + } + } else { + fprintf(stderr, + "[ERROR] render_template_segment: Malformed 'for' loop tag: " + "'%s'. Expected 'for var in list'. Appending loop tag as-is.\n", + trimmed_tag_content); + } + } + + else if (strcmp(trimmed_tag_content, "endfor") == 0) { + fprintf(stderr, "[WARNING] render_template_segment: '{{endfor}}' without " + "matching '{{for}}'. Appending endfor tag as-is.\n"); + } + + else if (strncmp(trimmed_tag_content, "include ", 8) == 0) { + const char *filename_start = trimmed_tag_content + strlen("include "); + if (*filename_start == '"') { + filename_start++; + char *filename_end = strchr(filename_start, '"'); + if (filename_end != NULL) { + size_t filename_len = filename_end - filename_start; + char *included_filename = (char *)malloc(filename_len + 1); + if (included_filename == NULL) { + perror("Failed to allocate memory for included filename"); + fprintf(stderr, "[ERROR] render_template_segment: Allocation " + "failed for included_filename.\n"); + free(rendered_buffer); + free(tag_content_raw); + return NULL; + } + strncpy(included_filename, filename_start, filename_len); + included_filename[filename_len] = '\0'; + + char *included_html = render_template(included_filename, ctx); + if (included_html) { + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + included_html); + free(included_html); + } else { + fprintf(stderr, + "[WARNING] render_template_segment: Failed to render " + "included template '%s'.\n", + included_filename); + + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + "<!-- Failed to include "); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + included_filename); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, " -->"); + } + free(included_filename); + current_pos = end_tag + 2; + free(tag_content_raw); + continue; + } else { + fprintf(stderr, + "[ERROR] render_template_segment: Malformed include tag " + "'%s'. Missing closing quote.\n", + trimmed_tag_content); + + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, "{{"); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + trimmed_tag_content); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, "}}"); + free(tag_content_raw); + + current_pos = end_tag + 2; + + continue; + + } + } else { + fprintf(stderr, + "[ERROR] render_template_segment: Malformed include tag '%s'. " + "Expected quoted filename.\n", + trimmed_tag_content); + + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, "{{"); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + trimmed_tag_content); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, "}}"); + } + } + + else { + bool is_safe = false; + char *processing_tag_content = strdup(trimmed_tag_content); + if (processing_tag_content == NULL) { + perror("Failed to duplicate tag content for processing"); + free(rendered_buffer); + free(tag_content_raw); + return NULL; + } + + char *safe_flag_pos_in_processing = + strstr(processing_tag_content, "|safe"); + if (safe_flag_pos_in_processing != NULL) { + is_safe = true; + *safe_flag_pos_in_processing = '\0'; + } + + int index_val = -1; + char *key_name = parse_indexed_tag(processing_tag_content, &index_val); + free(processing_tag_content); + + if (key_name == NULL) { + fprintf(stderr, + "[ERROR] render_template_segment: parse_indexed_tag failed for " + "'%s'.\n", + trimmed_tag_content); + free(rendered_buffer); + free(tag_content_raw); + return NULL; + } + + const char *value_to_append = NULL; + ContextVar *var = find_context_var(ctx, key_name); + + if (var != NULL) { + if (index_val == -1) { + if (var->type == CONTEXT_TYPE_STRING) { + value_to_append = var->value.string_val; + } else { + fprintf( + stderr, + "[WARNING] render_template_segment: Variable '%s' is not a " + "simple string (type %d). Not rendering as simple string.\n", + key_name, var->type); + + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, "{{"); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + trimmed_tag_content); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, "}}"); + } + } else { + if (var->type == CONTEXT_TYPE_STRING_ARRAY) { + if (index_val >= 0 && + index_val < var->value.string_array_data.count) { + value_to_append = var->value.string_array_data.values[index_val]; + } else { + fprintf(stderr, + "[ERROR] render_template_segment: Index %d out of bounds " + "for '%s' (count %d). Appending tag as-is.\n", + index_val, key_name, var->value.string_array_data.count); + } + } else { + fprintf( + stderr, + "[ERROR] render_template_segment: Variable '%s' (type %d) is " + "not a string array for indexed access. Appending tag as-is.\n", + key_name, var->type); + } + } + } else { + fprintf(stderr, + "[WARNING] render_template_segment: Key '%s' not found in " + "context. Appending tag as-is.\n", + key_name); + } + + if (value_to_append != NULL) { + if (is_safe) { + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + value_to_append); + } else { + char *escaped_value = html_escape(value_to_append); + if (escaped_value) { + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + escaped_value); + free(escaped_value); + } else { + fprintf(stderr, + "[ERROR] render_template_segment: Failed to HTML escape " + "value for key '%s'. Appending raw.\n", + key_name); + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, + value_to_append); + } + } + } + + free(key_name); + + free(tag_content_raw); + + current_pos = end_tag + 2; + + continue; + + } + + free(tag_content_raw); + current_pos = end_tag + 2; + } + + append_to_buffer(&rendered_buffer, ¤t_len, &max_len, current_pos); + return rendered_buffer; +} + +char *render_template(const char *template_file, TemplateContext *ctx) { + char full_path[MAX_PATH_LEN]; + + snprintf(full_path, sizeof(full_path), "%s%s", TEMPLATES_DIR, template_file); + + FILE *fp = fopen(full_path, "r"); + if (fp == NULL) { + perror("Error opening template file"); + fprintf(stderr, + "[ERROR] render_template: Could not open template '%s'. %s\n", + full_path, strerror(errno)); + return NULL; + } + + fseek(fp, 0, SEEK_END); + long file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + char *template_content = (char *)malloc(file_size + 1); + if (template_content == NULL) { + perror("Error allocating memory for template content"); + fprintf(stderr, + "[ERROR] render_template: Failed to allocate %ld bytes for " + "template content.\n", + file_size + 1); + fclose(fp); + return NULL; + } + fread(template_content, 1, file_size, fp); + template_content[file_size] = '\0'; + fclose(fp); + + char *rendered_html = render_template_segment(template_content, ctx); + free(template_content); + return rendered_html; +}
\ No newline at end of file |
