+
+ assert(j < (int)np->args->argc);
+
+ /*
+ * Accommodate for new-style groff column syntax. Shuffle the
+ * child nodes, all of which must be TEXT, as arguments for the
+ * column field. Then, delete the head children.
+ */
+
+ argv = np->args->argv + j;
+ i = argv->sz;
+ argv->sz += mdoc->last->nchild;
+ argv->value = mandoc_reallocarray(argv->value,
+ argv->sz, sizeof(char *));
+
+ mdoc->last->norm->Bl.ncols = argv->sz;
+ mdoc->last->norm->Bl.cols = (void *)argv->value;
+
+ for (nn = mdoc->last->child; nn; i++) {
+ argv->value[i] = nn->string;
+ nn->string = NULL;
+ nnp = nn;
+ nn = nn->next;
+ mdoc_node_delete(NULL, nnp);
+ }
+
+ mdoc->last->nchild = 0;
+ mdoc->last->child = NULL;
+
+ return(1);
+}
+
+static int
+post_bl(POST_ARGS)
+{
+ struct mdoc_node *nparent, *nprev; /* of the Bl block */
+ struct mdoc_node *nblock, *nbody; /* of the Bl */
+ struct mdoc_node *nchild, *nnext; /* of the Bl body */
+
+ nbody = mdoc->last;
+ switch (nbody->type) {
+ case MDOC_BLOCK:
+ return(post_bl_block(mdoc));
+ case MDOC_HEAD:
+ return(post_bl_head(mdoc));
+ case MDOC_BODY:
+ break;
+ default:
+ return(1);
+ }
+
+ bwarn_ge1(mdoc);
+
+ nchild = nbody->child;
+ while (NULL != nchild) {
+ if (MDOC_It == nchild->tok || MDOC_Sm == nchild->tok) {
+ nchild = nchild->next;
+ continue;
+ }
+
+ mandoc_msg(MANDOCERR_BL_MOVE, mdoc->parse,
+ nchild->line, nchild->pos,
+ mdoc_macronames[nchild->tok]);
+
+ /*
+ * Move the node out of the Bl block.
+ * First, collect all required node pointers.
+ */
+
+ nblock = nbody->parent;
+ nprev = nblock->prev;
+ nparent = nblock->parent;
+ nnext = nchild->next;
+
+ /*
+ * Unlink this child.
+ */
+
+ assert(NULL == nchild->prev);
+ if (0 == --nbody->nchild) {
+ nbody->child = NULL;
+ nbody->last = NULL;
+ assert(NULL == nnext);
+ } else {
+ nbody->child = nnext;
+ nnext->prev = NULL;
+ }
+
+ /*
+ * Relink this child.
+ */
+
+ nchild->parent = nparent;
+ nchild->prev = nprev;
+ nchild->next = nblock;
+
+ nblock->prev = nchild;
+ nparent->nchild++;
+ if (NULL == nprev)
+ nparent->child = nchild;
+ else
+ nprev->next = nchild;
+
+ nchild = nnext;
+ }
+
+ return(1);
+}
+
+static int
+post_bk(POST_ARGS)
+{
+
+ hwarn_eq0(mdoc);
+ bwarn_ge1(mdoc);
+ return(1);
+}
+
+static int
+ebool(struct mdoc *mdoc)
+{
+ struct mdoc_node *nch;
+ enum mdoct tok;
+
+ tok = mdoc->last->tok;
+ nch = mdoc->last->child;
+
+ if (NULL == nch) {
+ if (MDOC_Sm == tok)
+ mdoc->flags ^= MDOC_SMOFF;
+ return(1);
+ }
+
+ check_count(mdoc, MDOC_ELEM, CHECK_WARN, CHECK_LT, 2);
+
+ assert(MDOC_TEXT == nch->type);
+
+ if (0 == strcmp(nch->string, "on")) {
+ if (MDOC_Sm == tok)
+ mdoc->flags &= ~MDOC_SMOFF;
+ return(1);
+ }
+ if (0 == strcmp(nch->string, "off")) {
+ if (MDOC_Sm == tok)
+ mdoc->flags |= MDOC_SMOFF;
+ return(1);
+ }
+
+ mandoc_vmsg(MANDOCERR_SM_BAD,
+ mdoc->parse, nch->line, nch->pos,
+ "%s %s", mdoc_macronames[tok], nch->string);
+ return(mdoc_node_relink(mdoc, nch));
+}
+
+static int
+post_root(POST_ARGS)
+{
+ struct mdoc_node *n;
+
+ /* Add missing prologue data. */
+
+ if (mdoc->meta.date == NULL)
+ mdoc->meta.date = mdoc->quick ?
+ mandoc_strdup("") :
+ mandoc_normdate(mdoc->parse, NULL, 0, 0);
+
+ if (mdoc->meta.title == NULL) {
+ mandoc_msg(MANDOCERR_DT_NOTITLE,
+ mdoc->parse, 0, 0, "EOF");
+ mdoc->meta.title = mandoc_strdup("UNTITLED");
+ }
+
+ if (mdoc->meta.vol == NULL)
+ mdoc->meta.vol = mandoc_strdup("LOCAL");
+
+ if (mdoc->meta.os == NULL) {
+ mandoc_msg(MANDOCERR_OS_MISSING,
+ mdoc->parse, 0, 0, NULL);
+ mdoc->meta.os = mandoc_strdup("");
+ }
+
+ /* Check that we begin with a proper `Sh'. */
+
+ n = mdoc->first->child;
+ while (n != NULL && mdoc_macros[n->tok].flags & MDOC_PROLOGUE)
+ n = n->next;
+
+ if (n == NULL)
+ mandoc_msg(MANDOCERR_DOC_EMPTY, mdoc->parse, 0, 0, NULL);
+ else if (n->tok != MDOC_Sh)
+ mandoc_msg(MANDOCERR_SEC_BEFORE, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+
+ return(1);
+}
+
+static int
+post_st(POST_ARGS)
+{
+ struct mdoc_node *n, *nch;
+ const char *p;
+
+ n = mdoc->last;
+ nch = n->child;
+
+ if (NULL == nch) {
+ mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+ mdoc_node_delete(mdoc, n);
+ return(1);
+ }
+
+ assert(MDOC_TEXT == nch->type);
+
+ if (NULL == (p = mdoc_a2st(nch->string))) {
+ mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
+ nch->line, nch->pos, "St %s", nch->string);
+ mdoc_node_delete(mdoc, n);
+ } else {
+ free(nch->string);
+ nch->string = mandoc_strdup(p);
+ }
+
+ return(1);
+}
+
+static int
+post_rs(POST_ARGS)
+{
+ struct mdoc_node *nn, *next, *prev;
+ int i, j;
+
+ switch (mdoc->last->type) {
+ case MDOC_HEAD:
+ check_count(mdoc, MDOC_HEAD, CHECK_WARN, CHECK_EQ, 0);
+ return(1);
+ case MDOC_BODY:
+ if (mdoc->last->child)
+ break;
+ check_count(mdoc, MDOC_BODY, CHECK_WARN, CHECK_GT, 0);
+ return(1);
+ default:
+ return(1);
+ }
+
+ /*
+ * The full `Rs' block needs special handling to order the
+ * sub-elements according to `rsord'. Pick through each element
+ * and correctly order it. This is an insertion sort.
+ */
+
+ next = NULL;
+ for (nn = mdoc->last->child->next; nn; nn = next) {
+ /* Determine order of `nn'. */
+ for (i = 0; i < RSORD_MAX; i++)
+ if (rsord[i] == nn->tok)
+ break;
+
+ if (i == RSORD_MAX) {
+ mandoc_msg(MANDOCERR_RS_BAD,
+ mdoc->parse, nn->line, nn->pos,
+ mdoc_macronames[nn->tok]);
+ i = -1;
+ } else if (MDOC__J == nn->tok || MDOC__B == nn->tok)
+ mdoc->last->norm->Rs.quote_T++;
+
+ /*
+ * Remove `nn' from the chain. This somewhat
+ * repeats mdoc_node_unlink(), but since we're
+ * just re-ordering, there's no need for the
+ * full unlink process.
+ */
+
+ if (NULL != (next = nn->next))
+ next->prev = nn->prev;
+
+ if (NULL != (prev = nn->prev))
+ prev->next = nn->next;
+
+ nn->prev = nn->next = NULL;
+
+ /*
+ * Scan back until we reach a node that's
+ * ordered before `nn'.
+ */
+
+ for ( ; prev ; prev = prev->prev) {
+ /* Determine order of `prev'. */
+ for (j = 0; j < RSORD_MAX; j++)
+ if (rsord[j] == prev->tok)
+ break;
+ if (j == RSORD_MAX)
+ j = -1;
+
+ if (j <= i)
+ break;
+ }
+
+ /*
+ * Set `nn' back into its correct place in front
+ * of the `prev' node.
+ */
+
+ nn->prev = prev;
+
+ if (prev) {
+ if (prev->next)
+ prev->next->prev = nn;
+ nn->next = prev->next;
+ prev->next = nn;
+ } else {
+ mdoc->last->child->prev = nn;
+ nn->next = mdoc->last->child;
+ mdoc->last->child = nn;
+ }
+ }
+
+ return(1);
+}
+
+/*
+ * For some arguments of some macros,
+ * convert all breakable hyphens into ASCII_HYPH.
+ */
+static int
+post_hyph(POST_ARGS)
+{
+ struct mdoc_node *n, *nch;
+ char *cp;
+
+ n = mdoc->last;
+ switch (n->type) {
+ case MDOC_HEAD:
+ if (MDOC_Sh == n->tok || MDOC_Ss == n->tok)
+ break;
+ return(1);
+ case MDOC_BODY:
+ if (MDOC_D1 == n->tok || MDOC_Nd == n->tok)
+ break;
+ return(1);
+ case MDOC_ELEM:
+ break;
+ default:
+ return(1);
+ }
+
+ for (nch = n->child; nch; nch = nch->next) {
+ if (MDOC_TEXT != nch->type)
+ continue;
+ cp = nch->string;
+ if ('\0' == *cp)
+ continue;
+ while ('\0' != *(++cp))
+ if ('-' == *cp &&
+ isalpha((unsigned char)cp[-1]) &&
+ isalpha((unsigned char)cp[1]))
+ *cp = ASCII_HYPH;
+ }
+ return(1);
+}
+
+static int
+post_hyphtext(POST_ARGS)
+{
+
+ ewarn_ge1(mdoc);
+ return(post_hyph(mdoc));
+}
+
+static int
+post_ns(POST_ARGS)
+{
+
+ if (MDOC_LINE & mdoc->last->flags)
+ mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos, NULL);
+ return(1);
+}
+
+static int
+post_sh(POST_ARGS)
+{
+
+ post_ignpar(mdoc);
+
+ switch (mdoc->last->type) {
+ case MDOC_HEAD:
+ return(post_sh_head(mdoc));
+ case MDOC_BODY:
+ switch (mdoc->lastsec) {
+ case SEC_NAME:
+ return(post_sh_name(mdoc));
+ case SEC_SEE_ALSO:
+ return(post_sh_see_also(mdoc));
+ case SEC_AUTHORS:
+ return(post_sh_authors(mdoc));
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return(1);
+}
+
+static int
+post_sh_name(POST_ARGS)
+{
+ struct mdoc_node *n;
+
+ /*
+ * Warn if the NAME section doesn't contain the `Nm' and `Nd'
+ * macros (can have multiple `Nm' and one `Nd'). Note that the
+ * children of the BODY declaration can also be "text".
+ */
+
+ if (NULL == (n = mdoc->last->child)) {
+ mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
+ mdoc->last->line, mdoc->last->pos, "empty");
+ return(1);
+ }
+
+ for ( ; n && n->next; n = n->next) {
+ if (MDOC_ELEM == n->type && MDOC_Nm == n->tok)
+ continue;
+ if (MDOC_TEXT == n->type)
+ continue;
+ mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+ }
+
+ assert(n);
+ if (MDOC_BLOCK == n->type && MDOC_Nd == n->tok)
+ return(1);
+
+ mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
+ n->line, n->pos, mdoc_macronames[n->tok]);
+ return(1);
+}
+
+static int
+post_sh_see_also(POST_ARGS)
+{
+ const struct mdoc_node *n;
+ const char *name, *sec;
+ const char *lastname, *lastsec, *lastpunct;
+ int cmp;
+
+ n = mdoc->last->child;
+ lastname = lastsec = lastpunct = NULL;
+ while (n != NULL) {
+ if (n->tok != MDOC_Xr || n->nchild < 2)
+ break;
+
+ /* Process one .Xr node. */
+
+ name = n->child->string;
+ sec = n->child->next->string;
+ if (lastsec != NULL) {
+ if (lastpunct[0] != ',' || lastpunct[1] != '\0')
+ mandoc_vmsg(MANDOCERR_XR_PUNCT,
+ mdoc->parse, n->line, n->pos,
+ "%s before %s(%s)", lastpunct,
+ name, sec);
+ cmp = strcmp(lastsec, sec);
+ if (cmp > 0)
+ mandoc_vmsg(MANDOCERR_XR_ORDER,
+ mdoc->parse, n->line, n->pos,
+ "%s(%s) after %s(%s)", name,
+ sec, lastname, lastsec);
+ else if (cmp == 0 &&
+ strcasecmp(lastname, name) > 0)
+ mandoc_vmsg(MANDOCERR_XR_ORDER,
+ mdoc->parse, n->line, n->pos,
+ "%s after %s", name, lastname);
+ }
+ lastname = name;
+ lastsec = sec;
+
+ /* Process the following node. */
+
+ n = n->next;
+ if (n == NULL)