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