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