Annotation of mandoc/man_validate.c, Revision 1.152
1.152 ! schwarze 1: /* $Id: man_validate.c,v 1.151 2020/03/13 15:32:28 schwarze Exp $ */
1.1 kristaps 2: /*
1.151 schwarze 3: * Copyright (c) 2010, 2012-2020 Ingo Schwarze <schwarze@openbsd.org>
1.63 schwarze 4: * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
1.1 kristaps 5: *
6: * Permission to use, copy, modify, and distribute this software for any
1.8 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.114 schwarze 10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1.8 kristaps 11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1.114 schwarze 12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1.8 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.151 schwarze 17: *
18: * Validation module for man(7) syntax trees used by mandoc(1).
1.1 kristaps 19: */
1.28 kristaps 20: #include "config.h"
21:
1.1 kristaps 22: #include <sys/types.h>
23:
24: #include <assert.h>
25: #include <ctype.h>
1.16 kristaps 26: #include <errno.h>
27: #include <limits.h>
1.1 kristaps 28: #include <stdarg.h>
1.140 schwarze 29: #include <stdio.h>
1.1 kristaps 30: #include <stdlib.h>
1.46 kristaps 31: #include <string.h>
1.50 kristaps 32: #include <time.h>
1.1 kristaps 33:
1.114 schwarze 34: #include "mandoc_aux.h"
35: #include "mandoc.h"
36: #include "roff.h"
1.151 schwarze 37: #include "tag.h"
1.66 kristaps 38: #include "man.h"
1.114 schwarze 39: #include "libmandoc.h"
1.118 schwarze 40: #include "roff_int.h"
1.1 kristaps 41: #include "libman.h"
42:
1.117 schwarze 43: #define CHKARGS struct roff_man *man, struct roff_node *n
1.1 kristaps 44:
1.107 schwarze 45: typedef void (*v_check)(CHKARGS);
1.1 kristaps 46:
1.148 schwarze 47: static void check_abort(CHKARGS) __attribute__((__noreturn__));
1.107 schwarze 48: static void check_par(CHKARGS);
49: static void check_part(CHKARGS);
50: static void check_root(CHKARGS);
1.151 schwarze 51: static void check_tag(struct roff_node *, struct roff_node *);
1.107 schwarze 52: static void check_text(CHKARGS);
53:
54: static void post_AT(CHKARGS);
1.145 schwarze 55: static void post_EE(CHKARGS);
56: static void post_EX(CHKARGS);
1.107 schwarze 57: static void post_IP(CHKARGS);
1.113 schwarze 58: static void post_OP(CHKARGS);
1.139 schwarze 59: static void post_SH(CHKARGS);
1.107 schwarze 60: static void post_TH(CHKARGS);
1.151 schwarze 61: static void post_TP(CHKARGS);
1.107 schwarze 62: static void post_UC(CHKARGS);
63: static void post_UR(CHKARGS);
1.129 schwarze 64: static void post_in(CHKARGS);
1.51 kristaps 65:
1.135 schwarze 66: static const v_check man_valids[MAN_MAX - MAN_TH] = {
1.104 schwarze 67: post_TH, /* TH */
1.139 schwarze 68: post_SH, /* SH */
69: post_SH, /* SS */
1.151 schwarze 70: post_TP, /* TP */
71: post_TP, /* TQ */
1.138 schwarze 72: check_abort,/* LP */
1.104 schwarze 73: check_par, /* PP */
1.138 schwarze 74: check_abort,/* P */
1.104 schwarze 75: post_IP, /* IP */
76: NULL, /* HP */
77: NULL, /* SM */
78: NULL, /* SB */
79: NULL, /* BI */
80: NULL, /* IB */
81: NULL, /* BR */
82: NULL, /* RB */
83: NULL, /* R */
84: NULL, /* B */
85: NULL, /* I */
86: NULL, /* IR */
87: NULL, /* RI */
88: NULL, /* RE */
89: check_part, /* RS */
90: NULL, /* DT */
91: post_UC, /* UC */
1.112 schwarze 92: NULL, /* PD */
1.104 schwarze 93: post_AT, /* AT */
1.129 schwarze 94: post_in, /* in */
1.137 schwarze 95: NULL, /* SY */
96: NULL, /* YS */
1.113 schwarze 97: post_OP, /* OP */
1.145 schwarze 98: post_EX, /* EX */
99: post_EE, /* EE */
1.104 schwarze 100: post_UR, /* UR */
101: NULL, /* UE */
1.132 schwarze 102: post_UR, /* MT */
103: NULL, /* ME */
1.1 kristaps 104: };
105:
106:
1.138 schwarze 107: /* Validate the subtree rooted at man->last. */
1.107 schwarze 108: void
1.143 schwarze 109: man_validate(struct roff_man *man)
1.1 kristaps 110: {
1.115 schwarze 111: struct roff_node *n;
1.123 schwarze 112: const v_check *cp;
1.1 kristaps 113:
1.138 schwarze 114: /*
115: * Translate obsolete macros such that later code
116: * does not need to look for them.
117: */
118:
1.104 schwarze 119: n = man->last;
1.138 schwarze 120: switch (n->tok) {
121: case MAN_LP:
122: case MAN_P:
123: n->tok = MAN_PP;
124: break;
125: default:
126: break;
127: }
128:
129: /*
130: * Iterate over all children, recursing into each one
131: * in turn, depth-first.
132: */
133:
1.121 schwarze 134: man->last = man->last->child;
135: while (man->last != NULL) {
1.143 schwarze 136: man_validate(man);
1.121 schwarze 137: if (man->last == n)
138: man->last = man->last->child;
139: else
140: man->last = man->last->next;
141: }
1.1 kristaps 142:
1.138 schwarze 143: /* Finally validate the macro itself. */
144:
1.121 schwarze 145: man->last = n;
146: man->next = ROFF_NEXT_SIBLING;
1.104 schwarze 147: switch (n->type) {
1.114 schwarze 148: case ROFFT_TEXT:
1.107 schwarze 149: check_text(man, n);
150: break;
1.114 schwarze 151: case ROFFT_ROOT:
1.107 schwarze 152: check_root(man, n);
153: break;
1.134 schwarze 154: case ROFFT_COMMENT:
1.114 schwarze 155: case ROFFT_EQN:
156: case ROFFT_TBL:
1.107 schwarze 157: break;
1.1 kristaps 158: default:
1.124 schwarze 159: if (n->tok < ROFF_MAX) {
1.139 schwarze 160: roff_validate(man);
1.124 schwarze 161: break;
162: }
163: assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
1.135 schwarze 164: cp = man_valids + (n->tok - MAN_TH);
1.107 schwarze 165: if (*cp)
166: (*cp)(man, n);
1.121 schwarze 167: if (man->last == n)
1.146 schwarze 168: n->flags |= NODE_VALID;
1.107 schwarze 169: break;
1.1 kristaps 170: }
171: }
172:
1.107 schwarze 173: static void
1.91 schwarze 174: check_root(CHKARGS)
1.14 kristaps 175: {
1.101 schwarze 176: assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
1.17 kristaps 177:
1.134 schwarze 178: if (n->last == NULL || n->last->type == ROFFT_COMMENT)
1.141 schwarze 179: mandoc_msg(MANDOCERR_DOC_EMPTY, n->line, n->pos, NULL);
1.93 schwarze 180: else
181: man->meta.hasbody = 1;
182:
183: if (NULL == man->meta.title) {
1.141 schwarze 184: mandoc_msg(MANDOCERR_TH_NOTITLE, n->line, n->pos, NULL);
1.54 kristaps 185:
1.34 kristaps 186: /*
187: * If a title hasn't been set, do so now (by
188: * implication, date and section also aren't set).
189: */
1.54 kristaps 190:
1.105 schwarze 191: man->meta.title = mandoc_strdup("");
192: man->meta.msec = mandoc_strdup("");
1.150 schwarze 193: man->meta.date = mandoc_normdate(NULL, NULL);
1.34 kristaps 194: }
1.130 schwarze 195:
196: if (man->meta.os_e &&
197: (man->meta.rcsids & (1 << man->meta.os_e)) == 0)
1.141 schwarze 198: mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
1.131 schwarze 199: man->meta.os_e == MANDOC_OS_OPENBSD ?
200: "(OpenBSD)" : "(NetBSD)");
1.75 kristaps 201: }
202:
1.148 schwarze 203: static void
1.138 schwarze 204: check_abort(CHKARGS)
205: {
206: abort();
207: }
208:
1.151 schwarze 209: /*
210: * Skip leading whitespace, dashes, backslashes, and font escapes,
211: * then create a tag if the first following byte is a letter.
212: * Priority is high unless whitespace is present.
213: */
214: static void
215: check_tag(struct roff_node *n, struct roff_node *nt)
216: {
217: const char *cp, *arg;
218: int prio, sz;
219:
220: if (nt == NULL || nt->type != ROFFT_TEXT)
221: return;
222:
223: cp = nt->string;
224: prio = TAG_STRONG;
225: for (;;) {
226: switch (*cp) {
227: case ' ':
228: case '\t':
229: prio = TAG_WEAK;
230: /* FALLTHROUGH */
231: case '-':
232: cp++;
233: break;
234: case '\\':
235: cp++;
236: switch (mandoc_escape(&cp, &arg, &sz)) {
237: case ESCAPE_FONT:
238: case ESCAPE_FONTBOLD:
239: case ESCAPE_FONTITALIC:
240: case ESCAPE_FONTBI:
241: case ESCAPE_FONTROMAN:
242: case ESCAPE_FONTCW:
243: case ESCAPE_FONTPREV:
244: case ESCAPE_IGNORE:
245: break;
246: case ESCAPE_SPECIAL:
247: if (sz != 1)
248: return;
249: switch (*arg) {
250: case '-':
251: case 'e':
252: break;
253: default:
254: return;
255: }
256: break;
257: default:
258: return;
259: }
260: break;
261: default:
262: if (isalpha((unsigned char)*cp))
263: tag_put(cp, prio, n);
264: return;
265: }
266: }
267: }
268:
1.138 schwarze 269: static void
1.75 kristaps 270: check_text(CHKARGS)
271: {
272: char *cp, *p;
273:
1.145 schwarze 274: if (n->flags & NODE_NOFILL)
1.107 schwarze 275: return;
1.76 schwarze 276:
277: cp = n->string;
278: for (p = cp; NULL != (p = strchr(p, '\t')); p++)
1.141 schwarze 279: mandoc_msg(MANDOCERR_FI_TAB,
280: n->line, n->pos + (int)(p - cp), NULL);
1.145 schwarze 281: }
282:
283: static void
284: post_EE(CHKARGS)
285: {
286: if ((n->flags & NODE_NOFILL) == 0)
287: mandoc_msg(MANDOCERR_FI_SKIP, n->line, n->pos, "EE");
288: }
289:
290: static void
291: post_EX(CHKARGS)
292: {
293: if (n->flags & NODE_NOFILL)
294: mandoc_msg(MANDOCERR_NF_SKIP, n->line, n->pos, "EX");
1.1 kristaps 295: }
296:
1.113 schwarze 297: static void
298: post_OP(CHKARGS)
299: {
300:
1.122 schwarze 301: if (n->child == NULL)
1.141 schwarze 302: mandoc_msg(MANDOCERR_OP_EMPTY, n->line, n->pos, "OP");
1.122 schwarze 303: else if (n->child->next != NULL && n->child->next->next != NULL) {
1.113 schwarze 304: n = n->child->next->next;
1.141 schwarze 305: mandoc_msg(MANDOCERR_ARG_EXCESS,
1.113 schwarze 306: n->line, n->pos, "OP ... %s", n->string);
307: }
1.1 kristaps 308: }
309:
1.107 schwarze 310: static void
1.139 schwarze 311: post_SH(CHKARGS)
312: {
313: struct roff_node *nc;
1.152 ! schwarze 314: char *cp, *tag;
1.139 schwarze 315:
1.152 ! schwarze 316: nc = n->child;
! 317: switch (n->type) {
! 318: case ROFFT_HEAD:
! 319: tag = NULL;
! 320: deroff(&tag, n);
! 321: if (tag != NULL) {
! 322: for (cp = tag; *cp != '\0'; cp++)
! 323: if (*cp == ' ')
! 324: *cp = '_';
! 325: if (nc != NULL && nc->type == ROFFT_TEXT &&
! 326: strcmp(nc->string, tag) == 0)
! 327: tag_put(NULL, TAG_WEAK, n);
! 328: else
! 329: tag_put(tag, TAG_FALLBACK, n);
! 330: free(tag);
! 331: }
! 332: return;
! 333: case ROFFT_BODY:
! 334: if (nc != NULL)
! 335: break;
1.139 schwarze 336: return;
1.152 ! schwarze 337: default:
! 338: return;
! 339: }
1.139 schwarze 340:
341: if (nc->tok == MAN_PP && nc->body->child != NULL) {
342: while (nc->body->last != NULL) {
343: man->next = ROFF_NEXT_CHILD;
344: roff_node_relink(man, nc->body->last);
345: man->last = n;
346: }
347: }
348:
349: if (nc->tok == MAN_PP || nc->tok == ROFF_sp || nc->tok == ROFF_br) {
1.141 schwarze 350: mandoc_msg(MANDOCERR_PAR_SKIP, nc->line, nc->pos,
351: "%s after %s", roff_name[nc->tok], roff_name[n->tok]);
1.139 schwarze 352: roff_node_delete(man, nc);
353: }
354:
355: /*
356: * Trailing PP is empty, so it is deleted by check_par().
357: * Trailing sp is significant.
358: */
359:
360: if ((nc = n->last) != NULL && nc->tok == ROFF_br) {
1.141 schwarze 361: mandoc_msg(MANDOCERR_PAR_SKIP,
1.139 schwarze 362: nc->line, nc->pos, "%s at the end of %s",
363: roff_name[nc->tok], roff_name[n->tok]);
364: roff_node_delete(man, nc);
365: }
366: }
367:
368: static void
1.104 schwarze 369: post_UR(CHKARGS)
1.86 schwarze 370: {
1.114 schwarze 371: if (n->type == ROFFT_HEAD && n->child == NULL)
1.141 schwarze 372: mandoc_msg(MANDOCERR_UR_NOHEAD, n->line, n->pos,
373: "%s", roff_name[n->tok]);
1.107 schwarze 374: check_part(man, n);
1.55 kristaps 375: }
1.16 kristaps 376:
1.107 schwarze 377: static void
1.23 kristaps 378: check_part(CHKARGS)
379: {
380:
1.114 schwarze 381: if (n->type == ROFFT_BODY && n->child == NULL)
1.141 schwarze 382: mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
383: "%s", roff_name[n->tok]);
1.23 kristaps 384: }
385:
1.107 schwarze 386: static void
1.17 kristaps 387: check_par(CHKARGS)
388: {
389:
1.59 kristaps 390: switch (n->type) {
1.114 schwarze 391: case ROFFT_BLOCK:
1.122 schwarze 392: if (n->body->child == NULL)
1.118 schwarze 393: roff_node_delete(man, n);
1.59 kristaps 394: break;
1.114 schwarze 395: case ROFFT_BODY:
1.139 schwarze 396: if (n->child != NULL &&
397: (n->child->tok == ROFF_sp || n->child->tok == ROFF_br)) {
1.141 schwarze 398: mandoc_msg(MANDOCERR_PAR_SKIP,
399: n->child->line, n->child->pos,
1.139 schwarze 400: "%s after %s", roff_name[n->child->tok],
401: roff_name[n->tok]);
402: roff_node_delete(man, n->child);
403: }
1.122 schwarze 404: if (n->child == NULL)
1.141 schwarze 405: mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
1.123 schwarze 406: "%s empty", roff_name[n->tok]);
1.59 kristaps 407: break;
1.114 schwarze 408: case ROFFT_HEAD:
1.122 schwarze 409: if (n->child != NULL)
1.141 schwarze 410: mandoc_msg(MANDOCERR_ARG_SKIP,
411: n->line, n->pos, "%s %s%s",
1.123 schwarze 412: roff_name[n->tok], n->child->string,
1.122 schwarze 413: n->child->next != NULL ? " ..." : "");
1.59 kristaps 414: break;
415: default:
416: break;
417: }
1.17 kristaps 418: }
419:
1.107 schwarze 420: static void
1.83 schwarze 421: post_IP(CHKARGS)
422: {
423: switch (n->type) {
1.114 schwarze 424: case ROFFT_BLOCK:
1.122 schwarze 425: if (n->head->child == NULL && n->body->child == NULL)
1.118 schwarze 426: roff_node_delete(man, n);
1.83 schwarze 427: break;
1.151 schwarze 428: case ROFFT_HEAD:
429: check_tag(n, n->child);
430: break;
1.114 schwarze 431: case ROFFT_BODY:
1.122 schwarze 432: if (n->parent->head->child == NULL && n->child == NULL)
1.141 schwarze 433: mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
1.123 schwarze 434: "%s empty", roff_name[n->tok]);
1.83 schwarze 435: break;
436: default:
437: break;
438: }
1.151 schwarze 439: }
440:
441: /*
442: * The first next-line element in the head is the tag.
443: * If that's a font macro, use its first child instead.
444: */
445: static void
446: post_TP(CHKARGS)
447: {
448: struct roff_node *nt;
449:
450: if (n->type != ROFFT_HEAD || (nt = n->child) == NULL)
451: return;
452:
453: while ((nt->flags & NODE_LINE) == 0)
454: if ((nt = nt->next) == NULL)
455: return;
456:
457: switch (nt->tok) {
458: case MAN_B:
459: case MAN_BI:
460: case MAN_BR:
461: case MAN_I:
462: case MAN_IB:
463: case MAN_IR:
464: nt = nt->child;
465: break;
466: default:
467: break;
468: }
469: check_tag(n, nt);
1.83 schwarze 470: }
1.17 kristaps 471:
1.107 schwarze 472: static void
1.51 kristaps 473: post_TH(CHKARGS)
474: {
1.115 schwarze 475: struct roff_node *nb;
1.60 schwarze 476: const char *p;
1.51 kristaps 477:
1.85 schwarze 478: free(man->meta.title);
479: free(man->meta.vol);
1.116 schwarze 480: free(man->meta.os);
1.85 schwarze 481: free(man->meta.msec);
482: free(man->meta.date);
1.51 kristaps 483:
1.85 schwarze 484: man->meta.title = man->meta.vol = man->meta.date =
1.116 schwarze 485: man->meta.msec = man->meta.os = NULL;
1.51 kristaps 486:
1.92 schwarze 487: nb = n;
488:
1.116 schwarze 489: /* ->TITLE<- MSEC DATE OS VOL */
1.51 kristaps 490:
491: n = n->child;
1.149 schwarze 492: if (n != NULL && n->string != NULL) {
493: for (p = n->string; *p != '\0'; p++) {
1.60 schwarze 494: /* Only warn about this once... */
1.91 schwarze 495: if (isalpha((unsigned char)*p) &&
496: ! isupper((unsigned char)*p)) {
1.141 schwarze 497: mandoc_msg(MANDOCERR_TITLE_CASE, n->line,
498: n->pos + (int)(p - n->string),
1.102 schwarze 499: "TH %s", n->string);
1.60 schwarze 500: break;
501: }
502: }
1.85 schwarze 503: man->meta.title = mandoc_strdup(n->string);
1.105 schwarze 504: } else {
1.85 schwarze 505: man->meta.title = mandoc_strdup("");
1.141 schwarze 506: mandoc_msg(MANDOCERR_TH_NOTITLE, nb->line, nb->pos, "TH");
1.105 schwarze 507: }
1.51 kristaps 508:
1.116 schwarze 509: /* TITLE ->MSEC<- DATE OS VOL */
1.51 kristaps 510:
1.149 schwarze 511: if (n != NULL)
1.60 schwarze 512: n = n->next;
1.149 schwarze 513: if (n != NULL && n->string != NULL)
1.85 schwarze 514: man->meta.msec = mandoc_strdup(n->string);
1.105 schwarze 515: else {
1.85 schwarze 516: man->meta.msec = mandoc_strdup("");
1.141 schwarze 517: mandoc_msg(MANDOCERR_MSEC_MISSING,
1.105 schwarze 518: nb->line, nb->pos, "TH %s", man->meta.title);
519: }
1.51 kristaps 520:
1.116 schwarze 521: /* TITLE MSEC ->DATE<- OS VOL */
1.51 kristaps 522:
1.149 schwarze 523: if (n != NULL)
1.60 schwarze 524: n = n->next;
1.150 schwarze 525: if (man->quick && n != NULL)
1.85 schwarze 526: man->meta.date = mandoc_strdup("");
1.150 schwarze 527: else
528: man->meta.date = mandoc_normdate(n, nb);
1.51 kristaps 529:
1.116 schwarze 530: /* TITLE MSEC DATE ->OS<- VOL */
1.51 kristaps 531:
532: if (n && (n = n->next))
1.116 schwarze 533: man->meta.os = mandoc_strdup(n->string);
1.131 schwarze 534: else if (man->os_s != NULL)
535: man->meta.os = mandoc_strdup(man->os_s);
536: if (man->meta.os_e == MANDOC_OS_OTHER && man->meta.os != NULL) {
537: if (strstr(man->meta.os, "OpenBSD") != NULL)
538: man->meta.os_e = MANDOC_OS_OPENBSD;
539: else if (strstr(man->meta.os, "NetBSD") != NULL)
540: man->meta.os_e = MANDOC_OS_NETBSD;
541: }
1.51 kristaps 542:
1.116 schwarze 543: /* TITLE MSEC DATE OS ->VOL<- */
1.79 schwarze 544: /* If missing, use the default VOL name for MSEC. */
1.51 kristaps 545:
546: if (n && (n = n->next))
1.85 schwarze 547: man->meta.vol = mandoc_strdup(n->string);
548: else if ('\0' != man->meta.msec[0] &&
549: (NULL != (p = mandoc_a2msec(man->meta.msec))))
550: man->meta.vol = mandoc_strdup(p);
1.113 schwarze 551:
552: if (n != NULL && (n = n->next) != NULL)
1.141 schwarze 553: mandoc_msg(MANDOCERR_ARG_EXCESS,
1.113 schwarze 554: n->line, n->pos, "TH ... %s", n->string);
1.51 kristaps 555:
556: /*
557: * Remove the `TH' node after we've processed it for our
558: * meta-data.
559: */
1.118 schwarze 560: roff_node_delete(man, man->last);
1.51 kristaps 561: }
562:
1.107 schwarze 563: static void
1.51 kristaps 564: post_UC(CHKARGS)
565: {
566: static const char * const bsd_versions[] = {
567: "3rd Berkeley Distribution",
568: "4th Berkeley Distribution",
569: "4.2 Berkeley Distribution",
570: "4.3 Berkeley Distribution",
571: "4.4 Berkeley Distribution",
572: };
573:
574: const char *p, *s;
575:
576: n = n->child;
577:
1.114 schwarze 578: if (n == NULL || n->type != ROFFT_TEXT)
1.51 kristaps 579: p = bsd_versions[0];
580: else {
581: s = n->string;
582: if (0 == strcmp(s, "3"))
583: p = bsd_versions[0];
584: else if (0 == strcmp(s, "4"))
585: p = bsd_versions[1];
586: else if (0 == strcmp(s, "5"))
587: p = bsd_versions[2];
588: else if (0 == strcmp(s, "6"))
589: p = bsd_versions[3];
590: else if (0 == strcmp(s, "7"))
591: p = bsd_versions[4];
592: else
593: p = bsd_versions[0];
594: }
595:
1.116 schwarze 596: free(man->meta.os);
597: man->meta.os = mandoc_strdup(p);
1.51 kristaps 598: }
599:
1.107 schwarze 600: static void
1.51 kristaps 601: post_AT(CHKARGS)
602: {
603: static const char * const unix_versions[] = {
604: "7th Edition",
605: "System III",
606: "System V",
607: "System V Release 2",
608: };
609:
1.115 schwarze 610: struct roff_node *nn;
1.51 kristaps 611: const char *p, *s;
612:
613: n = n->child;
614:
1.114 schwarze 615: if (n == NULL || n->type != ROFFT_TEXT)
1.51 kristaps 616: p = unix_versions[0];
617: else {
618: s = n->string;
619: if (0 == strcmp(s, "3"))
620: p = unix_versions[0];
621: else if (0 == strcmp(s, "4"))
622: p = unix_versions[1];
623: else if (0 == strcmp(s, "5")) {
624: nn = n->next;
1.114 schwarze 625: if (nn != NULL &&
626: nn->type == ROFFT_TEXT &&
627: nn->string[0] != '\0')
1.51 kristaps 628: p = unix_versions[3];
629: else
630: p = unix_versions[2];
631: } else
632: p = unix_versions[0];
633: }
634:
1.116 schwarze 635: free(man->meta.os);
636: man->meta.os = mandoc_strdup(p);
1.129 schwarze 637: }
638:
639: static void
640: post_in(CHKARGS)
641: {
642: char *s;
643:
644: if (n->parent->tok != MAN_TP ||
645: n->parent->type != ROFFT_HEAD ||
646: n->child == NULL ||
647: *n->child->string == '+' ||
648: *n->child->string == '-')
649: return;
650: mandoc_asprintf(&s, "+%s", n->child->string);
651: free(n->child->string);
652: n->child->string = s;
1.51 kristaps 653: }
CVSweb