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 (&current, 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