/*
* (c) 2020 Vincenzo "KatolaZ" Nicosia <katolaz@freaknet.org>
* 
* A simple xml-to-tsv converter, based on xmlparser by Hiltjo Posthuma
* http://codemadness.org/git/xmlparser/
*
* You can use, distribute, modify, and/or redistribute this program under
* the terms of the ISC LICENSE. See LICENSE for details.
*
*/


#include <sys/types.h>

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "xml.h"
#include "config.h"

/* tag stack */

typedef struct {
	int top;
	char st[DEPTH_MAX][STR_MAX];
} tstack_t;

int stack_push(tstack_t *t, const char *c){
	if (t->top < DEPTH_MAX){
		t->top ++;
		strncpy(t->st[t->top], c, STR_MAX - 1);
		t->st[t->top][STR_MAX - 1] = '\0';
		return 0;
	}
	return -1;
}

char* stack_pop(tstack_t *t){
	if (t->top >= 0)
		return t->st[t->top--];
	return NULL;
}

char* stack_peek(tstack_t *t){
	if (t->top >= 0)
		return t->st[t->top];
	return NULL;
}

int stack_empty(tstack_t *t){
	return (t->top < 0);
}

void stack_init(tstack_t *t){
	t->top = -1;
}


/* utility functions */

/* quote_print: quote \\, \n, \t, and strip other ctrl chars */
void quote_print(FILE *f, const char *s){
	const char *tmp = s;
	size_t len;
	int i;
	while (*tmp != '\0'){
		len = strcspn(tmp, "\\\n\t");
		for(i=0; i<len; i++, tmp++){
			if (!iscntrl((unsigned char)*tmp)){
				fwrite(tmp, 1, 1, f);
			}
		}
		switch (*tmp){
			case '\n':
				if (len > 0){
					fprintf(f, "\\n");
				}
				tmp ++;
				break;
			case '\t':
				fprintf(f, "\\t");
				tmp ++;
				break;
			case '\r':
				fprintf(f, "\\r");
				tmp ++;
				break;
			case '\\':
				fprintf(f, "\\\\");
				tmp ++;
				break;
		}
	}
}

void print_cur_str(FILE *f, tstack_t *t){
	int i;
	for (i=0; i<=t->top; i++){
		fprintf(f, "/%s", t->st[i]);
	}
}

/* global variables */

tstack_t st;


/* xml callbacks */

void
xmlattr(XMLParser *x, const char *t, size_t tl, const char *a, size_t al,
        const char *v, size_t vl)
{
	printf("%s", v);
}

void
xmlattrentity(XMLParser *x, const char *t, size_t tl, const char *a, size_t al,
              const char *v, size_t vl)
{
	char buf[16];
	int n;

	if ((n = xml_entitytostr(v, buf, sizeof(buf))) > 0)
		xmlattr(x, t, tl, a, al, buf, (size_t)n);
	else
		xmlattr(x, t, tl, a, al, v, vl);
}

void
xmlattrend(XMLParser *x, const char *t, size_t tl, const char *a, size_t al)
{
}

void
xmlattrstart(XMLParser *x, const char *t, size_t tl, const char *a, size_t al)
{
	printf("%c%s%c", SEP, a, SATTR);
}

void
xmlcdatastart(XMLParser *x)
{
	printf("%c", SEP);
}

void
xmlcdata(XMLParser *x, const char *d, size_t dl)
{
	quote_print(stdout, d);
}

void
xmlcdataend(XMLParser *x)
{
}

void
xmlcommentstart(XMLParser *x)
{
}

void
xmlcomment(XMLParser *x, const char *c, size_t cl)
{
}

void
xmlcommentend(XMLParser *x)
{
}

void
xmldata(XMLParser *x, const char *d, size_t dl)
{
	quote_print(stdout, d);
}

void
xmldataend(XMLParser *x)
{
}

void
xmldataentity(XMLParser *x, const char *d, size_t dl)
{
	char buf[16];
	int n;

	if ((n = xml_entitytostr(d, buf, sizeof(buf))) > 0)
		xmldata(x, buf, (size_t)n);
	else
		xmldata(x, d, dl);
}

void
xmldatastart(XMLParser *x)
{
	printf("%c", SEP);
}

void
xmltagend(XMLParser *x, const char *t, size_t tl, int isshort)
{
	char *tag;
	if (stack_empty(&st)){
		fprintf(stderr, "Error: tag-end '%s' before any open tag", t);
	}
	tag = stack_pop(&st);
	if (strcmp(t, tag)){
		fprintf(stderr, "Error: tag-end '%s' closes tag '%s'", t, tag);
	}

	if (isshort) {
		printf("\n");
		print_cur_str(stdout, &st);
	}
}

void
xmltagstart(XMLParser *x, const char *t, size_t tl)
{
	if (stack_push(&st, t)){
		fprintf(stderr, "Error: stack full. Ignoring tag '%s' (parent tag: '%s')\n", t, stack_peek(&st));
		return;
	}
	printf("\n");
	print_cur_str(stdout, &st);
}

void
xmltagstartparsed(XMLParser *x, const char *t, size_t tl, int isshort)
{
	/* printf("inside tagstartparsed\n"); */
}

int
main(void)
{
	stack_init(&st);
	XMLParser x = { 0 };

	x.xmlattr = xmlattr;
	x.xmlattrend = xmlattrend;
	x.xmlattrstart = xmlattrstart;
	x.xmlattrentity = xmlattrentity;
	x.xmlcdatastart = xmlcdatastart;
	x.xmlcdata = xmlcdata;
	x.xmlcdataend = xmlcdataend;
	x.xmlcommentstart = xmlcommentstart;
	x.xmlcomment = xmlcomment;
	x.xmlcommentend = xmlcommentend;
	x.xmldata = xmldata;
	x.xmldataend = xmldataend;
	x.xmldataentity = xmldataentity;
	x.xmldatastart = xmldatastart;
	x.xmltagend = xmltagend;
	x.xmltagstart = xmltagstart;
	x.xmltagstartparsed = xmltagstartparsed;

	x.getnext = getchar;

	xml_parse(&x);
	printf("\n");
	if (! stack_empty(&st)) {
		fprintf(stderr, "Error: tags still open at EOF: ");
		print_cur_str(stderr, &st);
		fprintf(stderr, "\n");
	}
	return 0;
}