Return to man_validate.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
version 1.28, 2010/01/01 17:14:28 | version 1.53, 2010/12/06 13:50:41 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se> | * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv> | ||
* | * | ||
* Permission to use, copy, modify, and distribute this software for any | * Permission to use, copy, modify, and distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | * purpose with or without fee is hereby granted, provided that the above | ||
|
|
||
#include <limits.h> | #include <limits.h> | ||
#include <stdarg.h> | #include <stdarg.h> | ||
#include <stdlib.h> | #include <stdlib.h> | ||
#include <string.h> | |||
#include <time.h> | |||
#include "mandoc.h" | |||
#include "libman.h" | #include "libman.h" | ||
#include "libmandoc.h" | #include "libmandoc.h" | ||
#define CHKARGS struct man *m, const struct man_node *n | #define CHKARGS struct man *m, struct man_node *n | ||
typedef int (*v_check)(CHKARGS); | typedef int (*v_check)(CHKARGS); | ||
|
|
||
static int check_root(CHKARGS); | static int check_root(CHKARGS); | ||
static int check_sec(CHKARGS); | static int check_sec(CHKARGS); | ||
static int check_text(CHKARGS); | static int check_text(CHKARGS); | ||
static int check_title(CHKARGS); | |||
static int post_AT(CHKARGS); | |||
static int post_fi(CHKARGS); | |||
static int post_nf(CHKARGS); | |||
static int post_TH(CHKARGS); | |||
static int post_UC(CHKARGS); | |||
static v_check posts_at[] = { post_AT, NULL }; | |||
static v_check posts_eq0[] = { check_eq0, NULL }; | static v_check posts_eq0[] = { check_eq0, NULL }; | ||
static v_check posts_ge2_le5[] = { check_ge2, check_le5, NULL }; | static v_check posts_fi[] = { check_eq0, post_fi, NULL }; | ||
static v_check posts_le1[] = { check_le1, NULL }; | |||
static v_check posts_nf[] = { check_eq0, post_nf, NULL }; | |||
static v_check posts_par[] = { check_par, NULL }; | static v_check posts_par[] = { check_par, NULL }; | ||
static v_check posts_part[] = { check_part, NULL }; | static v_check posts_part[] = { check_part, NULL }; | ||
static v_check posts_sec[] = { check_sec, NULL }; | static v_check posts_sec[] = { check_sec, NULL }; | ||
static v_check posts_sp[] = { check_le1, NULL }; | static v_check posts_th[] = { check_ge2, check_le5, check_title, post_TH, NULL }; | ||
static v_check posts_uc[] = { post_UC, NULL }; | |||
static v_check pres_bline[] = { check_bline, NULL }; | static v_check pres_bline[] = { check_bline, NULL }; | ||
static const struct man_valid man_valids[MAN_MAX] = { | static const struct man_valid man_valids[MAN_MAX] = { | ||
{ pres_bline, posts_eq0 }, /* br */ | { NULL, posts_eq0 }, /* br */ | ||
{ pres_bline, posts_ge2_le5 }, /* TH */ /* FIXME: make sure capitalised. */ | { pres_bline, posts_th }, /* TH */ | ||
{ pres_bline, posts_sec }, /* SH */ | { pres_bline, posts_sec }, /* SH */ | ||
{ pres_bline, posts_sec }, /* SS */ | { pres_bline, posts_sec }, /* SS */ | ||
{ pres_bline, posts_par }, /* TP */ | { pres_bline, posts_par }, /* TP */ | ||
|
|
||
{ NULL, NULL }, /* I */ | { NULL, NULL }, /* I */ | ||
{ NULL, NULL }, /* IR */ | { NULL, NULL }, /* IR */ | ||
{ NULL, NULL }, /* RI */ | { NULL, NULL }, /* RI */ | ||
{ pres_bline, posts_eq0 }, /* na */ | { NULL, posts_eq0 }, /* na */ /* FIXME: should warn only. */ | ||
{ NULL, NULL }, /* i */ | { NULL, NULL }, /* i */ | ||
{ pres_bline, posts_sp }, /* sp */ | { NULL, posts_le1 }, /* sp */ /* FIXME: should warn only. */ | ||
{ pres_bline, posts_eq0 }, /* nf */ | { pres_bline, posts_nf }, /* nf */ | ||
{ pres_bline, posts_eq0 }, /* fi */ | { pres_bline, posts_fi }, /* fi */ | ||
{ NULL, NULL }, /* r */ | { NULL, NULL }, /* r */ | ||
{ NULL, NULL }, /* RE */ | { NULL, NULL }, /* RE */ | ||
{ NULL, posts_part }, /* RS */ | { NULL, posts_part }, /* RS */ | ||
{ NULL, NULL }, /* DT */ | { NULL, NULL }, /* DT */ | ||
{ NULL, NULL }, /* UC */ | { NULL, posts_uc }, /* UC */ | ||
{ NULL, NULL }, /* PD */ | { NULL, NULL }, /* PD */ | ||
{ NULL, posts_at }, /* AT */ | |||
{ NULL, NULL }, /* in */ | |||
}; | }; | ||
int | int | ||
man_valid_pre(struct man *m, const struct man_node *n) | man_valid_pre(struct man *m, struct man_node *n) | ||
{ | { | ||
v_check *cp; | v_check *cp; | ||
|
|
||
{ | { | ||
if (MAN_BLINE & m->flags) | if (MAN_BLINE & m->flags) | ||
return(man_nwarn(m, n, WEXITSCOPE)); | return(man_nmsg(m, n, MANDOCERR_SCOPEEXIT)); | ||
if (MAN_ELINE & m->flags) | if (MAN_ELINE & m->flags) | ||
return(man_nwarn(m, n, WEXITSCOPE)); | return(man_nmsg(m, n, MANDOCERR_SCOPEEXIT)); | ||
m->flags &= ~MAN_BLINE; | m->flags &= ~MAN_BLINE; | ||
m->flags &= ~MAN_ELINE; | m->flags &= ~MAN_ELINE; | ||
if (NULL == m->first->child) | if (NULL == m->first->child) { | ||
return(man_nerr(m, n, WNODATA)); | man_nmsg(m, n, MANDOCERR_NODOCBODY); | ||
if (NULL == m->meta.title) | return(0); | ||
return(man_nerr(m, n, WNOTITLE)); | } else if (NULL == m->meta.title) { | ||
if ( ! man_nmsg(m, n, MANDOCERR_NOTITLE)) | |||
return(0); | |||
/* | |||
* If a title hasn't been set, do so now (by | |||
* implication, date and section also aren't set). | |||
* | |||
* FIXME: this should be in man_action.c. | |||
*/ | |||
m->meta.title = mandoc_strdup("unknown"); | |||
m->meta.date = time(NULL); | |||
m->meta.msec = mandoc_strdup("1"); | |||
} | |||
return(1); | return(1); | ||
} | } | ||
static int | static int | ||
check_text(CHKARGS) | check_title(CHKARGS) | ||
{ | { | ||
const char *p; | const char *p; | ||
int pos, c; | |||
assert(n->string); | assert(n->child); | ||
/* FIXME: is this sufficient? */ | |||
if ('\0' == *n->child->string) { | |||
man_nmsg(m, n, MANDOCERR_SYNTARGCOUNT); | |||
return(0); | |||
} | |||
for (p = n->child->string; '\0' != *p; p++) | |||
if (isalpha((u_char)*p) && ! isupper((u_char)*p)) | |||
if ( ! man_nmsg(m, n, MANDOCERR_UPPERCASE)) | |||
return(0); | |||
return(1); | |||
} | |||
static int | |||
check_text(CHKARGS) | |||
{ | |||
char *p; | |||
int pos, c; | |||
size_t sz; | |||
for (p = n->string, pos = n->pos + 1; *p; p++, pos++) { | for (p = n->string, pos = n->pos + 1; *p; p++, pos++) { | ||
if ('\\' == *p) { | sz = strcspn(p, "\t\\"); | ||
c = mandoc_special(p); | p += (int)sz; | ||
if (c) { | |||
p += c - 1; | if ('\0' == *p) | ||
pos += c - 1; | break; | ||
pos += (int)sz; | |||
if ('\t' == *p) { | |||
if (MAN_LITERAL & m->flags) | |||
continue; | continue; | ||
} | if (man_pmsg(m, n->line, pos, MANDOCERR_BADTAB)) | ||
if ( ! (MAN_IGN_ESCAPE & m->pflags)) | continue; | ||
return(man_perr(m, n->line, pos, WESCAPE)); | return(0); | ||
if ( ! man_pwarn(m, n->line, pos, WESCAPE)) | |||
return(0); | |||
continue; | |||
} | } | ||
if ('\t' == *p || isprint((u_char)*p)) | /* Check the special character. */ | ||
continue; | |||
if (MAN_IGN_CHARS & m->pflags) | c = mandoc_special(p); | ||
return(man_pwarn(m, n->line, pos, WNPRINT)); | if (c) { | ||
return(man_perr(m, n->line, pos, WNPRINT)); | p += c - 1; | ||
pos += c - 1; | |||
} else | |||
man_pmsg(m, n->line, pos, MANDOCERR_BADESCAPE); | |||
} | } | ||
return(1); | return(1); | ||
|
|
||
{ \ | { \ | ||
if (n->nchild ineq (x)) \ | if (n->nchild ineq (x)) \ | ||
return(1); \ | return(1); \ | ||
return(man_verr(m, n->line, n->pos, \ | man_vmsg(m, MANDOCERR_SYNTARGCOUNT, n->line, n->pos, \ | ||
"expected line arguments %s %d, have %d", \ | "line arguments %s %d (have %d)", \ | ||
#ineq, (x), n->nchild)); \ | #ineq, (x), n->nchild); \ | ||
return(0); \ | |||
} | } | ||
INEQ_DEFINE(0, ==, eq0) | INEQ_DEFINE(0, ==, eq0) | ||
|
|
||
check_sec(CHKARGS) | check_sec(CHKARGS) | ||
{ | { | ||
if (MAN_BODY == n->type && 0 == n->nchild) | if (MAN_HEAD == n->type && 0 == n->nchild) { | ||
return(man_nwarn(m, n, WBODYARGS)); | man_nmsg(m, n, MANDOCERR_SYNTARGCOUNT); | ||
if (MAN_HEAD == n->type && 0 == n->nchild) | return(0); | ||
return(man_nerr(m, n, WHEADARGS)); | } else if (MAN_BODY == n->type && 0 == n->nchild) | ||
return(man_nmsg(m, n, MANDOCERR_NOBODY)); | |||
return(1); | return(1); | ||
} | } | ||
|
|
||
{ | { | ||
if (MAN_BODY == n->type && 0 == n->nchild) | if (MAN_BODY == n->type && 0 == n->nchild) | ||
return(man_nwarn(m, n, WBODYARGS)); | return(man_nmsg(m, n, MANDOCERR_NOBODY)); | ||
return(1); | return(1); | ||
} | } | ||
|
|
||
default: | default: | ||
if (n->nchild) | if (n->nchild) | ||
break; | break; | ||
return(man_nwarn(m, n, WBODYARGS)); | return(man_nmsg(m, n, MANDOCERR_NOBODY)); | ||
} | } | ||
if (MAN_HEAD == n->type) | if (MAN_HEAD == n->type) | ||
switch (n->tok) { | switch (n->tok) { | ||
|
|
||
case (MAN_LP): | case (MAN_LP): | ||
if (0 == n->nchild) | if (0 == n->nchild) | ||
break; | break; | ||
return(man_nwarn(m, n, WNHEADARGS)); | return(man_nmsg(m, n, MANDOCERR_ARGSLOST)); | ||
default: | default: | ||
if (n->nchild) | break; | ||
break; | |||
return(man_nwarn(m, n, WHEADARGS)); | |||
} | } | ||
return(1); | return(1); | ||
|
|
||
{ | { | ||
assert( ! (MAN_ELINE & m->flags)); | assert( ! (MAN_ELINE & m->flags)); | ||
if (MAN_BLINE & m->flags) | if (MAN_BLINE & m->flags) { | ||
return(man_nerr(m, n, WLNSCOPE)); | man_nmsg(m, n, MANDOCERR_SYNTLINESCOPE); | ||
return(0); | |||
} | |||
return(1); | return(1); | ||
} | } | ||
static int | |||
post_TH(CHKARGS) | |||
{ | |||
if (m->meta.title) | |||
free(m->meta.title); | |||
if (m->meta.vol) | |||
free(m->meta.vol); | |||
if (m->meta.source) | |||
free(m->meta.source); | |||
if (m->meta.msec) | |||
free(m->meta.msec); | |||
if (m->meta.rawdate) | |||
free(m->meta.rawdate); | |||
m->meta.title = m->meta.vol = m->meta.rawdate = | |||
m->meta.msec = m->meta.source = NULL; | |||
m->meta.date = 0; | |||
/* ->TITLE<- MSEC DATE SOURCE VOL */ | |||
n = n->child; | |||
assert(n); | |||
m->meta.title = mandoc_strdup(n->string); | |||
/* TITLE ->MSEC<- DATE SOURCE VOL */ | |||
n = n->next; | |||
assert(n); | |||
m->meta.msec = mandoc_strdup(n->string); | |||
/* TITLE MSEC ->DATE<- SOURCE VOL */ | |||
/* | |||
* Try to parse the date. If this works, stash the epoch (this | |||
* is optimal because we can reformat it in the canonical form). | |||
* If it doesn't parse, isn't specified at all, or is an empty | |||
* string, then use the current date. | |||
*/ | |||
n = n->next; | |||
if (n && n->string && *n->string) { | |||
m->meta.date = mandoc_a2time | |||
(MTIME_ISO_8601, n->string); | |||
if (0 == m->meta.date) { | |||
man_nmsg(m, n, MANDOCERR_BADDATE); | |||
m->meta.rawdate = mandoc_strdup(n->string); | |||
} | |||
} else | |||
m->meta.date = time(NULL); | |||
/* TITLE MSEC DATE ->SOURCE<- VOL */ | |||
if (n && (n = n->next)) | |||
m->meta.source = mandoc_strdup(n->string); | |||
/* TITLE MSEC DATE SOURCE ->VOL<- */ | |||
if (n && (n = n->next)) | |||
m->meta.vol = mandoc_strdup(n->string); | |||
/* | |||
* Remove the `TH' node after we've processed it for our | |||
* meta-data. | |||
*/ | |||
man_node_delete(m, m->last); | |||
return(1); | |||
} | |||
static int | |||
post_nf(CHKARGS) | |||
{ | |||
if (MAN_LITERAL & m->flags) | |||
man_nmsg(m, n, MANDOCERR_SCOPEREP); | |||
m->flags |= MAN_LITERAL; | |||
return(1); | |||
} | |||
static int | |||
post_fi(CHKARGS) | |||
{ | |||
if ( ! (MAN_LITERAL & m->flags)) | |||
man_nmsg(m, n, MANDOCERR_NOSCOPE); | |||
m->flags &= ~MAN_LITERAL; | |||
return(1); | |||
} | |||
static int | |||
post_UC(CHKARGS) | |||
{ | |||
static const char * const bsd_versions[] = { | |||
"3rd Berkeley Distribution", | |||
"4th Berkeley Distribution", | |||
"4.2 Berkeley Distribution", | |||
"4.3 Berkeley Distribution", | |||
"4.4 Berkeley Distribution", | |||
}; | |||
const char *p, *s; | |||
n = n->child; | |||
n = m->last->child; | |||
if (NULL == n || MAN_TEXT != n->type) | |||
p = bsd_versions[0]; | |||
else { | |||
s = n->string; | |||
if (0 == strcmp(s, "3")) | |||
p = bsd_versions[0]; | |||
else if (0 == strcmp(s, "4")) | |||
p = bsd_versions[1]; | |||
else if (0 == strcmp(s, "5")) | |||
p = bsd_versions[2]; | |||
else if (0 == strcmp(s, "6")) | |||
p = bsd_versions[3]; | |||
else if (0 == strcmp(s, "7")) | |||
p = bsd_versions[4]; | |||
else | |||
p = bsd_versions[0]; | |||
} | |||
if (m->meta.source) | |||
free(m->meta.source); | |||
m->meta.source = mandoc_strdup(p); | |||
return(1); | |||
} | |||
static int | |||
post_AT(CHKARGS) | |||
{ | |||
static const char * const unix_versions[] = { | |||
"7th Edition", | |||
"System III", | |||
"System V", | |||
"System V Release 2", | |||
}; | |||
const char *p, *s; | |||
struct man_node *nn; | |||
n = n->child; | |||
if (NULL == n || MAN_TEXT != n->type) | |||
p = unix_versions[0]; | |||
else { | |||
s = n->string; | |||
if (0 == strcmp(s, "3")) | |||
p = unix_versions[0]; | |||
else if (0 == strcmp(s, "4")) | |||
p = unix_versions[1]; | |||
else if (0 == strcmp(s, "5")) { | |||
nn = n->next; | |||
if (nn && MAN_TEXT == nn->type && nn->string[0]) | |||
p = unix_versions[3]; | |||
else | |||
p = unix_versions[2]; | |||
} else | |||
p = unix_versions[0]; | |||
} | |||
if (m->meta.source) | |||
free(m->meta.source); | |||
m->meta.source = mandoc_strdup(p); | |||
return(1); | |||
} |