xref: /plan9/sys/src/cmd/htmlfmt/html.c (revision c978705d5d0e92d0cafa4f6715e8dac79edc15f0)
19a747e4fSDavid du Colombier #include <u.h>
29a747e4fSDavid du Colombier #include <libc.h>
39a747e4fSDavid du Colombier #include <bio.h>
49a747e4fSDavid du Colombier #include <draw.h>
59a747e4fSDavid du Colombier #include <regexp.h>
69a747e4fSDavid du Colombier #include <html.h>
79a747e4fSDavid du Colombier #include <ctype.h>
89a747e4fSDavid du Colombier #include "dat.h"
99a747e4fSDavid du Colombier 
109a747e4fSDavid du Colombier char urlexpr[] = "^(https?|ftp|file|gopher|mailto|news|nntp|telnet|wais|prospero)://([a-zA-Z0-9_@\\-]+([.:][a-zA-Z0-9_@\\-]+)*)";
119a747e4fSDavid du Colombier Reprog	*urlprog;
129a747e4fSDavid du Colombier 
139a747e4fSDavid du Colombier int inword = 0;
149a747e4fSDavid du Colombier int col = 0;
159a747e4fSDavid du Colombier int wordi = 0;
169a747e4fSDavid du Colombier 
179a747e4fSDavid du Colombier char*
189a747e4fSDavid du Colombier loadhtml(int fd)
199a747e4fSDavid du Colombier {
209a747e4fSDavid du Colombier 	URLwin *u;
219a747e4fSDavid du Colombier 	Bytes *b;
229a747e4fSDavid du Colombier 	int n;
239a747e4fSDavid du Colombier 	char buf[4096];
249a747e4fSDavid du Colombier 
259a747e4fSDavid du Colombier 	u = emalloc(sizeof(URLwin));
269a747e4fSDavid du Colombier 	u->infd = fd;
279a747e4fSDavid du Colombier 	u->outfd = 1;
289a747e4fSDavid du Colombier 	u->url = estrdup(url);
299a747e4fSDavid du Colombier 	u->type = TextHtml;
309a747e4fSDavid du Colombier 
319a747e4fSDavid du Colombier 	b = emalloc(sizeof(Bytes));
329a747e4fSDavid du Colombier 	while((n = read(fd, buf, sizeof buf)) > 0)
339a747e4fSDavid du Colombier 		growbytes(b, buf, n);
349a747e4fSDavid du Colombier 	if(b->b == nil)
359a747e4fSDavid du Colombier 		return nil;	/* empty file */
369a747e4fSDavid du Colombier 	rendertext(u, b);
379a747e4fSDavid du Colombier 	freeurlwin(u);
389a747e4fSDavid du Colombier 	return nil;
399a747e4fSDavid du Colombier }
409a747e4fSDavid du Colombier 
419a747e4fSDavid du Colombier char*
429a747e4fSDavid du Colombier runetobyte(Rune *r, int n)
439a747e4fSDavid du Colombier {
449a747e4fSDavid du Colombier 	char *s;
459a747e4fSDavid du Colombier 
469a747e4fSDavid du Colombier 	if(n == 0)
479a747e4fSDavid du Colombier 		return emalloc(1);
489a747e4fSDavid du Colombier 	s = smprint("%.*S", n, r);
499a747e4fSDavid du Colombier 	if(s == nil)
509a747e4fSDavid du Colombier 		error("malloc failed");
519a747e4fSDavid du Colombier 	return s;
529a747e4fSDavid du Colombier }
539a747e4fSDavid du Colombier 
549a747e4fSDavid du Colombier int
559a747e4fSDavid du Colombier closingpunct(int c)
569a747e4fSDavid du Colombier {
579a747e4fSDavid du Colombier 	return strchr(".,:;'\")]}>!?", c) != nil;
589a747e4fSDavid du Colombier }
599a747e4fSDavid du Colombier 
609a747e4fSDavid du Colombier void
619a747e4fSDavid du Colombier emitword(Bytes *b, Rune *r, int nr)
629a747e4fSDavid du Colombier {
639a747e4fSDavid du Colombier 	char *s;
649a747e4fSDavid du Colombier 	int space;
659a747e4fSDavid du Colombier 
669a747e4fSDavid du Colombier 	if(nr == 0)
679a747e4fSDavid du Colombier 		return;
689a747e4fSDavid du Colombier 	s = smprint("%.*S", nr, r);
699a747e4fSDavid du Colombier 	space = (b->n>0) && !isspace(b->b[b->n-1]) && !closingpunct(r[0]);
709a747e4fSDavid du Colombier 	if(col>0 && col+space+nr > width){
719a747e4fSDavid du Colombier 		growbytes(b, "\n", 1);
729a747e4fSDavid du Colombier 		space = 0;
739a747e4fSDavid du Colombier 		col = 0;
749a747e4fSDavid du Colombier 	}
759a747e4fSDavid du Colombier 	if(space && col>0){
769a747e4fSDavid du Colombier 		growbytes(b, " ", 1);
779a747e4fSDavid du Colombier 		col++;
789a747e4fSDavid du Colombier 	}
799a747e4fSDavid du Colombier 	growbytes(b, s, strlen(s));
809a747e4fSDavid du Colombier 	col += nr;
819a747e4fSDavid du Colombier 	free(s);
829a747e4fSDavid du Colombier 	inword = 0;
839a747e4fSDavid du Colombier }
849a747e4fSDavid du Colombier 
859a747e4fSDavid du Colombier void
869a747e4fSDavid du Colombier renderrunes(Bytes *b, Rune *r)
879a747e4fSDavid du Colombier {
889a747e4fSDavid du Colombier 	int i, n;
899a747e4fSDavid du Colombier 
909a747e4fSDavid du Colombier 	n = runestrlen(r);
919a747e4fSDavid du Colombier 	for(i=0; i<n; i++){
929a747e4fSDavid du Colombier 		switch(r[i]){
939a747e4fSDavid du Colombier 		case '\n':
949a747e4fSDavid du Colombier 			if(inword)
959a747e4fSDavid du Colombier 				emitword(b, r+wordi, i-wordi);
969a747e4fSDavid du Colombier 			col = 0;
979a747e4fSDavid du Colombier 			if(b->n == 0)
989a747e4fSDavid du Colombier 				break;	/* don't start with blank lines */
999a747e4fSDavid du Colombier 			if(b->n<2 || b->b[b->n-1]!='\n' || b->b[b->n-2]!='\n')
1009a747e4fSDavid du Colombier 				growbytes(b, "\n", 1);
1019a747e4fSDavid du Colombier 			break;
1029a747e4fSDavid du Colombier 		case ' ':
1039a747e4fSDavid du Colombier 			if(inword)
1049a747e4fSDavid du Colombier 				emitword(b, r+wordi, i-wordi);
1059a747e4fSDavid du Colombier 			break;
1069a747e4fSDavid du Colombier 		default:
1079a747e4fSDavid du Colombier 			if(!inword)
1089a747e4fSDavid du Colombier 				wordi = i;
1099a747e4fSDavid du Colombier 			inword = 1;
1109a747e4fSDavid du Colombier 			break;
1119a747e4fSDavid du Colombier 		}
1129a747e4fSDavid du Colombier 	}
1139a747e4fSDavid du Colombier 	if(inword)
1149a747e4fSDavid du Colombier 		emitword(b, r+wordi, i-wordi);
1159a747e4fSDavid du Colombier }
1169a747e4fSDavid du Colombier 
1179a747e4fSDavid du Colombier void
1189a747e4fSDavid du Colombier renderbytes(Bytes *b, char *fmt, ...)
1199a747e4fSDavid du Colombier {
1209a747e4fSDavid du Colombier 	Rune *r;
1219a747e4fSDavid du Colombier 	va_list arg;
1229a747e4fSDavid du Colombier 
1239a747e4fSDavid du Colombier 	va_start(arg, fmt);
1249a747e4fSDavid du Colombier 	r = runevsmprint(fmt, arg);
1259a747e4fSDavid du Colombier 	va_end(arg);
1269a747e4fSDavid du Colombier 	renderrunes(b, r);
1279a747e4fSDavid du Colombier 	free(r);
1289a747e4fSDavid du Colombier }
1299a747e4fSDavid du Colombier 
1309a747e4fSDavid du Colombier char*
1319a747e4fSDavid du Colombier baseurl(char *url)
1329a747e4fSDavid du Colombier {
1339a747e4fSDavid du Colombier 	char *base, *slash;
1349a747e4fSDavid du Colombier 	Resub rs[10];
1359a747e4fSDavid du Colombier 
1369a747e4fSDavid du Colombier 	if(url == nil)
1379a747e4fSDavid du Colombier 		return nil;
1389a747e4fSDavid du Colombier 	if(urlprog == nil){
1399a747e4fSDavid du Colombier 		urlprog = regcomp(urlexpr);
1409a747e4fSDavid du Colombier 		if(urlprog == nil)
1419a747e4fSDavid du Colombier 			error("can't compile URL regexp");
1429a747e4fSDavid du Colombier 	}
1439a747e4fSDavid du Colombier 	memset(rs, 0, sizeof rs);
1449a747e4fSDavid du Colombier 	if(regexec(urlprog, url, rs, nelem(rs)) == 0)
1459a747e4fSDavid du Colombier 		return nil;
1469a747e4fSDavid du Colombier 	base = estrdup(url);
1479a747e4fSDavid du Colombier 	slash = strrchr(base, '/');
1489a747e4fSDavid du Colombier 	if(slash!=nil && slash>=&base[rs[0].ep-rs[0].sp])
1499a747e4fSDavid du Colombier 		*slash = '\0';
1509a747e4fSDavid du Colombier 	else
1519a747e4fSDavid du Colombier 		base[rs[0].ep-rs[0].sp] = '\0';
1529a747e4fSDavid du Colombier 	return base;
1539a747e4fSDavid du Colombier }
1549a747e4fSDavid du Colombier 
1559a747e4fSDavid du Colombier char*
1569a747e4fSDavid du Colombier fullurl(URLwin *u, Rune *rhref)
1579a747e4fSDavid du Colombier {
1589a747e4fSDavid du Colombier 	char *base, *href, *hrefbase;
1599a747e4fSDavid du Colombier 	char *result;
1609a747e4fSDavid du Colombier 
1619a747e4fSDavid du Colombier 	if(rhref == nil)
1629a747e4fSDavid du Colombier 		return estrdup("NULL URL");
1639a747e4fSDavid du Colombier 	href = runetobyte(rhref, runestrlen(rhref));
1649a747e4fSDavid du Colombier 	hrefbase = baseurl(href);
1659a747e4fSDavid du Colombier 	result = nil;
1669a747e4fSDavid du Colombier 	if(hrefbase==nil && (base = baseurl(u->url))!=nil){
1679a747e4fSDavid du Colombier 		result = estrdup(base);
1689a747e4fSDavid du Colombier 		if(base[strlen(base)-1]!='/' && (href==nil || href[0]!='/'))
1699a747e4fSDavid du Colombier 			result = eappend(result, "/", "");
1709a747e4fSDavid du Colombier 		free(base);
1719a747e4fSDavid du Colombier 	}
1729a747e4fSDavid du Colombier 	if(href){
1739a747e4fSDavid du Colombier 		if(result)
1749a747e4fSDavid du Colombier 			result = eappend(result, "", href);
1759a747e4fSDavid du Colombier 		else
1769a747e4fSDavid du Colombier 			result = estrdup(href);
1779a747e4fSDavid du Colombier 	}
1789a747e4fSDavid du Colombier 	free(hrefbase);
1799a747e4fSDavid du Colombier 	if(result == nil)
1809a747e4fSDavid du Colombier 		return estrdup("***unknown***");
1819a747e4fSDavid du Colombier 	return result;
1829a747e4fSDavid du Colombier }
1839a747e4fSDavid du Colombier 
1849a747e4fSDavid du Colombier void
1859a747e4fSDavid du Colombier render(URLwin *u, Bytes *t, Item *items, int curanchor)
1869a747e4fSDavid du Colombier {
1879a747e4fSDavid du Colombier 	Item *il;
1889a747e4fSDavid du Colombier 	Itext *it;
1899a747e4fSDavid du Colombier 	Ifloat *ifl;
1909a747e4fSDavid du Colombier 	Ispacer *is;
1919a747e4fSDavid du Colombier 	Itable *ita;
1929a747e4fSDavid du Colombier 	Iimage *im;
1939a747e4fSDavid du Colombier 	Anchor *a;
1949a747e4fSDavid du Colombier 	Table *tab;
1959a747e4fSDavid du Colombier 	Tablecell *cell;
1969a747e4fSDavid du Colombier 	char *href;
1979a747e4fSDavid du Colombier 
1989a747e4fSDavid du Colombier 	inword = 0;
1999a747e4fSDavid du Colombier 	col = 0;
2009a747e4fSDavid du Colombier 	wordi = 0;
2019a747e4fSDavid du Colombier 
2029a747e4fSDavid du Colombier 	for(il=items; il!=nil; il=il->next){
2039a747e4fSDavid du Colombier 		if(il->state & IFbrk)
2049a747e4fSDavid du Colombier 			renderbytes(t, "\n");
2059a747e4fSDavid du Colombier 		if(il->state & IFbrksp)
2069a747e4fSDavid du Colombier 			renderbytes(t, "\n");
2079a747e4fSDavid du Colombier 
2089a747e4fSDavid du Colombier 		switch(il->tag){
2099a747e4fSDavid du Colombier 		case Itexttag:
2109a747e4fSDavid du Colombier 			it = (Itext*)il;
211*c978705dSDavid du Colombier 			if(it->state & IFwrap)
2129a747e4fSDavid du Colombier 				renderrunes(t, it->s);
213*c978705dSDavid du Colombier 			else
214*c978705dSDavid du Colombier 				emitword(t, it->s, runestrlen(it->s));
2159a747e4fSDavid du Colombier 			break;
2169a747e4fSDavid du Colombier 		case Iruletag:
2179a747e4fSDavid du Colombier 			if(t->n>0 && t->b[t->n-1]!='\n')
2189a747e4fSDavid du Colombier 				renderbytes(t, "\n");
2199a747e4fSDavid du Colombier 			renderbytes(t, "=======\n");
2209a747e4fSDavid du Colombier 			break;
2219a747e4fSDavid du Colombier 		case Iimagetag:
2229a747e4fSDavid du Colombier 			if(!aflag)
2239a747e4fSDavid du Colombier 				break;
2249a747e4fSDavid du Colombier 			im = (Iimage*)il;
2259a747e4fSDavid du Colombier 			if(im->imsrc){
2269a747e4fSDavid du Colombier 				href = fullurl(u, im->imsrc);
2279a747e4fSDavid du Colombier 				renderbytes(t, "[image %s]", href);
2289a747e4fSDavid du Colombier 				free(href);
2299a747e4fSDavid du Colombier 			}
2309a747e4fSDavid du Colombier 			break;
2319a747e4fSDavid du Colombier 		case Iformfieldtag:
2329a747e4fSDavid du Colombier 			if(aflag)
2339a747e4fSDavid du Colombier 				renderbytes(t, "[formfield]");
2349a747e4fSDavid du Colombier 			break;
2359a747e4fSDavid du Colombier 		case Itabletag:
2369a747e4fSDavid du Colombier 			ita = (Itable*)il;
2379a747e4fSDavid du Colombier 			tab = ita->table;
2389a747e4fSDavid du Colombier 			for(cell=tab->cells; cell!=nil; cell=cell->next){
2399a747e4fSDavid du Colombier 				render(u, t, cell->content, curanchor);
2409a747e4fSDavid du Colombier 			}
2419a747e4fSDavid du Colombier 			if(t->n>0 && t->b[t->n-1]!='\n')
2429a747e4fSDavid du Colombier 				renderbytes(t, "\n");
2439a747e4fSDavid du Colombier 			break;
2449a747e4fSDavid du Colombier 		case Ifloattag:
2459a747e4fSDavid du Colombier 			ifl = (Ifloat*)il;
2469a747e4fSDavid du Colombier 			render(u, t, ifl->item, curanchor);
2479a747e4fSDavid du Colombier 			break;
2489a747e4fSDavid du Colombier 		case Ispacertag:
2499a747e4fSDavid du Colombier 			is = (Ispacer*)il;
2509a747e4fSDavid du Colombier 			if(is->spkind != ISPnull)
2519a747e4fSDavid du Colombier 				renderbytes(t, " ");
2529a747e4fSDavid du Colombier 			break;
2539a747e4fSDavid du Colombier 		default:
2549a747e4fSDavid du Colombier 			error("unknown item tag %d\n", il->tag);
2559a747e4fSDavid du Colombier 		}
2569a747e4fSDavid du Colombier 		if(il->anchorid != 0 && il->anchorid!=curanchor){
2579a747e4fSDavid du Colombier 			for(a=u->docinfo->anchors; a!=nil; a=a->next)
2589a747e4fSDavid du Colombier 				if(aflag && a->index == il->anchorid){
2599a747e4fSDavid du Colombier 					href = fullurl(u, a->href);
2609a747e4fSDavid du Colombier 					renderbytes(t, "[%s]", href);
2619a747e4fSDavid du Colombier 					free(href);
2629a747e4fSDavid du Colombier 					break;
2639a747e4fSDavid du Colombier 				}
2649a747e4fSDavid du Colombier 			curanchor = il->anchorid;
2659a747e4fSDavid du Colombier 		}
2669a747e4fSDavid du Colombier 	}
2679a747e4fSDavid du Colombier 	if(t->n>0 && t->b[t->n-1]!='\n')
2689a747e4fSDavid du Colombier 		renderbytes(t, "\n");
2699a747e4fSDavid du Colombier }
2709a747e4fSDavid du Colombier 
2719a747e4fSDavid du Colombier void
2729a747e4fSDavid du Colombier rerender(URLwin *u)
2739a747e4fSDavid du Colombier {
2749a747e4fSDavid du Colombier 	Bytes *t;
2759a747e4fSDavid du Colombier 
2769a747e4fSDavid du Colombier 	t = emalloc(sizeof(Bytes));
2779a747e4fSDavid du Colombier 
2789a747e4fSDavid du Colombier 	render(u, t, u->items, 0);
2799a747e4fSDavid du Colombier 
2809a747e4fSDavid du Colombier 	if(t->n)
2819a747e4fSDavid du Colombier 		write(u->outfd, (char*)t->b, t->n);
2829a747e4fSDavid du Colombier 	free(t->b);
2839a747e4fSDavid du Colombier 	free(t);
2849a747e4fSDavid du Colombier }
2859a747e4fSDavid du Colombier 
2869a747e4fSDavid du Colombier /*
2879a747e4fSDavid du Colombier  * Somewhat of a hack.  Not a full parse, just looks for strings in the beginning
2889a747e4fSDavid du Colombier  * of the document (cistrstr only looks at first somewhat bytes).
2899a747e4fSDavid du Colombier  */
2909a747e4fSDavid du Colombier int
2919a747e4fSDavid du Colombier charset(char *s)
2929a747e4fSDavid du Colombier {
2939a747e4fSDavid du Colombier 	char *meta, *emeta, *charset;
2949a747e4fSDavid du Colombier 
295d9306527SDavid du Colombier 	if(defcharset == 0)
296d9306527SDavid du Colombier 		defcharset = ISO_8859_1;
2979a747e4fSDavid du Colombier 	meta = cistrstr(s, "<meta");
2989a747e4fSDavid du Colombier 	if(meta == nil)
299d9306527SDavid du Colombier 		return defcharset;
3009a747e4fSDavid du Colombier 	for(emeta=meta; *emeta!='>' && *emeta!='\0'; emeta++)
3019a747e4fSDavid du Colombier 		;
3029a747e4fSDavid du Colombier 	charset = cistrstr(s, "charset=");
3039a747e4fSDavid du Colombier 	if(charset == nil)
304d9306527SDavid du Colombier 		return defcharset;
3059a747e4fSDavid du Colombier 	charset += 8;
3069a747e4fSDavid du Colombier 	if(*charset == '"')
3079a747e4fSDavid du Colombier 		charset++;
3089a747e4fSDavid du Colombier 	if(cistrncmp(charset, "utf-8", 5) || cistrncmp(charset, "utf8", 4))
3099a747e4fSDavid du Colombier 		return UTF_8;
310d9306527SDavid du Colombier 	return defcharset;
3119a747e4fSDavid du Colombier }
3129a747e4fSDavid du Colombier 
3139a747e4fSDavid du Colombier void
3149a747e4fSDavid du Colombier rendertext(URLwin *u, Bytes *b)
3159a747e4fSDavid du Colombier {
3169a747e4fSDavid du Colombier 	Rune *rurl;
3179a747e4fSDavid du Colombier 
3189a747e4fSDavid du Colombier 	rurl = toStr((uchar*)u->url, strlen(u->url), ISO_8859_1);
3199a747e4fSDavid du Colombier 	u->items = parsehtml(b->b, b->n, rurl, u->type, charset((char*)b->b), &u->docinfo);
3209a747e4fSDavid du Colombier //	free(rurl);
3219a747e4fSDavid du Colombier 
3229a747e4fSDavid du Colombier 	rerender(u);
3239a747e4fSDavid du Colombier }
3249a747e4fSDavid du Colombier 
3259a747e4fSDavid du Colombier 
3269a747e4fSDavid du Colombier void
3279a747e4fSDavid du Colombier freeurlwin(URLwin *u)
3289a747e4fSDavid du Colombier {
3299a747e4fSDavid du Colombier 	freeitems(u->items);
3309a747e4fSDavid du Colombier 	u->items = nil;
3319a747e4fSDavid du Colombier 	freedocinfo(u->docinfo);
3329a747e4fSDavid du Colombier 	u->docinfo = nil;
3339a747e4fSDavid du Colombier 	free(u);
3349a747e4fSDavid du Colombier }
335