ref: fb0e2e9c6043e8bba9efda507111bab946db50be
dir: /xslt.c/
#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, "<");
break;
case '>':
Bprint(bout, ">");
break;
case '&':
Bprint(bout, "&");
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);
}