blob: e386008122382f842a4a6c32f8ebee88b108e3f0 [file] [log] [blame]
/* Copyright 2010-2026 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 <https://www.gnu.org/licenses/>. */
/* In sync with Texinfo::Transformations */
#include <config.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <iconv.h>
#include "unistr.h"
#include "text.h"
#include "command_ids.h"
#include "element_types.h"
#include "tree_types.h"
#include "document_types.h"
#include "types_data.h"
/* fatal */
#include "base_utils.h"
#include "tree.h"
#include "extra.h"
#include "structure_list.h"
#include "translations.h"
#include "builtin_commands.h"
#include "errors.h"
#include "debug.h"
/* indices_info_index_by_name section_level whitespace_chars
parse_line_directive lookup_index_entry ... */
#include "utils.h"
/* for copy_contents normalized_menu_entry_internal_node modify_tree
protect_colon_in_tree new_asis_command_with_text
protect_comma_in_tree protect_node_after_label_in_tree */
#include "manipulate_tree.h"
#include "convert_to_texinfo.h"
#include "targets.h"
#include "unicode.h"
#include "node_name_normalization.h"
#include "structuring.h"
#include "transformations.h"
/* In Texinfo::ManipulateTree */
void
protect_first_parenthesis (ELEMENT *element)
{
size_t i;
if (element->e.c->contents.number <= 0)
return;
for (i = 0; i < element->e.c->contents.number; i++)
{
ELEMENT *content = element->e.c->contents.list[i];
const char *p;
if (content->type != ET_normal_text || content->e.text->end == 0)
continue;
p = content->e.text->text;
if (*p == '(')
{
ELEMENT *new_command
= new_asis_command_with_text ("(", element, content->type);
ELEMENT *removed = 0;
/* count UTF-8 encoded Unicode characters for source marks locations */
uint8_t *u8_text = 0;
size_t current_position;
uint8_t *u8_p;
size_t u8_len;
if (content->source_mark_list)
{
u8_text = utf8_from_string (p);
u8_p = u8_text;
current_position = 0;
u8_len = u8_mbsnlen (u8_p, 1);
u8_p += u8_len;
current_position
= relocate_source_marks (content->source_mark_list,
new_command->e.c->contents.list[0]->e.c->contents.list[0],
current_position, u8_len);
destroy_element_empty_source_mark_list (content);
}
if (!*(p+1))
/* should be the same as content */
removed = remove_from_contents (element, i);
else
{
/* remove leading open brace */
text_reset (content->e.text);
text_append (content->e.text, p+1);
if (u8_text && content->source_mark_list)
{
SOURCE_MARK_LIST *source_mark_list = content->source_mark_list;
content->source_mark_list = 0;
u8_len = u8_mbsnlen (u8_p, u8_strlen (u8_p));
u8_p += u8_len;
current_position
= relocate_source_marks (source_mark_list,
content, current_position, u8_len);
free (source_mark_list->list);
free (source_mark_list);
}
}
insert_into_contents (element, new_command, i);
free (u8_text);
/* could destroy children too, but it should only be text, no
children */
if (removed)
destroy_element (removed);
}
return;
}
}
/* Add raise/lowersections to be back at the normal level from
the SECTION level. The raise/lowersections are added at the
end of PARENT.
If MODIFIER is set to -1, add raise/lowersections to go from
the normal level to the SECTION level.
*/
static void
correct_level (const ELEMENT *section, ELEMENT *parent, int modifier)
{
int status;
int section_modifier = lookup_extra_integer (section, AI_key_level_modifier,
&status);
if (status >= 0)
{
int level_to_remove;
enum command_id cmd;
int remaining_level;
level_to_remove = section_modifier * modifier;
if (level_to_remove < 0)
cmd = CM_raisesections;
else
cmd = CM_lowersections;
remaining_level = abs (level_to_remove);
while (remaining_level > 0)
{
ELEMENT *element = new_command_element (ET_line_command, cmd);
ELEMENT *line_args = new_element (ET_line_arg);
ELEMENT *spaces_after = new_element (ET_spaces_after_argument);
add_to_element_contents (parent, element);
text_append (spaces_after->e.text, "\n");
add_to_element_contents (element, line_args);
line_args->elt_info[eit_spaces_after_argument] = spaces_after;
remaining_level--;
}
}
}
/* COMMAND_HEADING_CONTENT is an element whose content can be used for the
heading of the sectioning command used to fill a gap in sectioning.
*/
ELEMENT_LIST *
fill_gaps_in_sectioning_in_document (DOCUMENT *document,
const ELEMENT *commands_heading_content)
{
ELEMENT_LIST *added_sections = new_list ();
size_t nr_current_section = 0;
size_t nr_next_section = 0;
size_t idx = 0;
size_t idx_current_section, idx_next_section;
ELEMENT *root = document->tree;
/* index in sections_list */
size_t section_idx = 0;
/* This loop initializes the index of the first and next section
in the tree root element contents list */
while (idx < root->e.c->contents.number)
{
const ELEMENT *content = root->e.c->contents.list[idx];
enum command_id data_cmd = element_builtin_data_cmd (content);
unsigned long flags = builtin_command_data[data_cmd].flags;
if (!data_cmd || data_cmd == CM_node || !(CF_root & flags))
{
}
else if (nr_current_section == 0)
nr_current_section = idx +1;
else if (nr_next_section == 0)
{
nr_next_section = idx +1;
break;
}
idx++;
}
if (nr_current_section == 0)
return 0;
if (nr_next_section == 0)
return added_sections;
idx_current_section = nr_current_section -1;
idx_next_section = nr_next_section -1;
while (1)
{
ELEMENT *current_section = root->e.c->contents.list[idx_current_section];
ELEMENT *next_section = root->e.c->contents.list[idx_next_section];
int current_section_level = section_level (current_section);
int next_section_level = section_level (next_section);
if (next_section_level - current_section_level > 1)
{
ELEMENT_LIST *new_sections = new_list ();
/* to handle the case of a section having its level raised or lowered,
we go back to the "normal" level with a correct_level call before
inserting the @-commands that outputs @raisesections or @lowersections
@-commands
After having done the insertion, another call to correct_level with
a modifier argument outputs complementary @raisesections or @lowersections
to come back to the level corresponding to the current section level */
correct_level (next_section, current_section, 1);
while (next_section_level - current_section_level > 1)
{
ELEMENT *line_content;
ELEMENT *new_section;
ELEMENT *spaces_before_argument
= new_text_element (ET_spaces_before_argument);
ELEMENT *line_arg = new_element (ET_line_arg);
ELEMENT *arguments_line = new_element (ET_arguments_line);
ELEMENT *spaces_after_argument
= new_text_element (ET_spaces_after_argument);
ELEMENT *empty_line = new_text_element (ET_empty_line);
current_section_level++;
new_section = new_command_element (ET_line_command,
level_to_structuring_command[CM_unnumbered][current_section_level]);
text_append (spaces_before_argument->e.text, " ");
new_section->elt_info[eit_spaces_before_argument]
= spaces_before_argument;
text_append (spaces_after_argument->e.text, "\n");
add_to_element_contents (new_section, arguments_line);
line_arg->elt_info[eit_spaces_after_argument]
= spaces_after_argument;
add_to_element_contents (arguments_line, line_arg);
if (commands_heading_content)
{
line_content = copy_contents (commands_heading_content, 0,
commands_heading_content->type);
}
else
{
ELEMENT *asis_command
= new_command_element (ET_brace_command, CM_asis);
ELEMENT *brace_container
= new_element (ET_brace_container);
add_to_element_contents (asis_command, brace_container);
line_content = asis_command;
}
add_to_element_contents (line_arg, line_content);
text_append (empty_line->e.text, "\n");
add_to_contents_as_array (new_section, empty_line);
insert_section_into_section_relations_list (
&document->sections_list,
new_section, section_idx +1);
section_idx++;
add_extra_integer (new_section, AI_key_section_number,
section_idx +1);
add_to_element_list (new_sections, new_section);
new_section->e.c->parent = root;
}
insert_list_slice_into_contents (root, idx_current_section+1,
new_sections, 0,
new_sections->number);
idx_next_section += new_sections->number;
insert_list_slice_into_list (added_sections,
added_sections->number,
new_sections, 0,
new_sections->number);
correct_level (next_section,
new_sections->list[new_sections->number -1], -1);
destroy_list (new_sections);
document->modified_information |= F_DOCM_tree | F_DOCM_sections_list;
}
idx_current_section = idx_next_section;
section_idx++;
add_extra_integer (next_section, AI_key_section_number, section_idx +1);
/* find the new next section index */
idx_next_section = idx_current_section +1;
while (idx_next_section < root->e.c->contents.number)
{
const ELEMENT *content = root->e.c->contents.list[idx_next_section];
enum command_id data_cmd = element_builtin_data_cmd (content);
unsigned long flags = builtin_command_data[data_cmd].flags;
if (data_cmd && data_cmd != CM_node && (CF_root & flags))
break;
idx_next_section++;
}
if (idx_next_section >= root->e.c->contents.number)
break;
}
return added_sections;
}
static void
relate_index_entries_to_table_items_in (ELEMENT *table,
DOCUMENT *document)
{
size_t i;
const INDEX_LIST *indices_info;
if (table->e.c->contents.number <= 0)
return;
indices_info = &document->indices_info;
for (i = 0; i < table->e.c->contents.number; i++)
{
ELEMENT *table_entry = table->e.c->contents.list[i];
ELEMENT *term;
ELEMENT *definition;
ELEMENT *item = 0;
if (table_entry->type != ET_table_entry
|| table_entry->e.c->contents.number <= 0)
continue;
term = table_entry->e.c->contents.list[0];
/*
Move any index entries from the start of a 'table_definition' to
the 'table_term'.
*/
if (table_entry->e.c->contents.number > 1
&& table_entry->e.c->contents.list[1]->type == ET_table_definition)
{
size_t nr_index_entry_command = 0;
size_t j;
definition = table_entry->e.c->contents.list[1];
for (j = 0; j < definition->e.c->contents.number; j++)
{
ELEMENT *child = definition->e.c->contents.list[j];
if (child->type == ET_index_entry_command)
{
child->e.c->parent = term;
nr_index_entry_command++;
}
else
break;
}
if (nr_index_entry_command > 0)
{
insert_slice_into_contents (term, 0, definition, 0,
nr_index_entry_command);
remove_slice_from_contents (definition, 0,
nr_index_entry_command);
document->modified_information |= F_DOCM_tree;
}
}
if (term->type == ET_table_term)
{
INDEX_ENTRY_AND_INDEX *entry_idx_info = 0;
size_t j;
/*
Relate the first index_entry_command in the 'table_term' to
the term itself.
*/
for (j = 0; j < term->e.c->contents.number; j++)
{
/* see gather_previous_item, CM_item a term can only contain
leading index entries that were left out of the
preceding table_entry, or from the beginning of the table
and the @item command line
*/
ELEMENT *content = term->e.c->contents.list[j];
if (content->type == ET_index_entry_command)
{
if (!entry_idx_info)
{
INDEX_ENTRY_AND_INDEX *idx_info;
const INDEX_ENTRY_LOCATION *index_entry_info
= lookup_extra_index_entry (content,
AI_key_index_entry);
idx_info = lookup_index_entry (index_entry_info,
indices_info);
if (idx_info->index_entry)
entry_idx_info = idx_info;
else
free (idx_info);
}
}
/* the command in the tree is CM_item, not CM_item_LINE */
else if (content->e.c->cmd == CM_item)
{
if (!item)
item = content;
}
if (item && entry_idx_info)
{
INDEX_ENTRY_LOCATION *index_entry;
/*
This is better than overwriting 'entry_element', which
holds important information.
*/
entry_idx_info->index_entry->entry_associated_element = item;
document->modified_information |= F_DOCM_tree
| F_DOCM_index_names;
/* also add a reference from element to index entry in index */
index_entry = (INDEX_ENTRY_LOCATION *)
malloc (sizeof (INDEX_ENTRY_LOCATION));
index_entry->index_name
= strdup (entry_idx_info->index->name);
index_entry->number = entry_idx_info->entry_number;
add_extra_index_entry (item, AI_key_associated_index_entry,
index_entry);
free (entry_idx_info);
break;
}
}
}
}
}
static ELEMENT_LIST *
relate_index_entries_to_table_items_internal (const char *type,
ELEMENT *current,
void *argument)
{
if (!(type_data[current->type].flags & TF_text)
&& current->e.c->cmd == CM_table)
{
DOCUMENT *document = (DOCUMENT *)argument;
relate_index_entries_to_table_items_in (current, document);
}
return 0;
}
void
relate_index_entries_to_table_items_in_document (DOCUMENT *document)
{
modify_tree (document->tree, &relate_index_entries_to_table_items_internal,
document);
}
/* in itemize or enumerate */
static void
move_index_entries_after_items (ELEMENT *current, DOCUMENT *document)
{
ELEMENT *previous = 0;
size_t i;
for (i = 0; i < current->e.c->contents.number; i++)
{
/* item can only be before_item, @item, or @end */
ELEMENT *item = current->e.c->contents.list[i];
if (previous && item->e.c->cmd && item->e.c->cmd == CM_item
&& previous->e.c->contents.number > 0)
{
ELEMENT *previous_ending_container;
ELEMENT *prev_last_child = last_contents_child (previous);
size_t last_entry_nr = 0;
size_t j;
size_t contents_nr;
if (prev_last_child->type == ET_paragraph
|| prev_last_child->type == ET_preformatted)
previous_ending_container = prev_last_child;
else
previous_ending_container = previous;
contents_nr = previous_ending_container->e.c->contents.number;
for (j = contents_nr; j > 0; j--)
{
ELEMENT *content
= previous_ending_container->e.c->contents.list[j-1];
if (content->type == ET_index_entry_command)
last_entry_nr = j;
else if (!(!(type_data[content->type].flags & TF_text)
&& (content->e.c->cmd == CM_comment
|| content->e.c->cmd == CM_c
/* subentry is not within the index entry in the tree */
|| content->e.c->cmd == CM_subentry)))
break;
}
if (last_entry_nr > 0)
{
size_t insertion_idx = 0;
size_t last_entry_idx = last_entry_nr -1;
size_t k;
ELEMENT *item_container;
if (item->e.c->contents.number
&& item->e.c->contents.list[0]->type == ET_preformatted)
item_container = item->e.c->contents.list[0];
else
item_container = item;
for (k = last_entry_idx; k < contents_nr; k++)
/* can only be index_entry_command or comment as gathered just above */
previous_ending_container->e.c->contents.list[k]->e.c->parent
= item_container;
if (item_container->e.c->contents.number
&& item_container->e.c->contents.list[0]->type
== ET_ignorable_spaces_after_command)
{
TEXT *t = item_container->e.c->contents.list[0]->e.text;
/*
insert after leading spaces, and add an end of line if there
is none
*/
insertion_idx = 1;
if (t->text[t->end -1] != '\n')
{
text_append (t, "\n");
}
}
insert_slice_into_contents (item_container, insertion_idx,
previous_ending_container,
last_entry_idx, contents_nr);
remove_slice_from_contents (previous_ending_container,
last_entry_idx, contents_nr);
document->modified_information |= F_DOCM_tree;
}
}
previous = item;
}
}
static ELEMENT_LIST *
move_index_entries_after_items_internal (const char *type,
ELEMENT *current,
void *argument)
{
if (!(type_data[current->type].flags & TF_text)
&& (current->e.c->cmd == CM_enumerate
|| current->e.c->cmd == CM_itemize))
{
DOCUMENT *document = (DOCUMENT *)argument;
move_index_entries_after_items (current, document);
}
return 0;
}
void
move_index_entries_after_items_in_document (DOCUMENT *document)
{
modify_tree (document->tree, &move_index_entries_after_items_internal,
document);
}
/* ERROR_MESSAGES is not actually useful, as the code checks that
the new node target label does not exist already.
node_tree is actually used as an element list, but we use an
element to be able to do the transformations in new_node.
*/
ELEMENT *
new_node (ERROR_MESSAGE_LIST *error_messages, ELEMENT *node_tree,
DOCUMENT *document)
{
const C_HASHMAP *identifiers_target = &document->identifiers_target;
int empty_node = 0;
int appended_number;
int new_line_at_end = 0;
TEXT spaces_after_argument;
ELEMENT *last_content;
ELEMENT *comment_at_end = 0;
ELEMENT *node = 0;
char *normalized;
/*
We protect for all the contexts, as the node name should be
the same in the different contexts, even if some protections
are not needed for the parsing. Also, this way the node tree
can be directly reused in the menus for example, without
additional protection, some parts could be double protected
otherwise, those that are protected with @asis.
needed in nodes lines, @*ref and in menus with a label
*/
node_tree = protect_comma_in_tree (node_tree);
/* always */
protect_first_parenthesis (node_tree);
/* in menu entry without label */
node_tree = protect_colon_in_tree (node_tree);
/* in menu entry with label */
node_tree = protect_node_after_label_in_tree (node_tree);
node_tree = reference_to_arg_in_tree (node_tree, document);
if (node_tree->e.c->contents.number <= 0)
{
ELEMENT *empty_text = new_text_element (ET_normal_text);
add_to_contents_as_array (node_tree, empty_text);
empty_node = 1;
}
last_content = last_contents_child (node_tree);
if (!(type_data[last_content->type].flags & TF_text)
&& (last_content->e.c->cmd == CM_c
|| last_content->e.c->cmd == CM_comment))
{
comment_at_end = pop_element_from_contents (node_tree);
last_content = last_contents_child (node_tree);
}
text_init (&spaces_after_argument);
text_append (&spaces_after_argument, "");
if (last_content && last_content->type == ET_normal_text
&& last_content->e.text->end > 0)
{
int end = last_content->e.text->end;
char *p = last_content->e.text->text + end-1;
for (; p >= last_content->e.text->text; p--)
{
if (!strchr (whitespace_chars, *p))
break;
if (*p == '\n')
new_line_at_end = 1;
end--;
}
text_append (&spaces_after_argument, p+1);
last_content->e.text->end = end;
}
if (!new_line_at_end && !comment_at_end)
text_append (&spaces_after_argument, "\n");
appended_number = 0+empty_node;
while (1)
{
size_t i;
char *non_hyphen_char;
ELEMENT *target = 0;
ELEMENT *appended_text = 0;
ELEMENT *arguments_line = new_element (ET_arguments_line);
ELEMENT *node_line_arg = new_element (ET_line_arg);
ELEMENT *spaces_before = new_text_element (ET_spaces_before_argument);
ELEMENT *spaces_after = new_text_element (ET_spaces_after_argument);
node = new_command_element (ET_line_command, CM_node);
add_to_element_contents (node, arguments_line);
add_to_element_contents (arguments_line, node_line_arg);
text_append (spaces_before->e.text, " ");
text_append (spaces_after->e.text, spaces_after_argument.text);
node->elt_info[eit_spaces_before_argument] = spaces_before;
node_line_arg->elt_info[eit_spaces_after_argument] = spaces_after;
if (comment_at_end)
node_line_arg->elt_info[eit_comment_at_end] = comment_at_end;
insert_slice_into_contents (node_line_arg, 0, node_tree, 0,
node_tree->e.c->contents.number);
for (i = 0; i < node_line_arg->e.c->contents.number; i++)
{
ELEMENT *content = node_line_arg->e.c->contents.list[i];
if (!(type_data[content->type].flags & TF_text))
content->e.c->parent = node_line_arg;
}
if (appended_number)
{
appended_text = new_text_element (ET_normal_text);
text_printf (appended_text->e.text, " %d", appended_number);
add_to_contents_as_array (node_line_arg, appended_text);
}
normalized = convert_contents_to_node_identifier (node_line_arg);
non_hyphen_char = normalized + strspn (normalized, "-");
if (*non_hyphen_char)
{
if (identifiers_target_number (identifiers_target))
{
target = find_identifier_target (identifiers_target,
normalized);
}
if (!target)
break;
}
free (normalized);
destroy_element (node_line_arg);
destroy_element (arguments_line);
if (appended_text)
destroy_element (appended_text);
destroy_element (node);
appended_number++;
}
add_extra_string (node, AI_key_normalized, normalized);
register_label_element (document, node, error_messages);
free (spaces_after_argument.text);
return node;
}
ELEMENT_LIST *
reassociate_to_node (const char *type, ELEMENT *current, void *argument)
{
NODE_RELATIONS_LIST *new_previous = (NODE_RELATIONS_LIST *) argument;
NODE_RELATIONS *new_node_relations = new_previous->list[0];
NODE_RELATIONS *previous_node_relations = new_previous->list[1];
ELEMENT *added_node = new_node_relations->element;
if (!(type_data[current->type].flags & TF_text))
{
if (current->e.c->cmd == CM_menu)
{
if (previous_node_relations)
{
CONST_ELEMENT_LIST *menus = previous_node_relations->menus;
size_t previous_nr = 0;
if (menus)
{
size_t i;
for (i = 0; i < menus->number; i++)
{
if (menus->list[i] == current)
{
previous_nr = i + 1;
break;
}
}
}
if (previous_nr == 0)
fprintf (stderr, "BUG: menu %p not in previous node %p\n",
current, previous_node_relations->element);
else
{
size_t previous_idx = previous_nr -1;
/* removed element should be current */
remove_from_const_element_list (menus, previous_idx);
if (menus->number <= 0)
{
destroy_const_element_list (menus);
previous_node_relations->menus = 0;
}
}
}
if (!new_node_relations->menus)
new_node_relations->menus = new_const_element_list ();
add_to_const_element_list (new_node_relations->menus, current);
}
/* following for index entries */
else if (current->e.c->cmd == CM_item || current->e.c->cmd == CM_itemx
|| current->type == ET_index_entry_command
|| (current->e.c->parent
&& current->e.c->parent->flags & EF_def_line))
{
const char *element_node
= lookup_extra_string (current, AI_key_element_node);
if (element_node && previous_node_relations)
{
const char *new_normalized
= lookup_extra_string (added_node, AI_key_normalized);
const ELEMENT *previous_node = previous_node_relations->element;
const char *previous_normalized
= lookup_extra_string (previous_node, AI_key_normalized);
if (strcmp(element_node, previous_normalized))
{
char *element_debug = print_element_debug (current, 0);
char *previous_node_texi
= root_heading_command_to_texinfo (previous_node);
fprintf (stderr,
"BUG: element %p not in previous node %p; %s\n"
" previous node: %s\n"
" current node identifier: %s\n", current,
previous_node, element_debug, previous_node_texi,
element_node);
free (element_debug);
free (previous_node_texi);
}
add_extra_string_dup (current, AI_key_element_node,
new_normalized);
}
}
else if (current->e.c->cmd == CM_nodedescription)
{
if (!new_node_relations->node_description)
new_node_relations->node_description = current;
if (previous_node_relations
&& previous_node_relations->node_description
&& previous_node_relations->node_description == current)
previous_node_relations->node_description = 0;
}
else if (current->e.c->cmd == CM_nodedescriptionblock)
{
if (!new_node_relations->node_long_description)
new_node_relations->node_long_description = current;
if (previous_node_relations
&& previous_node_relations->node_long_description
&& previous_node_relations->node_long_description == current)
previous_node_relations->node_long_description = 0;
}
}
return 0;
}
ELEMENT_LIST *
insert_nodes_for_sectioning_commands (DOCUMENT *document)
{
const SECTION_RELATIONS_LIST *sections_list = &document->sections_list;
ELEMENT *root = document->tree;
ELEMENT_LIST *added_nodes = new_list ();
size_t idx;
NODE_RELATIONS *previous_node_relations = 0;
size_t node_idx = 0;
for (idx = 0; idx < root->e.c->contents.number; idx++)
{
ELEMENT *content = root->e.c->contents.list[idx];
enum command_id data_cmd = element_builtin_data_cmd (content);
unsigned long flags = builtin_command_data[data_cmd].flags;
if (data_cmd && data_cmd != CM_node && data_cmd != CM_part
&& flags & CF_root)
{
int status;
size_t section_number
= lookup_extra_integer (content,
AI_key_section_number, &status);
SECTION_RELATIONS *section_relations
= sections_list->list[section_number -1];
if (!section_relations->associated_node)
{
ELEMENT *added_node;
/* NOTE new_node_tree content is copied in new_node */
ELEMENT *new_node_tree;
document->modified_information
|= F_DOCM_tree | F_DOCM_nodes_list;
if (content->e.c->cmd == CM_top)
{
ELEMENT *top_node_text = new_text_element (ET_normal_text);
new_node_tree = new_element (ET_NONE);
text_append (top_node_text->e.text, "Top");
add_to_contents_as_array (new_node_tree, top_node_text);
}
else
{
const ELEMENT *arguments_line
= content->e.c->contents.list[0];
const ELEMENT *line_arg
= arguments_line->e.c->contents.list[0];
new_node_tree = copy_contents (line_arg, 0, ET_NONE);
}
added_node = new_node (&document->error_messages, new_node_tree,
document);
destroy_element (new_node_tree);
if (added_node)
{
NODE_RELATIONS_LIST new_previous;
memset (&new_previous, 0, sizeof (NODE_RELATIONS_LIST));
reallocate_node_relations_for (2, &new_previous);
NODE_RELATIONS *new_node_relations;
insert_into_contents (root, added_node, idx);
idx++;
new_node_relations
= insert_node_into_node_relations_list (
&document->nodes_list,
added_node, node_idx);
new_node_relations->associated_section = section_relations;
node_idx++;
add_extra_integer (added_node, AI_key_node_number,
node_idx);
section_relations->associated_node = new_node_relations;
added_node->e.c->parent = content->e.c->parent;
/* reassociate index entries and menus that are
in the sectioning command contents */
new_previous.list[0] = new_node_relations;
new_previous.list[1] = previous_node_relations;
modify_tree (content, &reassociate_to_node,
(void *)&new_previous);
free (new_previous.list);
add_to_element_list (added_nodes, added_node);
}
}
}
if (content->e.c->cmd == CM_node)
{
int is_target = (content->flags & EF_is_target);
if (is_target)
{
previous_node_relations = document->nodes_list.list[node_idx];
node_idx++;
/* reset node index taking into account the added nodes */
add_extra_integer (content, AI_key_node_number,
node_idx);
}
}
}
return added_nodes;
}
/*
This converts a reference @-command to simple text using one of the
arguments. This is used to remove reference @-command from
constructed node names trees, as node names cannot contain
reference @-command while there could be some in the tree used in
input for the node name tree.
*/
ELEMENT_LIST *
reference_to_arg_internal (const char *type,
ELEMENT *e,
void *argument)
{
if (!(type_data[e->type].flags & TF_text) && e->e.c->cmd
&& builtin_command_data[e->e.c->cmd].flags & CF_ref)
{
DOCUMENT *document = (DOCUMENT *) argument;
int order_index = 0;
int *arguments_order = ref_5_args_order;
/* container for the new elements to insert, will be destroyed
by the caller */
ELEMENT_LIST *container = new_list ();
ELEMENT *new = new_element (ET_NONE);
new->e.c->parent = e->e.c->parent;
add_to_element_list (container, new);
if (e->e.c->cmd == CM_inforef || e->e.c->cmd == CM_link)
arguments_order = ref_3_args_order;
while (arguments_order[order_index] >= 0)
{
size_t idx = (size_t) arguments_order[order_index];
if (e->e.c->contents.number > idx)
{
ELEMENT *arg = e->e.c->contents.list[idx];
/*
this will not detect if the content expands as spaces only, like
@asis{ }, @ , but it is not an issue or could even be considered
as a feature.
*/
if (!is_content_empty (arg, 0))
{
ELEMENT *removed = remove_from_contents (e, idx);
size_t i;
if (removed != arg)
fatal ("BUG: reference_to_arg_internal removed != arg");
/* avoid the type and spaces by getting only the contents */
insert_slice_into_contents (new, 0,
removed, 0,
removed->e.c->contents.number);
for (i = 0; i < new->e.c->contents.number; i++)
{
ELEMENT *content = new->e.c->contents.list[i];
if (!(type_data[content->type].flags & TF_text))
content->e.c->parent = new;
}
destroy_element (removed);
break;
}
}
order_index++;
}
if (document && document->internal_references.number > 0)
{
const ELEMENT *removed_internal_ref =
replace_remove_list_element (&document->internal_references, e, 0);
if (removed_internal_ref)
document->modified_information |= F_DOCM_internal_references;
}
if (document)
document->modified_information |= F_DOCM_tree;
destroy_element_and_children (e);
return container;
}
else
return 0;
}
ELEMENT *
reference_to_arg_in_tree (ELEMENT *tree, DOCUMENT *document)
{
return modify_tree (tree, &reference_to_arg_internal, (void *) document);
}
void
reference_to_arg_in_document (DOCUMENT *document)
{
reference_to_arg_in_tree (document->tree, document);
}
void
prepend_new_menu_in_node_section (NODE_RELATIONS *node_relations,
ELEMENT *section,
ELEMENT *current_menu)
{
ELEMENT *empty_line = new_text_element (ET_empty_line);
if (!node_relations->menus)
node_relations->menus = new_const_element_list ();
add_to_element_contents (section, current_menu);
text_append (empty_line->e.text, "\n");
add_to_contents_as_array (section, empty_line);
add_to_const_element_list (node_relations->menus, current_menu);
}
typedef struct EXISTING_ENTRY {
const char *normalized;
ELEMENT *menu;
ELEMENT *entry;
} EXISTING_ENTRY;
static void
complete_node_menu (NODE_RELATIONS *node_relations,
int use_sections)
{
CONST_NODE_RELATIONS_LIST *node_childs
= get_node_node_childs_from_sectioning (node_relations);
if (node_childs->number)
{
size_t existing_entries_nr = 0;
size_t existing_entries_space = 5;
EXISTING_ENTRY *existing_entries = 0;
ELEMENT_LIST *pending = new_list ();
ELEMENT *current_menu = 0;
size_t i;
const CONST_ELEMENT_LIST *menus = node_relations->menus;
if (menus)
{
existing_entries
= malloc (existing_entries_space * sizeof (EXISTING_ENTRY));
for (i = 0; i < menus->number; i++)
{
const ELEMENT *menu = menus->list[i];
size_t j;
for (j = 0; j < menu->e.c->contents.number; j++)
{
ELEMENT *entry = menu->e.c->contents.list[j];
if (entry->type == ET_menu_entry)
{
const char *normalized_entry_node
= normalized_menu_entry_internal_node (entry);
if (normalized_entry_node)
{
if (existing_entries_nr == existing_entries_space)
{
existing_entries_space += 5;
existing_entries = realloc (existing_entries,
existing_entries_space * sizeof (EXISTING_ENTRY));
}
existing_entries[existing_entries_nr].normalized
= normalized_entry_node;
/* cast to remove the const, as the menu is to be modified */
existing_entries[existing_entries_nr].menu = (ELEMENT *)menu;
existing_entries[existing_entries_nr].entry = entry;
existing_entries_nr++;
}
}
}
}
}
for (i = 0; i < node_childs->number; i++)
{
const NODE_RELATIONS *node_entry_relations = node_childs->list[i];
const ELEMENT *node_entry = node_entry_relations->element;
const char *normalized = lookup_extra_string (node_entry,
AI_key_normalized);
if (normalized)
{
size_t j;
ELEMENT *entry = 0;
for (j = 0; j < existing_entries_nr; j++)
{
if (!strcmp (existing_entries[j].normalized, normalized))
{
current_menu = existing_entries[j].menu;
entry = existing_entries[j].entry;
break;
}
}
if (entry)
{
if (pending->number)
{
size_t k;
for (j = 0; j < current_menu->e.c->contents.number; j++)
if (current_menu->e.c->contents.list[j] == entry)
break;
insert_list_slice_into_contents (current_menu, j,
pending, 0,
pending->number);
for (k = 0; k < pending->number; k++)
pending->list[k]->e.c->parent = current_menu;
pending->number = 0;
}
}
else
{
entry = new_node_menu_entry (node_entry_relations,
use_sections);
/*
not set entry should mean an empty node. We do not warn as
we try, in general, to be silent in the transformations.
*/
if (entry)
add_to_element_list (pending, entry);
}
}
}
if (pending->number)
{
size_t j;
if (!current_menu)
{
/* cast to remove const, as the section is modified, with the new menu
inserted */
ELEMENT *section
= (ELEMENT *)node_relations->associated_section->element;
current_menu = new_command_element (ET_block_command, CM_menu);
insert_list_slice_into_contents (current_menu, 0,
pending, 0,
pending->number);
current_menu->e.c->parent = section;
new_block_command (current_menu);
prepend_new_menu_in_node_section (node_relations, section,
current_menu);
}
else
{
int offset_at_end = 0;
const ELEMENT *last_menu_content
= last_contents_child (current_menu);
if (!(type_data[last_menu_content->type].flags & TF_text)
&& last_menu_content->e.c->cmd == CM_end)
offset_at_end = -1;
insert_list_slice_into_contents (current_menu,
current_menu->e.c->contents.number + offset_at_end,
pending, 0, pending->number);
}
for (j = 0; j < pending->number; j++)
pending->list[j]->e.c->parent = current_menu;
}
destroy_list (pending);
free (existing_entries);
}
free (node_childs->list);
free (node_childs);
}
static NODE_RELATIONS_LIST *
get_non_automatic_nodes_with_sections (const DOCUMENT *document)
{
NODE_RELATIONS_LIST *non_automatic_nodes = new_node_relations_list ();
const NODE_RELATIONS_LIST *nodes_list = &document->nodes_list;
size_t i;
for (i = 0; i < nodes_list->number; i++)
{
NODE_RELATIONS *node_relations = nodes_list->list[i];
ELEMENT *node_element = node_relations->element;
if (node_element->e.c->contents.list[0]->e.c->contents.number <= 1
&& node_relations->associated_section)
{
add_to_node_relations_list (non_automatic_nodes, node_relations);
}
}
return non_automatic_nodes;
}
/* This should be called after structuring.c sectioning_structure */
void
complete_tree_nodes_menus_in_document (DOCUMENT *document, int use_sections)
{
NODE_RELATIONS_LIST *non_automatic_nodes
= get_non_automatic_nodes_with_sections (document);
size_t i;
for (i = 0; i < non_automatic_nodes->number; i++)
{
NODE_RELATIONS *node_relations = non_automatic_nodes->list[i];
complete_node_menu (node_relations, use_sections);
document->modified_information |= F_DOCM_tree | F_DOCM_nodes_list;
}
free (non_automatic_nodes->list);
free (non_automatic_nodes);
}
void
complete_tree_nodes_missing_menu (DOCUMENT *document, int use_sections)
{
const OPTIONS *options = document->options;
NODE_RELATIONS_LIST *non_automatic_nodes
= get_non_automatic_nodes_with_sections (document);
LANG_TRANSLATION *lang_translation = 0;
int debug_level = 0;
if (options)
{
lang_translation = new_lang_translation (
options->documentlanguage.o.string);
debug_level = options->DEBUG.o.integer;
}
size_t i;
for (i = 0; i < non_automatic_nodes->number; i++)
{
NODE_RELATIONS *node_relations = non_automatic_nodes->list[i];
const CONST_ELEMENT_LIST *menus = node_relations->menus;
if (!(menus && menus->number > 0))
{
const ELEMENT *section
= node_relations->associated_section->element;
ELEMENT *current_menu = new_complete_node_menu (node_relations,
document, lang_translation,
debug_level, use_sections);
if (current_menu)
{
prepend_new_menu_in_node_section (node_relations,
/* cast to remove const, as the section is modified, with the new menu
inserted */
(ELEMENT *)section,
current_menu);
document->modified_information |= F_DOCM_tree
| F_DOCM_nodes_list;
}
}
}
free (non_automatic_nodes->list);
free (non_automatic_nodes);
if (lang_translation)
{
free_lang_translation (lang_translation);
free (lang_translation);
}
}
int
regenerate_master_menu (DOCUMENT *document, int use_sections)
{
const C_HASHMAP *identifiers_target = &document->identifiers_target;
const NODE_RELATIONS_LIST *nodes_list = &document->nodes_list;
const ELEMENT *top_node = find_identifier_target (identifiers_target, "Top");
const CONST_ELEMENT_LIST *menus;
ELEMENT *new_detailmenu_e;
ELEMENT *last_menu;
const ELEMENT *last_content;
size_t i;
size_t index;
LANG_TRANSLATION *lang_translation = 0;
if (top_node)
{
int status;
size_t top_node_number
= lookup_extra_integer (top_node, AI_key_node_number, &status);
const NODE_RELATIONS *top_node_relations
= nodes_list->list[top_node_number -1];
menus = top_node_relations->menus;
if (!menus || (menus->number <= 0))
return 0;
}
else
return 0;
if (document->options)
lang_translation = new_lang_translation (
document->options->documentlanguage.o.string);
new_detailmenu_e = new_detailmenu (&document->error_messages,
document->options,
lang_translation, identifiers_target,
nodes_list,
menus, use_sections);
if (lang_translation)
{
free_lang_translation (lang_translation);
free (lang_translation);
}
/* no need for a master menu */
if (!new_detailmenu_e)
return 0;
document->modified_information |= F_DOCM_tree;
for (i = 0; i < menus->number; i++)
{
size_t current_idx;
/* cast to remove const to be able to replace the detailmenu */
ELEMENT *menu = (ELEMENT *)menus->list[i];
for (current_idx = 0; current_idx < menu->e.c->contents.number;
current_idx++)
{
/* entry should be one of the menu specific containers, a
@detailmenu or @end */
const ELEMENT *entry = menu->e.c->contents.list[current_idx];
if (entry->e.c->cmd == CM_detailmenu)
{
size_t j;
ELEMENT *removed = remove_from_contents (menu, current_idx);
/* also replace in global commands */
replace_remove_list_element (
&document->global_commands.detailmenu,
removed, new_detailmenu_e);
/* remove internal refs of removed entries */
for (j = 0; j < removed->e.c->contents.number; j++)
{
const ELEMENT *content = removed->e.c->contents.list[j];
if (content->type == ET_menu_entry)
{
size_t k;
for (k = 0; k < content->e.c->contents.number; k++)
{
const ELEMENT *entry_content
= content->e.c->contents.list[k];
if (entry_content->type == ET_menu_entry_node)
{
const ELEMENT *removed_internal_ref =
replace_remove_list_element (
&document->internal_references,
entry_content, 0);
if (removed_internal_ref)
document->modified_information
|= F_DOCM_internal_references;
else
{
char *removed_internal_texi
= convert_to_texinfo (entry_content);
fprintf (stderr,
"BUG: %s: not found in internal refs\n",
removed_internal_texi);
free (removed_internal_texi);
}
}
}
}
}
destroy_element_and_children (removed);
insert_into_contents (menu, new_detailmenu_e, current_idx);
return 1;
}
}
}
/* cast to remove const, as the detailmenu will be inserted in this menu */
last_menu = (ELEMENT *)menus->list[menus->number -1];
index = last_menu->e.c->contents.number;
last_content = last_contents_child (last_menu);
/* In a regular setting the last content is @end, but here we also
allow for a missing @end, including for an empty @menu. In that
case last_content could be one of the menu specific containers */
if (last_content && last_content->e.c->cmd == CM_end)
index--;
new_detailmenu_e->e.c->parent = last_menu;
if (index)
{
const ELEMENT *last_element = last_menu->e.c->contents.list[index-1];
ELEMENT *preformatted = 0;
if (last_element->type == ET_menu_comment
&& last_element->e.c->contents.number > 0)
{
ELEMENT *last_menu_comment_elt = last_contents_child (last_element);
if (last_menu_comment_elt->type == ET_preformatted)
preformatted = last_menu_comment_elt;
}
if (preformatted)
{
ELEMENT *empty_line = new_text_element (ET_empty_line);
text_append (empty_line->e.text, "\n");
add_to_contents_as_array (preformatted, empty_line);
}
else if (last_element->type == ET_menu_entry)
{
/*
there is a last menu entry, add a menu comment containing an empty line
after it
*/
ELEMENT *after_line
= new_text_element (ET_after_menu_description_line);
ELEMENT *menu_comment = new_element (ET_menu_comment);
insert_into_contents (last_menu, menu_comment, index);
index++;
preformatted = new_element (ET_preformatted);
add_to_element_contents (menu_comment, preformatted);
text_append (after_line->e.text, "\n");
add_to_contents_as_array (preformatted, after_line);
}
}
/* insert master menu */
insert_into_contents (last_menu, new_detailmenu_e, index);
add_to_element_list (&document->global_commands.detailmenu,
new_detailmenu_e);
return 1;
}
ELEMENT_LIST *
protect_hashchar_at_line_beginning_internal (const char *type,
ELEMENT *parent,
void *argument)
{
size_t i;
size_t parent_contents_nr;
if (type_data[parent->type].flags & TF_text
|| parent->e.c->contents.number == 0)
return 0;
parent_contents_nr = parent->e.c->contents.number;
for (i = 0; i < parent_contents_nr; i++)
{
ELEMENT *current = parent->e.c->contents.list[i];
if ((current->type == ET_normal_text || current->type == ET_raw)
&& current->e.text->end > 0)
{
char *filename;
int line_no = 0;
int status = 0;
filename = parse_line_directive (current->e.text->text, &status, &line_no);
if (status)
{
if (filename)
free (filename);
int do_protect = 0;
if (i == 0
|| (i == 1
&& parent->e.c->contents.list[0]->type == ET_arguments_line))
do_protect = 1;
else
{
ELEMENT *previous = parent->e.c->contents.list[i-1];
if (type_data[previous->type].flags & TF_text
&& previous->e.text->end > 0)
{
int end = previous->e.text->end;
if (previous->e.text->text[end -1] == '\n')
do_protect = 1;
}
}
if (do_protect)
{
/* do not actually protect in raw block command, but warn */
if (current->type == ET_raw)
{
ELEMENT *parent_for_warn = parent;
while (parent_for_warn)
{
if (parent_for_warn->e.c->cmd
&& parent_for_warn->e.c->source_info.line_nr)
{
DOCUMENT *document = (DOCUMENT *) argument;
const OPTIONS *options = document->options;
message_list_command_warn (
&document->error_messages,
(options && options->DEBUG.o.integer > 0),
parent_for_warn, 0,
"could not protect hash character in @%s",
builtin_command_name (parent_for_warn->e.c->cmd));
break;
}
parent_for_warn = parent_for_warn->e.c->parent;
}
}
else
{
char *current_text = strdup (current->e.text->text);
char *p = current_text;
size_t leading_spaces_nr;
ELEMENT *leading_spaces
= new_text_element (ET_normal_text);
ELEMENT *hashchar
= new_command_element (ET_brace_command,
CM_hashchar);
ELEMENT *arg = new_element (ET_brace_container);
/* count UTF-8 encoded Unicode characters for
source marks locations */
uint8_t *u8_text = 0;
size_t current_position;
const uint8_t *u8_p;
size_t u8_len;
SOURCE_MARK_LIST *source_mark_list;
if (current->source_mark_list)
{
source_mark_list = current->source_mark_list;
current->source_mark_list = 0;
u8_text = utf8_from_string (p);
u8_p = u8_text;
current_position = 0;
}
/* NOTE not exactly the perl code, but use similar
code as line directive parsing so should be ok */
leading_spaces_nr = strspn (p, " \t");
if (leading_spaces_nr)
{
p += leading_spaces_nr;
*p = '\0'; /* as a side note, it replaces the # */
text_append (leading_spaces->e.text, current_text);
}
if (u8_text)
{
u8_len = u8_mbsnlen (u8_p, leading_spaces_nr);
u8_p += u8_len;
current_position
= relocate_source_marks (source_mark_list,
leading_spaces,
current_position, u8_len);
}
if (leading_spaces_nr
|| leading_spaces->source_mark_list)
{
insert_into_contents_as_array (parent,
leading_spaces, i);
parent_contents_nr++;
i++;
}
else
destroy_element (leading_spaces);
/* advance past # */
p++;
hashchar->e.c->parent = parent;
add_to_element_contents (hashchar, arg);
insert_into_contents (parent, hashchar, i);
parent_contents_nr++;
i++;
if (u8_text)
{
u8_len = u8_mbsnlen (u8_p, 1);
u8_p += u8_len;
current_position
= relocate_source_marks (source_mark_list,
hashchar,
current_position, u8_len);
}
text_reset (current->e.text);
text_append (current->e.text, p);
free (current_text);
if (u8_text)
{
/* relocate all the remaining source marks */
u8_len = u8_mbsnlen (u8_p, u8_strlen (u8_p));
u8_p += u8_len;
current_position
= relocate_source_marks (source_mark_list,
current, current_position, u8_len);
free (source_mark_list->list);
free (source_mark_list);
free (u8_text);
}
}
}
}
}
}
return 0;
}
/* NOTE in perl there is a customization_information, but here we use the
document both for error registration and customization */
ELEMENT *
protect_hashchar_at_line_beginning (ELEMENT *tree, DOCUMENT *document)
{
return modify_tree (tree, &protect_hashchar_at_line_beginning_internal,
(void *) document);
}
void
protect_hashchar_at_line_beginning_in_document (DOCUMENT *document)
{
protect_hashchar_at_line_beginning (document->tree, document);
document->modified_information |= F_DOCM_tree;
}
ELEMENT_LIST *
protect_first_parenthesis_in_targets_internal (const char *type,
ELEMENT *current,
void *argument)
{
ELEMENT *element_label;
if (type_data[current->type].flags & TF_text)
return 0;
element_label = get_label_element (current);
if (element_label)
protect_first_parenthesis (element_label);
return 0;
}
void
protect_first_parenthesis_in_targets (ELEMENT *tree)
{
modify_tree (tree, &protect_first_parenthesis_in_targets_internal, 0);
}
void
protect_first_parenthesis_in_targets_in_document (DOCUMENT *document)
{
protect_first_parenthesis_in_targets (document->tree);
document->modified_information |= F_DOCM_tree;
}