/* Copyright 2010-2019 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "parser.h" /* Possibly print an error message, and return CURRENT->parent. */ static ELEMENT * close_brace_command (ELEMENT *current, enum command_id closed_command, enum command_id interrupting_command) { KEY_PAIR *k; if (current->cmd != CM_verb) goto yes; k = lookup_extra (current, "delimiter"); if (!k || !*(char *)k->value) goto yes; if (0) { yes: if (closed_command) command_error (current, "@end %s seen before @%s closing brace", command_name(closed_command), command_name(current->cmd)); else if (interrupting_command) command_error (current, "@%s seen before @%s closing brace", command_name(interrupting_command), command_name(current->cmd)); else command_error (current, "@%s missing closing brace", command_name(current->cmd)); } else { command_error (current, "@%s missing closing delimiter sequence: %s}", command_name(current->cmd), (char *)k->value); } current = current->parent; return current; } /* Close out any brace commands that mark text, not allowing multiple paragraphs. */ ELEMENT * close_all_style_commands (ELEMENT *current, enum command_id closed_command, enum command_id interrupting_command) { while (current->parent && (command_flags(current->parent) & CF_brace) && !(command_data(current->parent->cmd).data == BRACE_context)) current = close_brace_command (current->parent, closed_command, interrupting_command); return current; } void close_command_cleanup (ELEMENT *current) { if (!current->cmd) return; if (current->cmd == CM_multitable) { int in_head_or_rows = -1, i; ELEMENT_LIST old_contents = current->contents; /* Clear current contents. */ memset (¤t->contents, 0, sizeof (ELEMENT_LIST)); /* Rearrange the contents of the multitable to collect rows into ET_multitable_head and ET_multitable_body elements. */ for (i = 0; i < old_contents.number; i++) { ELEMENT *row = old_contents.list[i]; if (counter_value (&count_cells, row) != -1) counter_pop (&count_cells); if (row->type == ET_row) { /* Check if we need to open a new container. */ if (contents_child_by_index (row, 0)->cmd == CM_headitem) { if (in_head_or_rows <= 0) { add_to_element_contents (current, new_element (ET_multitable_head)); in_head_or_rows = 1; } } else if (contents_child_by_index (row, 0)->cmd == CM_item) { if (in_head_or_rows == 1 || in_head_or_rows == -1) { add_to_element_contents (current, new_element (ET_multitable_body)); in_head_or_rows = 0; } } add_to_element_contents (last_contents_child(current), row); } else { add_to_element_contents (current, row); in_head_or_rows = -1; } } free (old_contents.list); } else if (current->cmd == CM_itemize || current->cmd == CM_enumerate) { counter_pop (&count_items); } /* Put everything after the last @def*x command in a def_item type container. */ if (command_data(current->cmd).flags & CF_def) { gather_def_item (current, 0); } if (current->cmd == CM_table || current->cmd == CM_ftable || current->cmd == CM_vtable) { if (current->contents.number > 0) gather_previous_item (current, 0); } /* Block commands that contain @item's - e.g. @multitable, @table, @itemize. */ if (command_data(current->cmd).flags & CF_blockitem && current->contents.number > 0) { int have_leading_spaces = 0; ELEMENT *before_item = 0; if (current->contents.number >= 2 && current->contents.list[0]->type == ET_empty_line_after_command && current->contents.list[1]->type == ET_before_item) { have_leading_spaces = 1; before_item = current->contents.list[1]; } else if (current->contents.number >= 1 && current->contents.list[0]->type == ET_before_item) { before_item = current->contents.list[0]; } if (before_item) { /* Reparent @end from a ET_before_item to the block command */ KEY_PAIR *k = lookup_extra (current, "end_command"); ELEMENT *e = k ? k->value : 0; if (k && last_contents_child (before_item) && last_contents_child (before_item) == e) { add_to_element_contents (current, pop_element_from_contents (before_item)); } /* Now if the ET_before_item is empty, remove it. */ if (before_item->contents.number == 0) { destroy_element (remove_from_contents (current, have_leading_spaces ? 1 : 0)); } else /* Non-empty ET_before_item */ { int empty_before_item = 1, i; /* Check if contents consist soley of @comment's. */ for (i = 0; i < before_item->contents.number; i++) { enum command_id c = before_item->contents.list[i]->cmd; if (c != CM_c && c != CM_comment) { empty_before_item = 0; } } if (!empty_before_item) { int empty_format = 1; /* Check for an element that could represent an @item in the block. The type of this element will depend on the block command we are in. */ for (i = 0; i < current->contents.number; i++) { ELEMENT *e = current->contents.list[i]; if (e == before_item) continue; if (e->cmd != CM_NONE && (e->cmd != CM_c && e->cmd != CM_comment && e->cmd != CM_end) || e->type != ET_NONE && e->type != ET_empty_line_after_command) { empty_format = 0; break; } } if (empty_format) command_warn (current, "@%s has text but no @item", command_name(current->cmd)); } } } } } ELEMENT * close_current (ELEMENT *current, enum command_id closed_command, enum command_id interrupting_command) { /* Element is a command */ if (current->cmd) { debug ("CLOSING (close_current) %s", command_name(current->cmd)); if (command_flags(current) & CF_brace) { if (command_data(current->cmd).data == BRACE_context) pop_context (); current = close_brace_command (current, closed_command, interrupting_command); } else if (command_flags(current) & CF_block) { enum command_id cmd = current->cmd; ELEMENT *parent = 0; if (closed_command) { line_error ("`@end' expected `%s', but saw `%s'", command_name(current->cmd), command_name(closed_command)); } else if (interrupting_command) { line_error ("@%s seen before @end %s", command_name(interrupting_command), command_name(current->cmd)); } else { line_error ("no matching `@end %s'", command_name(current->cmd)); /* Ignored conditional. */ if (command_data(current->cmd).data == BLOCK_conditional) { parent = current->parent; destroy_element_and_children (pop_element_from_contents (parent)); } } if (command_data(cmd).flags & (CF_preformatted | CF_menu | CF_format_raw)) { pop_context (); } if (command_data(cmd).data == BLOCK_region) { pop_region (); } if (!parent) parent = current->parent; current = parent; } else { /* @item and @tab commands are closed here, as well as line commands with invalid content. */ current = current->parent; } } else if (current->type != ET_NONE) { enum context c; debug ("CLOSING type %s", element_type_names[current->type]); switch (current->type) { case ET_bracketed: command_error (current, "misplaced {"); if (current->contents.number > 0 && current->contents.list[0]->type == ET_empty_spaces_before_argument) { /* remove spaces element from tree and update extra values */ abort_empty_line (¤t, 0); } current = current->parent; break; case ET_menu_comment: case ET_menu_entry_description: c = pop_context (); if (c != ct_preformatted) fatal ("preformatted context expected"); /* Remove empty menu_comment */ if (current->type == ET_menu_comment && current->contents.number == 0) { current = current->parent; destroy_element (pop_element_from_contents (current)); } else current = current->parent; break; case ET_line_arg: case ET_block_line_arg: c = pop_context (); if (c != ct_line && c != ct_def) { /* error */ fatal ("line or def context expected"); } current = current->parent; break; default: current = current->parent; break; } } else { /* should never get here */ if (current->parent) current = current->parent; } return current; } /* Return lowest level ancestor of CURRENT containing a CLOSED_COMMAND element. Set CLOSED_ELEMENT to the element itself. INTERRUPTING is used in close_brace_command to display an error message. Remove a context from context stack if it was added by this command. */ ELEMENT * close_commands (ELEMENT *current, enum command_id closed_command, ELEMENT **closed_element, enum command_id interrupting) { *closed_element = 0; current = end_paragraph (current, closed_command, interrupting); current = end_preformatted (current, closed_command, interrupting); while (current->parent && (!closed_command || current->cmd != closed_command) /* Stop if in a root command. */ && !(current->cmd && command_flags(current) & CF_root)) { close_command_cleanup (current); current = close_current (current, closed_command, interrupting); } if (closed_command && current->cmd == closed_command) { if (command_data(current->cmd).flags & CF_preformatted) { if (pop_context () != ct_preformatted) fatal ("preformatted context expected"); } else if (command_data(current->cmd).flags & CF_format_raw) { if (pop_context () != ct_rawpreformatted) fatal ("rawpreformatted context expected"); // TODO: pop expanded formats stack } else if (command_data(current->cmd).flags & CF_menu) { enum context c; c = pop_context (); if (c != ct_menu && c != ct_preformatted) fatal ("menu or preformatted context expected"); } else if (current->cmd == CM_math || current->cmd == CM_displaymath) { enum context c; c = pop_context (); if (c != ct_math) fatal ("math context expected"); } if (command_data(current->cmd).data == BLOCK_region) pop_region (); *closed_element = current; current = current->parent; } else if (closed_command) { line_error ("unmatched `@end %s'", command_name(closed_command)); } return current; }