Annotation of mandoc/html.c, Revision 1.236
1.236 ! schwarze 1: /* $Id: html.c,v 1.235 2018/06/25 13:45:57 schwarze Exp $ */
1.1 kristaps 2: /*
1.176 schwarze 3: * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
1.221 schwarze 4: * Copyright (c) 2011-2015, 2017, 2018 Ingo Schwarze <schwarze@openbsd.org>
1.1 kristaps 5: *
6: * Permission to use, copy, modify, and distribute this software for any
1.29 kristaps 7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
1.1 kristaps 9: *
1.186 schwarze 10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1.29 kristaps 11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1.186 schwarze 12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1.29 kristaps 13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1.1 kristaps 17: */
1.92 kristaps 18: #include "config.h"
19:
1.41 kristaps 20: #include <sys/types.h>
1.30 kristaps 21:
1.1 kristaps 22: #include <assert.h>
1.68 kristaps 23: #include <ctype.h>
1.76 kristaps 24: #include <stdarg.h>
1.229 schwarze 25: #include <stddef.h>
1.29 kristaps 26: #include <stdio.h>
1.63 kristaps 27: #include <stdint.h>
1.1 kristaps 28: #include <stdlib.h>
1.33 kristaps 29: #include <string.h>
1.45 kristaps 30: #include <unistd.h>
1.1 kristaps 31:
1.210 schwarze 32: #include "mandoc_aux.h"
1.229 schwarze 33: #include "mandoc_ohash.h"
1.100 kristaps 34: #include "mandoc.h"
1.210 schwarze 35: #include "roff.h"
1.58 kristaps 36: #include "out.h"
1.51 kristaps 37: #include "html.h"
1.186 schwarze 38: #include "manconf.h"
1.64 kristaps 39: #include "main.h"
1.63 kristaps 40:
1.29 kristaps 41: struct htmldata {
1.63 kristaps 42: const char *name;
1.29 kristaps 43: int flags;
1.196 schwarze 44: #define HTML_NOSTACK (1 << 0)
45: #define HTML_AUTOCLOSE (1 << 1)
46: #define HTML_NLBEFORE (1 << 2)
47: #define HTML_NLBEGIN (1 << 3)
48: #define HTML_NLEND (1 << 4)
49: #define HTML_NLAFTER (1 << 5)
50: #define HTML_NLAROUND (HTML_NLBEFORE | HTML_NLAFTER)
51: #define HTML_NLINSIDE (HTML_NLBEGIN | HTML_NLEND)
52: #define HTML_NLALL (HTML_NLAROUND | HTML_NLINSIDE)
53: #define HTML_INDENT (1 << 6)
54: #define HTML_NOINDENT (1 << 7)
1.29 kristaps 55: };
1.7 kristaps 56:
1.29 kristaps 57: static const struct htmldata htmltags[TAG_MAX] = {
1.196 schwarze 58: {"html", HTML_NLALL},
59: {"head", HTML_NLALL | HTML_INDENT},
60: {"body", HTML_NLALL},
61: {"meta", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL},
62: {"title", HTML_NLAROUND},
63: {"div", HTML_NLAROUND},
1.225 schwarze 64: {"div", 0},
1.196 schwarze 65: {"h1", HTML_NLAROUND},
66: {"h2", HTML_NLAROUND},
67: {"span", 0},
68: {"link", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL},
69: {"br", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL},
70: {"a", 0},
71: {"table", HTML_NLALL | HTML_INDENT},
72: {"tr", HTML_NLALL | HTML_INDENT},
73: {"td", HTML_NLAROUND},
74: {"li", HTML_NLAROUND | HTML_INDENT},
75: {"ul", HTML_NLALL | HTML_INDENT},
76: {"ol", HTML_NLALL | HTML_INDENT},
77: {"dl", HTML_NLALL | HTML_INDENT},
78: {"dt", HTML_NLAROUND},
79: {"dd", HTML_NLAROUND | HTML_INDENT},
80: {"pre", HTML_NLALL | HTML_NOINDENT},
1.207 schwarze 81: {"var", 0},
1.206 schwarze 82: {"cite", 0},
1.196 schwarze 83: {"b", 0},
84: {"i", 0},
85: {"code", 0},
86: {"small", 0},
87: {"style", HTML_NLALL | HTML_INDENT},
88: {"math", HTML_NLALL | HTML_INDENT},
89: {"mrow", 0},
90: {"mi", 0},
1.215 schwarze 91: {"mn", 0},
1.196 schwarze 92: {"mo", 0},
93: {"msup", 0},
94: {"msub", 0},
95: {"msubsup", 0},
96: {"mfrac", 0},
97: {"msqrt", 0},
98: {"mfenced", 0},
99: {"mtable", 0},
100: {"mtr", 0},
101: {"mtd", 0},
102: {"munderover", 0},
103: {"munder", 0},
104: {"mover", 0},
1.90 kristaps 105: };
106:
1.140 kristaps 107: static const char *const roffscales[SCALE_MAX] = {
108: "cm", /* SCALE_CM */
109: "in", /* SCALE_IN */
110: "pc", /* SCALE_PC */
111: "pt", /* SCALE_PT */
112: "em", /* SCALE_EM */
113: "em", /* SCALE_MM */
114: "ex", /* SCALE_EN */
115: "ex", /* SCALE_BU */
116: "em", /* SCALE_VS */
117: "ex", /* SCALE_FS */
118: };
119:
1.229 schwarze 120: /* Avoid duplicate HTML id= attributes. */
121: static struct ohash id_unique;
122:
1.197 schwarze 123: static void print_byte(struct html *, char);
124: static void print_endword(struct html *);
125: static void print_indent(struct html *);
126: static void print_word(struct html *, const char *);
127:
1.184 schwarze 128: static void print_ctag(struct html *, struct tag *);
1.197 schwarze 129: static int print_escape(struct html *, char);
1.195 schwarze 130: static int print_encode(struct html *, const char *, const char *, int);
131: static void print_href(struct html *, const char *, const char *, int);
1.141 kristaps 132: static void print_metaf(struct html *, enum mandoc_esc);
1.82 kristaps 133:
1.156 schwarze 134:
1.180 schwarze 135: void *
1.191 schwarze 136: html_alloc(const struct manoutput *outopts)
1.10 kristaps 137: {
1.30 kristaps 138: struct html *h;
139:
1.128 kristaps 140: h = mandoc_calloc(1, sizeof(struct html));
1.10 kristaps 141:
1.204 schwarze 142: h->tag = NULL;
1.186 schwarze 143: h->style = outopts->style;
144: h->base_man = outopts->man;
145: h->base_includes = outopts->includes;
146: if (outopts->fragment)
147: h->oflags |= HTML_FRAGMENT;
1.43 kristaps 148:
1.229 schwarze 149: mandoc_ohash_init(&id_unique, 4, 0);
150:
1.188 schwarze 151: return h;
1.29 kristaps 152: }
1.10 kristaps 153:
1.29 kristaps 154: void
155: html_free(void *p)
156: {
1.30 kristaps 157: struct tag *tag;
158: struct html *h;
1.229 schwarze 159: char *cp;
160: unsigned int slot;
1.30 kristaps 161:
162: h = (struct html *)p;
1.204 schwarze 163: while ((tag = h->tag) != NULL) {
164: h->tag = tag->next;
1.30 kristaps 165: free(tag);
166: }
1.229 schwarze 167: free(h);
1.53 kristaps 168:
1.229 schwarze 169: cp = ohash_first(&id_unique, &slot);
170: while (cp != NULL) {
171: free(cp);
172: cp = ohash_next(&id_unique, &slot);
173: }
174: ohash_delete(&id_unique);
1.10 kristaps 175: }
1.2 kristaps 176:
1.51 kristaps 177: void
1.29 kristaps 178: print_gen_head(struct html *h)
179: {
1.165 kristaps 180: struct tag *t;
1.41 kristaps 181:
1.194 schwarze 182: print_otag(h, TAG_META, "?", "charset", "utf-8");
1.222 schwarze 183: if (h->style != NULL) {
184: print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet",
185: h->style, "type", "text/css", "media", "all");
186: return;
187: }
1.165 kristaps 188:
1.168 kristaps 189: /*
1.222 schwarze 190: * Print a minimal embedded style sheet.
1.168 kristaps 191: */
1.196 schwarze 192:
1.194 schwarze 193: t = print_otag(h, TAG_STYLE, "");
1.196 schwarze 194: print_text(h, "table.head, table.foot { width: 100%; }");
1.197 schwarze 195: print_endline(h);
1.196 schwarze 196: print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }");
1.197 schwarze 197: print_endline(h);
1.196 schwarze 198: print_text(h, "td.head-vol { text-align: center; }");
1.197 schwarze 199: print_endline(h);
1.198 schwarze 200: print_text(h, "div.Pp { margin: 1ex 0ex; }");
1.225 schwarze 201: print_endline(h);
202: print_text(h, "div.Nd, div.Bf, div.Op { display: inline; }");
1.226 schwarze 203: print_endline(h);
1.227 schwarze 204: print_text(h, "span.Pa, span.Ad { font-style: italic; }");
1.228 schwarze 205: print_endline(h);
206: print_text(h, "span.Ms { font-weight: bold; }");
1.224 schwarze 207: print_endline(h);
208: print_text(h, "dl.Bl-diag ");
209: print_byte(h, '>');
210: print_text(h, " dt { font-weight: bold; }");
1.223 schwarze 211: print_endline(h);
212: print_text(h, "code.Nm, code.Fl, code.Cm, code.Ic, "
213: "code.In, code.Fd, code.Fn,");
214: print_endline(h);
215: print_text(h, "code.Cd { font-weight: bold; "
216: "font-family: inherit; }");
1.165 kristaps 217: print_tagq(h, t);
1.4 kristaps 218: }
219:
1.126 schwarze 220: static void
1.132 kristaps 221: print_metaf(struct html *h, enum mandoc_esc deco)
1.88 kristaps 222: {
1.90 kristaps 223: enum htmlfont font;
1.88 kristaps 224:
225: switch (deco) {
1.156 schwarze 226: case ESCAPE_FONTPREV:
1.90 kristaps 227: font = h->metal;
1.88 kristaps 228: break;
1.156 schwarze 229: case ESCAPE_FONTITALIC:
1.90 kristaps 230: font = HTMLFONT_ITALIC;
231: break;
1.156 schwarze 232: case ESCAPE_FONTBOLD:
1.90 kristaps 233: font = HTMLFONT_BOLD;
1.88 kristaps 234: break;
1.156 schwarze 235: case ESCAPE_FONTBI:
1.152 schwarze 236: font = HTMLFONT_BI;
237: break;
1.156 schwarze 238: case ESCAPE_FONT:
239: case ESCAPE_FONTROMAN:
1.90 kristaps 240: font = HTMLFONT_NONE;
1.88 kristaps 241: break;
242: default:
243: abort();
244: }
245:
1.122 kristaps 246: if (h->metaf) {
247: print_tagq(h, h->metaf);
248: h->metaf = NULL;
249: }
250:
251: h->metal = h->metac;
252: h->metac = font;
253:
1.152 schwarze 254: switch (font) {
1.156 schwarze 255: case HTMLFONT_ITALIC:
1.194 schwarze 256: h->metaf = print_otag(h, TAG_I, "");
1.152 schwarze 257: break;
1.156 schwarze 258: case HTMLFONT_BOLD:
1.194 schwarze 259: h->metaf = print_otag(h, TAG_B, "");
1.152 schwarze 260: break;
1.156 schwarze 261: case HTMLFONT_BI:
1.194 schwarze 262: h->metaf = print_otag(h, TAG_B, "");
263: print_otag(h, TAG_I, "");
1.152 schwarze 264: break;
265: default:
266: break;
267: }
1.210 schwarze 268: }
269:
270: char *
1.229 schwarze 271: html_make_id(const struct roff_node *n, int unique)
1.210 schwarze 272: {
273: const struct roff_node *nch;
1.229 schwarze 274: char *buf, *bufs, *cp;
275: unsigned int slot;
276: int suffix;
1.210 schwarze 277:
278: for (nch = n->child; nch != NULL; nch = nch->next)
279: if (nch->type != ROFFT_TEXT)
280: return NULL;
281:
282: buf = NULL;
283: deroff(&buf, n);
1.220 schwarze 284: if (buf == NULL)
285: return NULL;
1.210 schwarze 286:
1.230 schwarze 287: /*
288: * In ID attributes, only use ASCII characters that are
289: * permitted in URL-fragment strings according to the
290: * explicit list at:
291: * https://url.spec.whatwg.org/#url-fragment-string
292: */
1.210 schwarze 293:
294: for (cp = buf; *cp != '\0'; cp++)
1.230 schwarze 295: if (isalnum((unsigned char)*cp) == 0 &&
296: strchr("!$&'()*+,-./:;=?@_~", *cp) == NULL)
1.210 schwarze 297: *cp = '_';
298:
1.229 schwarze 299: if (unique == 0)
300: return buf;
301:
302: /* Avoid duplicate HTML id= attributes. */
303:
304: bufs = NULL;
305: suffix = 1;
306: slot = ohash_qlookup(&id_unique, buf);
307: cp = ohash_find(&id_unique, slot);
308: if (cp != NULL) {
309: while (cp != NULL) {
310: free(bufs);
311: if (++suffix > 127) {
312: free(buf);
313: return NULL;
314: }
315: mandoc_asprintf(&bufs, "%s_%d", buf, suffix);
316: slot = ohash_qlookup(&id_unique, bufs);
317: cp = ohash_find(&id_unique, slot);
318: }
319: free(buf);
320: buf = bufs;
321: }
322: ohash_insert(&id_unique, slot, buf);
1.210 schwarze 323: return buf;
1.88 kristaps 324: }
325:
1.138 kristaps 326: int
327: html_strlen(const char *cp)
328: {
1.151 schwarze 329: size_t rsz;
330: int skip, sz;
1.138 kristaps 331:
332: /*
333: * Account for escaped sequences within string length
334: * calculations. This follows the logic in term_strlen() as we
335: * must calculate the width of produced strings.
336: * Assume that characters are always width of "1". This is
337: * hacky, but it gets the job done for approximation of widths.
338: */
339:
340: sz = 0;
1.151 schwarze 341: skip = 0;
342: while (1) {
343: rsz = strcspn(cp, "\\");
344: if (rsz) {
345: cp += rsz;
346: if (skip) {
347: skip = 0;
348: rsz--;
349: }
350: sz += rsz;
351: }
352: if ('\0' == *cp)
353: break;
354: cp++;
355: switch (mandoc_escape(&cp, NULL, NULL)) {
1.156 schwarze 356: case ESCAPE_ERROR:
1.188 schwarze 357: return sz;
1.156 schwarze 358: case ESCAPE_UNICODE:
359: case ESCAPE_NUMBERED:
360: case ESCAPE_SPECIAL:
1.185 schwarze 361: case ESCAPE_OVERSTRIKE:
1.151 schwarze 362: if (skip)
363: skip = 0;
364: else
365: sz++;
366: break;
1.156 schwarze 367: case ESCAPE_SKIPCHAR:
1.151 schwarze 368: skip = 1;
1.138 kristaps 369: break;
370: default:
371: break;
372: }
373: }
1.188 schwarze 374: return sz;
1.138 kristaps 375: }
1.88 kristaps 376:
1.85 kristaps 377: static int
1.197 schwarze 378: print_escape(struct html *h, char c)
1.159 schwarze 379: {
380:
381: switch (c) {
382: case '<':
1.197 schwarze 383: print_word(h, "<");
1.159 schwarze 384: break;
385: case '>':
1.197 schwarze 386: print_word(h, ">");
1.159 schwarze 387: break;
388: case '&':
1.197 schwarze 389: print_word(h, "&");
1.159 schwarze 390: break;
391: case '"':
1.197 schwarze 392: print_word(h, """);
1.159 schwarze 393: break;
394: case ASCII_NBRSP:
1.197 schwarze 395: print_word(h, " ");
1.159 schwarze 396: break;
397: case ASCII_HYPH:
1.197 schwarze 398: print_byte(h, '-');
1.189 schwarze 399: break;
1.159 schwarze 400: case ASCII_BREAK:
401: break;
402: default:
1.188 schwarze 403: return 0;
1.159 schwarze 404: }
1.188 schwarze 405: return 1;
1.159 schwarze 406: }
407:
408: static int
1.195 schwarze 409: print_encode(struct html *h, const char *p, const char *pend, int norecurse)
1.29 kristaps 410: {
1.197 schwarze 411: char numbuf[16];
1.214 schwarze 412: struct tag *t;
413: const char *seq;
1.77 kristaps 414: size_t sz;
1.214 schwarze 415: int c, len, breakline, nospace;
1.132 kristaps 416: enum mandoc_esc esc;
1.214 schwarze 417: static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"',
1.154 schwarze 418: ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' };
1.14 kristaps 419:
1.195 schwarze 420: if (pend == NULL)
421: pend = strchr(p, '\0');
422:
1.214 schwarze 423: breakline = 0;
1.85 kristaps 424: nospace = 0;
425:
1.195 schwarze 426: while (p < pend) {
1.151 schwarze 427: if (HTML_SKIPCHAR & h->flags && '\\' != *p) {
428: h->flags &= ~HTML_SKIPCHAR;
429: p++;
430: continue;
431: }
432:
1.197 schwarze 433: for (sz = strcspn(p, rejs); sz-- && p < pend; p++)
1.214 schwarze 434: print_byte(h, *p);
435:
436: if (breakline &&
437: (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) {
438: t = print_otag(h, TAG_DIV, "");
439: print_text(h, "\\~");
440: print_tagq(h, t);
441: breakline = 0;
442: while (p < pend && (*p == ' ' || *p == ASCII_NBRSP))
443: p++;
444: continue;
445: }
1.77 kristaps 446:
1.195 schwarze 447: if (p >= pend)
1.132 kristaps 448: break;
449:
1.214 schwarze 450: if (*p == ' ') {
451: print_endword(h);
452: p++;
453: continue;
454: }
455:
1.197 schwarze 456: if (print_escape(h, *p++))
1.154 schwarze 457: continue;
1.77 kristaps 458:
1.132 kristaps 459: esc = mandoc_escape(&p, &seq, &len);
460: if (ESCAPE_ERROR == esc)
461: break;
1.82 kristaps 462:
1.132 kristaps 463: switch (esc) {
1.156 schwarze 464: case ESCAPE_FONT:
465: case ESCAPE_FONTPREV:
466: case ESCAPE_FONTBOLD:
467: case ESCAPE_FONTITALIC:
468: case ESCAPE_FONTBI:
469: case ESCAPE_FONTROMAN:
1.151 schwarze 470: if (0 == norecurse)
471: print_metaf(h, esc);
472: continue;
1.156 schwarze 473: case ESCAPE_SKIPCHAR:
1.151 schwarze 474: h->flags |= HTML_SKIPCHAR;
475: continue;
476: default:
477: break;
478: }
479:
480: if (h->flags & HTML_SKIPCHAR) {
481: h->flags &= ~HTML_SKIPCHAR;
482: continue;
483: }
484:
485: switch (esc) {
1.156 schwarze 486: case ESCAPE_UNICODE:
1.159 schwarze 487: /* Skip past "u" header. */
1.144 kristaps 488: c = mchars_num2uc(seq + 1, len - 1);
489: break;
1.156 schwarze 490: case ESCAPE_NUMBERED:
1.141 kristaps 491: c = mchars_num2char(seq, len);
1.181 schwarze 492: if (c < 0)
493: continue;
1.82 kristaps 494: break;
1.156 schwarze 495: case ESCAPE_SPECIAL:
1.191 schwarze 496: c = mchars_spec2cp(seq, len);
1.181 schwarze 497: if (c <= 0)
498: continue;
1.132 kristaps 499: break;
1.214 schwarze 500: case ESCAPE_BREAK:
501: breakline = 1;
502: continue;
1.156 schwarze 503: case ESCAPE_NOSPACE:
1.132 kristaps 504: if ('\0' == *p)
505: nospace = 1;
1.179 schwarze 506: continue;
1.185 schwarze 507: case ESCAPE_OVERSTRIKE:
508: if (len == 0)
509: continue;
510: c = seq[len - 1];
511: break;
1.82 kristaps 512: default:
1.179 schwarze 513: continue;
1.82 kristaps 514: }
1.181 schwarze 515: if ((c < 0x20 && c != 0x09) ||
516: (c > 0x7E && c < 0xA0))
1.179 schwarze 517: c = 0xFFFD;
1.197 schwarze 518: if (c > 0x7E) {
1.216 schwarze 519: (void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c);
1.197 schwarze 520: print_word(h, numbuf);
521: } else if (print_escape(h, c) == 0)
522: print_byte(h, c);
1.32 kristaps 523: }
1.85 kristaps 524:
1.188 schwarze 525: return nospace;
1.14 kristaps 526: }
527:
1.94 kristaps 528: static void
1.195 schwarze 529: print_href(struct html *h, const char *name, const char *sec, int man)
1.94 kristaps 530: {
1.195 schwarze 531: const char *p, *pp;
532:
533: pp = man ? h->base_man : h->base_includes;
534: while ((p = strchr(pp, '%')) != NULL) {
535: print_encode(h, pp, p, 1);
536: if (man && p[1] == 'S') {
537: if (sec == NULL)
1.197 schwarze 538: print_byte(h, '1');
1.195 schwarze 539: else
540: print_encode(h, sec, NULL, 1);
541: } else if ((man && p[1] == 'N') ||
542: (man == 0 && p[1] == 'I'))
543: print_encode(h, name, NULL, 1);
544: else
545: print_encode(h, p, p + 2, 1);
546: pp = p + 2;
547: }
548: if (*pp != '\0')
549: print_encode(h, pp, NULL, 1);
1.94 kristaps 550: }
551:
1.51 kristaps 552: struct tag *
1.194 schwarze 553: print_otag(struct html *h, enum htmltag tag, const char *fmt, ...)
1.14 kristaps 554: {
1.194 schwarze 555: va_list ap;
556: struct roffsu mysu, *su;
1.197 schwarze 557: char numbuf[16];
1.30 kristaps 558: struct tag *t;
1.195 schwarze 559: const char *attr;
1.203 schwarze 560: char *arg1, *arg2;
1.195 schwarze 561: double v;
1.196 schwarze 562: int i, have_style, tflags;
563:
564: tflags = htmltags[tag].flags;
1.30 kristaps 565:
1.204 schwarze 566: /* Push this tag onto the stack of open scopes. */
1.94 kristaps 567:
1.196 schwarze 568: if ((tflags & HTML_NOSTACK) == 0) {
1.128 kristaps 569: t = mandoc_malloc(sizeof(struct tag));
1.30 kristaps 570: t->tag = tag;
1.204 schwarze 571: t->next = h->tag;
572: h->tag = t;
1.30 kristaps 573: } else
574: t = NULL;
1.29 kristaps 575:
1.196 schwarze 576: if (tflags & HTML_NLBEFORE)
1.197 schwarze 577: print_endline(h);
578: if (h->col == 0)
579: print_indent(h);
1.196 schwarze 580: else if ((h->flags & HTML_NOSPACE) == 0) {
581: if (h->flags & HTML_KEEP)
1.216 schwarze 582: print_word(h, " ");
1.196 schwarze 583: else {
584: if (h->flags & HTML_PREKEEP)
585: h->flags |= HTML_KEEP;
1.197 schwarze 586: print_endword(h);
1.105 kristaps 587: }
1.196 schwarze 588: }
1.29 kristaps 589:
1.109 kristaps 590: if ( ! (h->flags & HTML_NONOSPACE))
591: h->flags &= ~HTML_NOSPACE;
1.110 kristaps 592: else
593: h->flags |= HTML_NOSPACE;
1.109 kristaps 594:
1.94 kristaps 595: /* Print out the tag name and attributes. */
596:
1.197 schwarze 597: print_byte(h, '<');
598: print_word(h, htmltags[tag].name);
1.194 schwarze 599:
600: va_start(ap, fmt);
601:
602: have_style = 0;
603: while (*fmt != '\0') {
604: if (*fmt == 's') {
605: have_style = 1;
606: fmt++;
607: break;
608: }
1.203 schwarze 609:
610: /* Parse a non-style attribute and its arguments. */
611:
612: arg1 = va_arg(ap, char *);
1.194 schwarze 613: switch (*fmt++) {
614: case 'c':
1.195 schwarze 615: attr = "class";
1.194 schwarze 616: break;
617: case 'h':
1.195 schwarze 618: attr = "href";
1.194 schwarze 619: break;
620: case 'i':
1.195 schwarze 621: attr = "id";
1.194 schwarze 622: break;
623: case '?':
1.203 schwarze 624: attr = arg1;
625: arg1 = va_arg(ap, char *);
1.194 schwarze 626: break;
627: default:
628: abort();
629: }
1.203 schwarze 630: arg2 = NULL;
631: if (*fmt == 'M')
632: arg2 = va_arg(ap, char *);
633: if (arg1 == NULL)
634: continue;
635:
636: /* Print the non-style attributes. */
637:
1.197 schwarze 638: print_byte(h, ' ');
639: print_word(h, attr);
640: print_byte(h, '=');
641: print_byte(h, '"');
1.195 schwarze 642: switch (*fmt) {
1.208 schwarze 643: case 'I':
644: print_href(h, arg1, NULL, 0);
645: fmt++;
646: break;
1.195 schwarze 647: case 'M':
1.203 schwarze 648: print_href(h, arg1, arg2, 1);
1.195 schwarze 649: fmt++;
650: break;
1.208 schwarze 651: case 'R':
652: print_byte(h, '#');
653: print_encode(h, arg1, NULL, 1);
1.195 schwarze 654: fmt++;
655: break;
1.208 schwarze 656: case 'T':
657: print_encode(h, arg1, NULL, 1);
658: print_word(h, "\" title=\"");
659: print_encode(h, arg1, NULL, 1);
1.195 schwarze 660: fmt++;
1.208 schwarze 661: break;
1.195 schwarze 662: default:
1.203 schwarze 663: print_encode(h, arg1, NULL, 1);
1.195 schwarze 664: break;
665: }
1.197 schwarze 666: print_byte(h, '"');
1.194 schwarze 667: }
668:
669: /* Print out styles. */
670:
671: while (*fmt != '\0') {
1.203 schwarze 672: arg1 = NULL;
673: su = NULL;
1.194 schwarze 674:
675: /* First letter: input argument type. */
676:
677: switch (*fmt++) {
678: case 'h':
679: i = va_arg(ap, int);
1.203 schwarze 680: su = &mysu;
1.194 schwarze 681: SCALE_HS_INIT(su, i);
682: break;
683: case 's':
1.203 schwarze 684: arg1 = va_arg(ap, char *);
1.194 schwarze 685: break;
686: case 'u':
687: su = va_arg(ap, struct roffsu *);
688: break;
689: default:
690: abort();
691: }
692:
693: /* Second letter: style name. */
694:
695: switch (*fmt++) {
1.233 schwarze 696: case 'h':
697: attr = "height";
1.194 schwarze 698: break;
699: case 'l':
1.195 schwarze 700: attr = "margin-left";
1.194 schwarze 701: break;
702: case '?':
1.203 schwarze 703: attr = arg1;
704: arg1 = va_arg(ap, char *);
705: break;
1.194 schwarze 706: default:
707: abort();
708: }
1.203 schwarze 709: if (su == NULL && arg1 == NULL)
710: continue;
711:
712: if (have_style == 1)
713: print_word(h, " style=\"");
714: else
715: print_byte(h, ' ');
1.197 schwarze 716: print_word(h, attr);
717: print_byte(h, ':');
718: print_byte(h, ' ');
1.203 schwarze 719: if (su != NULL) {
720: v = su->scale;
721: if (su->unit == SCALE_MM && (v /= 100.0) == 0.0)
722: v = 1.0;
723: else if (su->unit == SCALE_BU)
724: v /= 24.0;
725: (void)snprintf(numbuf, sizeof(numbuf), "%.2f", v);
726: print_word(h, numbuf);
727: print_word(h, roffscales[su->unit]);
728: } else
729: print_word(h, arg1);
1.197 schwarze 730: print_byte(h, ';');
1.203 schwarze 731: have_style = 2;
1.194 schwarze 732: }
1.203 schwarze 733: if (have_style == 2)
1.197 schwarze 734: print_byte(h, '"');
1.194 schwarze 735:
736: va_end(ap);
1.94 kristaps 737:
1.172 kristaps 738: /* Accommodate for "well-formed" singleton escaping. */
1.94 kristaps 739:
1.93 kristaps 740: if (HTML_AUTOCLOSE & htmltags[tag].flags)
1.197 schwarze 741: print_byte(h, '/');
1.93 kristaps 742:
1.197 schwarze 743: print_byte(h, '>');
1.14 kristaps 744:
1.196 schwarze 745: if (tflags & HTML_NLBEGIN)
1.197 schwarze 746: print_endline(h);
1.196 schwarze 747: else
748: h->flags |= HTML_NOSPACE;
1.117 kristaps 749:
1.196 schwarze 750: if (tflags & HTML_INDENT)
751: h->indent++;
752: if (tflags & HTML_NOINDENT)
753: h->noindent++;
1.117 kristaps 754:
1.188 schwarze 755: return t;
1.14 kristaps 756: }
757:
1.29 kristaps 758: static void
1.184 schwarze 759: print_ctag(struct html *h, struct tag *tag)
1.14 kristaps 760: {
1.196 schwarze 761: int tflags;
1.156 schwarze 762:
1.184 schwarze 763: /*
764: * Remember to close out and nullify the current
765: * meta-font and table, if applicable.
766: */
767: if (tag == h->metaf)
768: h->metaf = NULL;
769: if (tag == h->tblt)
770: h->tblt = NULL;
771:
1.196 schwarze 772: tflags = htmltags[tag->tag].flags;
773:
774: if (tflags & HTML_INDENT)
775: h->indent--;
776: if (tflags & HTML_NOINDENT)
777: h->noindent--;
778: if (tflags & HTML_NLEND)
1.197 schwarze 779: print_endline(h);
780: print_indent(h);
781: print_byte(h, '<');
782: print_byte(h, '/');
783: print_word(h, htmltags[tag->tag].name);
784: print_byte(h, '>');
1.196 schwarze 785: if (tflags & HTML_NLAFTER)
1.197 schwarze 786: print_endline(h);
1.184 schwarze 787:
1.204 schwarze 788: h->tag = tag->next;
1.184 schwarze 789: free(tag);
1.14 kristaps 790: }
791:
1.51 kristaps 792: void
1.93 kristaps 793: print_gen_decls(struct html *h)
1.1 kristaps 794: {
1.197 schwarze 795: print_word(h, "<!DOCTYPE html>");
796: print_endline(h);
1.221 schwarze 797: }
798:
799: void
800: print_gen_comment(struct html *h, struct roff_node *n)
801: {
802: int wantblank;
803:
804: print_word(h, "<!-- This is an automatically generated file."
805: " Do not edit.");
806: h->indent = 1;
807: wantblank = 0;
808: while (n != NULL && n->type == ROFFT_COMMENT) {
809: if (strstr(n->string, "-->") == NULL &&
810: (wantblank || *n->string != '\0')) {
811: print_endline(h);
812: print_indent(h);
813: print_word(h, n->string);
814: wantblank = *n->string != '\0';
815: }
816: n = n->next;
817: }
818: if (wantblank)
819: print_endline(h);
820: print_word(h, " -->");
821: print_endline(h);
822: h->indent = 0;
1.1 kristaps 823: }
824:
1.51 kristaps 825: void
1.104 kristaps 826: print_text(struct html *h, const char *word)
1.1 kristaps 827: {
1.197 schwarze 828: if (h->col && (h->flags & HTML_NOSPACE) == 0) {
1.105 kristaps 829: if ( ! (HTML_KEEP & h->flags)) {
830: if (HTML_PREKEEP & h->flags)
831: h->flags |= HTML_KEEP;
1.197 schwarze 832: print_endword(h);
1.105 kristaps 833: } else
1.216 schwarze 834: print_word(h, " ");
1.105 kristaps 835: }
1.30 kristaps 836:
1.122 kristaps 837: assert(NULL == h->metaf);
1.152 schwarze 838: switch (h->metac) {
1.156 schwarze 839: case HTMLFONT_ITALIC:
1.194 schwarze 840: h->metaf = print_otag(h, TAG_I, "");
1.152 schwarze 841: break;
1.156 schwarze 842: case HTMLFONT_BOLD:
1.194 schwarze 843: h->metaf = print_otag(h, TAG_B, "");
1.152 schwarze 844: break;
1.156 schwarze 845: case HTMLFONT_BI:
1.194 schwarze 846: h->metaf = print_otag(h, TAG_B, "");
847: print_otag(h, TAG_I, "");
1.152 schwarze 848: break;
849: default:
1.197 schwarze 850: print_indent(h);
1.152 schwarze 851: break;
852: }
1.122 kristaps 853:
1.104 kristaps 854: assert(word);
1.195 schwarze 855: if ( ! print_encode(h, word, NULL, 0)) {
1.109 kristaps 856: if ( ! (h->flags & HTML_NONOSPACE))
857: h->flags &= ~HTML_NOSPACE;
1.183 schwarze 858: h->flags &= ~HTML_NONEWLINE;
1.149 kristaps 859: } else
1.183 schwarze 860: h->flags |= HTML_NOSPACE | HTML_NONEWLINE;
1.122 kristaps 861:
862: if (h->metaf) {
863: print_tagq(h, h->metaf);
864: h->metaf = NULL;
865: }
1.113 schwarze 866:
867: h->flags &= ~HTML_IGNDELIM;
1.1 kristaps 868: }
1.30 kristaps 869:
1.51 kristaps 870: void
1.30 kristaps 871: print_tagq(struct html *h, const struct tag *until)
872: {
873: struct tag *tag;
874:
1.204 schwarze 875: while ((tag = h->tag) != NULL) {
1.184 schwarze 876: print_ctag(h, tag);
1.30 kristaps 877: if (until && tag == until)
878: return;
879: }
880: }
881:
1.51 kristaps 882: void
1.30 kristaps 883: print_stagq(struct html *h, const struct tag *suntil)
884: {
885: struct tag *tag;
886:
1.204 schwarze 887: while ((tag = h->tag) != NULL) {
1.30 kristaps 888: if (suntil && tag == suntil)
889: return;
1.184 schwarze 890: print_ctag(h, tag);
1.30 kristaps 891: }
892: }
1.171 kristaps 893:
894: void
895: print_paragraph(struct html *h)
896: {
897: struct tag *t;
898:
1.198 schwarze 899: t = print_otag(h, TAG_DIV, "c", "Pp");
1.171 kristaps 900: print_tagq(h, t);
901: }
902:
1.197 schwarze 903:
904: /***********************************************************************
905: * Low level output functions.
906: * They implement line breaking using a short static buffer.
907: ***********************************************************************/
908:
909: /*
910: * Buffer one HTML output byte.
911: * If the buffer is full, flush and deactivate it and start a new line.
912: * If the buffer is inactive, print directly.
913: */
914: static void
915: print_byte(struct html *h, char c)
916: {
917: if ((h->flags & HTML_BUFFER) == 0) {
918: putchar(c);
919: h->col++;
920: return;
921: }
922:
923: if (h->col + h->bufcol < sizeof(h->buf)) {
924: h->buf[h->bufcol++] = c;
925: return;
926: }
927:
928: putchar('\n');
929: h->col = 0;
930: print_indent(h);
931: putchar(' ');
932: putchar(' ');
933: fwrite(h->buf, h->bufcol, 1, stdout);
934: putchar(c);
935: h->col = (h->indent + 1) * 2 + h->bufcol + 1;
936: h->bufcol = 0;
937: h->flags &= ~HTML_BUFFER;
938: }
939:
1.196 schwarze 940: /*
941: * If something was printed on the current output line, end it.
1.197 schwarze 942: * Not to be called right after print_indent().
1.196 schwarze 943: */
1.202 schwarze 944: void
1.197 schwarze 945: print_endline(struct html *h)
1.196 schwarze 946: {
1.197 schwarze 947: if (h->col == 0)
1.196 schwarze 948: return;
949:
1.197 schwarze 950: if (h->bufcol) {
951: putchar(' ');
952: fwrite(h->buf, h->bufcol, 1, stdout);
953: h->bufcol = 0;
954: }
1.196 schwarze 955: putchar('\n');
1.197 schwarze 956: h->col = 0;
957: h->flags |= HTML_NOSPACE;
958: h->flags &= ~HTML_BUFFER;
959: }
960:
961: /*
962: * Flush the HTML output buffer.
963: * If it is inactive, activate it.
964: */
965: static void
966: print_endword(struct html *h)
967: {
968: if (h->noindent) {
969: print_byte(h, ' ');
970: return;
971: }
972:
973: if ((h->flags & HTML_BUFFER) == 0) {
974: h->col++;
975: h->flags |= HTML_BUFFER;
976: } else if (h->bufcol) {
977: putchar(' ');
978: fwrite(h->buf, h->bufcol, 1, stdout);
979: h->col += h->bufcol + 1;
980: }
981: h->bufcol = 0;
1.196 schwarze 982: }
983:
984: /*
985: * If at the beginning of a new output line,
986: * perform indentation and mark the line as containing output.
987: * Make sure to really produce some output right afterwards,
988: * but do not use print_otag() for producing it.
989: */
990: static void
1.197 schwarze 991: print_indent(struct html *h)
1.196 schwarze 992: {
1.197 schwarze 993: size_t i;
1.196 schwarze 994:
1.197 schwarze 995: if (h->col)
1.196 schwarze 996: return;
997:
1.197 schwarze 998: if (h->noindent == 0) {
999: h->col = h->indent * 2;
1000: for (i = 0; i < h->col; i++)
1.196 schwarze 1001: putchar(' ');
1.197 schwarze 1002: }
1003: h->flags &= ~HTML_NOSPACE;
1004: }
1005:
1006: /*
1007: * Print or buffer some characters
1008: * depending on the current HTML output buffer state.
1009: */
1010: static void
1011: print_word(struct html *h, const char *cp)
1012: {
1013: while (*cp != '\0')
1014: print_byte(h, *cp++);
1.68 kristaps 1015: }
CVSweb