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