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