aboutsummaryrefslogtreecommitdiff
path: root/src/template.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/template.c')
-rw-r--r--src/template.c961
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 = "&amp;";
+ break;
+ case '<':
+ replacement = "&lt;";
+ break;
+ case '>':
+ replacement = "&gt;";
+ break;
+ case '"':
+ replacement = "&quot;";
+ break;
+ case '\'':
+ replacement = "&apos;";
+ 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, &current_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, &current_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, &current_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, &current_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, &current_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, &current_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, &current_len, &max_len,
+ "<!-- Failed to include ");
+ append_to_buffer(&rendered_buffer, &current_len, &max_len,
+ included_filename);
+ append_to_buffer(&rendered_buffer, &current_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, &current_len, &max_len, "{{");
+ append_to_buffer(&rendered_buffer, &current_len, &max_len,
+ trimmed_tag_content);
+ append_to_buffer(&rendered_buffer, &current_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, &current_len, &max_len, "{{");
+ append_to_buffer(&rendered_buffer, &current_len, &max_len,
+ trimmed_tag_content);
+ append_to_buffer(&rendered_buffer, &current_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, &current_len, &max_len, "{{");
+ append_to_buffer(&rendered_buffer, &current_len, &max_len,
+ trimmed_tag_content);
+ append_to_buffer(&rendered_buffer, &current_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, &current_len, &max_len,
+ value_to_append);
+ } else {
+ char *escaped_value = html_escape(value_to_append);
+ if (escaped_value) {
+ append_to_buffer(&rendered_buffer, &current_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, &current_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, &current_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