/* 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;
}