shithub: xmltools

ref: fb0e2e9c6043e8bba9efda507111bab946db50be
dir: /xslt.c/

View raw version
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <xml.h>
#include <xpath.h>

void
usage(void)
{
	fprint(2, "usage: %s\n", argv0);
	exits(nil);
}

#define TAB '	'

Biobuf *bout = nil;

static void
indent(int level)
{
	while (level > 0) {
		Bprint(bout, "	");
		level--;
	}
}

static void
printesc(char *s, int noesc)
{
	if (noesc) {
		Bprint(bout, "%s", s);
		return;
	}
	for (; *s; s++) {
		switch (*s) {
		case '<':
			Bprint(bout, "&lt;");
			break;
		case '>':
			Bprint(bout, "&gt;");
			break;
		case '&':
			Bprint(bout, "&amp;");
			break;
		default:
			Bputc(bout, *s);
			break;
		}
	}
}

char Amatch[] = "match";
char Aselect[] = "select";

Xml *xml = nil;
Xml *style = nil;

Ns *xslns = nil;

static Elem*
docparent(Elem *el)
{
	el = el->parent;
	while (el->ns == xslns)
		el = el->parent;
	return el;
}

static int
hasdocchild(Elem *el)
{
	Elem *e;
	for (e = el->child; e; e = e->next) {
		if (e->ns != xslns)
			return 1;
		if (hasdocchild(e))
			return 1;
	}
	return 0;
}

typedef struct Template Template;
struct Template {
	Elem *el;
	Template *next;
};

Template *templates = nil;
Template *lasttemplate = nil;
Template *roottemplate = nil;

static void process(Elem *tel, Elem *el, int level);

static void
addtemplate(Elem *el)
{
	if (!templates) {
		templates = mallocz(sizeof(Template), 1);
		templates->el = el;
		lasttemplate = templates;
		return;
	}
	lasttemplate->next = mallocz(sizeof(Template), 1);
	lasttemplate = lasttemplate->next;
	lasttemplate->el = el;
}

static void
findtemplates(Elem *el)
{
	Elem *e;
	
	for (e = el; e; e = e->next) {
		if (e->ns == xslns && strcmp(e->name, "template") == 0)
			addtemplate(e);
		if (e->child)
			findtemplates(e->child);
	}
}

static char*
templatematch(Elem *el, char *s)
{
	Attr *a;
	for (a = el->attrs; a; a = a->next)
		if (a->name && strcmp(a->name, s) == 0)
			return a->value;
	return nil;
}

static char*
etemplatematch(Elem *el, char *a)
{
	char *s;
	s = templatematch(el, a);
	if (!s)
		fprint(2, "%s with missing attribute %s\n", el->name, a);
	return s;
}

static Template*
findtemp(Elem *e)
{
	Template *t;
	char *s, *u;
	s = templatematch(e, Aselect);
	if (!s)
		sysfatal("bad syntax: template without match");
	for (t = templates; t; t = t->next) {
		u = templatematch(t->el, Amatch);
		if (strcmp(s, u) == 0)
			return t;
	}
	werrstr("template '%s'", s);
	return nil;
}

static void
printelemstart(Elem *el, int level)
{
	Attr *a;
	indent(level);
	Bprint(bout, "<%s", el->name);
	for (a = el->attrs; a; a = a->next) {
		Bprint(bout, " %s", a->name);
		if (a->value)
			Bprint(bout, "='%s'", a->value);
	}
	if (!el->child)
		Bprint(bout, " />\n");
	else
		Bprint(bout, ">");

	if (el->pcdata)
		Bprint(bout, "%s", el->pcdata);
	else if (hasdocchild(el))
		Bprint(bout, "\n");
}

static void
printelemend(Elem *el, int level)
{
	if (!el->child)
		return;
	if (hasdocchild(el))
		indent(level);
	Bprint(bout, "</%s>\n", el->name);
}

typedef struct Efunc Efunc;
struct Efunc {
	char *name;
	void (*f)(Elem*,Elem*,int);
};

static void
fapplytemplates(Elem *tel, Elem *el, int level)
{
	Template *t;
	XpResult r;
	int i;
	
	t = findtemp(tel);
	if (!t) {
		fprint(2, "unable to find template: %r");
		return;
	}
	r = xmllookpath(el, templatematch(t->el, Amatch));
	if (!r.type)
		return;
	if (r.type != Xelem)
		return;
	for (i = 0; i < r.num; i++)
		process(t->el, r.elems[i], level);
}

static void
fattribute(Elem *tel, Elem *el, int)
{
	/* this should add the attributes to the parent node, which is
	not trivial with the current architecture. Think:
		addattribute(parent-elem, "name", pcdata)
	*/
	USED(tel, el);
	fprint(2, "attribute: not implemented yet!\n");
}

static void
fcomment(Elem *tel, Elem *el, int level)
{
	indent(level);
	Bprint(bout, "<!-- ");
	if (tel->pcdata)
		Bprint(bout, "%s", tel->pcdata);
	else
		process(tel, el, level);
	Bprint(bout, " -->\n");
}

static void
ffallback(Elem *tel, Elem *el, int level)
{
	process(tel, el, level);
}

static void
fforeach(Elem *tel, Elem *el, int level)
{
	XpResult r;
	int i;
	char *s;
	
	s = etemplatematch(tel, Aselect);
	if (!s)
		return;
	r = xmllookpath(el, s);
	if (!r.num)
		goto Out;
	if (r.type != Xelem) {
		fprint(2, "bad result type: %d != %d\n", r.type, Xelem);
		goto Out;
	}
	for (i = 0; i < r.num; i++)
		process(tel, r.elems[i], level+1);
Out:
	xmlfreeresult(&r);
}

static void
fif(Elem *tel, Elem *el, int level)
{
	char *s;
	XpResult r;
	
	s = etemplatematch(tel, "test");
	if (!s)
		return;
	
	r = xmllookpath(el, s);
	if (r.error) {
		fprint(2, "error: %r\n");
		return;
	}
	if (r.type != Xnum) {
		fprint(2, "if: result is not type Xnum (%d != %d)\n", r.type, Xnum);
		return;
	}
	if (r.num != 1) {
		fprint(2, "if: expected only 1 result, got %d\n", r.num);
		return;
	}
	if (!r.numbers[0])
		goto Out;
	process(tel, el, level);
Out:
	xmlfreeresult(&r);
}

static void
ftext(Elem *tel, Elem*, int)
{
	int noesc = 0;
	char *s;
	
	if (tel->child)
		fprint(2, "xslt text: Ignoring all children\n");
	
	s = templatematch(tel, "disable-output-escaping");
	if (s) {
		if (strcmp(s, "yes") == 0)
			noesc = 1;
		else if (strcmp(s, "no") == 0)
			noesc = 0;
		else {
			fprint(2, "xslt text: disable-output-escaping: invalid value %s\n", s);
		}
	}
	if (tel->pcdata)
		printesc(tel->pcdata, noesc);
}

static void
fvalueof(Elem *tel, Elem *el, int)
{
	XpResult r;
	char *s;
	
	s = etemplatematch(tel, Aselect);
	if (!s)
		return;
	r = xmllookpath(el, s);
	if (r.num != 1)
		goto Out;
	switch (r.type) {
	case Xelem:
		if (r.elems[0]->pcdata)
			Bprint(bout, "%s", r.elems[0]->pcdata);
		break;
	case Xstring:
		Bprint(bout, "%s", r.strings[0]);
		break;
	}
Out:
	xmlfreeresult(&r);
}

Efunc efuncs[] = {
	{ "apply-imports", nil },
	{ "apply-templates", fapplytemplates },
	{ "attribute", fattribute },
	{ "attribute-set", nil },
	{ "call-template", nil },
	{ "choose", nil },
	{ "comment", fcomment },
	{ "copy", nil },
	{ "copy-of", nil },
	{ "decimal-format", nil },
	{ "element", nil },
	{ "fallback", ffallback },
	{ "for-each", fforeach },
	{ "if", fif },
	{ "import", nil },
	{ "include", nil },
	{ "key", nil },
	{ "message", nil },
	{ "namespace-alias", nil },
	{ "number", nil },
	{ "otherwise", nil },
	{ "output", nil },
	{ "param", nil },
	{ "preserve-space", nil },
	{ "processing-instruction", nil },
	{ "sort", nil },
	{ "strip-space", nil },
	{ "stylesheet", nil },
	{ "template", nil },
	{ "text", ftext },
	{ "transform", nil },
	{ "value-of", fvalueof },
	{ "variable", nil },
	{ "when", nil },
	{ "with-param", nil },
	{ nil, nil },
};

static void
fbackupfunc(Elem *tel, Elem *el, int level)
{
	Elem *fel;
	
	for (fel = tel->child; fel; fel = fel->next) {
		if (fel->ns != xslns)
			continue;
		if (strcmp(fel->name, "fallback") != 0)
			continue;
		break;
	}
	if (!fel) {
		fprint(2, "xslt function not implemented: %s\n", tel->name);
		return;
	}
	process(fel, el, level);
}
Efunc fbackup = { nil, fbackupfunc };

static Efunc*
findfunc(char *s)
{
	Efunc *e;
	for (e = efuncs; e->name; e++)
		if (strcmp(e->name, s) == 0)
			if (e->f)
				return e;
			else
				break;
	return &fbackup;
}

static void
process(Elem *tel, Elem *el, int level)
{
	Elem *e;
	Efunc *f;
	
	for (e = tel->child; e; e = e->next) {
		if (e->ns != xslns) {
			printelemstart(e, level);
			process(e, el, level + 1);
			printelemend(e, level);
			continue;
		}
		f = findfunc(e->name);
		f->f(e, el, level);
	}
}

void
main(int argc, char **argv)
{
	int fd;
	Ns *ns;
	XpResult r;
	char *s = nil;

	ARGBEGIN{
	case 'h':
		usage();
		break;
	case 's':
		s = EARGF(usage());
		break;
	}ARGEND;

	if (argc && *argv[0]) {
		fd = open(argv[0], OREAD);
		if (fd < 0)
			sysfatal("unable to open file: %r");
		xml = xmlparse(fd, 8192, Fcrushwhite);
		close(fd);
	} else {
		xml = xmlparse(0, 8192, Fcrushwhite);
	}
	if (!xml)
		sysfatal("error parsing xml: %r");

	if (s) {
		fd = open(s, OREAD);
		if (fd < 0)
			sysfatal("unable to open file: %r");
		style = xmlparse(fd, 8192, Fcrushwhite);
		close(fd);
	}
	
	if (!style)
		sysfatal("error parsing xslt: %r");
	
	for (ns = style->ns; ns; ns = ns->next) {
		if (ns->decl && strstr(ns->decl, "/XSL/Transform")) {
			xslns = ns;
			break;
		}
	}
	
	findtemplates(style->root);
	
	for (Template *t = templates; t; t = t->next) {
		s = templatematch(t->el, Amatch);
		r = xmllookpath(xml->root, s);
		if (r.num != 1)
			continue;
		if (r.type != Xelem)
			continue;
		if (r.elems[0] != xml->root)
			continue;
		roottemplate = t;
		break;
	}
	
//	xmldebug = 1;
	
	if (!roottemplate)
		sysfatal("malformed data: no root template");
	
	bout = Bfdopen(1, OWRITE);
	if (!bout)
		sysfatal("%r");
	process(roottemplate->el, xml->root, 0);
	exits(nil);
}