/* 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" #include "input.h" #include "text.h" #include "convert.h" #include "labels.h" /* Save 'menu_entry_name' 'menu_entry_node', and 'menu_entry_description' extra keys on the top-level @menu element. */ void register_extra_menu_entry_information (ELEMENT *current) { int i; for (i = 0; i < current->args.number; i++) { ELEMENT *arg = current->args.list[i]; if (arg->type == ET_menu_entry_name) { add_extra_element (current, "menu_entry_name", arg); if (arg->contents.number == 0) { char *texi = convert_to_texinfo (current); line_warn ("empty menu entry name in `%s'", texi); free (texi); } } else if (arg->type == ET_menu_entry_node) { NODE_SPEC_EXTRA *parsed_entry_node; isolate_last_space (arg); parsed_entry_node = parse_node_manual (arg); if (!parsed_entry_node) { if (conf.show_menu) line_error ("empty node name in menu entry"); } else add_extra_node_spec (current, "menu_entry_node", parsed_entry_node); } else if (arg->type == ET_menu_entry_description) { add_extra_element (current, "menu_entry_description", arg); } } } /* Process the destination of the menu entry, and start a menu entry description. */ ELEMENT * enter_menu_entry_node (ELEMENT *current) { ELEMENT *description, *preformatted; description = new_element (ET_menu_entry_description); add_to_element_args (current, description); register_extra_menu_entry_information (current); current->line_nr = line_nr; remember_internal_xref (current); current = description; preformatted = new_element (ET_preformatted); add_to_element_contents (current, preformatted); current = preformatted; push_context (ct_preformatted); return current; } /* Called from 'process_remaining_on_line' in parser.c. Return 1 if we find menu syntax to process, otherwise return 0. */ int handle_menu (ELEMENT **current_inout, char **line_inout) { ELEMENT *current = *current_inout; char *line = *line_inout; int retval = 1; /* A "*" at the start of a line beginning a menu entry. */ if (*line == '*' && current->type == ET_preformatted && (current->parent->type == ET_menu_comment || current->parent->type == ET_menu_entry_description) && current->contents.number > 0 && last_contents_child(current)->type == ET_empty_line) { ELEMENT *star; debug ("MENU STAR"); abort_empty_line (¤t, 0); line++; /* Past the '*'. */ star = new_element (ET_menu_star); text_append (&star->text, "*"); add_to_element_contents (current, star); /* The ET_menu_star element won't appear in the final tree. */ } /* A space after a "*" at the beginning of a line. */ else if (strchr (whitespace_chars, *line) && current->contents.number > 0 && last_contents_child(current)->type == ET_menu_star) { ELEMENT *menu_entry, *leading_text, *entry_name; int leading_spaces; debug ("MENU ENTRY (certainly)"); leading_spaces = strspn (line, whitespace_chars); destroy_element (pop_element_from_contents (current)); if (current->type == ET_preformatted && current->parent->type == ET_menu_comment) { ELEMENT *menu = current->parent->parent; /* Remove an empty ET_preformatted, and an empty ET_menu_comment. */ if (current->contents.number == 0) { pop_element_from_contents (current->parent); if (current->parent->contents.number == 0) { pop_element_from_contents (menu); destroy_element (current->parent); } destroy_element (current); } current = menu; } else { /* current should be ET_preformatted, 1st parent ET_menu_entry_description, 2nd parent ET_menu_entry, 3rd parent @menu. */ current = current->parent->parent->parent; } if (pop_context () != ct_preformatted) fatal ("preformatted context expected"); menu_entry = new_element (ET_menu_entry); leading_text = new_element (ET_menu_entry_leading_text); entry_name = new_element (ET_menu_entry_name); add_to_element_contents (current, menu_entry); add_to_element_args (menu_entry, leading_text); add_to_element_args (menu_entry, entry_name); current = entry_name; text_append (&leading_text->text, "*"); text_append_n (&leading_text->text, line, leading_spaces); line += leading_spaces; } /* A "*" followed by anything other than a space. */ else if (current->contents.number > 0 && last_contents_child(current)->type == ET_menu_star) { debug ("ABORT MENU STAR"); last_contents_child(current)->type = ET_NONE; } /* After a separator in a menu (which would have been added in handle_separator in separator.c). */ else if (current->args.number > 0 && last_args_child (current)->type == ET_menu_entry_separator) { ELEMENT *last_child; char *separator; last_child = last_args_child (current); separator = last_child->text.text; /* Separator is "::". */ if (!strcmp (separator, ":") && *line == ':') { text_append (&last_child->text, ":"); line++; /* Whitespace following the "::" is subsequently appended to the separator element. */ } /* A "." not followed by a space. Not a separator. */ else if (!strcmp (separator, ".") && !strchr (whitespace_chars, *line)) { pop_element_from_args (current); current = last_args_child (current); merge_text (current, last_child->text.text); destroy_element (last_child); } else if (strchr (whitespace_chars_except_newline, *line)) { int n; n = strspn (line, whitespace_chars_except_newline); text_append_n (&last_child->text, line, n); line += n; } else if (!strncmp (separator, "::", 2)) { ELEMENT *entry_name; debug ("MENU NODE no entry %s", separator); entry_name = args_child_by_index (current, -2); /* Change it from ET_menu_entry_name (i.e. the label). */ entry_name->type = ET_menu_entry_node; current = enter_menu_entry_node (current); } /* End of the label. Begin the element for the destination. */ else if (*separator == ':') { ELEMENT *entry_node; debug ("MENU ENTRY %s", separator); entry_node = new_element (ET_menu_entry_node); add_to_element_args (current, entry_node); current = entry_node; } else { debug ("MENU NODE"); current = enter_menu_entry_node (current); } } else retval = 0; *current_inout = current; *line_inout = line; return retval; }