/* Copyright 2010-2023 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 "debug.h" #include "input.h" #include "text.h" #include "convert.h" #include "labels.h" #include "source_marks.h" /* Save 'menu_entry_node' extra keys. */ ELEMENT * register_extra_menu_entry_information (ELEMENT *current) { int i; ELEMENT *menu_entry_node = 0; for (i = 0; i < current->contents.number; i++) { ELEMENT *arg = current->contents.list[i]; if (arg->type == ET_menu_entry_name) { 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, 1); if (!parsed_entry_node->manual_content && !parsed_entry_node->node_content) { if (conf.show_menu) line_error ("empty node name in menu entry"); } else { menu_entry_node = arg; if (parsed_entry_node->node_content) add_extra_contents (arg, "node_content", parsed_entry_node->node_content); if (parsed_entry_node->manual_content) add_extra_contents (arg, "manual_content", parsed_entry_node->manual_content); } free (parsed_entry_node); } } return menu_entry_node; } /* Process the destination of the menu entry, and start a menu entry description. */ ELEMENT * enter_menu_entry_node (ELEMENT *current) { ELEMENT *description, *preformatted; ELEMENT *menu_entry_node; current->source_info = current_source_info; menu_entry_node = register_extra_menu_entry_information (current); if (menu_entry_node) remember_internal_xref (menu_entry_node); description = new_element (ET_menu_entry_description); add_to_element_contents (current, description); current = description; preformatted = new_element (ET_preformatted); add_to_element_contents (current, preformatted); current = 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_entry_separators (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 && last_contents_child(current)->text.end == 0) { ELEMENT *star; debug ("MENU STAR"); abort_empty_line (¤t, 0); line++; /* Past the '*'. */ star = new_element (ET_internal_menu_star); text_append (&star->text, "*"); add_to_element_contents (current, star); /* The ET_internal_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_internal_menu_star) { ELEMENT *menu_entry, *leading_text, *entry_name; ELEMENT *menu_star_element; int leading_spaces; debug ("MENU ENTRY (certainly)"); /* this is the menu star collected previously */ menu_star_element = pop_element_from_contents (current); leading_spaces = strspn (line, whitespace_chars); if (current->type == ET_preformatted && current->parent->type == ET_menu_comment) { /* Close ET_preformatted, and ET_menu_comment. */ current = close_container (current); current = close_container (current); } else { /* current should be ET_preformatted, 1st parent ET_menu_entry_description, 2nd parent ET_menu_entry, 3rd parent @menu. Close current, 1st and 2nd parent (which cannot be empty) */ current = close_container (current); current = close_container (current); current = close_container (current); } menu_entry = new_element (ET_menu_entry); leading_text = new_element (ET_menu_entry_leading_text); /* transfer source marks from removed menu star to leading text */ transfer_source_marks (menu_star_element, leading_text); destroy_element (menu_star_element); entry_name = new_element (ET_menu_entry_name); add_to_element_contents (current, menu_entry); add_to_element_contents (menu_entry, leading_text); add_to_element_contents (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_internal_menu_star) { debug_nonl ("ABORT MENU STAR before: "); debug_print_protected_string (line); debug (""); last_contents_child(current)->type = ET_NONE; } /* After a separator in a menu, end of menu entry node or menu entry name (. must be followed by a space to stop the node). */ else if (*line != '\0' && ((*line == ':' && current->type == ET_menu_entry_name) || (strchr (",\t.", *line) && current->type == ET_menu_entry_node))) { ELEMENT *e; char menu_separator = *line; line++; current = current->parent; e = new_element (ET_menu_entry_separator); text_append_n (&e->text, &menu_separator, 1); add_to_element_contents (current, e); /* Note, if a '.' is not followed by whitespace, we revert was was done here below. */ } /* After a separator in a menu */ else if (current->contents.number > 0 && last_contents_child (current)->type == ET_menu_entry_separator) { ELEMENT *last_child; char *separator; last_child = last_contents_child (current); separator = last_child->text.text; debug ("AFTER menu_entry_separator %s", separator); /* 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_contents (current); current = last_contents_child (current); merge_text (current, last_child->text.text, last_child); destroy_element (last_child); } /* here we collect spaces following separators. */ 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; } /* :: after a menu entry name => change to a menu entry node */ else if (!strncmp (separator, "::", 2)) { ELEMENT *entry_name; debug ("MENU NODE done (change from menu entry name) %s", separator); entry_name = contents_child_by_index (current, -2); /* Change from menu_entry_name (i.e. a label) to a menu entry node */ entry_name->type = ET_menu_entry_node; current = enter_menu_entry_node (current); } /* a :, but not ::, after a menu entry name => end of menu entry name */ else if (*separator == ':') { ELEMENT *entry_node; debug ("MENU ENTRY done %s", separator); entry_node = new_element (ET_menu_entry_node); add_to_element_contents (current, entry_node); current = entry_node; } else /* anything else corresponds to a separator that does not contain : and is after a menu node (itself following a menu_entry_name) */ { debug ("MENU NODE done %s", separator); current = enter_menu_entry_node (current); } } else retval = 0; *current_inout = current; *line_inout = line; return retval; } ELEMENT * end_line_menu_entry (ELEMENT *current) { ELEMENT *end_comment = 0; int empty_menu_entry_node = 0; if (current->type == ET_menu_entry_node) { ELEMENT *last = last_contents_child (current); if (current->contents.number > 0 && (last->cmd == CM_c || last->cmd == CM_comment)) { end_comment = pop_element_from_contents (current); } /* If contents empty or is all whitespace. */ if (current->contents.number == 0 || (current->contents.number == 1 && last->text.end > 0 && !last->text.text[strspn (last->text.text, whitespace_chars)])) { empty_menu_entry_node = 1; if (end_comment) add_to_element_contents (current, end_comment); } } /* Abort the menu entry if there is no destination node given. */ if (empty_menu_entry_node || current->type == ET_menu_entry_name) { ELEMENT *menu, *menu_entry, *description_or_menu_comment = 0; debug ("FINALLY NOT MENU ENTRY"); menu = current->parent->parent; menu_entry = pop_element_from_contents (menu); if (menu->contents.number > 0 && last_contents_child(menu)->type == ET_menu_entry) { ELEMENT *entry, *description = 0; int j; entry = last_contents_child(menu); for (j = entry->contents.number - 1; j >= 0; j--) { ELEMENT *e = contents_child_by_index (entry, j); if (e->type == ET_menu_entry_description) { description = e; break; } } if (description) description_or_menu_comment = description; else { ELEMENT *e; /* "Normally this cannot happen." */ bug ("no description in menu entry"); e = new_element (ET_menu_entry_description); add_to_element_contents (entry, e); description_or_menu_comment = e; } } else if (menu->contents.number > 0 && last_contents_child(menu)->type == ET_menu_comment) { description_or_menu_comment = last_contents_child(menu); } if (description_or_menu_comment) { current = description_or_menu_comment; if (current->contents.number > 0 && last_contents_child(current)->type == ET_preformatted) current = last_contents_child(current); else { ELEMENT *e; /* This should not happen */ bug ("description or menu comment not in preformatted"); e = new_element (ET_preformatted); add_to_element_contents (current, e); current = e; } } else { ELEMENT *e; e = new_element (ET_menu_comment); add_to_element_contents (menu, e); current = e; e = new_element (ET_preformatted); add_to_element_contents (current, e); current = e; debug ("THEN MENU_COMMENT OPEN"); } { int i, j; for (i = 0; i < menu_entry->contents.number; i++) { ELEMENT *arg = contents_child_by_index(menu_entry, i); if (arg->text.end > 0) current = merge_text (current, arg->text.text, arg); else { ELEMENT *e; for (j = 0; j < arg->contents.number; j++) { e = contents_child_by_index (arg, j); if (e->text.end > 0) { current = merge_text (current, e->text.text, e); destroy_element (e); } else { add_to_element_contents (current, e); } } } destroy_element (arg); } destroy_element (menu_entry); } } else { debug ("MENU ENTRY END LINE"); current = current->parent; current = enter_menu_entry_node (current); if (end_comment) add_to_element_contents (current, end_comment); } return current; }