shithub: riscv

Download patch

ref: a31e4f61a4c9afb9696a7ff4ff09e02b3281a2f3
parent: e7df0daa66531eccb2d37f7b66e27d16c9ae4391
author: cinap_lenrek <cinap_lenrek@centraldogma>
date: Mon Sep 19 20:38:28 EDT 2011

uhtml: add html to unicode converter, used by mothra and page/html2ms

--- a/sys/src/cmd/html2ms.c
+++ b/sys/src/cmd/html2ms.c
@@ -296,79 +296,6 @@
 	return n > 0;
 }
 
-struct {
-	char	*entity;
-	Rune	rune;
-} entities[] = {
-	"AElig", 198,	"Aacute", 193,	"Acirc", 194,	"Agrave", 192,	
-	"Alpha", 913,	"Aring", 197,	"Atilde", 195,	"Auml", 196,	
-	"Beta", 914,	"Ccedil", 199,	"Chi", 935,	"Dagger", 8225,	
-	"Delta", 916,	"ETH", 208,	"Eacute", 201,	"Ecirc", 202,	
-	"Egrave", 200,	"Epsilon", 917,	"Eta", 919,	"Euml", 203,	
-	"Gamma", 915,	"Iacute", 205,	"Icirc", 206,	"Igrave", 204,	
-	"Iota", 921,	"Iuml", 207,	"Kappa", 922,	"Lambda", 923,	
-	"Mu", 924,	"Ntilde", 209,	"Nu", 925,	"OElig", 338,	
-	"Oacute", 211,	"Ocirc", 212,	"Ograve", 210,	"Omega", 937,	
-	"Omicron", 927,	"Oslash", 216,	"Otilde", 213,	"Ouml", 214,	
-	"Phi", 934,	"Pi", 928,	"Prime", 8243,	"Psi", 936,	
-	"Rho", 929,	"Scaron", 352,	"Sigma", 931,	"THORN", 222,	
-	"Tau", 932,	"Theta", 920,	"Uacute", 218,	"Ucirc", 219,	
-	"Ugrave", 217,	"Upsilon", 933,	"Uuml", 220,	"Xi", 926,	
-	"Yacute", 221,	"Yuml", 376,	"Zeta", 918,	"aacute", 225,	
-	"acirc", 226,	"acute", 180,	"aelig", 230,	"agrave", 224,	
-	"alefsym", 8501,"alpha", 945,	"amp", 38,	"and", 8743,	
-	"ang", 8736,	"aring", 229,	"asymp", 8776,	"atilde", 227,	
-	"auml", 228,	"bdquo", 8222,	"beta", 946,	"brvbar", 166,	
-	"bull", 8226,	"cap", 8745,	"ccedil", 231,	"cdots", 8943,	
-	"cedil", 184,	"cent", 162,	"chi", 967,	"circ", 710,	
-	"clubs", 9827,	"cong", 8773,	"copy", 169,	"crarr", 8629,	
-	"cup", 8746,	"curren", 164,	"dArr", 8659,	"dagger", 8224,	
-	"darr", 8595,	"ddots", 8945,	"deg", 176,	"delta", 948,	
-	"diams", 9830,	"divide", 247,	"eacute", 233,	"ecirc", 234,	
-	"egrave", 232,	"emdash", 8212,	"empty", 8709,	"emsp", 8195,	
-	"endash", 8211,	"ensp", 8194,	"epsilon", 949,	"equiv", 8801,	
-	"eta", 951,	"eth", 240,	"euml", 235,	"euro", 8364,	
-	"exist", 8707,	"fnof", 402,	"forall", 8704,	"frac12", 189,	
-	"frac14", 188,	"frac34", 190,	"frasl", 8260,	"gamma", 947,	
-	"ge", 8805,	"gt", 62,	"hArr", 8660,	"harr", 8596,	
-	"hearts", 9829,	"hellip", 8230,	"iacute", 237,	"icirc", 238,	
-	"iexcl", 161,	"igrave", 236,	"image", 8465,	"infin", 8734,	
-	"int", 8747,	"iota", 953,	"iquest", 191,	"isin", 8712,	
-	"iuml", 239,	"kappa", 954,	"lArr", 8656,	"lambda", 955,	
-	"lang", 9001,	"laquo", 171,	"larr", 8592,	"lceil", 8968,	
-	"ldots", 8230,	"ldquo", 8220,	"le", 8804,	"lfloor", 8970,	
-	"lowast", 8727,	"loz", 9674,	"lrm", 8206,	"lsaquo", 8249,	
-	"lsquo", 8216,	"lt", 60,	"macr", 175,	"mdash", 8212,	
-	"micro", 181,	"middot", 183,	"minus", 8722,	"mu", 956,	
-	"nabla", 8711,	"nbsp", 160,	"ndash", 8211,	"ne", 8800,	
-	"ni", 8715,	"not", 172,	"notin", 8713,	"nsub", 8836,	
-	"ntilde", 241,	"nu", 957,	"oacute", 243,	"ocirc", 244,	
-	"oelig", 339,	"ograve", 242,	"oline", 8254,	"omega", 969,	
-	"omicron", 959,	"oplus", 8853,	"or", 8744,	"ordf", 170,	
-	"ordm", 186,	"oslash", 248,	"otilde", 245,	"otimes", 8855,	
-	"ouml", 246,	"para", 182,	"part", 8706,	"permil", 8240,	
-	"perp", 8869,	"phi", 966,	"pi", 960,	"piv", 982,	
-	"plusmn", 177,	"pound", 163,	"prime", 8242,	"prod", 8719,	
-	"prop", 8733,	"psi", 968,	"quad", 8193,	"quot", 34,	
-	"rArr", 8658,	"radic", 8730,	"rang", 9002,	"raquo", 187,	
-	"rarr", 8594,	"rceil", 8969,	"rdquo", 8221,	"real", 8476,	
-	"reg", 174,	"rfloor", 8971,	"rho", 961,	"rlm", 8207,	
-	"rsaquo", 8250,	"rsquo", 8217,	"sbquo", 8218,	"scaron", 353,	
-	"sdot", 8901,	"sect", 167,	"shy", 173,	"sigma", 963,	
-	"sigmaf", 962,	"sim", 8764,	"sp", 8194,	"spades", 9824,	
-	"sub", 8834,	"sube", 8838,	"sum", 8721,	"sup", 8835,	
-	"sup1", 185,	"sup2", 178,	"sup3", 179,	"supe", 8839,	
-	"szlig", 223,	"tau", 964,	"there4", 8756,	"theta", 952,	
-	"thetasym", 977,"thinsp", 8201,	"thorn", 254,	"tilde", 732,	
-	"times", 215,	"trade", 8482,	"uArr", 8657,	"uacute", 250,	
-	"uarr", 8593,	"ucirc", 251,	"ugrave", 249,	"uml", 168,	
-	"upsih", 978,	"upsilon", 965,	"uuml", 252,	"varepsilon", 8712,	
-	"varphi", 981,	"varpi", 982,	"varrho", 1009,	"vdots", 8942,	
-	"vsigma", 962,	"vtheta", 977,	"weierp", 8472,	"xi", 958,	
-	"yacute", 253,	"yen", 165,	"yuml", 255,	"zeta", 950,	
-	"zwj", 8205,	"zwnj", 8204,
-};
-
 Rune
 parserune(int c)
 {
@@ -379,7 +306,7 @@
 	n = 0;
 	if(c == '&'){
 		while((c = Bgetc(&in)) > 0){
-			if(strchr("\n\r\t ;</>", c)){
+			if(strchr(";&</>\n\r\t ", c)){
 				if(c != ';')
 					Bungetc(&in);
 				if(n == 0)
@@ -391,15 +318,15 @@
 			buf[n++] = c;
 		}
 		buf[n] = 0;
-		if(buf[0] == '#')
-			return atoi(buf+1);
-		for(i=0; i<nelem(entities); i++){
-			n = strcmp(buf, entities[i].entity);
-			if(n == 0)
-				return entities[i].rune;
-			if(n < 0)
-				break;
-		}
+		if(strcmp(buf, "lt") == 0)
+			return '<';
+		if(strcmp(buf, "gt") == 0)
+			return '>';
+		if(strcmp(buf, "quot") == 0)
+			return '"';
+		if(strcmp(buf, "amp") == 0)
+			return '&';
+		/* use tcs -f html to handle the rest. */
 	} else {
 		do {
 			buf[n++] = c;
--- a/sys/src/cmd/mothra/html.h
+++ b/sys/src/cmd/mothra/html.h
@@ -192,8 +192,6 @@
 	ERR,		/* tag must not occur */
 };
 Tag tag[];
-Entity pl_entity[];
-int pl_entities;
 void rdform(Hglob *);
 void endform(Hglob *);
 char *pl_getattr(Pair *, char *);
--- a/sys/src/cmd/mothra/html.syntax.c
+++ b/sys/src/cmd/mothra/html.syntax.c
@@ -69,272 +69,3 @@
 [Tag_frame]	"frame",	NOEND,
 [Tag_end]	0,		ERR,
 };
-Entity pl_entity[]={
-"AElig",	L'Æ',
-"Aacute",	L'Á',
-"Acirc",	L'Â',
-"Agrave",	L'À',
-"Alpha",	L'Α',
-"Aring",	L'Å',
-"Atilde",	L'Ã',
-"Auml",	L'Ä',
-"Beta",	L'Β',
-"Ccedil",	L'Ç',
-"Chi",	L'Χ',
-"Dagger",	L'‡',
-"Delta",	L'Δ',
-"ETH",	L'Ð',
-"Eacute",	L'É',
-"Ecirc",	L'Ê',
-"Egrave",	L'È',
-"Epsilon",	L'Ε',
-"Eta",	L'Η',
-"Euml",	L'Ë',
-"Gamma",	L'Γ',
-"Iacute",	L'Í',
-"Icirc",	L'Î',
-"Igrave",	L'Ì',
-"Iota",	L'Ι',
-"Iuml",	L'Ï',
-"Kappa",	L'Κ',
-"Lambda",	L'Λ',
-"Mu",	L'Μ',
-"Ntilde",	L'Ñ',
-"Nu",	L'Ν',
-"OElig",	L'Œ',
-"Oacute",	L'Ó',
-"Ocirc",	L'Ô',
-"Ograve",	L'Ò',
-"Omega",	L'Ω',
-"Omicron",	L'Ο',
-"Oslash",	L'Ø',
-"Otilde",	L'Õ',
-"Ouml",	L'Ö',
-"Phi",	L'Φ',
-"Pi",	L'Π',
-"Prime",	L'″',
-"Psi",	L'Ψ',
-"Rho",	L'Ρ',
-"Scaron",	L'Š',
-"Sigma",	L'Σ',
-"THORN",	L'Þ',
-"Tau",	L'Τ',
-"Theta",	L'Θ',
-"Uacute",	L'Ú',
-"Ucirc",	L'Û',
-"Ugrave",	L'Ù',
-"Upsilon",	L'Υ',
-"Uuml",	L'Ü',
-"Xi",	L'Ξ',
-"Yacute",	L'Ý',
-"Yuml",	L'Ÿ',
-"Zeta",	L'Ζ',
-"aacute",	L'á',
-"acirc",	L'â',
-"acute",	L'´',
-"aelig",	L'æ',
-"agrave",	L'à',
-"alefsym",	L'ℵ',
-"alpha",	L'α',
-"amp",	L'&',
-"and",	L'∧',
-"ang",	L'∠',
-"aring",	L'å',
-"asymp",	L'≈',
-"atilde",	L'ã',
-"auml",	L'ä',
-"bdquo",	L'„',
-"beta",	L'β',
-"brvbar",	L'¦',
-"bull",	L'•',
-"cap",	L'∩',
-"ccedil",	L'ç',
-"cdots",	L'⋯',
-"cedil",	L'¸',
-"cent",	L'¢',
-"chi",	L'χ',
-"circ",	L'ˆ',
-"clubs",	L'♣',
-"cong",	L'≅',
-"copy",	L'©',
-"crarr",	L'↵',
-"cup",	L'∪',
-"curren",	L'¤',
-"dArr",	L'⇓',
-"dagger",	L'†',
-"darr",	L'↓',
-"ddots",	L'⋱',
-"deg",	L'°',
-"delta",	L'δ',
-"diams",	L'♦',
-"divide",	L'÷',
-"eacute",	L'é',
-"ecirc",	L'ê',
-"egrave",	L'è',
-"emdash",	L'—',
-"empty",	L'∅',
-"emsp",	L' ',
-"endash",	L'–',
-"ensp",	L' ',
-"epsilon",	L'ε',
-"equiv",	L'≡',
-"eta",	L'η',
-"eth",	L'ð',
-"euml",	L'ë',
-"euro",	L'€',
-"exist",	L'∃',
-"fnof",	L'ƒ',
-"forall",	L'∀',
-"frac12",	L'½',
-"frac14",	L'¼',
-"frac34",	L'¾',
-"frasl",	L'⁄',
-"gamma",	L'γ',
-"ge",	L'≥',
-"gt",	L'>',
-"hArr",	L'⇔',
-"harr",	L'↔',
-"hearts",	L'♥',
-"hellip",	L'…',
-"iacute",	L'í',
-"icirc",	L'î',
-"iexcl",	L'¡',
-"igrave",	L'ì',
-"image",	L'ℑ',
-"infin",	L'∞',
-"int",	L'∫',
-"iota",	L'ι',
-"iquest",	L'¿',
-"isin",	L'∈',
-"iuml",	L'ï',
-"kappa",	L'κ',
-"lArr",	L'⇐',
-"lambda",	L'λ',
-"lang",	L'〈',
-"laquo",	L'«',
-"larr",	L'←',
-"lceil",	L'⌈',
-"ldots",	L'…',
-"ldquo",	L'“',
-"le",	L'≤',
-"lfloor",	L'⌊',
-"lowast",	L'∗',
-"loz",	L'◊',
-"lrm",	L'‎',
-"lsaquo",	L'‹',
-"lsquo",	L'‘',
-"lt",	L'<',
-"macr",	L'¯',
-"mdash",	L'—',
-"micro",	L'µ',
-"middot",	L'·',
-"minus",	L'−',
-"mu",	L'μ',
-"nabla",	L'∇',
-"nbsp",	L' ',
-"ndash",	L'–',
-"ne",	L'≠',
-"ni",	L'∋',
-"not",	L'¬',
-"notin",	L'∉',
-"nsub",	L'⊄',
-"ntilde",	L'ñ',
-"nu",	L'ν',
-"oacute",	L'ó',
-"ocirc",	L'ô',
-"oelig",	L'œ',
-"ograve",	L'ò',
-"oline",	L'‾',
-"omega",	L'ω',
-"omicron",	L'ο',
-"oplus",	L'⊕',
-"or",	L'∨',
-"ordf",	L'ª',
-"ordm",	L'º',
-"oslash",	L'ø',
-"otilde",	L'õ',
-"otimes",	L'⊗',
-"ouml",	L'ö',
-"para",	L'¶',
-"part",	L'∂',
-"permil",	L'‰',
-"perp",	L'⊥',
-"phi",	L'φ',
-"pi",	L'π',
-"piv",	L'ϖ',
-"plusmn",	L'±',
-"pound",	L'£',
-"prime",	L'′',
-"prod",	L'∏',
-"prop",	L'∝',
-"psi",	L'ψ',
-"quad",	L' ',
-"quot",	L'"',
-"rArr",	L'⇒',
-"radic",	L'√',
-"rang",	L'〉',
-"raquo",	L'»',
-"rarr",	L'→',
-"rceil",	L'⌉',
-"rdquo",	L'”',
-"real",	L'ℜ',
-"reg",	L'®',
-"rfloor",	L'⌋',
-"rho",	L'ρ',
-"rlm",	L'‏',
-"rsaquo",	L'›',
-"rsquo",	L'’',
-"sbquo",	L'‚',
-"scaron",	L'š',
-"sdot",	L'⋅',
-"sect",	L'§',
-"shy",	L'­',
-"sigma",	L'σ',
-"sigmaf",	L'ς',
-"sim",	L'∼',
-"sp",	L' ',
-"spades",	L'♠',
-"sub",	L'⊂',
-"sube",	L'⊆',
-"sum",	L'∑',
-"sup",	L'⊃',
-"sup1",	L'¹',
-"sup2",	L'²',
-"sup3",	L'³',
-"supe",	L'⊇',
-"szlig",	L'ß',
-"tau",	L'τ',
-"there4",	L'∴',
-"theta",	L'θ',
-"thetasym",	L'ϑ',
-"thinsp",	L' ',
-"thorn",	L'þ',
-"tilde",	L'˜',
-"times",	L'×',
-"trade",	L'™',
-"uArr",	L'⇑',
-"uacute",	L'ú',
-"uarr",	L'↑',
-"ucirc",	L'û',
-"ugrave",	L'ù',
-"uml",	L'¨',
-"upsih",	L'ϒ',
-"upsilon",	L'υ',
-"uuml",	L'ü',
-"varepsilon",	L'∈',
-"varphi",	L'ϕ',
-"varpi",	L'ϖ',
-"varrho",	L'ϱ',
-"vdots",	L'⋮',
-"vsigma",	L'ς',
-"vtheta",	L'ϑ',
-"weierp",	L'℘',
-"xi",	L'ξ',
-"yacute",	L'ý',
-"yen",	L'¥',
-"yuml",	L'ÿ',
-"zeta",	L'ζ',
-"zwj",	L'‍',
-"zwnj",	L'‌',
-};
-int pl_entities = nelem(pl_entity);
--- a/sys/src/cmd/mothra/mkfile
+++ b/sys/src/cmd/mothra/mkfile
@@ -11,7 +11,7 @@
 	rdhtml.c \
 
 OFILES=${CFILES:%.c=%.$O} version.$O
-HFILES=mothra.h html.h tcs.h libpanel/panel.h libpanel/rtext.h
+HFILES=mothra.h html.h libpanel/panel.h libpanel/rtext.h
 BIN=/$objtype/bin
 </sys/src/cmd/mkone
 
--- a/sys/src/cmd/mothra/mothra.c
+++ b/sys/src/cmd/mothra/mothra.c
@@ -898,12 +898,15 @@
 			fd=pipeline("/bin/uncompress", fd);
 		else if(selection->type&GUNZIP)
 			fd=pipeline("/bin/gunzip", fd);
+		snprint(cmd, sizeof(cmd), selection->charset[0] ?
+			"/bin/uhtml -c %s" : "/bin/uhtml", selection->charset);
+		fd = pipeline(cmd, fd);
 		switch(selection->type&~COMPRESSION){
 		default:
 			message("Bad type %x in geturl", selection->type);
 			break;
-		case PLAIN:
 		case HTML:
+		case PLAIN:
 			w = www(i = wwwtop++);
 			if(i >= NWWW){
 				extern void freeform(void *p);
--- a/sys/src/cmd/mothra/rdhtml.c
+++ b/sys/src/cmd/mothra/rdhtml.c
@@ -154,58 +154,6 @@
 	g->dst->changed=1;
 }
 
-void pl_applycharset(Hglob *g)
-{
-	int fd, pfd[2], n;
-	char buf[NHBUF];
-	char **cs, *charset;
-
-	charset = nil;
-	for(cs = tcs; *cs; cs += 2){
-		if(cistrcmp(cs[0], g->charset) == 0){
-			charset = cs[1];
-			break;
-		}
-	}
-	/* make sure we dont convet multiple times */
-	g->charset[0]=0;
-
-	/* no match, dont convert */
-	if(charset == nil)
-		return;
-
-	fd = g->hfd;
-	n = g->ehbuf - g->hbufp;
-	memcpy(buf, g->hbufp, n);
-
-	if(pipe(pfd)==-1)
-		return;
-	switch(rfork(RFFDG|RFPROC|RFNOWAIT)){
-	case -1:
-		close(pfd[0]);
-		close(pfd[1]);
-		return;
-	case 0:
-		dup(fd, 0);
-		dup(pfd[1], 1);
-		close(pfd[0]);
-		close(pfd[1]);
-		close(fd);
-
-		write(1, buf, n);
-		while((n=read(0, buf, sizeof(buf)))>0)
-			write(1, buf, n);
-		_exits("no exec!");
-	}
-	dup(pfd[0], fd);
-	close(pfd[0]);
-	close(pfd[1]);
-	g->hbufp = g->ehbuf;
-	snprint(buf, sizeof(buf), "tcs -s -f %s -t utf", charset);
-	if((fd=pipeline(buf, fd)) >= 0)
-		g->hfd = fd;
-}
-
 /*
  * Buffered read, no translation
  * Save in cache.
@@ -297,22 +245,6 @@
 int entchar(int c){
 	return c=='#' || 'a'<=c && c<='z' || 'A'<=c && c<='Z' || '0'<=c && c<='9';
 }
-Entity *entsearch(char *s){
-	int i, m, n, r;
-	i=0;
-	n=pl_entities;
-	while ((n-i) > 0) {
-		m=i+(n-i)/2;
-		r=strcmp(s, pl_entity[m].name);
-		if (r > 0)
-			i=m+1;
-		else if (r < 0)
-			n=m;
-		else
-			return &pl_entity[m];
-	}
-	return 0;
-}
 /*
  * remove entity references, in place.
  * Potential bug:
@@ -333,31 +265,23 @@
 			u=s;
 			while(entchar(*s)) s++;
 			svc=*s;
-			if(svc!=';')
-				htmlerror(g->name, g->lineno, "entity syntax error");
-			*s++='\0';
-			if(*u=='#'){
-				if (u[1]=='X' || u[1]=='x')
-					r=strtol(u+2, 0, 16);
-				else
-					r=atoi(u+1);
-				t+=runetochar(t, &r);
-				if(svc!=';') *--s=svc;
+			*s = 0;
+			if(svc==';') s++;
+			if(strcmp(u, "lt") == 0)
+				*t++='<';
+			else if(strcmp(u, "gt") == 0)
+				*t++='>';
+			else if(strcmp(u, "quot") == 0)
+				*t++='"';
+			else if(strcmp(u, "amp") == 0)
+				*t++='&';
+			else {
+				if(svc==';') s--;
+				*s=svc;
+				*t++='&';
+				while(u<s)
+					*t++=*u++;
 			}
-			else{
-				ep=entsearch(u);
-				if(ep && ep->name){
-					t+=runetochar(t, &ep->value);
-					if(svc!=';') *--s=svc;
-				}
-				else{
-					htmlerror(g->name, g->lineno,
-						"unknown entity %s", u);
-					s[-1]=svc;
-					s=u;
-					*t++='&';
-				}
-			}
 		}	
 		else *t++=c;
 	}while(c);
@@ -644,7 +568,6 @@
 	g.spacc=0;
 	g.form=0;
 	strncpy(g.text, name, NTITLE);
-	pl_applycharset(&g);
 	plaintext(&g);
 	dst->finished=1;
 }
@@ -713,13 +636,6 @@
 		case Tag_end:	/* unrecognized start tag */
 			break;
 		case Tag_meta:
-			if((str=pl_getattr(g.attr, "http-equiv")) &&
-			   (cistrcmp(str, "content-type"))==0 &&
-			   (str=pl_getattr(g.attr, "content")) &&
-			   (str=cistrstr(str, "charset="))){
-				strncpy(g.charset, str+8, sizeof(g.charset));
-				pl_applycharset(&g);
-			}
 			break;
 		case Tag_img:
 			if(str=pl_getattr(g.attr, "src"))
@@ -805,7 +721,7 @@
 			g.state->indent=20;
 			break;
 		case Tag_body:
-			pl_applycharset(&g);
+			break;
 		case Tag_head:
 			g.state->font=ROMAN;
 			g.state->size=NORMAL;
--- a/sys/src/cmd/page.c
+++ b/sys/src/cmd/page.c
@@ -626,7 +626,7 @@
 	else if(cistrncmp(buf, "<?xml", 5) == 0 ||
 		cistrncmp(buf, "<!DOCTYPE", 9) == 0 ||
 		cistrncmp(buf, "<HTML", 5) == 0){
-		p->data = "html2ms | troff -ms | lp -dstdout";
+		p->data = "uhtml | html2ms | troff -ms | lp -dstdout";
 		p->open = popengs;
 	}
 	else if(memcmp(buf, "\xF7\x02\x01\x83\x92\xC0\x1C;", 8) == 0){
--- /dev/null
+++ b/sys/src/cmd/uhtml.c
@@ -1,0 +1,121 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+int nbuf;
+char buf[4096+1];
+char *cset = "utf";
+
+void
+usage(void)
+{
+	fprint(2, "%s [ -h ] [ -c charset ] [ file ]\n", argv0);
+	exits("usage");
+}
+
+char*
+strval(char *s)
+{
+	char *e, q;
+
+	while(strchr("\t ", *s))
+		s++;
+	q = 0;
+	if(*s == '"' || *s == '\'')
+		q = *s++;
+	for(e = s; *e; e++){
+		if(*e == q)
+			break;
+		if(isalnum(*e))
+			continue;
+		if(*e == '-' || *e == '_')
+			continue;
+		break;
+	}
+	if(e - s > 1)
+		return smprint("%.*s", (int)(e-s), s);
+	return nil;
+}
+
+void
+main(int argc, char *argv[])
+{
+	int pfd[2], pflag = 0;
+	char *arg[4], *s;
+
+	ARGBEGIN {
+	case 'h':
+		usage();
+	case 'c':
+		cset = EARGF(usage());
+		break;
+	case 'p':
+		pflag = 1;
+		break;
+	} ARGEND;
+
+	if(*argv){
+		close(0);
+		if(open(*argv, OREAD) != 1)
+			sysfatal("open: %r");
+	}
+	if((nbuf = read(0, buf, sizeof(buf)-1)) < 0)
+		sysfatal("read: %r");
+	buf[nbuf] = 0;
+	for(;;){
+		if(s = cistrstr(buf, "encoding="))
+			if(s = strval(s+9)){
+				cset = s;
+				break;
+			}
+		if(s = cistrstr(buf, "charset="))
+			if(s = strval(s+8)){
+				cset = s;
+				break;
+			}
+		break;
+	}
+
+	if(pflag){
+		print("%s\n", cset);
+		exits(0);
+	}
+
+	if(pipe(pfd) < 0)
+		sysfatal("pipe: %r");
+
+	if(nbuf == 0){
+		write(1, buf, 0);
+		exits(0);
+	}
+
+	switch(rfork(RFFDG|RFREND|RFPROC|RFNOWAIT)){
+	case -1:
+		sysfatal("fork: %r");
+	case 0:
+		dup(pfd[0], 0);
+		close(pfd[0]);
+		close(pfd[1]);
+
+		arg[0] = "rc";
+		arg[1] = "-c";
+		if(strcmp(cset, "utf"))
+			arg[2] = smprint("tcs -f %s -t utf | tcs -f html -t utf", cset);
+		else
+			arg[2] = "tcs -f html -t utf";
+		arg[3] = nil;
+		exec("/bin/rc", arg);
+	}
+
+	dup(pfd[1], 1);
+	close(pfd[0]);
+	close(pfd[1]);
+
+	while(nbuf > 0){
+		if(write(1, buf, nbuf) != nbuf)
+			sysfatal("write: %r");
+		if((nbuf = read(0, buf, sizeof(buf))) < 0)
+			sysfatal("read: %r");
+	}
+	exits(0);
+}