Home | History | Annotate | Line # | Download | only in dist
man_validate.c revision 1.2.12.1
      1  1.2.12.1  perseant /* Id: man_validate.c,v 1.156 2021/08/10 12:55:03 schwarze Exp  */
      2       1.1     joerg /*
      3  1.2.12.1  perseant  * 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.2.12.1  perseant  *
     18  1.2.12.1  perseant  * 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.2.12.1  perseant #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.2.12.1  perseant 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.2.12.1  perseant 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.2.12.1  perseant 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.2.12.1  perseant 	post_TP,    /* TP */
     71  1.2.12.1  perseant 	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.2.12.1  perseant 		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.2.12.1  perseant /*
    210  1.2.12.1  perseant  * Skip leading whitespace, dashes, backslashes, and font escapes,
    211  1.2.12.1  perseant  * then create a tag if the first following byte is a letter.
    212  1.2.12.1  perseant  * Priority is high unless whitespace is present.
    213  1.2.12.1  perseant  */
    214  1.2.12.1  perseant static void
    215  1.2.12.1  perseant check_tag(struct roff_node *n, struct roff_node *nt)
    216  1.2.12.1  perseant {
    217  1.2.12.1  perseant 	const char	*cp, *arg;
    218  1.2.12.1  perseant 	int		 prio, sz;
    219  1.2.12.1  perseant 
    220  1.2.12.1  perseant 	if (nt == NULL || nt->type != ROFFT_TEXT)
    221  1.2.12.1  perseant 		return;
    222  1.2.12.1  perseant 
    223  1.2.12.1  perseant 	cp = nt->string;
    224  1.2.12.1  perseant 	prio = TAG_STRONG;
    225  1.2.12.1  perseant 	for (;;) {
    226  1.2.12.1  perseant 		switch (*cp) {
    227  1.2.12.1  perseant 		case ' ':
    228  1.2.12.1  perseant 		case '\t':
    229  1.2.12.1  perseant 			prio = TAG_WEAK;
    230  1.2.12.1  perseant 			/* FALLTHROUGH */
    231  1.2.12.1  perseant 		case '-':
    232  1.2.12.1  perseant 			cp++;
    233  1.2.12.1  perseant 			break;
    234  1.2.12.1  perseant 		case '\\':
    235  1.2.12.1  perseant 			cp++;
    236  1.2.12.1  perseant 			switch (mandoc_escape(&cp, &arg, &sz)) {
    237  1.2.12.1  perseant 			case ESCAPE_FONT:
    238  1.2.12.1  perseant 			case ESCAPE_FONTBOLD:
    239  1.2.12.1  perseant 			case ESCAPE_FONTITALIC:
    240  1.2.12.1  perseant 			case ESCAPE_FONTBI:
    241  1.2.12.1  perseant 			case ESCAPE_FONTROMAN:
    242  1.2.12.1  perseant 			case ESCAPE_FONTCR:
    243  1.2.12.1  perseant 			case ESCAPE_FONTCB:
    244  1.2.12.1  perseant 			case ESCAPE_FONTCI:
    245  1.2.12.1  perseant 			case ESCAPE_FONTPREV:
    246  1.2.12.1  perseant 			case ESCAPE_IGNORE:
    247  1.2.12.1  perseant 				break;
    248  1.2.12.1  perseant 			case ESCAPE_SPECIAL:
    249  1.2.12.1  perseant 				if (sz != 1)
    250  1.2.12.1  perseant 					return;
    251  1.2.12.1  perseant 				switch (*arg) {
    252  1.2.12.1  perseant 				case '-':
    253  1.2.12.1  perseant 				case 'e':
    254  1.2.12.1  perseant 					break;
    255  1.2.12.1  perseant 				default:
    256  1.2.12.1  perseant 					return;
    257  1.2.12.1  perseant 				}
    258  1.2.12.1  perseant 				break;
    259  1.2.12.1  perseant 			default:
    260  1.2.12.1  perseant 				return;
    261  1.2.12.1  perseant 			}
    262  1.2.12.1  perseant 			break;
    263  1.2.12.1  perseant 		default:
    264  1.2.12.1  perseant 			if (isalpha((unsigned char)*cp))
    265  1.2.12.1  perseant 				tag_put(cp, prio, n);
    266  1.2.12.1  perseant 			return;
    267  1.2.12.1  perseant 		}
    268  1.2.12.1  perseant 	}
    269  1.2.12.1  perseant }
    270  1.2.12.1  perseant 
    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.2.12.1  perseant 	char			*cp, *tag;
    317       1.1     joerg 
    318  1.2.12.1  perseant 	nc = n->child;
    319  1.2.12.1  perseant 	switch (n->type) {
    320  1.2.12.1  perseant 	case ROFFT_HEAD:
    321  1.2.12.1  perseant 		tag = NULL;
    322  1.2.12.1  perseant 		deroff(&tag, n);
    323  1.2.12.1  perseant 		if (tag != NULL) {
    324  1.2.12.1  perseant 			for (cp = tag; *cp != '\0'; cp++)
    325  1.2.12.1  perseant 				if (*cp == ' ')
    326  1.2.12.1  perseant 					*cp = '_';
    327  1.2.12.1  perseant 			if (nc != NULL && nc->type == ROFFT_TEXT &&
    328  1.2.12.1  perseant 			    strcmp(nc->string, tag) == 0)
    329  1.2.12.1  perseant 				tag_put(NULL, TAG_STRONG, n);
    330  1.2.12.1  perseant 			else
    331  1.2.12.1  perseant 				tag_put(tag, TAG_FALLBACK, n);
    332  1.2.12.1  perseant 			free(tag);
    333  1.2.12.1  perseant 		}
    334  1.2.12.1  perseant 		return;
    335  1.2.12.1  perseant 	case ROFFT_BODY:
    336  1.2.12.1  perseant 		if (nc != NULL)
    337  1.2.12.1  perseant 			break;
    338       1.2  christos 		return;
    339  1.2.12.1  perseant 	default:
    340  1.2.12.1  perseant 		return;
    341  1.2.12.1  perseant 	}
    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.2.12.1  perseant 	case ROFFT_HEAD:
    431  1.2.12.1  perseant 		check_tag(n, n->child);
    432  1.2.12.1  perseant 		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.2.12.1  perseant /*
    444  1.2.12.1  perseant  * The first next-line element in the head is the tag.
    445  1.2.12.1  perseant  * If that's a font macro, use its first child instead.
    446  1.2.12.1  perseant  */
    447  1.2.12.1  perseant static void
    448  1.2.12.1  perseant post_TP(CHKARGS)
    449  1.2.12.1  perseant {
    450  1.2.12.1  perseant 	struct roff_node *nt;
    451  1.2.12.1  perseant 
    452  1.2.12.1  perseant 	if (n->type != ROFFT_HEAD || (nt = n->child) == NULL)
    453  1.2.12.1  perseant 		return;
    454  1.2.12.1  perseant 
    455  1.2.12.1  perseant 	while ((nt->flags & NODE_LINE) == 0)
    456  1.2.12.1  perseant 		if ((nt = nt->next) == NULL)
    457  1.2.12.1  perseant 			return;
    458  1.2.12.1  perseant 
    459  1.2.12.1  perseant 	switch (nt->tok) {
    460  1.2.12.1  perseant 	case MAN_B:
    461  1.2.12.1  perseant 	case MAN_BI:
    462  1.2.12.1  perseant 	case MAN_BR:
    463  1.2.12.1  perseant 	case MAN_I:
    464  1.2.12.1  perseant 	case MAN_IB:
    465  1.2.12.1  perseant 	case MAN_IR:
    466  1.2.12.1  perseant 		nt = nt->child;
    467  1.2.12.1  perseant 		break;
    468  1.2.12.1  perseant 	default:
    469  1.2.12.1  perseant 		break;
    470  1.2.12.1  perseant 	}
    471  1.2.12.1  perseant 	check_tag(n, nt);
    472  1.2.12.1  perseant }
    473  1.2.12.1  perseant 
    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.2.12.1  perseant 	if (n != NULL && n->string != NULL) {
    495  1.2.12.1  perseant 		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.2.12.1  perseant 	if (n != NULL)
    514       1.2  christos 		n = n->next;
    515  1.2.12.1  perseant 	if (n != NULL && n->string != NULL) {
    516       1.2  christos 		man->meta.msec = mandoc_strdup(n->string);
    517  1.2.12.1  perseant 		if (man->filesec != '\0' &&
    518  1.2.12.1  perseant 		    man->filesec != *n->string &&
    519  1.2.12.1  perseant 		    *n->string >= '1' && *n->string <= '9')
    520  1.2.12.1  perseant 			mandoc_msg(MANDOCERR_MSEC_FILE, n->line, n->pos,
    521  1.2.12.1  perseant 			    "*.%c vs TH ... %c", man->filesec, *n->string);
    522  1.2.12.1  perseant 	} 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.2.12.1  perseant 	if (n != NULL)
    531       1.2  christos 		n = n->next;
    532  1.2.12.1  perseant 	if (man->quick && n != NULL)
    533       1.2  christos 		man->meta.date = mandoc_strdup("");
    534  1.2.12.1  perseant 	else
    535  1.2.12.1  perseant 		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