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