shithub: xmltools

ref: dd05e41b9a71be3441f36d9b944274da3cd64f35
dir: /xq.c/

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

void
usage(void)
{
	fprint(2, "usage: %s [-t] [-f file]\n", argv0);
	exits("usage");
}

char Enotfound[] = "not found\n";
char Einvalidsyntax[] = "invalid syntax\n";

int flags;

void
printattr(Elem *e, char *attr)
{
	Attr *a;
	
	for (a = e->attrs; a; a = a->next) {
		if (strcmp(a->name, attr) == 0) {
			print("%s\n", a->value);
			return;
		}
	}
}

void
printtext(Elem *e)
{
	print("%s\n", e->pcdata);
}

void
printelem(Elem *e)
{
	Attr *a;
	
	print("<%s", e->name);
	for (a = e->attrs; a; a = a->next) {
		print(" %s='%s'", a->name, a->value);
	}
	print(" />\n");
}

Reprog *fattr = nil;
Reprog *fnum = nil;

Elem*
getfiltered(Elem *e, char *s, char **q)
{
	Resub match[3];
	Elem *el;
	char *attr, *val;
	char *new;
	int id, i;
	
	if (!fattr)
		fattr = regcomp("\\[@(.+)=\\'(.+)\\'\\]");
	if (!fnum)
		fnum = regcomp("\\[([0-9]+)\\]");
	
//	fprint(2, "e: %s\nq: %s\n", e->name, s);
	
	memset(match, 0, 3*sizeof(Resub));
	if (regexec(fattr, s, match, 3)) {
		*match[0].sp = 0;
		new = match[0].ep;
		
		attr = match[1].sp;
		*match[1].ep = 0;
		
		val = match[2].sp;
		*match[2].ep = 0;
		el = xmllook(e, s, attr, val);
		if (!el) {
			fprint(2, Enotfound);
			return nil;
		}
		
		/* new path has to start with the self element */
		attr = strrchr(s, '/');
		if (!attr) {
			fprint(2, Einvalidsyntax);
			return nil;
		}
		attr++;
		i = strlen(attr);
		new -= i;
		memmove(new, attr, i);
		return getfiltered(el, new, q);
	}
	memset(match, 0, 3*sizeof(Resub));
	if (regexec(fnum, s, match, 3)) {
		*match[0].sp = 0;
		new = match[0].ep;
		
		*match[1].ep = 0;
		id = atoi(match[1].sp);
		
		attr = strrchr(s, '/');
		if (!attr) {
			fprint(2, Einvalidsyntax);
			return nil;
		}
		
		*attr = 0;
		attr++;
		
		el = xmllook(e, s, nil, nil);
		if (!el) {
			fprint(2, Enotfound);
			return nil;
		}
		
		i = 0;
		for (el = el->child; el; el = el->next) {
			if (strcmp(el->name, attr) == 0) {
				i++;
				if (i == id) {
					/* new path has to start with the self element */
					i = strlen(attr);
					new -= i;
					memmove(new, attr, i);
					return getfiltered(el, new, q);
				}
			}
		}
		fprint(2, Enotfound);
		return nil;
	}
	/* simple checks for obvious syntax errors, if nothing matches */
	if (strpbrk(s, "[]=\n")) {
		fprint(2, Einvalidsyntax);
		return nil;
	}
	
	*q = s;
	return e;
}

void
query(char *q, Xml *x)
{
	Elem *e;
	char *at;
	char *text;
	
	e = getfiltered(x->root, q, &q);
	if (!e) {
		return;
	}
	
	at = strstr(q, "/@");
	if (at) {
		*at = 0;
		at += 2;
	}
	
	text = strstr(q, "/text()");
	if (text) {
		*text = 0;
	}
	
	e = xmllook(e, q, at, nil);
	if (!e) {
		fprint(2, Enotfound);
		return;
	}
	
	if (text) {
		printtext(e);
		return;
	}
	
	if (at) {
		printattr(e, at);
		return;
	}
	
	printelem(e);
}

static void
printstr(char *s)
{
	char *p;
	char *t;
	
	if (!(flags & Fcrushwhite)) {
		print("%s\n", s);
		return;
	}
	
	t = strdup(s);
	for (p = &t[strlen(t)-1]; *p == ' '; p--)
		*p = 0;
	print("%s\n", t);
	free(t);
}

void
query2(char *q, Xml *x)
{
	XpResult r;
	int i;
	
//	xmldebug = 1;
	
	r = xmllookpath(x->root, q);
	
	switch (r.type) {
	default:
		fprint(2, "not found\n");
		break;
	case XTstring:
		for (i = 0; i < r.num; i++)
			printstr(r.strings[i]);
		break;
	case XTelem:
		for (i = 0; i < r.num; i++)
			printelem(r.elems[i]);
		break;
	}
}

char prompt[] = "X: ";

void
main(int argc, char **argv)
{
	Xml *x;
	int fd;
	char *file = nil;
	char *q;
	Biobuf *bin;
	
	flags = 0;
	ARGBEGIN{
	case 'f':
		file = EARGF(usage());
		break;
	case 't':
		flags |= Fcrushwhite;
		break;
	default:
		break;
	}ARGEND;
	
	fd = 0;
	if (file) {
		fd = open(file, OREAD);
		if (fd < 0)
			sysfatal("error opening file: %r");
	}
	
	x = xmlparse(fd, 8192, flags);
	if (!x)
		sysfatal("error parsing file");
	
	if (argc) {
		q = argv[0];
		query2(q, x);
		exits(nil);
	}
	
	bin = Bfdopen(0, OREAD);
	if (!bin)
		sysfatal("error: %r");
	
	print(prompt);
	while (q = Brdstr(bin, '\n', 1)) {
		if (!q)
			exits(nil);
		if (*q == 0) {
			free(q);
			continue;
		}
		query(q, x);
		free(q);
		print(prompt);
	}
}