nrelease - fix/improve livecd
[dragonfly.git] / contrib / mdocml / man_html.c
1 /* $Id: man_html.c,v 1.179 2020/10/16 17:22:43 schwarze Exp $ */
2 /*
3  * Copyright (c) 2013-2015, 2017-2020 Ingo Schwarze <schwarze@openbsd.org>
4  * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
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.
17  *
18  * HTML formatter for man(7) used by mandoc(1).
19  */
20 #include "config.h"
21
22 #include <sys/types.h>
23
24 #include <assert.h>
25 #include <ctype.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "mandoc_aux.h"
31 #include "mandoc.h"
32 #include "roff.h"
33 #include "man.h"
34 #include "out.h"
35 #include "html.h"
36 #include "main.h"
37
38 #define MAN_ARGS          const struct roff_meta *man, \
39                           struct roff_node *n, \
40                           struct html *h
41
42 struct  man_html_act {
43         int             (*pre)(MAN_ARGS);
44         int             (*post)(MAN_ARGS);
45 };
46
47 static  void              print_man_head(const struct roff_meta *,
48                                 struct html *);
49 static  void              print_man_nodelist(MAN_ARGS);
50 static  void              print_man_node(MAN_ARGS);
51 static  char              list_continues(const struct roff_node *,
52                                 const struct roff_node *);
53 static  int               man_B_pre(MAN_ARGS);
54 static  int               man_IP_pre(MAN_ARGS);
55 static  int               man_I_pre(MAN_ARGS);
56 static  int               man_OP_pre(MAN_ARGS);
57 static  int               man_PP_pre(MAN_ARGS);
58 static  int               man_RS_pre(MAN_ARGS);
59 static  int               man_SH_pre(MAN_ARGS);
60 static  int               man_SM_pre(MAN_ARGS);
61 static  int               man_SY_pre(MAN_ARGS);
62 static  int               man_UR_pre(MAN_ARGS);
63 static  int               man_abort_pre(MAN_ARGS);
64 static  int               man_alt_pre(MAN_ARGS);
65 static  int               man_ign_pre(MAN_ARGS);
66 static  int               man_in_pre(MAN_ARGS);
67 static  void              man_root_post(const struct roff_meta *,
68                                 struct html *);
69 static  void              man_root_pre(const struct roff_meta *,
70                                 struct html *);
71
72 static  const struct man_html_act man_html_acts[MAN_MAX - MAN_TH] = {
73         { NULL, NULL }, /* TH */
74         { man_SH_pre, NULL }, /* SH */
75         { man_SH_pre, NULL }, /* SS */
76         { man_IP_pre, NULL }, /* TP */
77         { man_IP_pre, NULL }, /* TQ */
78         { man_abort_pre, NULL }, /* LP */
79         { man_PP_pre, NULL }, /* PP */
80         { man_abort_pre, NULL }, /* P */
81         { man_IP_pre, NULL }, /* IP */
82         { man_PP_pre, NULL }, /* HP */
83         { man_SM_pre, NULL }, /* SM */
84         { man_SM_pre, NULL }, /* SB */
85         { man_alt_pre, NULL }, /* BI */
86         { man_alt_pre, NULL }, /* IB */
87         { man_alt_pre, NULL }, /* BR */
88         { man_alt_pre, NULL }, /* RB */
89         { NULL, NULL }, /* R */
90         { man_B_pre, NULL }, /* B */
91         { man_I_pre, NULL }, /* I */
92         { man_alt_pre, NULL }, /* IR */
93         { man_alt_pre, NULL }, /* RI */
94         { NULL, NULL }, /* RE */
95         { man_RS_pre, NULL }, /* RS */
96         { man_ign_pre, NULL }, /* DT */
97         { man_ign_pre, NULL }, /* UC */
98         { man_ign_pre, NULL }, /* PD */
99         { man_ign_pre, NULL }, /* AT */
100         { man_in_pre, NULL }, /* in */
101         { man_SY_pre, NULL }, /* SY */
102         { NULL, NULL }, /* YS */
103         { man_OP_pre, NULL }, /* OP */
104         { NULL, NULL }, /* EX */
105         { NULL, NULL }, /* EE */
106         { man_UR_pre, NULL }, /* UR */
107         { NULL, NULL }, /* UE */
108         { man_UR_pre, NULL }, /* MT */
109         { NULL, NULL }, /* ME */
110 };
111
112
113 void
114 html_man(void *arg, const struct roff_meta *man)
115 {
116         struct html             *h;
117         struct roff_node        *n;
118         struct tag              *t;
119
120         h = (struct html *)arg;
121         n = man->first->child;
122
123         if ((h->oflags & HTML_FRAGMENT) == 0) {
124                 print_gen_decls(h);
125                 print_otag(h, TAG_HTML, "");
126                 if (n != NULL && n->type == ROFFT_COMMENT)
127                         print_gen_comment(h, n);
128                 t = print_otag(h, TAG_HEAD, "");
129                 print_man_head(man, h);
130                 print_tagq(h, t);
131                 print_otag(h, TAG_BODY, "");
132         }
133
134         man_root_pre(man, h);
135         t = print_otag(h, TAG_DIV, "c", "manual-text");
136         print_man_nodelist(man, n, h);
137         print_tagq(h, t);
138         man_root_post(man, h);
139         print_tagq(h, NULL);
140 }
141
142 static void
143 print_man_head(const struct roff_meta *man, struct html *h)
144 {
145         char    *cp;
146
147         print_gen_head(h);
148         mandoc_asprintf(&cp, "%s(%s)", man->title, man->msec);
149         print_otag(h, TAG_TITLE, "");
150         print_text(h, cp);
151         free(cp);
152 }
153
154 static void
155 print_man_nodelist(MAN_ARGS)
156 {
157         while (n != NULL) {
158                 print_man_node(man, n, h);
159                 n = n->next;
160         }
161 }
162
163 static void
164 print_man_node(MAN_ARGS)
165 {
166         struct tag      *t;
167         int              child;
168
169         if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
170                 return;
171
172         if ((n->flags & NODE_NOFILL) == 0)
173                 html_fillmode(h, ROFF_fi);
174         else if (html_fillmode(h, ROFF_nf) == ROFF_nf &&
175             n->tok != ROFF_fi && n->flags & NODE_LINE &&
176             (n->prev == NULL || n->prev->tok != MAN_YS))
177                 print_endline(h);
178
179         child = 1;
180         switch (n->type) {
181         case ROFFT_TEXT:
182                 if (*n->string == '\0') {
183                         print_endline(h);
184                         return;
185                 }
186                 if (*n->string == ' ' && n->flags & NODE_LINE &&
187                     (h->flags & HTML_NONEWLINE) == 0)
188                         print_otag(h, TAG_BR, "");
189                 else if (n->flags & NODE_DELIMC)
190                         h->flags |= HTML_NOSPACE;
191                 t = h->tag;
192                 t->refcnt++;
193                 print_text(h, n->string);
194                 break;
195         case ROFFT_EQN:
196                 t = h->tag;
197                 t->refcnt++;
198                 print_eqn(h, n->eqn);
199                 break;
200         case ROFFT_TBL:
201                 /*
202                  * This will take care of initialising all of the table
203                  * state data for the first table, then tearing it down
204                  * for the last one.
205                  */
206                 print_tbl(h, n->span);
207                 return;
208         default:
209                 /*
210                  * Close out scope of font prior to opening a macro
211                  * scope.
212                  */
213                 if (h->metac != ESCAPE_FONTROMAN) {
214                         h->metal = h->metac;
215                         h->metac = ESCAPE_FONTROMAN;
216                 }
217
218                 /*
219                  * Close out the current table, if it's open, and unset
220                  * the "meta" table state.  This will be reopened on the
221                  * next table element.
222                  */
223                 if (h->tblt != NULL)
224                         print_tblclose(h);
225                 t = h->tag;
226                 t->refcnt++;
227                 if (n->tok < ROFF_MAX) {
228                         roff_html_pre(h, n);
229                         t->refcnt--;
230                         print_stagq(h, t);
231                         return;
232                 }
233                 assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
234                 if (man_html_acts[n->tok - MAN_TH].pre != NULL)
235                         child = (*man_html_acts[n->tok - MAN_TH].pre)(man,
236                             n, h);
237                 break;
238         }
239
240         if (child && n->child != NULL)
241                 print_man_nodelist(man, n->child, h);
242
243         /* This will automatically close out any font scope. */
244         t->refcnt--;
245         if (n->type == ROFFT_BLOCK &&
246             (n->tok == MAN_IP || n->tok == MAN_TP || n->tok == MAN_TQ)) {
247                 t = h->tag;
248                 while (t->tag != TAG_DL && t->tag != TAG_UL)
249                         t = t->next;
250                 /*
251                  * Close the list if no further item of the same type
252                  * follows; otherwise, close the item only.
253                  */
254                 if (list_continues(n, roff_node_next(n)) == '\0') {
255                         print_tagq(h, t);
256                         t = NULL;
257                 }
258         }
259         if (t != NULL)
260                 print_stagq(h, t);
261 }
262
263 static void
264 man_root_pre(const struct roff_meta *man, struct html *h)
265 {
266         struct tag      *t, *tt;
267         char            *title;
268
269         assert(man->title);
270         assert(man->msec);
271         mandoc_asprintf(&title, "%s(%s)", man->title, man->msec);
272
273         t = print_otag(h, TAG_TABLE, "c", "head");
274         tt = print_otag(h, TAG_TR, "");
275
276         print_otag(h, TAG_TD, "c", "head-ltitle");
277         print_text(h, title);
278         print_stagq(h, tt);
279
280         print_otag(h, TAG_TD, "c", "head-vol");
281         if (man->vol != NULL)
282                 print_text(h, man->vol);
283         print_stagq(h, tt);
284
285         print_otag(h, TAG_TD, "c", "head-rtitle");
286         print_text(h, title);
287         print_tagq(h, t);
288         free(title);
289 }
290
291 static void
292 man_root_post(const struct roff_meta *man, struct html *h)
293 {
294         struct tag      *t, *tt;
295
296         t = print_otag(h, TAG_TABLE, "c", "foot");
297         tt = print_otag(h, TAG_TR, "");
298
299         print_otag(h, TAG_TD, "c", "foot-date");
300         print_text(h, man->date);
301         print_stagq(h, tt);
302
303         print_otag(h, TAG_TD, "c", "foot-os");
304         if (man->os != NULL)
305                 print_text(h, man->os);
306         print_tagq(h, t);
307 }
308
309 static int
310 man_SH_pre(MAN_ARGS)
311 {
312         const char      *class;
313         enum htmltag     tag;
314
315         if (n->tok == MAN_SH) {
316                 tag = TAG_H1;
317                 class = "Sh";
318         } else {
319                 tag = TAG_H2;
320                 class = "Ss";
321         }
322         switch (n->type) {
323         case ROFFT_BLOCK:
324                 html_close_paragraph(h);
325                 print_otag(h, TAG_SECTION, "c", class);
326                 break;
327         case ROFFT_HEAD:
328                 print_otag_id(h, tag, class, n);
329                 break;
330         case ROFFT_BODY:
331                 break;
332         default:
333                 abort();
334         }
335         return 1;
336 }
337
338 static int
339 man_alt_pre(MAN_ARGS)
340 {
341         const struct roff_node  *nn;
342         struct tag      *t;
343         int              i;
344         enum htmltag     fp;
345
346         for (i = 0, nn = n->child; nn != NULL; nn = nn->next, i++) {
347                 switch (n->tok) {
348                 case MAN_BI:
349                         fp = i % 2 ? TAG_I : TAG_B;
350                         break;
351                 case MAN_IB:
352                         fp = i % 2 ? TAG_B : TAG_I;
353                         break;
354                 case MAN_RI:
355                         fp = i % 2 ? TAG_I : TAG_MAX;
356                         break;
357                 case MAN_IR:
358                         fp = i % 2 ? TAG_MAX : TAG_I;
359                         break;
360                 case MAN_BR:
361                         fp = i % 2 ? TAG_MAX : TAG_B;
362                         break;
363                 case MAN_RB:
364                         fp = i % 2 ? TAG_B : TAG_MAX;
365                         break;
366                 default:
367                         abort();
368                 }
369
370                 if (i)
371                         h->flags |= HTML_NOSPACE;
372
373                 if (fp != TAG_MAX)
374                         t = print_otag(h, fp, "");
375
376                 print_text(h, nn->string);
377
378                 if (fp != TAG_MAX)
379                         print_tagq(h, t);
380         }
381         return 0;
382 }
383
384 static int
385 man_SM_pre(MAN_ARGS)
386 {
387         print_otag(h, TAG_SMALL, "");
388         if (n->tok == MAN_SB)
389                 print_otag(h, TAG_B, "");
390         return 1;
391 }
392
393 static int
394 man_PP_pre(MAN_ARGS)
395 {
396         switch (n->type) {
397         case ROFFT_BLOCK:
398                 html_close_paragraph(h);
399                 break;
400         case ROFFT_HEAD:
401                 return 0;
402         case ROFFT_BODY:
403                 if (n->child != NULL &&
404                     (n->child->flags & NODE_NOFILL) == 0)
405                         print_otag(h, TAG_P, "c",
406                             n->tok == MAN_PP ? "Pp" : "Pp HP");
407                 break;
408         default:
409                 abort();
410         }
411         return 1;
412 }
413
414 static char
415 list_continues(const struct roff_node *n1, const struct roff_node *n2)
416 {
417         const char *s1, *s2;
418         char c1, c2;
419
420         if (n1 == NULL || n1->type != ROFFT_BLOCK ||
421             n2 == NULL || n2->type != ROFFT_BLOCK)
422                 return '\0';
423         if ((n1->tok == MAN_TP || n1->tok == MAN_TQ) &&
424             (n2->tok == MAN_TP || n2->tok == MAN_TQ))
425                 return ' ';
426         if (n1->tok != MAN_IP || n2->tok != MAN_IP)
427                 return '\0';
428         n1 = n1->head->child;
429         n2 = n2->head->child;
430         s1 = n1 == NULL ? "" : n1->string;
431         s2 = n2 == NULL ? "" : n2->string;
432         c1 = strcmp(s1, "*") == 0 ? '*' :
433              strcmp(s1, "\\-") == 0 ? '-' :
434              strcmp(s1, "\\(bu") == 0 ? 'b' : ' ';
435         c2 = strcmp(s2, "*") == 0 ? '*' :
436              strcmp(s2, "\\-") == 0 ? '-' :
437              strcmp(s2, "\\(bu") == 0 ? 'b' : ' ';
438         return c1 != c2 ? '\0' : c1 == 'b' ? '*' : c1;
439 }
440
441 static int
442 man_IP_pre(MAN_ARGS)
443 {
444         struct roff_node        *nn;
445         const char              *list_class;
446         enum htmltag             list_elem, body_elem;
447         char                     list_type;
448
449         nn = n->type == ROFFT_BLOCK ? n : n->parent;
450         list_type = list_continues(roff_node_prev(nn), nn);
451         if (list_type == '\0') {
452                 /* Start a new list. */
453                 list_type = list_continues(nn, roff_node_next(nn));
454                 if (list_type == '\0')
455                         list_type = ' ';
456                 switch (list_type) {
457                 case ' ':
458                         list_class = "Bl-tag";
459                         list_elem = TAG_DL;
460                         break;
461                 case '*':
462                         list_class = "Bl-bullet";
463                         list_elem = TAG_UL;
464                         break;
465                 case '-':
466                         list_class = "Bl-dash";
467                         list_elem = TAG_UL;
468                         break;
469                 default:
470                         abort();
471                 }
472         } else {
473                 /* Continue a list that was started earlier. */
474                 list_class = NULL;
475                 list_elem = TAG_MAX;
476         }
477         body_elem = list_type == ' ' ? TAG_DD : TAG_LI;
478
479         switch (n->type) {
480         case ROFFT_BLOCK:
481                 html_close_paragraph(h);
482                 if (list_elem != TAG_MAX)
483                         print_otag(h, list_elem, "c", list_class);
484                 return 1;
485         case ROFFT_HEAD:
486                 if (body_elem == TAG_LI)
487                         return 0;
488                 print_otag_id(h, TAG_DT, NULL, n);
489                 break;
490         case ROFFT_BODY:
491                 print_otag(h, body_elem, "");
492                 return 1;
493         default:
494                 abort();
495         }
496         switch(n->tok) {
497         case MAN_IP:  /* Only print the first header element. */
498                 if (n->child != NULL)
499                         print_man_node(man, n->child, h);
500                 break;
501         case MAN_TP:  /* Only print next-line header elements. */
502         case MAN_TQ:
503                 nn = n->child;
504                 while (nn != NULL && (NODE_LINE & nn->flags) == 0)
505                         nn = nn->next;
506                 while (nn != NULL) {
507                         print_man_node(man, nn, h);
508                         nn = nn->next;
509                 }
510                 break;
511         default:
512                 abort();
513         }
514         return 0;
515 }
516
517 static int
518 man_OP_pre(MAN_ARGS)
519 {
520         struct tag      *tt;
521
522         print_text(h, "[");
523         h->flags |= HTML_NOSPACE;
524         tt = print_otag(h, TAG_SPAN, "c", "Op");
525
526         if ((n = n->child) != NULL) {
527                 print_otag(h, TAG_B, "");
528                 print_text(h, n->string);
529         }
530
531         print_stagq(h, tt);
532
533         if (n != NULL && n->next != NULL) {
534                 print_otag(h, TAG_I, "");
535                 print_text(h, n->next->string);
536         }
537
538         print_stagq(h, tt);
539         h->flags |= HTML_NOSPACE;
540         print_text(h, "]");
541         return 0;
542 }
543
544 static int
545 man_B_pre(MAN_ARGS)
546 {
547         print_otag(h, TAG_B, "");
548         return 1;
549 }
550
551 static int
552 man_I_pre(MAN_ARGS)
553 {
554         print_otag(h, TAG_I, "");
555         return 1;
556 }
557
558 static int
559 man_in_pre(MAN_ARGS)
560 {
561         print_otag(h, TAG_BR, "");
562         return 0;
563 }
564
565 static int
566 man_ign_pre(MAN_ARGS)
567 {
568         return 0;
569 }
570
571 static int
572 man_RS_pre(MAN_ARGS)
573 {
574         switch (n->type) {
575         case ROFFT_BLOCK:
576                 html_close_paragraph(h);
577                 break;
578         case ROFFT_HEAD:
579                 return 0;
580         case ROFFT_BODY:
581                 print_otag(h, TAG_DIV, "c", "Bd-indent");
582                 break;
583         default:
584                 abort();
585         }
586         return 1;
587 }
588
589 static int
590 man_SY_pre(MAN_ARGS)
591 {
592         switch (n->type) {
593         case ROFFT_BLOCK:
594                 html_close_paragraph(h);
595                 print_otag(h, TAG_TABLE, "c", "Nm");
596                 print_otag(h, TAG_TR, "");
597                 break;
598         case ROFFT_HEAD:
599                 print_otag(h, TAG_TD, "");
600                 print_otag(h, TAG_CODE, "c", "Nm");
601                 break;
602         case ROFFT_BODY:
603                 print_otag(h, TAG_TD, "");
604                 break;
605         default:
606                 abort();
607         }
608         return 1;
609 }
610
611 static int
612 man_UR_pre(MAN_ARGS)
613 {
614         char *cp;
615
616         n = n->child;
617         assert(n->type == ROFFT_HEAD);
618         if (n->child != NULL) {
619                 assert(n->child->type == ROFFT_TEXT);
620                 if (n->tok == MAN_MT) {
621                         mandoc_asprintf(&cp, "mailto:%s", n->child->string);
622                         print_otag(h, TAG_A, "ch", "Mt", cp);
623                         free(cp);
624                 } else
625                         print_otag(h, TAG_A, "ch", "Lk", n->child->string);
626         }
627
628         assert(n->next->type == ROFFT_BODY);
629         if (n->next->child != NULL)
630                 n = n->next;
631
632         print_man_nodelist(man, n->child, h);
633         return 0;
634 }
635
636 static int
637 man_abort_pre(MAN_ARGS)
638 {
639         abort();
640 }