diff options
Diffstat (limited to 'sternenblog/xml.c')
-rw-r--r-- | sternenblog/xml.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/sternenblog/xml.c b/sternenblog/xml.c new file mode 100644 index 0000000..5965a09 --- /dev/null +++ b/sternenblog/xml.c @@ -0,0 +1,309 @@ +// TODO indent, html escaping +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "xml.h" + +#define DEBUG_WARN(ctx, ...) \ + if(ctx->warn != NULL) { \ + fprintf(ctx->warn, __VA_ARGS__); \ + } + +void debug_xml_stack(FILE *out, struct xml_stack *stack) { + if(stack != NULL) { + fprintf(out, "%s ", stack->tag); + debug_xml_stack(out, stack->next); + } else { + fputc('\n', out); + } +} + +void free_xml_stack(struct xml_stack *stack) { + if(stack == NULL) { + return; + } + + if(stack->tag != NULL) { + free(stack->tag); + } + + if(stack->next != NULL) { + free_xml_stack(stack->next); + } + + free(stack); +} + +void new_xml_context(struct xml_context *ctx) { + ctx->stack = NULL; + ctx->warn = NULL; + ctx->out = stdout; + ctx->closing_slash = 1; +} + +void del_xml_context(struct xml_context *ctx) { + if(ctx->stack != NULL) { + if(ctx->warn != NULL) { + fputs("Unclosed tags remaining: ", ctx->warn); + debug_xml_stack(ctx->warn, ctx->stack); + } + + free_xml_stack(ctx->stack); + } +} + +void output_xml_escaped_char(FILE *out, char c) { + switch(c) { + case '&': + fputs("&", out); + break; + case '<': + fputs("<", out); + break; + case '>': + fputs(">", out); + break; + case '\'': + fputs("'", out); + break; + case '\"': + fputs(""", out); + break; + default: + fputc(c, out); + break; + } +} + +void xml_escaped(struct xml_context *ctx, const char *str) { + for(size_t i = 0; str[i] != '\0'; i++) { + output_xml_escaped_char(ctx->out, str[i]); + } +} + +void xml_raw(struct xml_context *ctx, const char *str) { + fputs(str, ctx->out); +} + +void output_attrs(FILE *out, va_list attrs, size_t arg_count) { + if(arg_count > 0) { + for(size_t i = 1; i<=arg_count; i++) { + if(i % 2) { + char *name = va_arg(attrs, char *); + if(name == NULL) { + break; + } + + fputc(' ', out); + fputs(name, out); + } else { + char *maybe_val = va_arg(attrs, char *); + if(maybe_val != NULL) { + fputs("=\"", out); + for(size_t i = 0; maybe_val[i] != '\0'; i++) { + output_xml_escaped_char(out, maybe_val[i]); + } + fputc('\"', out); + } + } + } + } +} + +void xml_empty_tag(struct xml_context *ctx, const char *tag, size_t attr_count, ...) { + if(tag == NULL || ctx == NULL) { + DEBUG_WARN(ctx, "Got no tag or ctx\n"); + return; + } + + fputc('<', ctx->out); + fputs(tag, ctx->out); + + if(attr_count > 0) { + size_t arg_count = attr_count * 2; + + va_list attrs; + va_start(attrs, attr_count); + + output_attrs(ctx->out, attrs, arg_count); + + va_end(attrs); + } + + if(ctx->closing_slash) { + fputc('/', ctx->out); + } + + fputc('>', ctx->out); +} + +void xml_open_tag_attrs(struct xml_context *ctx, const char *tag, size_t attr_count, ...) { + if(tag == NULL || ctx == NULL) { + DEBUG_WARN(ctx, "Got no tag or ctx\n"); + return; + } + + struct xml_stack *old_stack = ctx->stack; + + fputc('<', ctx->out); + fputs(tag, ctx->out); + + + if(attr_count > 0) { + size_t arg_count = attr_count * 2; + + va_list attrs; + va_start(attrs, attr_count); + + output_attrs(ctx->out, attrs, arg_count); + + va_end(attrs); + } + + fputc('>', ctx->out); + + ctx->stack = malloc(sizeof(struct xml_context)); + + if(ctx->stack == NULL) { + ctx->stack = old_stack; + DEBUG_WARN(ctx, "Could not allocate memory for tag stack, now everything will break.\n") + return; + } + + ctx->stack->next = old_stack; + + size_t tag_size = strlen(tag) + 1; + ctx->stack->type = XML_NORMAL_TAG; + ctx->stack->tag = malloc(sizeof(char) * tag_size); + memcpy(ctx->stack->tag, tag, tag_size); +} + +void xml_open_tag(struct xml_context *ctx, const char *tag) { + xml_open_tag_attrs(ctx, tag, 0); +} + +void xml_close_tag(struct xml_context *ctx, const char *tag) { + if(tag == NULL || ctx == NULL) { + DEBUG_WARN(ctx, "Got no tag or ctx\n"); + return; + } + + if(ctx->stack == NULL) { + DEBUG_WARN(ctx, "Refusing to close tag %s, no tags left to be closed\n", tag); + return; + } + + if(ctx->stack->type != XML_NORMAL_TAG) { + DEBUG_WARN(ctx, "Refusing to close tag %s, wrong tag type\n", tag); + return; + } + + if(strcmp(tag, ctx->stack->tag) != 0) { + DEBUG_WARN(ctx, "Refusing to close tag %s, unclosed tags remaining\n", tag); + return; + } + + fputs("</", ctx->out); + fputs(tag, ctx->out); + fputc('>', ctx->out); + + struct xml_stack *old_head = ctx->stack; + + ctx->stack = old_head->next; + + free(old_head->tag); + free(old_head); +} + +void xml_close_all(struct xml_context *ctx) { + xml_close_including(ctx, NULL); +} + +void xml_close_including(struct xml_context *ctx, const char *tag) { + if(ctx == NULL) { + DEBUG_WARN(ctx, "Got no ctx\n"); + return; + } + + if(ctx->stack == NULL) { + if(tag != NULL) { + DEBUG_WARN(ctx, "Hit end of tag stack while searching for tag %s to close\n", tag); + } + return; + } else { + int last_tag = tag != NULL && strcmp(tag, ctx->stack->tag) == 0; + + switch(ctx->stack->type) { + case XML_NORMAL_TAG: + xml_close_tag(ctx, ctx->stack->tag); + break; + case XML_CDATA: + xml_close_cdata(ctx); + break; + default: + DEBUG_WARN(ctx, "Unexpected tag type on stack, aborting\n"); + return; + } + + if(!last_tag) { + xml_close_including(ctx, tag); + } + } +} + +void xml_open_cdata(struct xml_context *ctx) { + if(ctx == NULL) { + DEBUG_WARN(ctx, "Got no ctx\n"); + return; + } + + struct xml_stack *old_stack = ctx->stack; + + ctx->stack = malloc(sizeof(struct xml_stack)); + + if(ctx->stack == NULL) { + ctx->stack = old_stack; + + DEBUG_WARN(ctx, "Could not allocate memory for tag stack, now everything will break.\n"); + return; + } + + ctx->stack->next = old_stack; + ctx->stack->tag = NULL; + ctx->stack->type = XML_CDATA; + + fputs("<![CDATA[", ctx->out); +} + +void xml_close_cdata(struct xml_context *ctx) { + if(ctx == NULL) { + DEBUG_WARN(ctx, "Got no ctx\n"); + return; + } + + if(ctx->stack == NULL) { + DEBUG_WARN(ctx, "No CDATA to close\n"); + return; + } + + if(ctx->stack->type != XML_CDATA) { + DEBUG_WARN(ctx, "No CDATA on top of stack, refusing to close\n"); + return; + } + + struct xml_stack *old_head = ctx->stack; + + ctx->stack = old_head->next; + + if(old_head->tag != NULL) { + // shouldn't happen though + free(old_head->tag); + } + + free(old_head); + + fputs("]]>", ctx->out); +} |