Home | History | Annotate | Line # | Download | only in dist
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