Merge remote branch 'crater/vendor/MDOCML' into HEAD
[dragonfly.git] / contrib / mdocml / man_validate.c
1 /*      $Id: man_validate.c,v 1.57 2011/01/01 12:59:17 kristaps Exp $ */
2 /*
3  * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20
21 #include <sys/types.h>
22
23 #include <assert.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <limits.h>
27 #include <stdarg.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <time.h>
31
32 #include "mandoc.h"
33 #include "libman.h"
34 #include "libmandoc.h"
35
36 #define CHKARGS   struct man *m, struct man_node *n
37
38 typedef int     (*v_check)(CHKARGS);
39
40 struct  man_valid {
41         v_check  *pres;
42         v_check  *posts;
43 };
44
45 static  int       check_bline(CHKARGS);
46 static  int       check_eq0(CHKARGS);
47 static  int       check_ft(CHKARGS);
48 static  int       check_le1(CHKARGS);
49 static  int       check_ge2(CHKARGS);
50 static  int       check_le5(CHKARGS);
51 static  int       check_par(CHKARGS);
52 static  int       check_part(CHKARGS);
53 static  int       check_root(CHKARGS);
54 static  int       check_sec(CHKARGS);
55 static  int       check_text(CHKARGS);
56 static  int       check_title(CHKARGS);
57
58 static  int       post_AT(CHKARGS);
59 static  int       post_fi(CHKARGS);
60 static  int       post_nf(CHKARGS);
61 static  int       post_TH(CHKARGS);
62 static  int       post_UC(CHKARGS);
63
64 static  v_check   posts_at[] = { post_AT, NULL };
65 static  v_check   posts_eq0[] = { check_eq0, NULL };
66 static  v_check   posts_fi[] = { check_eq0, post_fi, NULL };
67 static  v_check   posts_le1[] = { check_le1, NULL };
68 static  v_check   posts_ft[] = { check_ft, NULL };
69 static  v_check   posts_nf[] = { check_eq0, post_nf, NULL };
70 static  v_check   posts_par[] = { check_par, NULL };
71 static  v_check   posts_part[] = { check_part, NULL };
72 static  v_check   posts_sec[] = { check_sec, NULL };
73 static  v_check   posts_th[] = { check_ge2, check_le5, check_title, post_TH, NULL };
74 static  v_check   posts_uc[] = { post_UC, NULL };
75 static  v_check   pres_bline[] = { check_bline, NULL };
76
77
78 static  const struct man_valid man_valids[MAN_MAX] = {
79         { NULL, posts_eq0 }, /* br */
80         { pres_bline, posts_th }, /* TH */
81         { pres_bline, posts_sec }, /* SH */
82         { pres_bline, posts_sec }, /* SS */
83         { pres_bline, posts_par }, /* TP */
84         { pres_bline, posts_par }, /* LP */
85         { pres_bline, posts_par }, /* PP */
86         { pres_bline, posts_par }, /* P */
87         { pres_bline, posts_par }, /* IP */
88         { pres_bline, posts_par }, /* HP */
89         { NULL, NULL }, /* SM */
90         { NULL, NULL }, /* SB */
91         { NULL, NULL }, /* BI */
92         { NULL, NULL }, /* IB */
93         { NULL, NULL }, /* BR */
94         { NULL, NULL }, /* RB */
95         { NULL, NULL }, /* R */
96         { NULL, NULL }, /* B */
97         { NULL, NULL }, /* I */
98         { NULL, NULL }, /* IR */
99         { NULL, NULL }, /* RI */
100         { NULL, posts_eq0 }, /* na */ /* FIXME: should warn only. */
101         { NULL, posts_le1 }, /* sp */ /* FIXME: should warn only. */
102         { pres_bline, posts_nf }, /* nf */
103         { pres_bline, posts_fi }, /* fi */
104         { NULL, NULL }, /* RE */
105         { NULL, posts_part }, /* RS */
106         { NULL, NULL }, /* DT */
107         { NULL, posts_uc }, /* UC */
108         { NULL, NULL }, /* PD */
109         { NULL, posts_at }, /* AT */
110         { NULL, NULL }, /* in */
111         { NULL, posts_ft }, /* ft */
112 };
113
114
115 int
116 man_valid_pre(struct man *m, struct man_node *n)
117 {
118         v_check         *cp;
119
120         switch (n->type) {
121         case (MAN_TEXT):
122                 /* FALLTHROUGH */
123         case (MAN_ROOT):
124                 /* FALLTHROUGH */
125         case (MAN_TBL):
126                 return(1);
127         default:
128                 break;
129         }
130
131         if (NULL == (cp = man_valids[n->tok].pres))
132                 return(1);
133         for ( ; *cp; cp++)
134                 if ( ! (*cp)(m, n)) 
135                         return(0);
136         return(1);
137 }
138
139
140 int
141 man_valid_post(struct man *m)
142 {
143         v_check         *cp;
144
145         if (MAN_VALID & m->last->flags)
146                 return(1);
147         m->last->flags |= MAN_VALID;
148
149         switch (m->last->type) {
150         case (MAN_TEXT): 
151                 return(check_text(m, m->last));
152         case (MAN_ROOT):
153                 return(check_root(m, m->last));
154         case (MAN_TBL):
155                 return(1);
156         default:
157                 break;
158         }
159
160         if (NULL == (cp = man_valids[m->last->tok].posts))
161                 return(1);
162         for ( ; *cp; cp++)
163                 if ( ! (*cp)(m, m->last))
164                         return(0);
165
166         return(1);
167 }
168
169
170 static int
171 check_root(CHKARGS) 
172 {
173
174         if (MAN_BLINE & m->flags)
175                 man_nmsg(m, n, MANDOCERR_SCOPEEXIT);
176         else if (MAN_ELINE & m->flags)
177                 man_nmsg(m, n, MANDOCERR_SCOPEEXIT);
178
179         m->flags &= ~MAN_BLINE;
180         m->flags &= ~MAN_ELINE;
181
182         if (NULL == m->first->child) {
183                 man_nmsg(m, n, MANDOCERR_NODOCBODY);
184                 return(0);
185         } else if (NULL == m->meta.title) {
186                 man_nmsg(m, n, MANDOCERR_NOTITLE);
187
188                 /*
189                  * If a title hasn't been set, do so now (by
190                  * implication, date and section also aren't set).
191                  */
192
193                 m->meta.title = mandoc_strdup("unknown");
194                 m->meta.date = time(NULL);
195                 m->meta.msec = mandoc_strdup("1");
196         }
197
198         return(1);
199 }
200
201
202 static int
203 check_title(CHKARGS) 
204 {
205         const char      *p;
206
207         assert(n->child);
208         /* FIXME: is this sufficient? */
209         if ('\0' == *n->child->string) {
210                 man_nmsg(m, n, MANDOCERR_SYNTARGCOUNT);
211                 return(0);
212         }
213
214         for (p = n->child->string; '\0' != *p; p++)
215                 /* Only warn about this once... */
216                 if (isalpha((u_char)*p) && ! isupper((u_char)*p)) {
217                         man_nmsg(m, n, MANDOCERR_UPPERCASE);
218                         break;
219                 }
220
221         return(1);
222 }
223
224
225 static int
226 check_text(CHKARGS) 
227 {
228         char            *p;
229         int              pos, c;
230         size_t           sz;
231
232         for (p = n->string, pos = n->pos + 1; *p; p++, pos++) {
233                 sz = strcspn(p, "\t\\");
234                 p += (int)sz;
235
236                 if ('\0' == *p)
237                         break;
238
239                 pos += (int)sz;
240
241                 if ('\t' == *p) {
242                         if (MAN_LITERAL & m->flags)
243                                 continue;
244                         if (man_pmsg(m, n->line, pos, MANDOCERR_BADTAB))
245                                 continue;
246                         return(0);
247                 }
248
249                 /* Check the special character. */
250
251                 c = mandoc_special(p);
252                 if (c) {
253                         p += c - 1;
254                         pos += c - 1;
255                 } else
256                         man_pmsg(m, n->line, pos, MANDOCERR_BADESCAPE);
257         }
258
259         return(1);
260 }
261
262
263 #define INEQ_DEFINE(x, ineq, name) \
264 static int \
265 check_##name(CHKARGS) \
266 { \
267         if (n->nchild ineq (x)) \
268                 return(1); \
269         man_vmsg(m, MANDOCERR_SYNTARGCOUNT, n->line, n->pos, \
270                         "line arguments %s %d (have %d)", \
271                         #ineq, (x), n->nchild); \
272         return(0); \
273 }
274
275 INEQ_DEFINE(0, ==, eq0)
276 INEQ_DEFINE(1, <=, le1)
277 INEQ_DEFINE(2, >=, ge2)
278 INEQ_DEFINE(5, <=, le5)
279
280 static int
281 check_ft(CHKARGS)
282 {
283         char    *cp;
284         int      ok;
285
286         if (0 == n->nchild)
287                 return(1);
288
289         ok = 0;
290         cp = n->child->string;
291         switch (*cp) {
292         case ('1'):
293                 /* FALLTHROUGH */
294         case ('2'):
295                 /* FALLTHROUGH */
296         case ('3'):
297                 /* FALLTHROUGH */
298         case ('4'):
299                 /* FALLTHROUGH */
300         case ('I'):
301                 /* FALLTHROUGH */
302         case ('P'):
303                 /* FALLTHROUGH */
304         case ('R'):
305                 if ('\0' == cp[1])
306                         ok = 1;
307                 break;
308         case ('B'):
309                 if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2]))
310                         ok = 1;
311                 break;
312         case ('C'):
313                 if ('W' == cp[1] && '\0' == cp[2])
314                         ok = 1;
315                 break;
316         default:
317                 break;
318         }
319
320         if (0 == ok) {
321                 man_vmsg(m, MANDOCERR_BADFONT, 
322                                 n->line, n->pos, "%s", cp);
323                 *cp = '\0';
324         }
325
326         if (1 < n->nchild)
327                 man_vmsg(m, MANDOCERR_ARGCOUNT, n->line, n->pos,
328                                 "want one child (have %d)", n->nchild);
329
330         return(1);
331 }
332
333 static int
334 check_sec(CHKARGS)
335 {
336
337         if (MAN_HEAD == n->type && 0 == n->nchild) {
338                 man_nmsg(m, n, MANDOCERR_SYNTARGCOUNT);
339                 return(0);
340         } else if (MAN_BODY == n->type && 0 == n->nchild)
341                 man_nmsg(m, n, MANDOCERR_NOBODY);
342
343         return(1);
344 }
345
346
347 static int
348 check_part(CHKARGS)
349 {
350
351         if (MAN_BODY == n->type && 0 == n->nchild)
352                 man_nmsg(m, n, MANDOCERR_NOBODY);
353
354         return(1);
355 }
356
357
358 static int
359 check_par(CHKARGS)
360 {
361
362         if (MAN_BODY == n->type) 
363                 switch (n->tok) {
364                 case (MAN_IP):
365                         /* FALLTHROUGH */
366                 case (MAN_HP):
367                         /* FALLTHROUGH */
368                 case (MAN_TP):
369                         /* Body-less lists are ok. */
370                         break;
371                 default:
372                         if (0 == n->nchild)
373                                 man_nmsg(m, n, MANDOCERR_NOBODY);
374                         break;
375                 }
376         if (MAN_HEAD == n->type)
377                 switch (n->tok) {
378                 case (MAN_PP):
379                         /* FALLTHROUGH */
380                 case (MAN_P):
381                         /* FALLTHROUGH */
382                 case (MAN_LP):
383                         if (n->nchild)
384                                 man_nmsg(m, n, MANDOCERR_ARGSLOST);
385                         break;
386                 default:
387                         break;
388                 }
389
390         return(1);
391 }
392
393
394 static int
395 check_bline(CHKARGS)
396 {
397
398         assert( ! (MAN_ELINE & m->flags));
399         if (MAN_BLINE & m->flags) {
400                 man_nmsg(m, n, MANDOCERR_SYNTLINESCOPE);
401                 return(0);
402         }
403
404         return(1);
405 }
406
407 static int
408 post_TH(CHKARGS)
409 {
410
411         if (m->meta.title)
412                 free(m->meta.title);
413         if (m->meta.vol)
414                 free(m->meta.vol);
415         if (m->meta.source)
416                 free(m->meta.source);
417         if (m->meta.msec)
418                 free(m->meta.msec);
419         if (m->meta.rawdate)
420                 free(m->meta.rawdate);
421
422         m->meta.title = m->meta.vol = m->meta.rawdate =
423                 m->meta.msec = m->meta.source = NULL;
424         m->meta.date = 0;
425
426         /* ->TITLE<- MSEC DATE SOURCE VOL */
427
428         n = n->child;
429         assert(n);
430         m->meta.title = mandoc_strdup(n->string);
431
432         /* TITLE ->MSEC<- DATE SOURCE VOL */
433
434         n = n->next;
435         assert(n);
436         m->meta.msec = mandoc_strdup(n->string);
437
438         /* TITLE MSEC ->DATE<- SOURCE VOL */
439
440         /*
441          * Try to parse the date.  If this works, stash the epoch (this
442          * is optimal because we can reformat it in the canonical form).
443          * If it doesn't parse, isn't specified at all, or is an empty
444          * string, then use the current date.
445          */
446
447         n = n->next;
448         if (n && n->string && *n->string) {
449                 m->meta.date = mandoc_a2time
450                         (MTIME_ISO_8601, n->string);
451                 if (0 == m->meta.date) {
452                         man_nmsg(m, n, MANDOCERR_BADDATE);
453                         m->meta.rawdate = mandoc_strdup(n->string);
454                 }
455         } else
456                 m->meta.date = time(NULL);
457
458         /* TITLE MSEC DATE ->SOURCE<- VOL */
459
460         if (n && (n = n->next))
461                 m->meta.source = mandoc_strdup(n->string);
462
463         /* TITLE MSEC DATE SOURCE ->VOL<- */
464
465         if (n && (n = n->next))
466                 m->meta.vol = mandoc_strdup(n->string);
467
468         /*
469          * Remove the `TH' node after we've processed it for our
470          * meta-data.
471          */
472         man_node_delete(m, m->last);
473         return(1);
474 }
475
476 static int
477 post_nf(CHKARGS)
478 {
479
480         if (MAN_LITERAL & m->flags)
481                 man_nmsg(m, n, MANDOCERR_SCOPEREP);
482
483         m->flags |= MAN_LITERAL;
484         return(1);
485 }
486
487 static int
488 post_fi(CHKARGS)
489 {
490
491         if ( ! (MAN_LITERAL & m->flags))
492                 man_nmsg(m, n, MANDOCERR_NOSCOPE);
493
494         m->flags &= ~MAN_LITERAL;
495         return(1);
496 }
497
498 static int
499 post_UC(CHKARGS)
500 {
501         static const char * const bsd_versions[] = {
502             "3rd Berkeley Distribution",
503             "4th Berkeley Distribution",
504             "4.2 Berkeley Distribution",
505             "4.3 Berkeley Distribution",
506             "4.4 Berkeley Distribution",
507         };
508
509         const char      *p, *s;
510
511         n = n->child;
512         n = m->last->child;
513
514         if (NULL == n || MAN_TEXT != n->type)
515                 p = bsd_versions[0];
516         else {
517                 s = n->string;
518                 if (0 == strcmp(s, "3"))
519                         p = bsd_versions[0];
520                 else if (0 == strcmp(s, "4"))
521                         p = bsd_versions[1];
522                 else if (0 == strcmp(s, "5"))
523                         p = bsd_versions[2];
524                 else if (0 == strcmp(s, "6"))
525                         p = bsd_versions[3];
526                 else if (0 == strcmp(s, "7"))
527                         p = bsd_versions[4];
528                 else
529                         p = bsd_versions[0];
530         }
531
532         if (m->meta.source)
533                 free(m->meta.source);
534
535         m->meta.source = mandoc_strdup(p);
536         return(1);
537 }
538
539 static int
540 post_AT(CHKARGS)
541 {
542         static const char * const unix_versions[] = {
543             "7th Edition",
544             "System III",
545             "System V",
546             "System V Release 2",
547         };
548
549         const char      *p, *s;
550         struct man_node *nn;
551
552         n = n->child;
553
554         if (NULL == n || MAN_TEXT != n->type)
555                 p = unix_versions[0];
556         else {
557                 s = n->string;
558                 if (0 == strcmp(s, "3"))
559                         p = unix_versions[0];
560                 else if (0 == strcmp(s, "4"))
561                         p = unix_versions[1];
562                 else if (0 == strcmp(s, "5")) {
563                         nn = n->next;
564                         if (nn && MAN_TEXT == nn->type && nn->string[0])
565                                 p = unix_versions[3];
566                         else
567                                 p = unix_versions[2];
568                 } else
569                         p = unix_versions[0];
570         }
571
572         if (m->meta.source)
573                 free(m->meta.source);
574
575         m->meta.source = mandoc_strdup(p);
576         return(1);
577 }