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