/* 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,
                                 options->COMMAND_LINE_ENCODING.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,
                    document->options->COMMAND_LINE_ENCODING.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;
}

