1 /* $Id: html.c,v 1.279 2022/08/09 11:23:11 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2011-2015, 2017-2021 Ingo Schwarze <schwarze@openbsd.org>
5 * Copyright (c) 2022 Anna Vyalkova <cyber@sysrq.in>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 *
19 * Common functions for mandoc(1) HTML formatters.
20 * For use by individual formatters and by the main program.
21 */
22 #include "config.h"
23
24 #include <sys/types.h>
25 #include <sys/stat.h>
26
27 #include <assert.h>
28 #include <ctype.h>
29 #include <stdarg.h>
30 #include <stddef.h>
31 #include <stdio.h>
32 #include <stdint.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36
37 #include "mandoc_aux.h"
38 #include "mandoc_ohash.h"
39 #include "mandoc.h"
40 #include "roff.h"
41 #include "out.h"
42 #include "html.h"
43 #include "manconf.h"
44 #include "main.h"
45
46 struct htmldata {
47 const char *name;
48 int flags;
49 #define HTML_INPHRASE (1 << 0) /* Can appear in phrasing context. */
50 #define HTML_TOPHRASE (1 << 1) /* Establishes phrasing context. */
51 #define HTML_NOSTACK (1 << 2) /* Does not have an end tag. */
52 #define HTML_NLBEFORE (1 << 3) /* Output line break before opening. */
53 #define HTML_NLBEGIN (1 << 4) /* Output line break after opening. */
54 #define HTML_NLEND (1 << 5) /* Output line break before closing. */
55 #define HTML_NLAFTER (1 << 6) /* Output line break after closing. */
56 #define HTML_NLAROUND (HTML_NLBEFORE | HTML_NLAFTER)
57 #define HTML_NLINSIDE (HTML_NLBEGIN | HTML_NLEND)
58 #define HTML_NLALL (HTML_NLAROUND | HTML_NLINSIDE)
59 #define HTML_INDENT (1 << 7) /* Indent content by two spaces. */
60 #define HTML_NOINDENT (1 << 8) /* Exception: never indent content. */
61 };
62
63 static const struct htmldata htmltags[TAG_MAX] = {
64 {"html", HTML_NLALL},
65 {"head", HTML_NLALL | HTML_INDENT},
66 {"meta", HTML_NOSTACK | HTML_NLALL},
67 {"link", HTML_NOSTACK | HTML_NLALL},
68 {"style", HTML_NLALL | HTML_INDENT},
69 {"title", HTML_NLAROUND},
70 {"body", HTML_NLALL},
71 {"main", HTML_NLALL},
72 {"div", HTML_NLAROUND},
73 {"section", HTML_NLALL},
74 {"nav", HTML_NLALL},
75 {"table", HTML_NLALL | HTML_INDENT},
76 {"tr", HTML_NLALL | HTML_INDENT},
77 {"td", HTML_NLAROUND},
78 {"li", HTML_NLAROUND | HTML_INDENT},
79 {"ul", HTML_NLALL | HTML_INDENT},
80 {"ol", HTML_NLALL | HTML_INDENT},
81 {"dl", HTML_NLALL | HTML_INDENT},
82 {"dt", HTML_NLAROUND},
83 {"dd", HTML_NLAROUND | HTML_INDENT},
84 {"h2", HTML_TOPHRASE | HTML_NLAROUND},
85 {"h3", HTML_TOPHRASE | HTML_NLAROUND},
86 {"p", HTML_TOPHRASE | HTML_NLAROUND | HTML_INDENT},
87 {"pre", HTML_TOPHRASE | HTML_NLAROUND | HTML_NOINDENT},
88 {"a", HTML_INPHRASE | HTML_TOPHRASE},
89 {"b", HTML_INPHRASE | HTML_TOPHRASE},
90 {"cite", HTML_INPHRASE | HTML_TOPHRASE},
91 {"code", HTML_INPHRASE | HTML_TOPHRASE},
92 {"i", HTML_INPHRASE | HTML_TOPHRASE},
93 {"small", HTML_INPHRASE | HTML_TOPHRASE},
94 {"span", HTML_INPHRASE | HTML_TOPHRASE},
95 {"var", HTML_INPHRASE | HTML_TOPHRASE},
96 {"br", HTML_INPHRASE | HTML_NOSTACK | HTML_NLALL},
97 {"hr", HTML_INPHRASE | HTML_NOSTACK},
98 {"mark", HTML_INPHRASE },
99 {"math", HTML_INPHRASE | HTML_NLALL | HTML_INDENT},
100 {"mrow", 0},
101 {"mi", 0},
102 {"mn", 0},
103 {"mo", 0},
104 {"msup", 0},
105 {"msub", 0},
106 {"msubsup", 0},
107 {"mfrac", 0},
108 {"msqrt", 0},
109 {"mfenced", 0},
110 {"mtable", 0},
111 {"mtr", 0},
112 {"mtd", 0},
113 {"munderover", 0},
114 {"munder", 0},
115 {"mover", 0},
116 };
117
118 /* Avoid duplicate HTML id= attributes. */
119
120 struct id_entry {
121 int ord; /* Ordinal number of the latest occurrence. */
122 char id[]; /* The id= attribute without any ordinal suffix. */
123 };
124 static struct ohash id_unique;
125
126 static void html_reset_internal(struct html *);
127 static void print_byte(struct html *, char);
128 static void print_endword(struct html *);
129 static void print_indent(struct html *);
130 static void print_word(struct html *, const char *);
131
132 static void print_ctag(struct html *, struct tag *);
133 static int print_escape(struct html *, char);
134 static int print_encode(struct html *, const char *, const char *, int);
135 static void print_href(struct html *, const char *, const char *, int);
136 static void print_metaf(struct html *);
137
138
139 void *
html_alloc(const struct manoutput * outopts)140 html_alloc(const struct manoutput *outopts)
141 {
142 struct html *h;
143
144 h = mandoc_calloc(1, sizeof(struct html));
145
146 h->tag = NULL;
147 h->metac = h->metal = ESCAPE_FONTROMAN;
148 h->style = outopts->style;
149 if ((h->base_man1 = outopts->man) == NULL)
150 h->base_man2 = NULL;
151 else if ((h->base_man2 = strchr(h->base_man1, ';')) != NULL)
152 *h->base_man2++ = '\0';
153 h->base_includes = outopts->includes;
154 if (outopts->fragment)
155 h->oflags |= HTML_FRAGMENT;
156 if (outopts->toc)
157 h->oflags |= HTML_TOC;
158
159 mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id));
160
161 return h;
162 }
163
164 static void
html_reset_internal(struct html * h)165 html_reset_internal(struct html *h)
166 {
167 struct tag *tag;
168 struct id_entry *entry;
169 unsigned int slot;
170
171 while ((tag = h->tag) != NULL) {
172 h->tag = tag->next;
173 free(tag);
174 }
175 entry = ohash_first(&id_unique, &slot);
176 while (entry != NULL) {
177 free(entry);
178 entry = ohash_next(&id_unique, &slot);
179 }
180 ohash_delete(&id_unique);
181 }
182
183 void
html_reset(void * p)184 html_reset(void *p)
185 {
186 html_reset_internal(p);
187 mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id));
188 }
189
190 void
html_free(void * p)191 html_free(void *p)
192 {
193 html_reset_internal(p);
194 free(p);
195 }
196
197 void
print_gen_head(struct html * h)198 print_gen_head(struct html *h)
199 {
200 struct tag *t;
201
202 print_otag(h, TAG_META, "?", "charset", "utf-8");
203 print_otag(h, TAG_META, "??", "name", "viewport",
204 "content", "width=device-width, initial-scale=1.0");
205 if (h->style != NULL) {
206 print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet",
207 h->style, "type", "text/css", "media", "all");
208 return;
209 }
210
211 /*
212 * Print a minimal embedded style sheet.
213 */
214
215 t = print_otag(h, TAG_STYLE, "");
216 print_text(h, "table.head, table.foot { width: 100%; }");
217 print_endline(h);
218 print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }");
219 print_endline(h);
220 print_text(h, "td.head-vol { text-align: center; }");
221 print_endline(h);
222 print_text(h, ".Nd, .Bf, .Op { display: inline; }");
223 print_endline(h);
224 print_text(h, ".Pa, .Ad { font-style: italic; }");
225 print_endline(h);
226 print_text(h, ".Ms { font-weight: bold; }");
227 print_endline(h);
228 print_text(h, ".Bl-diag ");
229 print_byte(h, '>');
230 print_text(h, " dt { font-weight: bold; }");
231 print_endline(h);
232 print_text(h, "code.Nm, .Fl, .Cm, .Ic, code.In, .Fd, .Fn, .Cd "
233 "{ font-weight: bold; font-family: inherit; }");
234 print_tagq(h, t);
235 }
236
237 int
html_setfont(struct html * h,enum mandoc_esc font)238 html_setfont(struct html *h, enum mandoc_esc font)
239 {
240 switch (font) {
241 case ESCAPE_FONTPREV:
242 font = h->metal;
243 break;
244 case ESCAPE_FONTITALIC:
245 case ESCAPE_FONTBOLD:
246 case ESCAPE_FONTBI:
247 case ESCAPE_FONTROMAN:
248 case ESCAPE_FONTCR:
249 case ESCAPE_FONTCB:
250 case ESCAPE_FONTCI:
251 break;
252 case ESCAPE_FONT:
253 font = ESCAPE_FONTROMAN;
254 break;
255 default:
256 return 0;
257 }
258 h->metal = h->metac;
259 h->metac = font;
260 return 1;
261 }
262
263 static void
print_metaf(struct html * h)264 print_metaf(struct html *h)
265 {
266 if (h->metaf) {
267 print_tagq(h, h->metaf);
268 h->metaf = NULL;
269 }
270 switch (h->metac) {
271 case ESCAPE_FONTITALIC:
272 h->metaf = print_otag(h, TAG_I, "");
273 break;
274 case ESCAPE_FONTBOLD:
275 h->metaf = print_otag(h, TAG_B, "");
276 break;
277 case ESCAPE_FONTBI:
278 h->metaf = print_otag(h, TAG_B, "");
279 print_otag(h, TAG_I, "");
280 break;
281 case ESCAPE_FONTCR:
282 h->metaf = print_otag(h, TAG_SPAN, "c", "Li");
283 break;
284 case ESCAPE_FONTCB:
285 h->metaf = print_otag(h, TAG_SPAN, "c", "Li");
286 print_otag(h, TAG_B, "");
287 break;
288 case ESCAPE_FONTCI:
289 h->metaf = print_otag(h, TAG_SPAN, "c", "Li");
290 print_otag(h, TAG_I, "");
291 break;
292 default:
293 break;
294 }
295 }
296
297 void
html_close_paragraph(struct html * h)298 html_close_paragraph(struct html *h)
299 {
300 struct tag *this, *next;
301 int flags;
302
303 this = h->tag;
304 for (;;) {
305 next = this->next;
306 flags = htmltags[this->tag].flags;
307 if (flags & (HTML_INPHRASE | HTML_TOPHRASE))
308 print_ctag(h, this);
309 if ((flags & HTML_INPHRASE) == 0)
310 break;
311 this = next;
312 }
313 }
314
315 /*
316 * ROFF_nf switches to no-fill mode, ROFF_fi to fill mode.
317 * TOKEN_NONE does not switch. The old mode is returned.
318 */
319 enum roff_tok
html_fillmode(struct html * h,enum roff_tok want)320 html_fillmode(struct html *h, enum roff_tok want)
321 {
322 struct tag *t;
323 enum roff_tok had;
324
325 for (t = h->tag; t != NULL; t = t->next)
326 if (t->tag == TAG_PRE)
327 break;
328
329 had = t == NULL ? ROFF_fi : ROFF_nf;
330
331 if (want != had) {
332 switch (want) {
333 case ROFF_fi:
334 print_tagq(h, t);
335 break;
336 case ROFF_nf:
337 html_close_paragraph(h);
338 print_otag(h, TAG_PRE, "");
339 break;
340 case TOKEN_NONE:
341 break;
342 default:
343 abort();
344 }
345 }
346 return had;
347 }
348
349 /*
350 * Allocate a string to be used for the "id=" attribute of an HTML
351 * element and/or as a segment identifier for a URI in an <a> element.
352 * The function may fail and return NULL if the node lacks text data
353 * to create the attribute from.
354 * The caller is responsible for free(3)ing the returned string.
355 *
356 * If the "unique" argument is non-zero, the "id_unique" ohash table
357 * is used for de-duplication. If the "unique" argument is 1,
358 * it is the first time the function is called for this tag and
359 * location, so if an ordinal suffix is needed, it is incremented.
360 * If the "unique" argument is 2, it is the second time the function
361 * is called for this tag and location, so the ordinal suffix
362 * remains unchanged.
363 */
364 char *
html_make_id(const struct roff_node * n,int unique)365 html_make_id(const struct roff_node *n, int unique)
366 {
367 const struct roff_node *nch;
368 struct id_entry *entry;
369 char *buf, *cp;
370 size_t len;
371 unsigned int slot;
372
373 if (n->tag != NULL)
374 buf = mandoc_strdup(n->tag);
375 else {
376 switch (n->tok) {
377 case MDOC_Sh:
378 case MDOC_Ss:
379 case MDOC_Sx:
380 case MAN_SH:
381 case MAN_SS:
382 for (nch = n->child; nch != NULL; nch = nch->next)
383 if (nch->type != ROFFT_TEXT)
384 return NULL;
385 buf = NULL;
386 deroff(&buf, n);
387 if (buf == NULL)
388 return NULL;
389 break;
390 default:
391 if (n->child == NULL || n->child->type != ROFFT_TEXT)
392 return NULL;
393 buf = mandoc_strdup(n->child->string);
394 break;
395 }
396 }
397
398 /*
399 * In ID attributes, only use ASCII characters that are
400 * permitted in URL-fragment strings according to the
401 * explicit list at:
402 * https://url.spec.whatwg.org/#url-fragment-string
403 * In addition, reserve '~' for ordinal suffixes.
404 */
405
406 for (cp = buf; *cp != '\0'; cp++) {
407 if (*cp == ASCII_HYPH)
408 *cp = '-';
409 else if (isalnum((unsigned char)*cp) == 0 &&
410 strchr("!$&'()*+,-./:;=?@_", *cp) == NULL)
411 *cp = '_';
412 }
413
414 if (unique == 0)
415 return buf;
416
417 /* Avoid duplicate HTML id= attributes. */
418
419 slot = ohash_qlookup(&id_unique, buf);
420 if ((entry = ohash_find(&id_unique, slot)) == NULL) {
421 len = strlen(buf) + 1;
422 entry = mandoc_malloc(sizeof(*entry) + len);
423 entry->ord = 1;
424 memcpy(entry->id, buf, len);
425 ohash_insert(&id_unique, slot, entry);
426 } else if (unique == 1)
427 entry->ord++;
428
429 if (entry->ord > 1) {
430 cp = buf;
431 mandoc_asprintf(&buf, "%s~%d", cp, entry->ord);
432 free(cp);
433 }
434 return buf;
435 }
436
437 static int
print_escape(struct html * h,char c)438 print_escape(struct html *h, char c)
439 {
440
441 switch (c) {
442 case '<':
443 print_word(h, "<");
444 break;
445 case '>':
446 print_word(h, ">");
447 break;
448 case '&':
449 print_word(h, "&");
450 break;
451 case '"':
452 print_word(h, """);
453 break;
454 case ASCII_NBRSP:
455 print_word(h, " ");
456 break;
457 case ASCII_HYPH:
458 print_byte(h, '-');
459 break;
460 case ASCII_BREAK:
461 break;
462 default:
463 return 0;
464 }
465 return 1;
466 }
467
468 static int
print_encode(struct html * h,const char * p,const char * pend,int norecurse)469 print_encode(struct html *h, const char *p, const char *pend, int norecurse)
470 {
471 char numbuf[16];
472 const char *seq;
473 size_t sz;
474 int c, len, breakline, nospace;
475 enum mandoc_esc esc;
476 static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"',
477 ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' };
478
479 if (pend == NULL)
480 pend = strchr(p, '\0');
481
482 breakline = 0;
483 nospace = 0;
484
485 while (p < pend) {
486 if (HTML_SKIPCHAR & h->flags && '\\' != *p) {
487 h->flags &= ~HTML_SKIPCHAR;
488 p++;
489 continue;
490 }
491
492 for (sz = strcspn(p, rejs); sz-- && p < pend; p++)
493 print_byte(h, *p);
494
495 if (breakline &&
496 (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) {
497 print_otag(h, TAG_BR, "");
498 breakline = 0;
499 while (p < pend && (*p == ' ' || *p == ASCII_NBRSP))
500 p++;
501 continue;
502 }
503
504 if (p >= pend)
505 break;
506
507 if (*p == ' ') {
508 print_endword(h);
509 p++;
510 continue;
511 }
512
513 if (print_escape(h, *p++))
514 continue;
515
516 esc = mandoc_escape(&p, &seq, &len);
517 switch (esc) {
518 case ESCAPE_FONT:
519 case ESCAPE_FONTPREV:
520 case ESCAPE_FONTBOLD:
521 case ESCAPE_FONTITALIC:
522 case ESCAPE_FONTBI:
523 case ESCAPE_FONTROMAN:
524 case ESCAPE_FONTCR:
525 case ESCAPE_FONTCB:
526 case ESCAPE_FONTCI:
527 if (0 == norecurse) {
528 h->flags |= HTML_NOSPACE;
529 if (html_setfont(h, esc))
530 print_metaf(h);
531 h->flags &= ~HTML_NOSPACE;
532 }
533 continue;
534 case ESCAPE_SKIPCHAR:
535 h->flags |= HTML_SKIPCHAR;
536 continue;
537 case ESCAPE_ERROR:
538 continue;
539 default:
540 break;
541 }
542
543 if (h->flags & HTML_SKIPCHAR) {
544 h->flags &= ~HTML_SKIPCHAR;
545 continue;
546 }
547
548 switch (esc) {
549 case ESCAPE_UNICODE:
550 /* Skip past "u" header. */
551 c = mchars_num2uc(seq + 1, len - 1);
552 break;
553 case ESCAPE_NUMBERED:
554 c = mchars_num2char(seq, len);
555 if (c < 0)
556 continue;
557 break;
558 case ESCAPE_SPECIAL:
559 c = mchars_spec2cp(seq, len);
560 if (c <= 0)
561 continue;
562 break;
563 case ESCAPE_UNDEF:
564 c = *seq;
565 break;
566 case ESCAPE_DEVICE:
567 print_word(h, "html");
568 continue;
569 case ESCAPE_BREAK:
570 breakline = 1;
571 continue;
572 case ESCAPE_NOSPACE:
573 if ('\0' == *p)
574 nospace = 1;
575 continue;
576 case ESCAPE_OVERSTRIKE:
577 if (len == 0)
578 continue;
579 c = seq[len - 1];
580 break;
581 default:
582 continue;
583 }
584 if ((c < 0x20 && c != 0x09) ||
585 (c > 0x7E && c < 0xA0))
586 c = 0xFFFD;
587 if (c > 0x7E) {
588 (void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c);
589 print_word(h, numbuf);
590 } else if (print_escape(h, c) == 0)
591 print_byte(h, c);
592 }
593
594 return nospace;
595 }
596
597 static void
print_href(struct html * h,const char * name,const char * sec,int man)598 print_href(struct html *h, const char *name, const char *sec, int man)
599 {
600 struct stat sb;
601 const char *p, *pp;
602 char *filename;
603
604 if (man) {
605 pp = h->base_man1;
606 if (h->base_man2 != NULL) {
607 mandoc_asprintf(&filename, "%s.%s", name, sec);
608 if (stat(filename, &sb) == -1)
609 pp = h->base_man2;
610 free(filename);
611 }
612 } else
613 pp = h->base_includes;
614
615 while ((p = strchr(pp, '%')) != NULL) {
616 print_encode(h, pp, p, 1);
617 if (man && p[1] == 'S') {
618 if (sec == NULL)
619 print_byte(h, '1');
620 else
621 print_encode(h, sec, NULL, 1);
622 } else if ((man && p[1] == 'N') ||
623 (man == 0 && p[1] == 'I'))
624 print_encode(h, name, NULL, 1);
625 else
626 print_encode(h, p, p + 2, 1);
627 pp = p + 2;
628 }
629 if (*pp != '\0')
630 print_encode(h, pp, NULL, 1);
631 }
632
633 struct tag *
print_otag(struct html * h,enum htmltag tag,const char * fmt,...)634 print_otag(struct html *h, enum htmltag tag, const char *fmt, ...)
635 {
636 va_list ap;
637 struct tag *t;
638 const char *attr;
639 char *arg1, *arg2;
640 int style_written, tflags;
641
642 tflags = htmltags[tag].flags;
643
644 /* Flow content is not allowed in phrasing context. */
645
646 if ((tflags & HTML_INPHRASE) == 0) {
647 for (t = h->tag; t != NULL; t = t->next) {
648 if (t->closed)
649 continue;
650 assert((htmltags[t->tag].flags & HTML_TOPHRASE) == 0);
651 break;
652 }
653
654 /*
655 * Always wrap phrasing elements in a paragraph
656 * unless already contained in some flow container;
657 * never put them directly into a section.
658 */
659
660 } else if (tflags & HTML_TOPHRASE && h->tag->tag == TAG_SECTION)
661 print_otag(h, TAG_P, "c", "Pp");
662
663 /* Push this tag onto the stack of open scopes. */
664
665 if ((tflags & HTML_NOSTACK) == 0) {
666 t = mandoc_malloc(sizeof(struct tag));
667 t->tag = tag;
668 t->next = h->tag;
669 t->refcnt = 0;
670 t->closed = 0;
671 h->tag = t;
672 } else
673 t = NULL;
674
675 if (tflags & HTML_NLBEFORE)
676 print_endline(h);
677 if (h->col == 0)
678 print_indent(h);
679 else if ((h->flags & HTML_NOSPACE) == 0) {
680 if (h->flags & HTML_KEEP)
681 print_word(h, " ");
682 else {
683 if (h->flags & HTML_PREKEEP)
684 h->flags |= HTML_KEEP;
685 print_endword(h);
686 }
687 }
688
689 if ( ! (h->flags & HTML_NONOSPACE))
690 h->flags &= ~HTML_NOSPACE;
691 else
692 h->flags |= HTML_NOSPACE;
693
694 /* Print out the tag name and attributes. */
695
696 print_byte(h, '<');
697 print_word(h, htmltags[tag].name);
698
699 va_start(ap, fmt);
700
701 while (*fmt != '\0' && *fmt != 's') {
702
703 /* Parse attributes and arguments. */
704
705 arg1 = va_arg(ap, char *);
706 arg2 = NULL;
707 switch (*fmt++) {
708 case 'c':
709 attr = "class";
710 break;
711 case 'h':
712 attr = "href";
713 break;
714 case 'i':
715 attr = "id";
716 break;
717 case 'r':
718 attr = "role";
719 break;
720 case '?':
721 attr = arg1;
722 arg1 = va_arg(ap, char *);
723 break;
724 default:
725 abort();
726 }
727 if (*fmt == 'M')
728 arg2 = va_arg(ap, char *);
729 if (arg1 == NULL)
730 continue;
731
732 /* Print the attributes. */
733
734 print_byte(h, ' ');
735 print_word(h, attr);
736 print_byte(h, '=');
737 print_byte(h, '"');
738 switch (*fmt) {
739 case 'I':
740 print_href(h, arg1, NULL, 0);
741 fmt++;
742 break;
743 case 'M':
744 print_href(h, arg1, arg2, 1);
745 fmt++;
746 break;
747 case 'R':
748 print_byte(h, '#');
749 print_encode(h, arg1, NULL, 1);
750 fmt++;
751 break;
752 default:
753 print_encode(h, arg1, NULL, 1);
754 break;
755 }
756 print_byte(h, '"');
757 }
758
759 style_written = 0;
760 while (*fmt++ == 's') {
761 arg1 = va_arg(ap, char *);
762 arg2 = va_arg(ap, char *);
763 if (arg2 == NULL)
764 continue;
765 print_byte(h, ' ');
766 if (style_written == 0) {
767 print_word(h, "style=\"");
768 style_written = 1;
769 }
770 print_word(h, arg1);
771 print_byte(h, ':');
772 print_byte(h, ' ');
773 print_word(h, arg2);
774 print_byte(h, ';');
775 }
776 if (style_written)
777 print_byte(h, '"');
778
779 va_end(ap);
780
781 /* Accommodate for "well-formed" singleton escaping. */
782
783 if (htmltags[tag].flags & HTML_NOSTACK)
784 print_byte(h, '/');
785
786 print_byte(h, '>');
787
788 if (tflags & HTML_NLBEGIN)
789 print_endline(h);
790 else
791 h->flags |= HTML_NOSPACE;
792
793 if (tflags & HTML_INDENT)
794 h->indent++;
795 if (tflags & HTML_NOINDENT)
796 h->noindent++;
797
798 return t;
799 }
800
801 /*
802 * Print an element with an optional "id=" attribute.
803 * If the element has phrasing content and an "id=" attribute,
804 * also add a permalink: outside if it can be in phrasing context,
805 * inside otherwise.
806 */
807 struct tag *
print_otag_id(struct html * h,enum htmltag elemtype,const char * cattr,struct roff_node * n)808 print_otag_id(struct html *h, enum htmltag elemtype, const char *cattr,
809 struct roff_node *n)
810 {
811 struct roff_node *nch;
812 struct tag *ret, *t;
813 char *id, *href;
814
815 ret = NULL;
816 id = href = NULL;
817 if (n->flags & NODE_ID)
818 id = html_make_id(n, 1);
819 if (n->flags & NODE_HREF)
820 href = id == NULL ? html_make_id(n, 2) : id;
821 if (href != NULL && htmltags[elemtype].flags & HTML_INPHRASE)
822 ret = print_otag(h, TAG_A, "chR", "permalink", href);
823 t = print_otag(h, elemtype, "ci", cattr, id);
824 if (ret == NULL) {
825 ret = t;
826 if (href != NULL && (nch = n->child) != NULL) {
827 /* man(7) is safe, it tags phrasing content only. */
828 if (n->tok > MDOC_MAX ||
829 htmltags[elemtype].flags & HTML_TOPHRASE)
830 nch = NULL;
831 else /* For mdoc(7), beware of nested blocks. */
832 while (nch != NULL && nch->type == ROFFT_TEXT)
833 nch = nch->next;
834 if (nch == NULL)
835 print_otag(h, TAG_A, "chR", "permalink", href);
836 }
837 }
838 free(id);
839 if (id == NULL)
840 free(href);
841 return ret;
842 }
843
844 static void
print_ctag(struct html * h,struct tag * tag)845 print_ctag(struct html *h, struct tag *tag)
846 {
847 int tflags;
848
849 if (tag->closed == 0) {
850 tag->closed = 1;
851 if (tag == h->metaf)
852 h->metaf = NULL;
853 if (tag == h->tblt)
854 h->tblt = NULL;
855
856 tflags = htmltags[tag->tag].flags;
857 if (tflags & HTML_INDENT)
858 h->indent--;
859 if (tflags & HTML_NOINDENT)
860 h->noindent--;
861 if (tflags & HTML_NLEND)
862 print_endline(h);
863 print_indent(h);
864 print_byte(h, '<');
865 print_byte(h, '/');
866 print_word(h, htmltags[tag->tag].name);
867 print_byte(h, '>');
868 if (tflags & HTML_NLAFTER)
869 print_endline(h);
870 }
871 if (tag->refcnt == 0) {
872 h->tag = tag->next;
873 free(tag);
874 }
875 }
876
877 void
print_gen_decls(struct html * h)878 print_gen_decls(struct html *h)
879 {
880 print_word(h, "<!DOCTYPE html>");
881 print_endline(h);
882 }
883
884 void
print_gen_comment(struct html * h,struct roff_node * n)885 print_gen_comment(struct html *h, struct roff_node *n)
886 {
887 int wantblank;
888
889 print_word(h, "<!-- This is an automatically generated file."
890 " Do not edit.");
891 h->indent = 1;
892 wantblank = 0;
893 while (n != NULL && n->type == ROFFT_COMMENT) {
894 if (strstr(n->string, "-->") == NULL &&
895 (wantblank || *n->string != '\0')) {
896 print_endline(h);
897 print_indent(h);
898 print_word(h, n->string);
899 wantblank = *n->string != '\0';
900 }
901 n = n->next;
902 }
903 if (wantblank)
904 print_endline(h);
905 print_word(h, " -->");
906 print_endline(h);
907 h->indent = 0;
908 }
909
910 void
print_text(struct html * h,const char * word)911 print_text(struct html *h, const char *word)
912 {
913 print_tagged_text(h, word, NULL);
914 }
915
916 void
print_tagged_text(struct html * h,const char * word,struct roff_node * n)917 print_tagged_text(struct html *h, const char *word, struct roff_node *n)
918 {
919 struct tag *t;
920 char *href;
921
922 /*
923 * Always wrap text in a paragraph unless already contained in
924 * some flow container; never put it directly into a section.
925 */
926
927 if (h->tag->tag == TAG_SECTION)
928 print_otag(h, TAG_P, "c", "Pp");
929
930 /* Output whitespace before this text? */
931
932 if (h->col && (h->flags & HTML_NOSPACE) == 0) {
933 if ( ! (HTML_KEEP & h->flags)) {
934 if (HTML_PREKEEP & h->flags)
935 h->flags |= HTML_KEEP;
936 print_endword(h);
937 } else
938 print_word(h, " ");
939 }
940
941 /*
942 * Optionally switch fonts, optionally write a permalink, then
943 * print the text, optionally surrounded by HTML whitespace.
944 */
945
946 assert(h->metaf == NULL);
947 print_metaf(h);
948 print_indent(h);
949
950 if (n != NULL && (href = html_make_id(n, 2)) != NULL) {
951 t = print_otag(h, TAG_A, "chR", "permalink", href);
952 free(href);
953 } else
954 t = NULL;
955
956 if ( ! print_encode(h, word, NULL, 0)) {
957 if ( ! (h->flags & HTML_NONOSPACE))
958 h->flags &= ~HTML_NOSPACE;
959 h->flags &= ~HTML_NONEWLINE;
960 } else
961 h->flags |= HTML_NOSPACE | HTML_NONEWLINE;
962
963 if (h->metaf != NULL) {
964 print_tagq(h, h->metaf);
965 h->metaf = NULL;
966 } else if (t != NULL)
967 print_tagq(h, t);
968
969 h->flags &= ~HTML_IGNDELIM;
970 }
971
972 void
print_tagq(struct html * h,const struct tag * until)973 print_tagq(struct html *h, const struct tag *until)
974 {
975 struct tag *this, *next;
976
977 for (this = h->tag; this != NULL; this = next) {
978 next = this == until ? NULL : this->next;
979 print_ctag(h, this);
980 }
981 }
982
983 /*
984 * Close out all open elements up to but excluding suntil.
985 * Note that a paragraph just inside stays open together with it
986 * because paragraphs include subsequent phrasing content.
987 */
988 void
print_stagq(struct html * h,const struct tag * suntil)989 print_stagq(struct html *h, const struct tag *suntil)
990 {
991 struct tag *this, *next;
992
993 for (this = h->tag; this != NULL; this = next) {
994 next = this->next;
995 if (this == suntil || (next == suntil &&
996 (this->tag == TAG_P || this->tag == TAG_PRE)))
997 break;
998 print_ctag(h, this);
999 }
1000 }
1001
1002
1003 /***********************************************************************
1004 * Low level output functions.
1005 * They implement line breaking using a short static buffer.
1006 ***********************************************************************/
1007
1008 /*
1009 * Buffer one HTML output byte.
1010 * If the buffer is full, flush and deactivate it and start a new line.
1011 * If the buffer is inactive, print directly.
1012 */
1013 static void
print_byte(struct html * h,char c)1014 print_byte(struct html *h, char c)
1015 {
1016 if ((h->flags & HTML_BUFFER) == 0) {
1017 putchar(c);
1018 h->col++;
1019 return;
1020 }
1021
1022 if (h->col + h->bufcol < sizeof(h->buf)) {
1023 h->buf[h->bufcol++] = c;
1024 return;
1025 }
1026
1027 putchar('\n');
1028 h->col = 0;
1029 print_indent(h);
1030 putchar(' ');
1031 putchar(' ');
1032 fwrite(h->buf, h->bufcol, 1, stdout);
1033 putchar(c);
1034 h->col = (h->indent + 1) * 2 + h->bufcol + 1;
1035 h->bufcol = 0;
1036 h->flags &= ~HTML_BUFFER;
1037 }
1038
1039 /*
1040 * If something was printed on the current output line, end it.
1041 * Not to be called right after print_indent().
1042 */
1043 void
print_endline(struct html * h)1044 print_endline(struct html *h)
1045 {
1046 if (h->col == 0)
1047 return;
1048
1049 if (h->bufcol) {
1050 putchar(' ');
1051 fwrite(h->buf, h->bufcol, 1, stdout);
1052 h->bufcol = 0;
1053 }
1054 putchar('\n');
1055 h->col = 0;
1056 h->flags |= HTML_NOSPACE;
1057 h->flags &= ~HTML_BUFFER;
1058 }
1059
1060 /*
1061 * Flush the HTML output buffer.
1062 * If it is inactive, activate it.
1063 */
1064 static void
print_endword(struct html * h)1065 print_endword(struct html *h)
1066 {
1067 if (h->noindent) {
1068 print_byte(h, ' ');
1069 return;
1070 }
1071
1072 if ((h->flags & HTML_BUFFER) == 0) {
1073 h->col++;
1074 h->flags |= HTML_BUFFER;
1075 } else if (h->bufcol) {
1076 putchar(' ');
1077 fwrite(h->buf, h->bufcol, 1, stdout);
1078 h->col += h->bufcol + 1;
1079 }
1080 h->bufcol = 0;
1081 }
1082
1083 /*
1084 * If at the beginning of a new output line,
1085 * perform indentation and mark the line as containing output.
1086 * Make sure to really produce some output right afterwards,
1087 * but do not use print_otag() for producing it.
1088 */
1089 static void
print_indent(struct html * h)1090 print_indent(struct html *h)
1091 {
1092 size_t i;
1093
1094 if (h->col || h->noindent)
1095 return;
1096
1097 h->col = h->indent * 2;
1098 for (i = 0; i < h->col; i++)
1099 putchar(' ');
1100 }
1101
1102 /*
1103 * Print or buffer some characters
1104 * depending on the current HTML output buffer state.
1105 */
1106 static void
print_word(struct html * h,const char * cp)1107 print_word(struct html *h, const char *cp)
1108 {
1109 while (*cp != '\0')
1110 print_byte(h, *cp++);
1111 }
1112