1 /* Copyright 2010-2019 Free Software Foundation, Inc.
2
3 This program is free software: you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation, either version 3 of the License, or
6 (at your option) any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>. */
15
16 #include <config.h>
17 #include <stdlib.h>
18 #include <string.h>
19
20 #include "parser.h"
21 #include "input.h"
22 #include "text.h"
23 #include "convert.h"
24 #include "labels.h"
25
26 /* Save 'menu_entry_name' 'menu_entry_node', and 'menu_entry_description'
27 extra keys on the top-level @menu element. */
28 void
register_extra_menu_entry_information(ELEMENT * current)29 register_extra_menu_entry_information (ELEMENT *current)
30 {
31 int i;
32
33 for (i = 0; i < current->args.number; i++)
34 {
35 ELEMENT *arg = current->args.list[i];
36
37 if (arg->type == ET_menu_entry_name)
38 {
39 add_extra_element (current, "menu_entry_name", arg);
40 if (arg->contents.number == 0)
41 {
42 char *texi = convert_to_texinfo (current);
43 line_warn ("empty menu entry name in `%s'", texi);
44 free (texi);
45 }
46 }
47 else if (arg->type == ET_menu_entry_node)
48 {
49 NODE_SPEC_EXTRA *parsed_entry_node;
50
51 isolate_last_space (arg);
52
53 parsed_entry_node = parse_node_manual (arg);
54 if (!parsed_entry_node)
55 {
56 if (conf.show_menu)
57 line_error ("empty node name in menu entry");
58 }
59 else
60 add_extra_node_spec (current, "menu_entry_node",
61 parsed_entry_node);
62 }
63 else if (arg->type == ET_menu_entry_description)
64 {
65 add_extra_element (current, "menu_entry_description", arg);
66 }
67 }
68 }
69
70 /* Process the destination of the menu entry, and start a menu entry
71 description. */
72 ELEMENT *
enter_menu_entry_node(ELEMENT * current)73 enter_menu_entry_node (ELEMENT *current)
74 {
75 ELEMENT *description, *preformatted;
76
77 description = new_element (ET_menu_entry_description);
78 add_to_element_args (current, description);
79 register_extra_menu_entry_information (current);
80 current->line_nr = line_nr;
81 remember_internal_xref (current);
82
83 current = description;
84 preformatted = new_element (ET_preformatted);
85 add_to_element_contents (current, preformatted);
86 current = preformatted;
87 push_context (ct_preformatted);
88 return current;
89 }
90
91 /* Called from 'process_remaining_on_line' in parser.c. Return 1 if we find
92 menu syntax to process, otherwise return 0. */
93 int
handle_menu(ELEMENT ** current_inout,char ** line_inout)94 handle_menu (ELEMENT **current_inout, char **line_inout)
95 {
96 ELEMENT *current = *current_inout;
97 char *line = *line_inout;
98 int retval = 1;
99
100 /* A "*" at the start of a line beginning a menu entry. */
101 if (*line == '*'
102 && current->type == ET_preformatted
103 && (current->parent->type == ET_menu_comment
104 || current->parent->type == ET_menu_entry_description)
105 && current->contents.number > 0
106 && last_contents_child(current)->type == ET_empty_line)
107 {
108 ELEMENT *star;
109
110 debug ("MENU STAR");
111 abort_empty_line (¤t, 0);
112 line++; /* Past the '*'. */
113
114 star = new_element (ET_menu_star);
115 text_append (&star->text, "*");
116 add_to_element_contents (current, star);
117
118 /* The ET_menu_star element won't appear in the final tree. */
119 }
120 /* A space after a "*" at the beginning of a line. */
121 else if (strchr (whitespace_chars, *line)
122 && current->contents.number > 0
123 && last_contents_child(current)->type == ET_menu_star)
124 {
125 ELEMENT *menu_entry, *leading_text, *entry_name;
126 int leading_spaces;
127
128 debug ("MENU ENTRY (certainly)");
129 leading_spaces = strspn (line, whitespace_chars);
130
131 destroy_element (pop_element_from_contents (current));
132
133 if (current->type == ET_preformatted
134 && current->parent->type == ET_menu_comment)
135 {
136 ELEMENT *menu = current->parent->parent;
137
138 /* Remove an empty ET_preformatted, and an empty ET_menu_comment. */
139 if (current->contents.number == 0)
140 {
141 pop_element_from_contents (current->parent);
142 if (current->parent->contents.number == 0)
143 {
144 pop_element_from_contents (menu);
145 destroy_element (current->parent);
146 }
147 destroy_element (current);
148 }
149
150 current = menu;
151 }
152 else
153 {
154 /* current should be ET_preformatted,
155 1st parent ET_menu_entry_description,
156 2nd parent ET_menu_entry,
157 3rd parent @menu. */
158 current = current->parent->parent->parent;
159 }
160
161 if (pop_context () != ct_preformatted)
162 fatal ("preformatted context expected");
163
164 menu_entry = new_element (ET_menu_entry);
165 leading_text = new_element (ET_menu_entry_leading_text);
166 entry_name = new_element (ET_menu_entry_name);
167 add_to_element_contents (current, menu_entry);
168 add_to_element_args (menu_entry, leading_text);
169 add_to_element_args (menu_entry, entry_name);
170 current = entry_name;
171
172 text_append (&leading_text->text, "*");
173 text_append_n (&leading_text->text, line, leading_spaces);
174 line += leading_spaces;
175 }
176 /* A "*" followed by anything other than a space. */
177 else if (current->contents.number > 0
178 && last_contents_child(current)->type == ET_menu_star)
179 {
180 debug ("ABORT MENU STAR");
181 last_contents_child(current)->type = ET_NONE;
182 }
183 /* After a separator in a menu (which would have been added in
184 handle_separator in separator.c). */
185 else if (current->args.number > 0
186 && last_args_child (current)->type == ET_menu_entry_separator)
187 {
188 ELEMENT *last_child;
189 char *separator;
190
191 last_child = last_args_child (current);
192 separator = last_child->text.text;
193
194 /* Separator is "::". */
195 if (!strcmp (separator, ":") && *line == ':')
196 {
197 text_append (&last_child->text, ":");
198 line++;
199 /* Whitespace following the "::" is subsequently appended to
200 the separator element. */
201 }
202 /* A "." not followed by a space. Not a separator. */
203 else if (!strcmp (separator, ".") && !strchr (whitespace_chars, *line))
204 {
205 pop_element_from_args (current);
206 current = last_args_child (current);
207 merge_text (current, last_child->text.text);
208 destroy_element (last_child);
209 }
210 else if (strchr (whitespace_chars_except_newline, *line))
211 {
212 int n;
213
214 n = strspn (line, whitespace_chars_except_newline);
215 text_append_n (&last_child->text, line, n);
216 line += n;
217 }
218 else if (!strncmp (separator, "::", 2))
219 {
220 ELEMENT *entry_name;
221
222 debug ("MENU NODE no entry %s", separator);
223 entry_name = args_child_by_index (current, -2);
224
225 /* Change it from ET_menu_entry_name (i.e. the label). */
226 entry_name->type = ET_menu_entry_node;
227 current = enter_menu_entry_node (current);
228 }
229 /* End of the label. Begin the element for the destination. */
230 else if (*separator == ':')
231 {
232 ELEMENT *entry_node;
233
234 debug ("MENU ENTRY %s", separator);
235 entry_node = new_element (ET_menu_entry_node);
236 add_to_element_args (current, entry_node);
237 current = entry_node;
238 }
239 else
240 {
241 debug ("MENU NODE");
242 current = enter_menu_entry_node (current);
243 }
244 }
245 else
246 retval = 0;
247
248 *current_inout = current;
249 *line_inout = line;
250
251 return retval;
252 }
253