// TODO indent, html escaping #include #include #include #include #include #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("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("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); }