texinfo-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

branch master updated: * tp/Texinfo/ManipulateTree.pm, tp/Texinfo/Common


From: Patrice Dumas
Subject: branch master updated: * tp/Texinfo/ManipulateTree.pm, tp/Texinfo/Common.pm, tp/Makefile.am (dist_modules_DATA), doc/tp_api/Makefile.am (texi2any_internals_dependencies), : move tree transformation functions depending on modify_tree from Texinfo/Common.pm to the new Texinfo/ManipulateTree.pm module. Update callers.
Date: Sat, 13 Apr 2024 15:53:56 -0400

This is an automated email from the git hooks/post-receive script.

pertusus pushed a commit to branch master
in repository texinfo.

The following commit(s) were added to refs/heads/master by this push:
     new 7f4f9a2dc2 * tp/Texinfo/ManipulateTree.pm, tp/Texinfo/Common.pm, 
tp/Makefile.am (dist_modules_DATA), doc/tp_api/Makefile.am 
(texi2any_internals_dependencies), : move tree transformation functions 
depending on modify_tree from Texinfo/Common.pm to the new 
Texinfo/ManipulateTree.pm module.  Update callers.
7f4f9a2dc2 is described below

commit 7f4f9a2dc2717d191e99e3015e1f42a0acc87a79
Author: Patrice Dumas <pertusus@free.fr>
AuthorDate: Sat Apr 13 21:53:47 2024 +0200

    * tp/Texinfo/ManipulateTree.pm, tp/Texinfo/Common.pm,
    tp/Makefile.am (dist_modules_DATA), doc/tp_api/Makefile.am
    (texi2any_internals_dependencies), : move tree transformation
    functions depending on modify_tree from Texinfo/Common.pm to the new
    Texinfo/ManipulateTree.pm module.  Update callers.
---
 ChangeLog                                    |   8 +
 Pod-Simple-Texinfo/lib/Pod/Simple/Texinfo.pm |  11 +-
 doc/tp_api/Makefile.am                       |   1 +
 tp/Makefile.am                               |   1 +
 tp/Texinfo/Common.pm                         | 808 +-----------------------
 tp/Texinfo/Convert/HTML.pm                   |   3 +-
 tp/Texinfo/ManipulateTree.pm                 | 900 +++++++++++++++++++++++++++
 tp/Texinfo/Structuring.pm                    |  27 +-
 tp/Texinfo/Transformations.pm                |  35 +-
 tp/Texinfo/Translations.pm                   |  13 +-
 tp/t/index_before_item.t                     |   6 +-
 tp/t/protect_character_in_texinfo.t          |   9 +-
 tp/t/test_protect_contents.t                 |   1 -
 tp/t/test_tree_copy.t                        |   9 +-
 tp/t/test_utils.pl                           |  12 +-
 tp/texi2any.pl                               |   5 +-
 16 files changed, 988 insertions(+), 861 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 5820cd6193..64fd654361 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2024-04-13  Patrice Dumas  <pertusus@free.fr>
+
+       * tp/Texinfo/ManipulateTree.pm, tp/Texinfo/Common.pm,
+       tp/Makefile.am (dist_modules_DATA), doc/tp_api/Makefile.am
+       (texi2any_internals_dependencies), : move tree transformation
+       functions depending on modify_tree from Texinfo/Common.pm to the new
+       Texinfo/ManipulateTree.pm module.  Update callers.
+
 2024-04-13  Patrice Dumas  <pertusus@free.fr>
 
        * tp/Texinfo/XS/structuring_transfo/structuring.c: make some functions
diff --git a/Pod-Simple-Texinfo/lib/Pod/Simple/Texinfo.pm 
b/Pod-Simple-Texinfo/lib/Pod/Simple/Texinfo.pm
index 2657b06e3a..1b3345cddd 100644
--- a/Pod-Simple-Texinfo/lib/Pod/Simple/Texinfo.pm
+++ b/Pod-Simple-Texinfo/lib/Pod/Simple/Texinfo.pm
@@ -52,9 +52,8 @@ use Texinfo::Convert::NodeNameNormalization 
qw(convert_to_identifier);
 use Texinfo::Parser qw(parse_texi_line parse_texi_piece);
 use Texinfo::Convert::Texinfo;
 use Texinfo::Convert::TextContent;
-use Texinfo::Common qw(protect_colon_in_tree protect_comma_in_tree
-                                         protect_first_parenthesis);
 use Texinfo::Document;
+use Texinfo::ManipulateTree;
 use Texinfo::Transformations qw(protect_hashchar_at_line_beginning
                                           reference_to_arg_in_tree);
 
@@ -354,7 +353,7 @@ sub _protect_comma($)
 {
   my $texinfo = shift;
   my $tree = parse_texi_line(undef, $texinfo);
-  protect_comma_in_tree($tree);
+  Texinfo::ManipulateTree::protect_comma_in_tree($tree);
   $tree = Texinfo::Document::rebuild_tree($tree);
   return Texinfo::Convert::Texinfo::convert_to_texinfo($tree);
 }
@@ -363,7 +362,7 @@ sub _protect_colon($)
 {
   my $texinfo = shift;
   my $tree = parse_texi_line(undef, $texinfo);
-  protect_colon_in_tree($tree);
+  Texinfo::ManipulateTree::protect_colon_in_tree($tree);
   $tree = Texinfo::Document::rebuild_tree($tree);
   return Texinfo::Convert::Texinfo::convert_to_texinfo($tree);
 }
@@ -500,8 +499,8 @@ sub _prepare_anchor($$)
     $texinfo_node_name = "$node $number_appended";
     $node_tree = parse_texi_line(undef, $texinfo_node_name);
   }
-  protect_comma_in_tree($node_tree);
-  protect_colon_in_tree($node_tree);
+  Texinfo::ManipulateTree::protect_comma_in_tree($node_tree);
+  Texinfo::ManipulateTree::protect_colon_in_tree($node_tree);
   $node_tree = Texinfo::Document::rebuild_tree($node_tree);
   $self->{'texinfo_nodes'}->{$normalized} = $node_tree;
   my $final_node_name = 
Texinfo::Convert::Texinfo::convert_to_texinfo($node_tree);
diff --git a/doc/tp_api/Makefile.am b/doc/tp_api/Makefile.am
index 946fd389b0..b8c9c27bc7 100644
--- a/doc/tp_api/Makefile.am
+++ b/doc/tp_api/Makefile.am
@@ -53,6 +53,7 @@ texi2any_internals_dependencies = \
   $(top_srcdir)/tp/Texinfo/Common.pm \
   $(top_srcdir)/tp/Texinfo/ParserNonXS.pm \
   $(top_srcdir)/tp/Texinfo/Document.pm \
+  $(top_srcdir)/tp/Texinfo/ManipulateTree.pm \
   $(top_srcdir)/tp/Texinfo/Structuring.pm \
   $(top_srcdir)/tp/Texinfo/Report.pm \
   $(top_srcdir)/tp/Texinfo/Translations.pm \
diff --git a/tp/Makefile.am b/tp/Makefile.am
index d8a8900ba3..4ffc1b3d64 100644
--- a/tp/Makefile.am
+++ b/tp/Makefile.am
@@ -74,6 +74,7 @@ dist_modules_DATA = \
  Texinfo/Documentlanguages.pm \
  Texinfo/Indices.pm \
  Texinfo/IndicesXS.pm \
+ Texinfo/ManipulateTree.pm \
  Texinfo/MiscXS.pm \
  Texinfo/Options.pm \
  Texinfo/Parser.pm \
diff --git a/tp/Texinfo/Common.pm b/tp/Texinfo/Common.pm
index 975a23aaf4..528dcdd733 100644
--- a/tp/Texinfo/Common.pm
+++ b/tp/Texinfo/Common.pm
@@ -20,15 +20,15 @@
 
 package Texinfo::Common;
 
+# for unicode/layer support in binmode
+# for binmode documented as pushing :utf8 on top of :encoding
+use 5.008001;
+
 use strict;
 
 # To check if there is no erroneous autovivification
 #no autovivification qw(fetch delete exists store strict);
 
-# for unicode/layer support in binmode
-# for binmode documented as pushing :utf8 on top of :encoding
-use 5.008001;
-
 # to determine the null file
 use Config;
 use File::Spec;
@@ -43,13 +43,6 @@ use Carp qw(cluck confess);
 
 use Locale::Messages;
 
-# FIXME do we really want XS in Common.pm?  Move overriden functions
-# to other modules?
-use Texinfo::DocumentXS;
-use Texinfo::StructTransfXS;
-
-use Texinfo::XSLoader;
-
 use Texinfo::Documentlanguages;
 use Texinfo::Commands;
 use Texinfo::Options;
@@ -61,12 +54,6 @@ use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
 %EXPORT_TAGS = ( 'all' => [ qw(
 collect_commands_in_tree
 collect_commands_list_in_tree
-move_index_entries_after_items_in_tree
-relate_index_entries_to_table_items_in_tree
-protect_colon_in_tree
-protect_comma_in_tree
-protect_first_parenthesis
-protect_node_after_label_in_tree
 valid_customization_option
 valid_tree_transformation
 ) ] );
@@ -81,37 +68,6 @@ __ __p
 
 $VERSION = '7.1dev';
 
-my $XS_structuring = Texinfo::XSLoader::XS_structuring_enabled();
-
-our %XS_overrides = (
-  "Texinfo::Common::copy_tree"
-    => "Texinfo::StructTransfXS::copy_tree",
-  "Texinfo::Common::relate_index_entries_to_table_items_in_tree"
-    => "Texinfo::StructTransfXS::relate_index_entries_to_table_items_in_tree",
-  "Texinfo::Common::move_index_entries_after_items_in_tree"
-    => "Texinfo::StructTransfXS::move_index_entries_after_items_in_tree",
-  "Texinfo::Common::protect_colon_in_tree"
-    => "Texinfo::StructTransfXS::protect_colon_in_tree",
-  "Texinfo::Common::protect_comma_in_tree"
-    => "Texinfo::StructTransfXS::protect_comma_in_tree",
-  "Texinfo::Common::protect_node_after_label_in_tree"
-    => "Texinfo::StructTransfXS::protect_node_after_label_in_tree",
-);
-
-our $module_loaded = 0;
-sub import {
-  if (!$module_loaded) {
-    if ($XS_structuring) {
-      for my $sub (keys %XS_overrides) {
-        Texinfo::XSLoader::override ($sub, $XS_overrides{$sub});
-      }
-    }
-    $module_loaded = 1;
-  }
-  # The usual import method
-  goto &Exporter::import;
-}
-
 # i18n
 # For the messages translations.
 my $messages_textdomain = 'texinfo';
@@ -1860,724 +1816,6 @@ sub _collect_commands_list_in_tree($$$)
 }
 
 
-# functions useful for Texinfo tree transformations
-# and some tree transformations functions, mostly those
-# used in conversion to main output formats.  In general,
-# tree transformations functions are documented in the POD section.
-
-# Some helper functions defined here are used in other
-# modules but are not generally useful in converters
-# and therefore not public.
-
-
-# This implementation of tree copy is designed such as to be
-# implemntable easily in XS with reference to copy local to
-# the element and not in a hash
-
-# It is important to go through the tree in the same order
-# in _copy_tree and _copy_extra_info, to be sure that elements already
-# seen are the same in both cases, such that _counter is at 0 in
-# _copy_extra_info when all the dependent elements have been seen
-# and going through the target element.
-
-# the *_directions extra items are not elements, they contain
-# up, next and prev that point to elements.
-# it could also have been possible to determine that it is
-# an extra_directions if the keys are only up, next and prev
-my %extra_directions;
-foreach my $type ('menu', 'node', 'section', 'toplevel') {
-  $extra_directions{$type.'_directions'} = 1;
-}
-
-sub _copy_tree($$);
-sub _copy_tree($$)
-{
-  my $current = shift;
-  my $parent = shift;
-
-  # either a duplicate in a tree (should be rare/avoided) or an
-  # element referred to in extra/info, either directly or
-  # (probably rare) in the extra element tree that has already
-  # been seen in the tree
-  if ($current->{'_copy'}) {
-    #print STDERR "RCT $current ".debug_print_element($current)
-    # .": $current->{'_counter'}\n";
-    $current->{'_copy'}->{'parent'} = $parent
-        if (not $current->{'_copy'}->{'parent'} and $parent);
-    return $current->{'_copy'};
-  }
-
-  my $new = {};
-  $new->{'parent'} = $parent if ($parent);
-  foreach my $key ('type', 'cmdname', 'text') {
-    $new->{$key} = $current->{$key} if (exists($current->{$key}));
-  }
-
-  $current->{'_copy'} = $new;
-  $current->{'_counter'} = 0 if !exists($current->{'_counter'});
-  $current->{'_counter'}++;
-
-  #print STDERR "CTNEW $current ".debug_print_element($current)." $new\n";
-
-  foreach my $key ('args', 'contents') {
-    if ($current->{$key}) {
-      if (ref($current->{$key}) ne 'ARRAY') {
-        my $command_or_type = '';
-        if ($new->{'cmdname'}) {
-          $command_or_type = '@'.$new->{'cmdname'};
-        } elsif ($new->{'type'}) {
-          $command_or_type = $new->{'type'};
-        }
-        print STDERR "BUG: Not an array [$command_or_type] $key ".
-                                             ref($current->{$key})."\n";
-      }
-      $new->{$key} = [];
-      foreach my $child (@{$current->{$key}}) {
-        push @{$new->{$key}}, _copy_tree($child, $new);
-      }
-    }
-  }
-  foreach my $info_type ('info', 'extra') {
-    next if (!$current->{$info_type});
-    $new->{$info_type} = {};
-    foreach my $key (sort(keys(%{$current->{$info_type}}))) {
-      my $value = $current->{$info_type}->{$key};
-      if (ref($value) eq 'ARRAY' and ref($value->[0]) eq 'HASH') {
-        #print STDERR "II ARRAY $key $value\n";
-        $new->{$info_type}->{$key} = [];
-        foreach my $target (@{$value}) {
-          if ($target->{'_copy'}) {
-            push @{$new->{$info_type}->{$key}}, $target->{'_copy'};
-          } else {
-            push @{$new->{$info_type}->{$key}}, undef;
-            $target->{'_counter'}++;
-            #print STDERR "AC $target ".debug_print_element($target)
-            #   .": $target->{'_counter'}\n";
-          }
-          _copy_tree($target, undef);
-        }
-      } elsif (ref($value) eq 'HASH') {
-        #print STDERR "II HASH $key $value\n";
-        if ($extra_directions{$key}) {
-          $new->{$info_type}->{$key} = {};
-          foreach my $direction (sort (keys(%$value))) {
-            my $target = $value->{$direction};
-            if ($target->{'_copy'}) {
-              $new->{$info_type}->{$key}->{$direction} = $target->{'_copy'};
-            } else {
-              $target->{'_counter'}++;
-            }
-            _copy_tree($target, undef);
-          }
-        } else {
-          if ($value->{'_copy'}) {
-            $new->{$info_type}->{$key} = $value->{'_copy'};
-          } else {
-            $value->{'_counter'}++;
-            #print STDERR "AC $value ".debug_print_element($value)
-            #   .": $value->{'_counter'}\n";
-          }
-          _copy_tree($value, undef);
-        }
-      }
-    }
-  }
-  return $new;
-}
-
-sub _get_copy_ref($$)
-{
-  my $target = shift;
-  my $context = shift;
-
-  if (ref($target) ne 'HASH' or !$target->{'_counter'}) {
-    print STDERR "BUG: $context: unexpected target $target\n";
-    print STDERR "    ".debug_print_element($target)."\n";
-    die;
-  }
-  $target->{'_counter'}--;
-  if ($target->{'_counter'} == 0) {
-    delete $target->{'_counter'};
-    my $copy = $target->{'_copy'};
-    delete $target->{'_copy'};
-    return $copy;
-  }
-  return $target->{'_copy'};
-}
-
-sub _copy_extra_info($$;$);
-sub _copy_extra_info($$;$)
-{
-  my $current = shift;
-  my $new = shift;
-  my $level = shift;
-
-  my $command_or_type = '';
-  if ($new->{'cmdname'}) {
-    $command_or_type = '@'.$new->{'cmdname'};
-  } elsif ($new->{'type'}) {
-    $command_or_type = $new->{'type'};
-  }
-
-  $level = 0 if (!defined($level));
-
-  if (!$current->{'_copy'}) {
-    #print STDERR "DONE $current ".debug_print_element($current)."\n";
-    return;
-  }
-
-  $level++;
-  #print STDERR (' ' x $level)
-  #   .Texinfo::Common::debug_print_element($current).": $current ".
-  #   (defined($current->{'_counter'}) ? $current->{'_counter'} : 'N')."\n";
-
-  _get_copy_ref($current, "myself[$command_or_type]");
-
-  foreach my $key ('args', 'contents') {
-    if ($current->{$key}) {
-      my $index = 0;
-      foreach my $child (@{$current->{$key}}) {
-        _copy_extra_info($child, $new->{$key}->[$index], $level);
-        $index++;
-      }
-    }
-  }
-
-  foreach my $info_type ('info', 'extra') {
-    next if (!$current->{$info_type});
-    foreach my $key (sort(keys(%{$current->{$info_type}}))) {
-      my $value = $current->{$info_type}->{$key};
-      #print STDERR (' ' x $level) . "K $info_type $key |$value\n";
-      if (ref($value) eq '') {
-        $new->{$info_type}->{$key} = $value;
-      } elsif (ref($value) eq 'ARRAY') {
-        #print STDERR (' ' x $level) .
-        #           "Array $command_or_type $info_type -> $key\n";
-        # misc_args index_entry
-        if (ref($value->[0]) eq '') {
-          $new->{$info_type}->{$key} = [@$value];
-        } else {
-          # authors manual_content menus node_content
-          my $new_array = $new->{$info_type}->{$key};
-          for (my $index = 0; $index < scalar(@{$value}); $index++) {
-            if (!defined($new_array->[$index])) {
-              my $context = "$info_type [$command_or_type]{$key} [$index]";
-              $new_array->[$index] = _get_copy_ref($value->[$index], $context);
-            }
-            _copy_extra_info($value->[$index],
-                             $value->[$index]->{'_copy'}, $level)
-              if ($value->[$index]->{'_copy'});
-          }
-        }
-      } elsif (ref($value) eq 'HASH') {
-        #print STDERR (' ' x $level)
-        #         . "Hash $command_or_type $info_type -> $key\n";
-        if ($extra_directions{$key}) {
-          my $new_directions = $new->{$info_type}->{$key};
-          foreach my $direction (sort(keys(%$value))) {
-            if (!exists($new_directions->{$direction})) {
-              my $context = "$info_type [$command_or_type]{$key} {$direction}";
-              $new_directions->{$direction}
-                = _get_copy_ref($value->{$direction}, $context);
-            }
-            _copy_extra_info($value->{$direction},
-                             $value->{$direction}->{'_copy'}, $level)
-              if ($value->{$direction}->{'_copy'});
-          }
-        } else {
-          if (not defined($value->{'cmdname'}) and not 
defined($value->{'type'})
-              and not defined($value->{'text'}) and not 
defined($value->{'extra'})
-              and not defined($value->{'contents'})
-              and not defined($value->{'args'})
-              and scalar(keys(%$value))) {
-            print STDERR "HASH NOT ELEMENT $info_type 
[$command_or_type]{$key}\n";
-          }
-          if (!exists($new->{$info_type}->{$key})) {
-            my $context = "${info_type}[$command_or_type]{$key}";
-            $new->{$info_type}->{$key} = _get_copy_ref($value, $context);
-          }
-          if ($value->{'_copy'}) {
-            _copy_extra_info($value, $value->{'_copy'}, $level);
-          }
-        }
-      } else {
-        print STDERR "Unexpected $info_type [$command_or_type]{$key}: "
-                                                          .ref($value)."\n";
-      }
-    }
-  }
-}
-
-sub copy_tree($)
-{
-  my $current = shift;
-  my $copy = _copy_tree($current, undef);
-  _copy_extra_info($current, $copy);
-  return $copy;
-}
-
-# Never overriden by XS version
-sub copy_treeNonXS($)
-{
-  my $current = shift;
-  my $copy = _copy_tree($current, undef);
-  _copy_extra_info($current, $copy);
-  return $copy;
-}
-
-sub copy_contents($;$)
-{
-  my $element = shift;
-  my $type = shift;
-  my $tmp = {'contents' => $element->{'contents'}};
-  my $copy = copy_tree($tmp);
-  if (defined($type)) {
-    $copy->{'type'} = $type;
-  }
-  return $copy;
-}
-
-sub copy_contentsNonXS($;$)
-{
-  my $element = shift;
-  my $type = shift;
-  my $tmp = {'contents' => $element->{'contents'}};
-  my $copy = copy_treeNonXS($tmp);
-  if (defined($type)) {
-    $copy->{'type'} = $type;
-  }
-  return $copy;
-}
-
-sub modify_tree($$;$);
-sub modify_tree($$;$)
-{
-  my $tree = shift;
-  my $operation = shift;
-  my $argument = shift;
-  #print STDERR "modify_tree tree: $tree\n";
-
-  if (!defined($tree) or ref($tree) ne 'HASH') {
-    cluck "tree ".(!defined($tree) ? 'UNDEF' : "not a hash: $tree");
-    return undef;
-  }
-
-  if ($tree->{'args'}) {
-    my $args_nr = scalar(@{$tree->{'args'}});
-    for (my $i = 0; $i < $args_nr; $i++) {
-      my $new_args = &$operation('arg', $tree->{'args'}->[$i], $argument);
-      if ($new_args) {
-        # replace by new content
-        splice(@{$tree->{'args'}}, $i, 1, @$new_args);
-        $i += scalar(@$new_args) -1;
-        $args_nr += scalar(@$new_args) -1;
-      } else {
-        modify_tree($tree->{'args'}->[$i], $operation, $argument);
-      }
-    }
-  }
-  if ($tree->{'contents'}) {
-    my $contents_nr = scalar(@{$tree->{'contents'}});
-    for (my $i = 0; $i < $contents_nr; $i++) {
-      my $new_contents = &$operation('content',
-                                     $tree->{'contents'}->[$i], $argument);
-      if ($new_contents) {
-        # replace by new content
-        splice(@{$tree->{'contents'}}, $i, 1, @$new_contents);
-        $i += scalar(@$new_contents) -1;
-        $contents_nr += scalar(@$new_contents) -1;
-      } else {
-        modify_tree($tree->{'contents'}->[$i], $operation, $argument);
-      }
-    }
-  }
-  if ($tree->{'source_marks'}) {
-    my @source_marks = @{$tree->{'source_marks'}};
-    for (my $i = 0; $i <= $#source_marks; $i++) {
-      if ($source_marks[$i]->{'element'}) {
-        my $new_element
-          = &$operation('source_mark', $source_marks[$i]->{'element'},
-                        $argument);
-        if ($new_element) {
-          $source_marks[$i]->{'element'} = $new_element->[0];
-        }
-      }
-    }
-  }
-  return $tree;
-}
-
-sub _protect_comma($$)
-{
-  my $type = shift;
-  my $current = shift;
-
-  return _protect_text($current, quotemeta(','));
-}
-
-sub protect_comma_in_tree($)
-{
-  my $tree = shift;
-
-  return modify_tree($tree, \&_protect_comma);
-}
-
-sub _new_asis_command_with_text($$;$)
-{
-  my $text = shift;
-  my $parent = shift;
-  my $text_type = shift;
-  my $new_command = {'cmdname' => 'asis', 'parent' => $parent };
-  push @{$new_command->{'args'}}, {'type' => 'brace_command_arg',
-                                   'parent' => $new_command};
-  push @{$new_command->{'args'}->[0]->{'contents'}}, {
-    'text' => $text,
-    'parent' => $new_command->{'args'}->[0]};
-  if (defined($text_type)) {
-    $new_command->{'args'}->[0]->{'contents'}->[0]->{'type'} = $text_type;
-  }
-  return $new_command;
-}
-
-sub _protect_text($$)
-{
-  my $current = shift;
-  my $to_protect = shift;
-
-  #print STDERR "_protect_text: $to_protect: $current "
-  #                         .debug_print_element($current, 1)."\n";
-  if (defined($current->{'text'}) and $current->{'text'} =~ /$to_protect/
-      and !(defined($current->{'type'})
-            and ($current->{'type'} eq 'raw'
-                 or $current->{'type'} eq 'rawline_arg'))) {
-    my @result = ();
-    my $remaining_text = $current->{'text'};
-
-    my $remaining_source_marks;
-    my $current_position = 0;
-    if ($current->{'source_marks'}) {
-      $remaining_source_marks = [@{$current->{'source_marks'}}];
-      delete $current->{'source_marks'};
-    }
-    while ($remaining_text) {
-      if ($remaining_text =~ s/^(.*?)(($to_protect)+)//) {
-        # Note that it includes for completeness the case of $1 eq ''
-        # although it is unclear that source marks may happen in that case
-        # as they are rather associated to the previous element.
-        my $e = {'text' => $1, 'parent' => $current->{'parent'}};
-        $e->{'type'} = $current->{'type'} if defined($current->{'type'});
-        $current_position = Texinfo::Common::relocate_source_marks(
-                                        $remaining_source_marks, $e,
-                                        $current_position, length($1));
-        if ($e->{'text'} ne '' or $e->{'source_marks'}) {
-          push @result, $e;
-        }
-        if ($to_protect eq quotemeta(',')) {
-          for (my $i = 0; $i < length($2); $i++) {
-            my $e = {'cmdname' => 'comma', 'parent' => $current->{'parent'},
-                           'args' => [{'type' => 'brace_command_arg'}]};
-            $current_position = Texinfo::Common::relocate_source_marks(
-                                          $remaining_source_marks, $e,
-                                          $current_position, 1);
-            push @result, $e;
-          }
-        } else {
-          my $new_asis = _new_asis_command_with_text($2, $current->{'parent'},
-                                                    $current->{'type'});
-          my $e = $new_asis->{'args'}->[0]->{'contents'}->[0];
-          $current_position = Texinfo::Common::relocate_source_marks(
-                                          $remaining_source_marks, $e,
-                                          $current_position, length($2));
-          push @result, $new_asis;
-        }
-      } else {
-        my $e = {'text' => $remaining_text, 'parent' => $current->{'parent'}};
-        $e->{'type'} = $current->{'type'} if defined($current->{'type'});
-        $current_position = Texinfo::Common::relocate_source_marks(
-                                      $remaining_source_marks, $e,
-                                      $current_position, 
length($remaining_text));
-        push @result, $e;
-        last;
-      }
-    }
-    #print STDERR "_protect_text: Result: @result\n";
-    return \@result;
-  } else {
-    #print STDERR "_protect_text: No change\n";
-    return undef;
-  }
-}
-
-sub _protect_colon($$)
-{
-  my $type = shift;
-  my $current = shift;
-
-  return _protect_text($current, quotemeta(':'));
-}
-
-sub protect_colon_in_tree($)
-{
-  my $tree = shift;
-
-  return modify_tree($tree, \&_protect_colon);
-}
-
-sub _protect_node_after_label($$)
-{
-  my $type = shift;
-  my $current = shift;
-
-  return _protect_text($current, '['. quotemeta(".\t,") .']');
-}
-
-sub protect_node_after_label_in_tree($)
-{
-  my $tree = shift;
-
-  return modify_tree($tree, \&_protect_node_after_label);
-}
-
-sub protect_first_parenthesis($)
-{
-  my $element = shift;
-  confess("BUG: protect_first_parenthesis element undef")
-    if (!defined($element));
-  confess("BUG: protect_first_parenthesis not a hash")
-    if (ref($element) ne 'HASH');
-  #print STDERR "protect_first_parenthesis: $element->{'contents'}\n";
-  return if (!$element->{'contents'} or !scalar(@{$element->{'contents'}}));
-
-  my $current_position = 0;
-  my $nr_contents = scalar(@{$element->{'contents'}});
-  for (my $i = 0; $i < $nr_contents; $i++) {
-    my $content = $element->{'contents'}->[$i];
-    return if (!defined($content->{'text'}));
-    if ($content->{'text'} eq '') {
-      next;
-    }
-    if ($content->{'text'} =~ /^\(/) {
-      my $remaining_source_marks;
-      my $current_position = 0;
-      if ($content->{'source_marks'}) {
-        $remaining_source_marks = [@{$content->{'source_marks'}}];
-        delete $content->{'source_marks'};
-      }
-      my $new_asis = _new_asis_command_with_text('(', $content->{'parent'},
-                                                 $content->{'type'});
-      my $e = $new_asis->{'args'}->[0]->{'contents'}->[0];
-      $current_position = Texinfo::Common::relocate_source_marks(
-                                       $remaining_source_marks, $e,
-                                       $current_position, length('('));
-      if ($content->{'text'} !~ /^\($/) {
-        $content->{'text'} =~ s/^\(//;
-        $current_position = Texinfo::Common::relocate_source_marks(
-                                      $remaining_source_marks, $content,
-                                $current_position, length($content->{'text'}));
-      } else {
-        splice (@{$element->{'contents'}}, $i, 1);
-      }
-      splice (@{$element->{'contents'}}, $i, 0, $new_asis);
-    }
-    return;
-  }
-}
-
-sub move_index_entries_after_items($)
-{
-  # enumerate or itemize
-  my $current = shift;
-
-  return unless ($current->{'contents'});
-
-  my $previous;
-  foreach my $item (@{$current->{'contents'}}) {
-    #print STDERR "Before proceeding: $previous $item->{'cmdname'} 
(@{$previous->{'contents'}})\n" if ($previous and $previous->{'contents'});
-    if (defined($previous) and $item->{'cmdname'}
-        and $item->{'cmdname'} eq 'item'
-        and $previous->{'contents'} and scalar(@{$previous->{'contents'}})) {
-
-      my $previous_ending_container;
-      if ($previous->{'contents'}->[-1]->{'type'}
-          and ($previous->{'contents'}->[-1]->{'type'} eq 'paragraph'
-               or $previous->{'contents'}->[-1]->{'type'} eq 'preformatted')) {
-        $previous_ending_container = $previous->{'contents'}->[-1];
-      } else {
-        $previous_ending_container = $previous;
-      }
-
-      my $contents_nr = scalar(@{$previous_ending_container->{'contents'}});
-
-      # find the last index entry, with possibly comments after
-      my $last_entry_idx = -1;
-      for (my $i = $contents_nr -1; $i >= 0; $i--) {
-        my $content = $previous_ending_container->{'contents'}->[$i];
-        if ($content->{'type'} and $content->{'type'} eq 
'index_entry_command') {
-          $last_entry_idx = $i;
-        } elsif (not $content->{'cmdname'}
-                 or ($content->{'cmdname'} ne 'c'
-                     and $content->{'cmdname'} ne 'comment')) {
-          last;
-        }
-      }
-
-      if ($last_entry_idx >= 0) {
-        my $item_container;
-        if ($item->{'contents'} and $item->{'contents'}->[0]
-            and $item->{'contents'}->[0]->{'type'}
-            and $item->{'contents'}->[0]->{'type'} eq 'preformatted') {
-          $item_container = $item->{'contents'}->[0];
-        } else {
-          $item_container = $item;
-        }
-
-        for (my $i = $last_entry_idx; $i < $contents_nr; $i++) {
-          $previous_ending_container->{'contents'}->[$i]->{'parent'}
-               = $item_container;
-        }
-
-        my $insertion_idx = 0;
-        if ($item_container->{'contents'}
-            and $item_container->{'contents'}->[0]
-            and $item_container->{'contents'}->[0]->{'type'}
-            and $item_container->{'contents'}->[0]->{'type'} eq 
'ignorable_spaces_after_command') {
-          # insert after leading spaces, and add an end of line if there
-          # is none
-          $insertion_idx = 1;
-          $item_container->{'contents'}->[0]->{'text'} .= "\n"
-            if ($item_container->{'contents'}->[0]->{'text'} !~ /\n$/);
-        }
-        # first part of the splice is the insertion in $item_container
-        splice (@{$item_container->{'contents'}},
-                $insertion_idx, 0,
-                    # this splice removes from the previous container starting
-                    # at $last_entry_idx and returns the contents to be 
inserted
-                    splice (@{$previous_ending_container->{'contents'}},
-                            $last_entry_idx, $contents_nr - $last_entry_idx));
-        delete $previous_ending_container->{'contents'}
-          if (!scalar(@{$previous_ending_container->{'contents'}}))
-      }
-    }
-    $previous = $item;
-  }
-}
-
-sub _move_index_entries_after_items($$)
-{
-  my $type = shift;
-  my $current = shift;
-
-  if ($current->{'cmdname'} and ($current->{'cmdname'} eq 'enumerate'
-                                 or $current->{'cmdname'} eq 'itemize')) {
-    move_index_entries_after_items($current);
-  }
-  return undef;
-}
-
-# For @itemize/@enumerate
-sub move_index_entries_after_items_in_tree($)
-{
-  my $tree = shift;
-
-  modify_tree($tree, \&_move_index_entries_after_items);
-}
-
-sub _relate_index_entries_to_table_items_in($$)
-{
-  my $table = shift;
-  my $indices_information = shift;
-
-  return unless $table->{'contents'};
-
-  foreach my $table_entry (@{$table->{'contents'}}) {
-    next unless $table_entry->{'contents'}
-      and $table_entry->{'type'} eq 'table_entry';
-
-    my $term = $table_entry->{'contents'}->[0];
-    my $definition;
-    my $item;
-
-    # Move any index entries from the start of a 'table_definition' to
-    # the 'table_term'.
-    if (defined($table_entry->{'contents'}->[1])
-        and defined($table_entry->{'contents'}->[1]->{'type'})
-        and $table_entry->{'contents'}->[1]->{'type'} eq 'table_definition') {
-      $definition = $table_entry->{'contents'}->[1];
-      my $nr_index_entry_command = 0;
-      foreach my $child (@{$definition->{'contents'}}) {
-        if ($child->{'type'} and $child->{'type'} eq 'index_entry_command') {
-          $child->{'parent'} = $term;
-          $nr_index_entry_command++;
-        } else {
-          last;
-        }
-      }
-      if ($nr_index_entry_command > 0) {
-        unshift @{$term->{'contents'}},
-          splice (@{$definition->{'contents'}}, 0, $nr_index_entry_command);
-      }
-    }
-
-    if (defined($term->{'type'}) and $term->{'type'} eq 'table_term') {
-      # Relate the first index_entry_command in the 'table_term' to
-      # the term itself.
-
-      my $index_entry;
-      my $index_element;
-      foreach my $content (@{$term->{'contents'}}) {
-        if ($content->{'type'}
-            and $content->{'type'} eq 'index_entry_command') {
-          if (!$index_entry) {
-            my $index_info;
-            $index_element = $content;
-            ($index_entry, $index_info)
-              = Texinfo::Common::lookup_index_entry(
-                              $content->{'extra'}->{'index_entry'},
-                              $indices_information);
-          }
-        } elsif ($content->{'cmdname'} and $content->{'cmdname'} eq 'item') {
-          $item = $content unless $item;
-        }
-        if ($item and $index_entry) {
-          # This is better than overwriting 'entry_element', which
-          # holds important information.
-          $index_entry->{'entry_associated_element'} = $item;
-          # also add a reference from element to index entry in index
-          $item->{'extra'} = {} if (!$item->{'extra'});
-          $item->{'extra'}->{'associated_index_entry'}
-             = [@{$index_element->{'extra'}->{'index_entry'}}];
-          last;
-        }
-      }
-    }
-  }
-}
-
-# Locate all @tables in the tree, and relate index entries to
-# the @item that immediately follows or precedes them.
-sub _relate_index_entries_to_table_items($$$)
-{
-  my $type = shift;
-  my $current = shift;
-  my $indices_information = shift;
-
-  if ($current->{'cmdname'} and $current->{'cmdname'} eq 'table') {
-    _relate_index_entries_to_table_items_in($current, $indices_information);
-  }
-  return undef;
-}
-
-sub relate_index_entries_to_table_items_in_tree($)
-{
-  my $document = shift;
-
-  my $tree = $document->tree();
-  my $indices_information = $document->indices_information();
-
-  modify_tree($tree, \&_relate_index_entries_to_table_items,
-              $indices_information);
-}
-
 # Common to different module, but not meant to be used in user-defined
 # codes.
 
@@ -3076,50 +2314,12 @@ is an L<C<extra> 
I<index_entry>|Texinfo::Parser/index_entry> associated to an el
 The I<$index_entry> hash is described in L<Texinfo::Document/index_entries>.  
The
 I<$index_info> hash is described in L<< 
C<Texinfo::Document::indices_information>|Texinfo::Document/$indices_information
 = $document->indices_information() >>.
 
-
-=item move_index_entries_after_items_in_tree($tree)
-X<C<move_index_entries_after_items_in_tree>>
-
-In C<@enumerate> and C<@itemize> from the tree, move index entries
-appearing just before C<@item> after the C<@item>.  Comment lines
-between index entries are moved too.
-
-=item relate_index_entries_to_table_items_in_tree($document)
-X<C<relate_index_entries_to_table_items_in_tree>>
-
-In tables, relate index entries preceding and following an
-entry with said item.  Reference one of them in the entry's
-C<entry_associated_element>.
-
 =item $normalized_name = normalize_top_node_name($node_string)
 X<C<normalize_top_node_name>>
 
 Normalize the node name string given in argument, by normalizing
 Top node case.
 
-=item protect_colon_in_tree($tree)
-
-=item protect_node_after_label_in_tree($tree)
-X<C<protect_colon_in_tree>>
-X<C<protect_node_after_label_in_tree>>
-
-Protect colon with C<protect_colon_in_tree> and characters that
-are special in node names after a label in menu entries (tab
-dot and comma) with C<protect_node_after_label_in_tree>.
-The protection is achieved by putting protected characters
-in C<@asis{}>.
-
-=item protect_comma_in_tree($tree)
-X<C<protect_comma_in_tree>>
-
-Protect comma characters, replacing C<,> with @comma{} in tree.
-
-=item protect_first_parenthesis($element)
-X<C<protect_first_parenthesis>>
-
-Modify I<$element> contents by protecting the first parenthesis.
-If I<$element> is undef a fatal error with a backtrace will be emitted.
-
 =item $level = section_level($section)
 X<C<section_level>>
 
diff --git a/tp/Texinfo/Convert/HTML.pm b/tp/Texinfo/Convert/HTML.pm
index 538f00bd64..6937da333b 100644
--- a/tp/Texinfo/Convert/HTML.pm
+++ b/tp/Texinfo/Convert/HTML.pm
@@ -71,6 +71,7 @@ use Texinfo::Convert::Texinfo;
 use Texinfo::Convert::Utils;
 use Texinfo::Convert::Text;
 use Texinfo::Convert::NodeNameNormalization;
+use Texinfo::ManipulateTree;
 use Texinfo::Structuring;
 use Texinfo::Indices;
 use Texinfo::Convert::Converter;
@@ -1259,7 +1260,7 @@ sub _internal_command_tree($$$)
           my $substituted_strings
             = {'number' => {'text' => $section_number},
                'section_title'
-             => Texinfo::Common::copy_treeNonXS($command->{'args'}->[0])};
+          => Texinfo::ManipulateTree::copy_treeNonXS($command->{'args'}->[0])};
 
           if ($command->{'cmdname'} eq 'appendix'
               and $command->{'extra'}->{'section_level'} == 1) {
diff --git a/tp/Texinfo/ManipulateTree.pm b/tp/Texinfo/ManipulateTree.pm
new file mode 100644
index 0000000000..12f5c406e7
--- /dev/null
+++ b/tp/Texinfo/ManipulateTree.pm
@@ -0,0 +1,900 @@
+# ManipulateTree.pm: common Texinfo tree manipulation
+#
+# Copyright 2010-2024 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 <http://www.gnu.org/licenses/>.
+#
+# Original author: Patrice Dumas <pertusus@free.fr>
+
+package Texinfo::ManipulateTree;
+
+use 5.00405;
+
+# stop \s from matching non-ASCII spaces, etc.  \p{...} can still be
+# used to match Unicode character classes.
+use if $] >= 5.014, re => '/a';
+
+use strict;
+
+# To check if there is no erroneous autovivification
+#no autovivification qw(fetch delete exists store strict);
+  
+# debugging
+use Carp qw(cluck confess);
+
+use Texinfo::StructTransfXS;
+
+use Texinfo::XSLoader;
+
+use Texinfo::Common;
+
+require Exporter;
+use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS);
+@ISA = qw(Exporter);
+
+%EXPORT_TAGS = ( 'all' => [ qw(
+move_index_entries_after_items_in_tree
+relate_index_entries_to_table_items_in_tree
+protect_colon_in_tree
+protect_comma_in_tree
+protect_first_parenthesis
+protect_node_after_label_in_tree
+) ] );
+
+@EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
+
+$VERSION = '7.1dev';
+
+my $XS_structuring = Texinfo::XSLoader::XS_structuring_enabled();
+
+our %XS_overrides = (
+  "Texinfo::ManipulateTree::copy_tree"
+    => "Texinfo::StructTransfXS::copy_tree",
+  "Texinfo::ManipulateTree::relate_index_entries_to_table_items_in_tree"
+    => "Texinfo::StructTransfXS::relate_index_entries_to_table_items_in_tree",
+  "Texinfo::ManipulateTree::move_index_entries_after_items_in_tree"
+    => "Texinfo::StructTransfXS::move_index_entries_after_items_in_tree",
+  "Texinfo::ManipulateTree::protect_colon_in_tree"
+    => "Texinfo::StructTransfXS::protect_colon_in_tree",
+  "Texinfo::ManipulateTree::protect_comma_in_tree"
+    => "Texinfo::StructTransfXS::protect_comma_in_tree",
+  "Texinfo::ManipulateTree::protect_node_after_label_in_tree"
+    => "Texinfo::StructTransfXS::protect_node_after_label_in_tree",
+);
+
+our $module_loaded = 0;
+sub import {
+  if (!$module_loaded) {
+    if ($XS_structuring) {
+      for my $sub (keys %XS_overrides) {
+        Texinfo::XSLoader::override ($sub, $XS_overrides{$sub});
+      }
+    }
+    $module_loaded = 1;
+  }
+  # The usual import method
+  goto &Exporter::import;
+}
+
+# functions useful for Texinfo tree transformations
+# and some tree transformations functions, mostly those
+# used in conversion to main output formats.  In general,
+# tree transformations functions are documented in the POD section.
+
+# Some helper functions defined here are used in other
+# modules but are not generally useful in converters
+# and therefore not public.
+
+
+# This implementation of tree copy is designed such as to be
+# implementable easily in XS with reference to copy local to
+# the element and not in a hash
+
+# It is important to go through the tree in the same order
+# in _copy_tree and _copy_extra_info, to be sure that elements already
+# seen are the same in both cases, such that _counter is at 0 in
+# _copy_extra_info when all the dependent elements have been seen
+# and going through the target element.
+
+# the *_directions extra items are not elements, they contain
+# up, next and prev that point to elements.
+# it could also have been possible to determine that it is
+# an extra_directions if the keys are only up, next and prev
+my %extra_directions;
+foreach my $type ('menu', 'node', 'section', 'toplevel') {
+  $extra_directions{$type.'_directions'} = 1;
+}
+
+sub _copy_tree($$);
+sub _copy_tree($$)
+{
+  my $current = shift;
+  my $parent = shift;
+
+  # either a duplicate in a tree (should be rare/avoided) or an
+  # element referred to in extra/info, either directly or
+  # (probably rare) in the extra element tree that has already
+  # been seen in the tree
+  if ($current->{'_copy'}) {
+    #print STDERR "RCT $current ".debug_print_element($current)
+    # .": $current->{'_counter'}\n";
+    $current->{'_copy'}->{'parent'} = $parent
+        if (not $current->{'_copy'}->{'parent'} and $parent);
+    return $current->{'_copy'};
+  }
+
+  my $new = {};
+  $new->{'parent'} = $parent if ($parent);
+  foreach my $key ('type', 'cmdname', 'text') {
+    $new->{$key} = $current->{$key} if (exists($current->{$key}));
+  }
+
+  $current->{'_copy'} = $new;
+  $current->{'_counter'} = 0 if !exists($current->{'_counter'});
+  $current->{'_counter'}++;
+
+  #print STDERR "CTNEW $current ".debug_print_element($current)." $new\n";
+
+  foreach my $key ('args', 'contents') {
+    if ($current->{$key}) {
+      if (ref($current->{$key}) ne 'ARRAY') {
+        my $command_or_type = '';
+        if ($new->{'cmdname'}) {
+          $command_or_type = '@'.$new->{'cmdname'};
+        } elsif ($new->{'type'}) {
+          $command_or_type = $new->{'type'};
+        }
+        print STDERR "BUG: Not an array [$command_or_type] $key ".
+                                             ref($current->{$key})."\n";
+      }
+      $new->{$key} = [];
+      foreach my $child (@{$current->{$key}}) {
+        push @{$new->{$key}}, _copy_tree($child, $new);
+      }
+    }
+  }
+  foreach my $info_type ('info', 'extra') {
+    next if (!$current->{$info_type});
+    $new->{$info_type} = {};
+    foreach my $key (sort(keys(%{$current->{$info_type}}))) {
+      my $value = $current->{$info_type}->{$key};
+      if (ref($value) eq 'ARRAY' and ref($value->[0]) eq 'HASH') {
+        #print STDERR "II ARRAY $key $value\n";
+        $new->{$info_type}->{$key} = [];
+        foreach my $target (@{$value}) {
+          if ($target->{'_copy'}) {
+            push @{$new->{$info_type}->{$key}}, $target->{'_copy'};
+          } else {
+            push @{$new->{$info_type}->{$key}}, undef;
+            $target->{'_counter'}++;
+            #print STDERR "AC $target ".debug_print_element($target)
+            #   .": $target->{'_counter'}\n";
+          }
+          _copy_tree($target, undef);
+        }
+      } elsif (ref($value) eq 'HASH') {
+        #print STDERR "II HASH $key $value\n";
+        if ($extra_directions{$key}) {
+          $new->{$info_type}->{$key} = {};
+          foreach my $direction (sort (keys(%$value))) {
+            my $target = $value->{$direction};
+            if ($target->{'_copy'}) {
+              $new->{$info_type}->{$key}->{$direction} = $target->{'_copy'};
+            } else {
+              $target->{'_counter'}++;
+            }
+            _copy_tree($target, undef);
+          }
+        } else {
+          if ($value->{'_copy'}) {
+            $new->{$info_type}->{$key} = $value->{'_copy'};
+          } else {
+            $value->{'_counter'}++;
+            #print STDERR "AC $value ".debug_print_element($value)
+            #   .": $value->{'_counter'}\n";
+          }
+          _copy_tree($value, undef);
+        }
+      }
+    }
+  }
+  return $new;
+}
+
+sub _get_copy_ref($$)
+{
+  my $target = shift;
+  my $context = shift;
+
+  if (ref($target) ne 'HASH' or !$target->{'_counter'}) {
+    print STDERR "BUG: $context: unexpected target $target\n";
+    print STDERR "    ".debug_print_element($target)."\n";
+    die;
+  }
+  $target->{'_counter'}--;
+  if ($target->{'_counter'} == 0) {
+    delete $target->{'_counter'};
+    my $copy = $target->{'_copy'};
+    delete $target->{'_copy'};
+    return $copy;
+  }
+  return $target->{'_copy'};
+}
+
+sub _copy_extra_info($$;$);
+sub _copy_extra_info($$;$)
+{
+  my $current = shift;
+  my $new = shift;
+  my $level = shift;
+
+  my $command_or_type = '';
+  if ($new->{'cmdname'}) {
+    $command_or_type = '@'.$new->{'cmdname'};
+  } elsif ($new->{'type'}) {
+    $command_or_type = $new->{'type'};
+  }
+
+  $level = 0 if (!defined($level));
+
+  if (!$current->{'_copy'}) {
+    #print STDERR "DONE $current ".debug_print_element($current)."\n";
+    return;
+  }
+
+  $level++;
+  #print STDERR (' ' x $level)
+  #   .Texinfo::Common::debug_print_element($current).": $current ".
+  #   (defined($current->{'_counter'}) ? $current->{'_counter'} : 'N')."\n";
+
+  _get_copy_ref($current, "myself[$command_or_type]");
+
+  foreach my $key ('args', 'contents') {
+    if ($current->{$key}) {
+      my $index = 0;
+      foreach my $child (@{$current->{$key}}) {
+        _copy_extra_info($child, $new->{$key}->[$index], $level);
+        $index++;
+      }
+    }
+  }
+
+  foreach my $info_type ('info', 'extra') {
+    next if (!$current->{$info_type});
+    foreach my $key (sort(keys(%{$current->{$info_type}}))) {
+      my $value = $current->{$info_type}->{$key};
+      #print STDERR (' ' x $level) . "K $info_type $key |$value\n";
+      if (ref($value) eq '') {
+        $new->{$info_type}->{$key} = $value;
+      } elsif (ref($value) eq 'ARRAY') {
+        #print STDERR (' ' x $level) .
+        #           "Array $command_or_type $info_type -> $key\n";
+        # misc_args index_entry
+        if (ref($value->[0]) eq '') {
+          $new->{$info_type}->{$key} = [@$value];
+        } else {
+          # authors manual_content menus node_content
+          my $new_array = $new->{$info_type}->{$key};
+          for (my $index = 0; $index < scalar(@{$value}); $index++) {
+            if (!defined($new_array->[$index])) {
+              my $context = "$info_type [$command_or_type]{$key} [$index]";
+              $new_array->[$index] = _get_copy_ref($value->[$index], $context);
+            }
+            _copy_extra_info($value->[$index],
+                             $value->[$index]->{'_copy'}, $level)
+              if ($value->[$index]->{'_copy'});
+          }
+        }
+      } elsif (ref($value) eq 'HASH') {
+        #print STDERR (' ' x $level)
+        #         . "Hash $command_or_type $info_type -> $key\n";
+        if ($extra_directions{$key}) {
+          my $new_directions = $new->{$info_type}->{$key};
+          foreach my $direction (sort(keys(%$value))) {
+            if (!exists($new_directions->{$direction})) {
+              my $context = "$info_type [$command_or_type]{$key} {$direction}";
+              $new_directions->{$direction}
+                = _get_copy_ref($value->{$direction}, $context);
+            }
+            _copy_extra_info($value->{$direction},
+                             $value->{$direction}->{'_copy'}, $level)
+              if ($value->{$direction}->{'_copy'});
+          }
+        } else {
+          if (not defined($value->{'cmdname'}) and not 
defined($value->{'type'})
+              and not defined($value->{'text'}) and not 
defined($value->{'extra'})
+              and not defined($value->{'contents'})
+              and not defined($value->{'args'})
+              and scalar(keys(%$value))) {
+            print STDERR "HASH NOT ELEMENT $info_type 
[$command_or_type]{$key}\n";
+          }
+          if (!exists($new->{$info_type}->{$key})) {
+            my $context = "${info_type}[$command_or_type]{$key}";
+            $new->{$info_type}->{$key} = _get_copy_ref($value, $context);
+          }
+          if ($value->{'_copy'}) {
+            _copy_extra_info($value, $value->{'_copy'}, $level);
+          }
+        }
+      } else {
+        print STDERR "Unexpected $info_type [$command_or_type]{$key}: "
+                                                          .ref($value)."\n";
+      }
+    }
+  }
+}
+
+sub copy_tree($)
+{
+  my $current = shift;
+  my $copy = _copy_tree($current, undef);
+  _copy_extra_info($current, $copy);
+  return $copy;
+}
+
+# Never overriden by XS version
+sub copy_treeNonXS($)
+{
+  my $current = shift;
+  my $copy = _copy_tree($current, undef);
+  _copy_extra_info($current, $copy);
+  return $copy;
+}
+
+sub copy_contents($;$)
+{
+  my $element = shift;
+  my $type = shift;
+  my $tmp = {'contents' => $element->{'contents'}};
+  my $copy = copy_tree($tmp);
+  if (defined($type)) {
+    $copy->{'type'} = $type;
+  }
+  return $copy;
+}
+
+sub copy_contentsNonXS($;$)
+{
+  my $element = shift;
+  my $type = shift;
+  my $tmp = {'contents' => $element->{'contents'}};
+  my $copy = copy_treeNonXS($tmp);
+  if (defined($type)) {
+    $copy->{'type'} = $type;
+  }
+  return $copy;
+}
+
+sub modify_tree($$;$);
+sub modify_tree($$;$)
+{
+  my $tree = shift;
+  my $operation = shift;
+  my $argument = shift;
+  #print STDERR "modify_tree tree: $tree\n";
+
+  if (!defined($tree) or ref($tree) ne 'HASH') {
+    cluck "tree ".(!defined($tree) ? 'UNDEF' : "not a hash: $tree");
+    return undef;
+  }
+
+  if ($tree->{'args'}) {
+    my $args_nr = scalar(@{$tree->{'args'}});
+    for (my $i = 0; $i < $args_nr; $i++) {
+      my $new_args = &$operation('arg', $tree->{'args'}->[$i], $argument);
+      if ($new_args) {
+        # replace by new content
+        splice(@{$tree->{'args'}}, $i, 1, @$new_args);
+        $i += scalar(@$new_args) -1;
+        $args_nr += scalar(@$new_args) -1;
+      } else {
+        modify_tree($tree->{'args'}->[$i], $operation, $argument);
+      }
+    }
+  }
+  if ($tree->{'contents'}) {
+    my $contents_nr = scalar(@{$tree->{'contents'}});
+    for (my $i = 0; $i < $contents_nr; $i++) {
+      my $new_contents = &$operation('content',
+                                     $tree->{'contents'}->[$i], $argument);
+      if ($new_contents) {
+        # replace by new content
+        splice(@{$tree->{'contents'}}, $i, 1, @$new_contents);
+        $i += scalar(@$new_contents) -1;
+        $contents_nr += scalar(@$new_contents) -1;
+      } else {
+        modify_tree($tree->{'contents'}->[$i], $operation, $argument);
+      }
+    }
+  }
+  if ($tree->{'source_marks'}) {
+    my @source_marks = @{$tree->{'source_marks'}};
+    for (my $i = 0; $i <= $#source_marks; $i++) {
+      if ($source_marks[$i]->{'element'}) {
+        my $new_element
+          = &$operation('source_mark', $source_marks[$i]->{'element'},
+                        $argument);
+        if ($new_element) {
+          $source_marks[$i]->{'element'} = $new_element->[0];
+        }
+      }
+    }
+  }
+  return $tree;
+}
+
+sub _protect_comma($$)
+{
+  my $type = shift;
+  my $current = shift;
+
+  return _protect_text($current, quotemeta(','));
+}
+
+sub protect_comma_in_tree($)
+{
+  my $tree = shift;
+
+  return modify_tree($tree, \&_protect_comma);
+}
+
+sub _new_asis_command_with_text($$;$)
+{
+  my $text = shift;
+  my $parent = shift;
+  my $text_type = shift;
+  my $new_command = {'cmdname' => 'asis', 'parent' => $parent };
+  push @{$new_command->{'args'}}, {'type' => 'brace_command_arg',
+                                   'parent' => $new_command};
+  push @{$new_command->{'args'}->[0]->{'contents'}}, {
+    'text' => $text,
+    'parent' => $new_command->{'args'}->[0]};
+  if (defined($text_type)) {
+    $new_command->{'args'}->[0]->{'contents'}->[0]->{'type'} = $text_type;
+  }
+  return $new_command;
+}
+
+sub _protect_text($$)
+{
+  my $current = shift;
+  my $to_protect = shift;
+
+  #print STDERR "_protect_text: $to_protect: $current "
+  #                         .debug_print_element($current, 1)."\n";
+  if (defined($current->{'text'}) and $current->{'text'} =~ /$to_protect/
+      and !(defined($current->{'type'})
+            and ($current->{'type'} eq 'raw'
+                 or $current->{'type'} eq 'rawline_arg'))) {
+    my @result = ();
+    my $remaining_text = $current->{'text'};
+
+    my $remaining_source_marks;
+    my $current_position = 0;
+    if ($current->{'source_marks'}) {
+      $remaining_source_marks = [@{$current->{'source_marks'}}];
+      delete $current->{'source_marks'};
+    }
+    while ($remaining_text) {
+      if ($remaining_text =~ s/^(.*?)(($to_protect)+)//) {
+        # Note that it includes for completeness the case of $1 eq ''
+        # although it is unclear that source marks may happen in that case
+        # as they are rather associated to the previous element.
+        my $e = {'text' => $1, 'parent' => $current->{'parent'}};
+        $e->{'type'} = $current->{'type'} if defined($current->{'type'});
+        $current_position = Texinfo::Common::relocate_source_marks(
+                                        $remaining_source_marks, $e,
+                                        $current_position, length($1));
+        if ($e->{'text'} ne '' or $e->{'source_marks'}) {
+          push @result, $e;
+        }
+        if ($to_protect eq quotemeta(',')) {
+          for (my $i = 0; $i < length($2); $i++) {
+            my $e = {'cmdname' => 'comma', 'parent' => $current->{'parent'},
+                           'args' => [{'type' => 'brace_command_arg'}]};
+            $current_position = Texinfo::Common::relocate_source_marks(
+                                          $remaining_source_marks, $e,
+                                          $current_position, 1);
+            push @result, $e;
+          }
+        } else {
+          my $new_asis = _new_asis_command_with_text($2, $current->{'parent'},
+                                                    $current->{'type'});
+          my $e = $new_asis->{'args'}->[0]->{'contents'}->[0];
+          $current_position = Texinfo::Common::relocate_source_marks(
+                                          $remaining_source_marks, $e,
+                                          $current_position, length($2));
+          push @result, $new_asis;
+        }
+      } else {
+        my $e = {'text' => $remaining_text, 'parent' => $current->{'parent'}};
+        $e->{'type'} = $current->{'type'} if defined($current->{'type'});
+        $current_position = Texinfo::Common::relocate_source_marks(
+                                      $remaining_source_marks, $e,
+                                      $current_position, 
length($remaining_text));
+        push @result, $e;
+        last;
+      }
+    }
+    #print STDERR "_protect_text: Result: @result\n";
+    return \@result;
+  } else {
+    #print STDERR "_protect_text: No change\n";
+    return undef;
+  }
+}
+
+sub _protect_colon($$)
+{
+  my $type = shift;
+  my $current = shift;
+
+  return _protect_text($current, quotemeta(':'));
+}
+
+sub protect_colon_in_tree($)
+{
+  my $tree = shift;
+
+  return modify_tree($tree, \&_protect_colon);
+}
+
+sub _protect_node_after_label($$)
+{
+  my $type = shift;
+  my $current = shift;
+
+  return _protect_text($current, '['. quotemeta(".\t,") .']');
+}
+
+sub protect_node_after_label_in_tree($)
+{
+  my $tree = shift;
+
+  return modify_tree($tree, \&_protect_node_after_label);
+}
+
+sub protect_first_parenthesis($)
+{
+  my $element = shift;
+  confess("BUG: protect_first_parenthesis element undef")
+    if (!defined($element));
+  confess("BUG: protect_first_parenthesis not a hash")
+    if (ref($element) ne 'HASH');
+  #print STDERR "protect_first_parenthesis: $element->{'contents'}\n";
+  return if (!$element->{'contents'} or !scalar(@{$element->{'contents'}}));
+
+  my $current_position = 0;
+  my $nr_contents = scalar(@{$element->{'contents'}});
+  for (my $i = 0; $i < $nr_contents; $i++) {
+    my $content = $element->{'contents'}->[$i];
+    return if (!defined($content->{'text'}));
+    if ($content->{'text'} eq '') {
+      next;
+    }
+    if ($content->{'text'} =~ /^\(/) {
+      my $remaining_source_marks;
+      my $current_position = 0;
+      if ($content->{'source_marks'}) {
+        $remaining_source_marks = [@{$content->{'source_marks'}}];
+        delete $content->{'source_marks'};
+      }
+      my $new_asis = _new_asis_command_with_text('(', $content->{'parent'},
+                                                 $content->{'type'});
+      my $e = $new_asis->{'args'}->[0]->{'contents'}->[0];
+      $current_position = Texinfo::Common::relocate_source_marks(
+                                       $remaining_source_marks, $e,
+                                       $current_position, length('('));
+      if ($content->{'text'} !~ /^\($/) {
+        $content->{'text'} =~ s/^\(//;
+        $current_position = Texinfo::Common::relocate_source_marks(
+                                      $remaining_source_marks, $content,
+                                $current_position, length($content->{'text'}));
+      } else {
+        splice (@{$element->{'contents'}}, $i, 1);
+      }
+      splice (@{$element->{'contents'}}, $i, 0, $new_asis);
+    }
+    return;
+  }
+}
+
+sub move_index_entries_after_items($)
+{
+  # enumerate or itemize
+  my $current = shift;
+
+  return unless ($current->{'contents'});
+
+  my $previous;
+  foreach my $item (@{$current->{'contents'}}) {
+    #print STDERR "Before proceeding: $previous $item->{'cmdname'} 
(@{$previous->{'contents'}})\n" if ($previous and $previous->{'contents'});
+    if (defined($previous) and $item->{'cmdname'}
+        and $item->{'cmdname'} eq 'item'
+        and $previous->{'contents'} and scalar(@{$previous->{'contents'}})) {
+
+      my $previous_ending_container;
+      if ($previous->{'contents'}->[-1]->{'type'}
+          and ($previous->{'contents'}->[-1]->{'type'} eq 'paragraph'
+               or $previous->{'contents'}->[-1]->{'type'} eq 'preformatted')) {
+        $previous_ending_container = $previous->{'contents'}->[-1];
+      } else {
+        $previous_ending_container = $previous;
+      }
+
+      my $contents_nr = scalar(@{$previous_ending_container->{'contents'}});
+
+      # find the last index entry, with possibly comments after
+      my $last_entry_idx = -1;
+      for (my $i = $contents_nr -1; $i >= 0; $i--) {
+        my $content = $previous_ending_container->{'contents'}->[$i];
+        if ($content->{'type'} and $content->{'type'} eq 
'index_entry_command') {
+          $last_entry_idx = $i;
+        } elsif (not $content->{'cmdname'}
+                 or ($content->{'cmdname'} ne 'c'
+                     and $content->{'cmdname'} ne 'comment')) {
+          last;
+        }
+      }
+
+      if ($last_entry_idx >= 0) {
+        my $item_container;
+        if ($item->{'contents'} and $item->{'contents'}->[0]
+            and $item->{'contents'}->[0]->{'type'}
+            and $item->{'contents'}->[0]->{'type'} eq 'preformatted') {
+          $item_container = $item->{'contents'}->[0];
+        } else {
+          $item_container = $item;
+        }
+
+        for (my $i = $last_entry_idx; $i < $contents_nr; $i++) {
+          $previous_ending_container->{'contents'}->[$i]->{'parent'}
+               = $item_container;
+        }
+
+        my $insertion_idx = 0;
+        if ($item_container->{'contents'}
+            and $item_container->{'contents'}->[0]
+            and $item_container->{'contents'}->[0]->{'type'}
+            and $item_container->{'contents'}->[0]->{'type'} eq 
'ignorable_spaces_after_command') {
+          # insert after leading spaces, and add an end of line if there
+          # is none
+          $insertion_idx = 1;
+          $item_container->{'contents'}->[0]->{'text'} .= "\n"
+            if ($item_container->{'contents'}->[0]->{'text'} !~ /\n$/);
+        }
+        # first part of the splice is the insertion in $item_container
+        splice (@{$item_container->{'contents'}},
+                $insertion_idx, 0,
+                    # this splice removes from the previous container starting
+                    # at $last_entry_idx and returns the contents to be 
inserted
+                    splice (@{$previous_ending_container->{'contents'}},
+                            $last_entry_idx, $contents_nr - $last_entry_idx));
+        delete $previous_ending_container->{'contents'}
+          if (!scalar(@{$previous_ending_container->{'contents'}}))
+      }
+    }
+    $previous = $item;
+  }
+}
+
+sub _move_index_entries_after_items($$)
+{
+  my $type = shift;
+  my $current = shift;
+
+  if ($current->{'cmdname'} and ($current->{'cmdname'} eq 'enumerate'
+                                 or $current->{'cmdname'} eq 'itemize')) {
+    move_index_entries_after_items($current);
+  }
+  return undef;
+}
+
+# For @itemize/@enumerate
+sub move_index_entries_after_items_in_tree($)
+{
+  my $tree = shift;
+
+  modify_tree($tree, \&_move_index_entries_after_items);
+}
+
+sub _relate_index_entries_to_table_items_in($$)
+{
+  my $table = shift;
+  my $indices_information = shift;
+
+  return unless $table->{'contents'};
+
+  foreach my $table_entry (@{$table->{'contents'}}) {
+    next unless $table_entry->{'contents'}
+      and $table_entry->{'type'} eq 'table_entry';
+
+    my $term = $table_entry->{'contents'}->[0];
+    my $definition;
+    my $item;
+
+    # Move any index entries from the start of a 'table_definition' to
+    # the 'table_term'.
+    if (defined($table_entry->{'contents'}->[1])
+        and defined($table_entry->{'contents'}->[1]->{'type'})
+        and $table_entry->{'contents'}->[1]->{'type'} eq 'table_definition') {
+      $definition = $table_entry->{'contents'}->[1];
+      my $nr_index_entry_command = 0;
+      foreach my $child (@{$definition->{'contents'}}) {
+        if ($child->{'type'} and $child->{'type'} eq 'index_entry_command') {
+          $child->{'parent'} = $term;
+          $nr_index_entry_command++;
+        } else {
+          last;
+        }
+      }
+      if ($nr_index_entry_command > 0) {
+        unshift @{$term->{'contents'}},
+          splice (@{$definition->{'contents'}}, 0, $nr_index_entry_command);
+      }
+    }
+
+    if (defined($term->{'type'}) and $term->{'type'} eq 'table_term') {
+      # Relate the first index_entry_command in the 'table_term' to
+      # the term itself.
+
+      my $index_entry;
+      my $index_element;
+      foreach my $content (@{$term->{'contents'}}) {
+        if ($content->{'type'}
+            and $content->{'type'} eq 'index_entry_command') {
+          if (!$index_entry) {
+            my $index_info;
+            $index_element = $content;
+            ($index_entry, $index_info)
+              = Texinfo::Common::lookup_index_entry(
+                              $content->{'extra'}->{'index_entry'},
+                              $indices_information);
+          }
+        } elsif ($content->{'cmdname'} and $content->{'cmdname'} eq 'item') {
+          $item = $content unless $item;
+        }
+        if ($item and $index_entry) {
+          # This is better than overwriting 'entry_element', which
+          # holds important information.
+          $index_entry->{'entry_associated_element'} = $item;
+          # also add a reference from element to index entry in index
+          $item->{'extra'} = {} if (!$item->{'extra'});
+          $item->{'extra'}->{'associated_index_entry'}
+             = [@{$index_element->{'extra'}->{'index_entry'}}];
+          last;
+        }
+      }
+    }
+  }
+}
+
+# Locate all @tables in the tree, and relate index entries to
+# the @item that immediately follows or precedes them.
+sub _relate_index_entries_to_table_items($$$)
+{
+  my $type = shift;
+  my $current = shift;
+  my $indices_information = shift;
+
+  if ($current->{'cmdname'} and $current->{'cmdname'} eq 'table') {
+    _relate_index_entries_to_table_items_in($current, $indices_information);
+  }
+  return undef;
+}
+
+sub relate_index_entries_to_table_items_in_tree($)
+{
+  my $document = shift;
+
+  my $tree = $document->tree();
+  my $indices_information = $document->indices_information();
+
+  modify_tree($tree, \&_relate_index_entries_to_table_items,
+              $indices_information);
+}
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+Texinfo::ManipulateTree - Texinfo modules common tree manipulation functions
+
+=head1 SYNOPSIS
+
+  use Texinfo::ManipulateTree;
+
+=head1 NOTES
+
+The Texinfo Perl module main purpose is to be used in C<texi2any> to convert
+Texinfo to other formats.  There is no promise of API stability.
+
+=head1 DESCRIPTION
+
+Texinfo::ManipulateTree contains methods for copying and modifying the
+Texinfo tree used for default conversion to output formats.
+
+For optional tree transformation, see L<Texinfo::Transformations>.
+
+=head1 METHODS
+
+The Texinfo tree and Texinfo tree elements used in argument of some functions
+are documented in L<Texinfo::Parser/TEXINFO TREE>.  When customization
+information is needed, an object that defines C<get_conf> is
+expected, normally a L<Texinfo::Document/Getting customization
+options values registered in document> object. 
+
+=over
+
+=item move_index_entries_after_items_in_tree($tree)
+X<C<move_index_entries_after_items_in_tree>>
+
+In C<@enumerate> and C<@itemize> from the tree, move index entries
+appearing just before C<@item> after the C<@item>.  Comment lines
+between index entries are moved too.
+
+=item protect_colon_in_tree($tree)
+
+=item protect_node_after_label_in_tree($tree)
+X<C<protect_colon_in_tree>>
+X<C<protect_node_after_label_in_tree>>
+
+Protect colon with C<protect_colon_in_tree> and characters that
+are special in node names after a label in menu entries (tab
+dot and comma) with C<protect_node_after_label_in_tree>.
+The protection is achieved by putting protected characters
+in C<@asis{}>.
+
+=item protect_comma_in_tree($tree)
+X<C<protect_comma_in_tree>>
+
+Protect comma characters, replacing C<,> with @comma{} in tree.
+
+=item protect_first_parenthesis($element)
+X<C<protect_first_parenthesis>>
+
+Modify I<$element> contents by protecting the first parenthesis.
+If I<$element> is undef a fatal error with a backtrace will be emitted.
+
+=item relate_index_entries_to_table_items_in_tree($document)
+X<C<relate_index_entries_to_table_items_in_tree>>
+
+In tables, relate index entries preceding and following an
+entry with said item.  Reference one of them in the entry's
+C<entry_associated_element>.
+
+=back
+
+=head1 SEE ALSO
+
+L<Texinfo::Document>, L<Texinfo::Structuring>, L<Texinfo::Transformations>.
+
+=head1 AUTHOR
+
+Patrice Dumas, E<lt>pertusus@free.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2010- Free Software Foundation, Inc.  See the source file for
+all copyright years.
+
+This library 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.
+
+=cut
diff --git a/tp/Texinfo/Structuring.pm b/tp/Texinfo/Structuring.pm
index d8a280b5a7..daffcf776c 100644
--- a/tp/Texinfo/Structuring.pm
+++ b/tp/Texinfo/Structuring.pm
@@ -26,14 +26,15 @@ use 5.00405;
 # See comment at start of HTML.pm
 use if $] >= 5.012, feature => 'unicode_strings';
 
-use strict;
-# Can be used to check that there is no incorrect autovivfication
-# no autovivification qw(fetch delete exists store strict);
-
 # stop \s from matching non-ASCII spaces, etc.  \p{...} can still be
 # used to match Unicode character classes.
 use if $] >= 5.014, re => '/a';
 
+use strict;
+
+# Can be used to check that there is no incorrect autovivfication
+# no autovivification qw(fetch delete exists store strict);
+
 use Carp qw(cluck confess);
 
 use Texinfo::StructTransfXS;
@@ -42,6 +43,7 @@ use Texinfo::XSLoader;
 
 use Texinfo::Commands;
 use Texinfo::Common;
+use Texinfo::ManipulateTree;
 
 # for error messages
 use Texinfo::Convert::Texinfo qw(target_element_to_texi_label
@@ -1307,26 +1309,28 @@ sub new_node_menu_entry
     }
 
     $menu_entry_name
-     = Texinfo::Common::copy_contentsNonXS($name_element, 'menu_entry_name');
+     = Texinfo::ManipulateTree::copy_contentsNonXS($name_element,
+                                                   'menu_entry_name');
     foreach my $content (@{$menu_entry_name->{'contents'}}) {
       $content->{'parent'} = $menu_entry_name;
     }
     # colons could be doubly protected, but it is probably better
     # than not protected at all.
-    Texinfo::Common::protect_colon_in_tree($menu_entry_name);
+    Texinfo::ManipulateTree::protect_colon_in_tree($menu_entry_name);
   }
 
   my $entry = {'type' => 'menu_entry'};
 
   my $menu_entry_node
-   = Texinfo::Common::copy_contentsNonXS($node_name_element, 
'menu_entry_node');
+   = Texinfo::ManipulateTree::copy_contentsNonXS($node_name_element,
+                                                 'menu_entry_node');
   foreach my $content (@{$menu_entry_node->{'contents'}}) {
     $content->{'parent'} = $menu_entry_node;
   }
 
   # do not protect here, as it could already be protected, and
   # the menu entry should be the same as the node
-  #Texinfo::Common::protect_colon_in_tree($menu_entry_node);
+  #Texinfo::ManipulateTree::protect_colon_in_tree($menu_entry_node);
 
   my $description = {'type' => 'menu_entry_description',
                      'contents' => []};
@@ -1476,7 +1480,7 @@ sub new_complete_node_menu
         if ($associated_part and $associated_part->{'args'}
             and scalar(@{$associated_part->{'args'}}) > 0) {
           my $part_title_copy
-            = Texinfo::Common::copy_contentsNonXS(
+            = Texinfo::ManipulateTree::copy_contentsNonXS(
                                 $associated_part->{'args'}->[0]);
           my $part_title
            = Texinfo::Translations::gdt('Part: {part_title}',
@@ -1628,7 +1632,8 @@ sub _print_down_menus($$$$$;$)
   foreach my $menu (@menus) {
     foreach my $entry (@{$menu->{'contents'}}) {
       if ($entry->{'type'} and $entry->{'type'} eq 'menu_entry') {
-        push @master_menu_contents, Texinfo::Common::copy_treeNonXS($entry);
+        push @master_menu_contents,
+               Texinfo::ManipulateTree::copy_treeNonXS($entry);
         # gather node children to recursively print their menus
         my $node
              = _normalized_entry_associated_internal_node($entry,
@@ -1650,7 +1655,7 @@ sub _print_down_menus($$$$$;$)
     }
 
     my $node_title_copy
-      = Texinfo::Common::copy_contentsNonXS($node_name_element);
+      = Texinfo::ManipulateTree::copy_contentsNonXS($node_name_element);
 
     _insert_menu_comment_content(\@master_menu_contents, 0,
                                  $node_title_copy, 0);
diff --git a/tp/Texinfo/Transformations.pm b/tp/Texinfo/Transformations.pm
index e2ffa6b9dc..769d8524c2 100644
--- a/tp/Texinfo/Transformations.pm
+++ b/tp/Texinfo/Transformations.pm
@@ -37,8 +37,9 @@ use Texinfo::XSLoader;
 use Texinfo::Commands;
 use Texinfo::Common;
 use Texinfo::Translations;
-use Texinfo::Structuring;
 use Texinfo::Document;
+use Texinfo::ManipulateTree;
+use Texinfo::Structuring;
 
 require Exporter;
 use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS);
@@ -181,7 +182,8 @@ sub fill_gaps_in_sectioning($;$)
         my $line_content;
         if ($commands_heading_content) {
           $line_content
-            = Texinfo::Common::copy_contentsNonXS($commands_heading_content);
+            = Texinfo::ManipulateTree::copy_contentsNonXS(
+                                               $commands_heading_content);
           $line_content->{'parent'} = $line_arg;
         } else {
           my $asis_command = {'cmdname' => 'asis',
@@ -270,7 +272,7 @@ sub reference_to_arg_in_tree($)
 {
   my $tree = shift;
 
-  return Texinfo::Common::modify_tree($tree, \&_reference_to_arg);
+  return Texinfo::ManipulateTree::modify_tree($tree, \&_reference_to_arg);
 }
 
 # prepare and add a new node as a possible cross reference targets
@@ -315,13 +317,14 @@ sub _new_node($$;$)
   # otherwise, those that are protected with @asis.
   #
   # needed in nodes lines, @*ref and in menus with a label
-  $node_tree = Texinfo::Common::protect_comma_in_tree($node_tree);
+  $node_tree = Texinfo::ManipulateTree::protect_comma_in_tree($node_tree);
   # always
-  Texinfo::Common::protect_first_parenthesis($node_tree);
+  Texinfo::ManipulateTree::protect_first_parenthesis($node_tree);
   # in menu entry without label
-  $node_tree = Texinfo::Common::protect_colon_in_tree($node_tree);
+  $node_tree = Texinfo::ManipulateTree::protect_colon_in_tree($node_tree);
   # in menu entry with label
-  $node_tree = Texinfo::Common::protect_node_after_label_in_tree($node_tree);
+  $node_tree
+    = Texinfo::ManipulateTree::protect_node_after_label_in_tree($node_tree);
   $node_tree = reference_to_arg_in_tree($node_tree);
 
   my $empty_node = 0;
@@ -452,7 +455,7 @@ sub insert_nodes_for_sectioning_commands($)
         $new_node_tree = {'contents' => [{'text' => 'Top'}]};
       } else {
         $new_node_tree
-           = Texinfo::Common::copy_contentsNonXS($content->{'args'}->[0]);
+         = 
Texinfo::ManipulateTree::copy_contentsNonXS($content->{'args'}->[0]);
       }
       my $new_node = _new_node($new_node_tree, $document,
                                $customization_information);
@@ -467,8 +470,8 @@ sub insert_nodes_for_sectioning_commands($)
         $content->{'extra'}->{'associated_node'} = $new_node;
         $new_node->{'parent'} = $content->{'parent'};
         # reassociate index entries and menus
-        Texinfo::Common::modify_tree($content, \&_reassociate_to_node,
-                                     [$new_node, $previous_node]);
+        Texinfo::ManipulateTree::modify_tree($content, \&_reassociate_to_node,
+                                             [$new_node, $previous_node]);
       }
     }
     # check is_target to avoid erroneous nodes, such as duplicates
@@ -909,7 +912,7 @@ sub protect_hashchar_at_line_beginning($;$$)
   my $registrar = shift;
   my $customization_information = shift;
 
-  return Texinfo::Common::modify_tree($tree,
+  return Texinfo::ManipulateTree::modify_tree($tree,
                      \&_protect_hashchar_at_line_beginning,
                       [$registrar, $customization_information]);
 }
@@ -922,7 +925,7 @@ sub _protect_first_parenthesis_in_targets($$$)
 
   my $element_label = Texinfo::Common::get_label_element($current);
   if ($element_label) {
-    Texinfo::Common::protect_first_parenthesis($element_label);
+    Texinfo::ManipulateTree::protect_first_parenthesis($element_label);
   }
   return undef;
 }
@@ -931,7 +934,8 @@ sub protect_first_parenthesis_in_targets($)
 {
   my $tree = shift;
 
-  Texinfo::Common::modify_tree($tree, \&_protect_first_parenthesis_in_targets);
+  Texinfo::ManipulateTree::modify_tree($tree,
+                           \&_protect_first_parenthesis_in_targets);
 }
 
 1;
@@ -955,6 +959,9 @@ without nodes and C<complete_tree_nodes_menus> and
 C<complete_tree_nodes_missing_menu> that completes the node menus based on the
 sectioning tree.
 
+Methods for copying and modifying the Texinfo tree used for default
+conversion to output formats are in L<Texinfo::ManipulateTree>.
+
 =head1 METHODS
 
 No method is exported in the default case.
@@ -1057,7 +1064,7 @@ nodes are used as labels in the generated master menu.
 =head1 SEE ALSO
 
 L<Texinfo manual|http://www.gnu.org/s/texinfo/manual/texinfo/>,
-L<Texinfo::Parser>.
+L<Texinfo::Parser>, L<Texinfo::ManipulateTree>.
 
 =head1 AUTHOR
 
diff --git a/tp/Texinfo/Translations.pm b/tp/Texinfo/Translations.pm
index fb9ed212ab..dbe9da62c7 100644
--- a/tp/Texinfo/Translations.pm
+++ b/tp/Texinfo/Translations.pm
@@ -47,6 +47,8 @@ use Texinfo::Convert::Unicode;
 # to load a parser
 use Texinfo::Parser;
 
+use Texinfo::ManipulateTree;
+
 # we want a reliable way to switch locale for the document
 # strings translations so we don't use the system gettext.
 Locale::Messages->select_package ('gettext_pp');
@@ -410,7 +412,8 @@ sub complete_indices($;$)
               $name = $arg;
             } elsif ($role eq 'class') {
               $class = $arg;
-            } elsif ($role eq 'arg' or $role eq 'typearg' or $role eq 
'delimiter') {
+            } elsif ($role eq 'arg' or $role eq 'typearg'
+                     or $role eq 'delimiter') {
               last;
             }
           }
@@ -422,10 +425,10 @@ sub complete_indices($;$)
 
           my $def_command = $main_entry_element->{'extra'}->{'def_command'};
 
-          my $class_copy = Texinfo::Common::copy_treeNonXS($class);
-          my $name_copy = Texinfo::Common::copy_treeNonXS($name);
-          my $ref_class_copy = Texinfo::Common::copy_treeNonXS($class);
-          my $ref_name_copy = Texinfo::Common::copy_treeNonXS($name);
+          my $class_copy = Texinfo::ManipulateTree::copy_treeNonXS($class);
+          my $name_copy = Texinfo::ManipulateTree::copy_treeNonXS($name);
+          my $ref_class_copy = Texinfo::ManipulateTree::copy_treeNonXS($class);
+          my $ref_name_copy = Texinfo::ManipulateTree::copy_treeNonXS($name);
 
           # Use the document language that was current when the command was
           # used for getting the translation.
diff --git a/tp/t/index_before_item.t b/tp/t/index_before_item.t
index 936698e3b7..26aef75be3 100644
--- a/tp/t/index_before_item.t
+++ b/tp/t/index_before_item.t
@@ -8,9 +8,9 @@ use Test::More;
 BEGIN { plan tests => 6; }
 
 use Texinfo::Parser qw(parse_texi_piece);
-use Texinfo::Common qw(move_index_entries_after_items_in_tree);
-use Texinfo::Document;
 use Texinfo::Convert::Texinfo;
+use Texinfo::Document;
+use Texinfo::ManipulateTree;
 use Texinfo::DebugTree;
 
 ok(1);
@@ -26,7 +26,7 @@ sub run_test($$$)
 
   #print STDERR Texinfo::DebugTree::convert_tree(undef, $tree)."\n";
 
-  move_index_entries_after_items_in_tree($tree);
+  Texinfo::ManipulateTree::move_index_entries_after_items_in_tree($tree);
   # rebuild tree
   $tree = $document->tree();
 
diff --git a/tp/t/protect_character_in_texinfo.t 
b/tp/t/protect_character_in_texinfo.t
index 75128975c6..caef11dca9 100644
--- a/tp/t/protect_character_in_texinfo.t
+++ b/tp/t/protect_character_in_texinfo.t
@@ -8,10 +8,9 @@ use Test::More;
 BEGIN { plan tests => 7; }
 
 use Texinfo::Parser qw(parse_texi_line parse_texi_piece);
-use Texinfo::Common qw(protect_comma_in_tree protect_colon_in_tree
-      protect_node_after_label_in_tree);
 use Texinfo::Convert::Texinfo;
 use Texinfo::Document;
+use Texinfo::ManipulateTree;
 use Texinfo::XSLoader;
 
 my $XS_structuring = Texinfo::XSLoader::XS_structuring_enabled();
@@ -45,13 +44,13 @@ sub run_test($$$$)
 
   foreach my $tree ($tree_as_text, $tree_as_line) {
     if ($do->{'protect_comma'}) {
-      protect_comma_in_tree($tree);
+      Texinfo::ManipulateTree::protect_comma_in_tree($tree);
     }
     if ($do->{'protect_colon'}) {
-      protect_colon_in_tree($tree);
+      Texinfo::ManipulateTree::protect_colon_in_tree($tree);
     }
     if ($do->{'protect_node_after_label'}) {
-      protect_node_after_label_in_tree($tree);
+      Texinfo::ManipulateTree::protect_node_after_label_in_tree($tree);
     }
   }
 
diff --git a/tp/t/test_protect_contents.t b/tp/t/test_protect_contents.t
index f055b8306a..b10572a8f7 100644
--- a/tp/t/test_protect_contents.t
+++ b/tp/t/test_protect_contents.t
@@ -8,7 +8,6 @@ use Test::More;
 BEGIN { plan tests => 2; }
 
 use Texinfo::Parser qw(parse_texi_line parse_texi_piece);
-use Texinfo::Common qw(protect_first_parenthesis);
 use Texinfo::Convert::Texinfo;
 use Texinfo::Document;
 use Texinfo::Transformations;
diff --git a/tp/t/test_tree_copy.t b/tp/t/test_tree_copy.t
index d880a5f179..cc15c6fc38 100644
--- a/tp/t/test_tree_copy.t
+++ b/tp/t/test_tree_copy.t
@@ -16,6 +16,7 @@ use Texinfo::Common;
 use Texinfo::Parser;
 use Texinfo::Convert::Texinfo;
 use Texinfo::Document;
+use Texinfo::ManipulateTree;
 use Texinfo::Structuring;
 
 my $srcdir = $ENV{'srcdir'};
@@ -57,7 +58,7 @@ 
$tref->{'contents'}->[1]->{'extra'}->{'thing'}->{'contents'}->[0]->{'extra'}->{'
 
 my $tref_texi = Texinfo::Convert::Texinfo::convert_to_texinfo($tref);
 
-my $tref_copy = Texinfo::Common::copy_treeNonXS($tref);
+my $tref_copy = Texinfo::ManipulateTree::copy_treeNonXS($tref);
 
 my $tref_copy_texi = Texinfo::Convert::Texinfo::convert_to_texinfo($tref_copy);
 
@@ -137,7 +138,7 @@ T
 my $test_parser = Texinfo::Parser::parser();
 my $document = Texinfo::Parser::parse_texi_piece($test_parser, $text);
 my $tree = $document->tree();
-my $copy = Texinfo::Common::copy_tree($tree);
+my $copy = Texinfo::ManipulateTree::copy_tree($tree);
 
 my $texi_tree = Texinfo::Convert::Texinfo::convert_to_texinfo($tree);
 
@@ -151,7 +152,7 @@ Texinfo::Structuring::sectioning_structure($document);
 # rebuild the tree
 $tree = $document->tree();
 
-my $copy_with_sec = Texinfo::Common::copy_tree($tree);
+my $copy_with_sec = Texinfo::ManipulateTree::copy_tree($tree);
 
 my $texi_tree_with_sec = Texinfo::Convert::Texinfo::convert_to_texinfo($tree);
 my $texi_copy_with_sec
@@ -182,7 +183,7 @@ foreach my $file_include (['Texinfo', $manual_file, 
$manual_include_dir],
     warn "$label: ".$error_message->{'error_line'}
       if ($debug);
   }
-  my $test_tree_copy = Texinfo::Common::copy_tree($texinfo_test_tree);
+  my $test_tree_copy = Texinfo::ManipulateTree::copy_tree($texinfo_test_tree);
 
   my $test_texi
      = Texinfo::Convert::Texinfo::convert_to_texinfo($texinfo_test_tree);
diff --git a/tp/t/test_utils.pl b/tp/t/test_utils.pl
index 32700a76d2..9774668ef5 100644
--- a/tp/t/test_utils.pl
+++ b/tp/t/test_utils.pl
@@ -70,6 +70,7 @@ use Texinfo::Parser;
 use Texinfo::Convert::Text;
 use Texinfo::Document;
 use Texinfo::Convert::PlainTexinfo;
+use Texinfo::ManipulateTree;
 use Texinfo::Structuring;
 use Texinfo::Indices;
 use Texinfo::Translations;
@@ -891,10 +892,10 @@ sub test($$)
     # There are other specific tests for comparison to texinfo, but here
     # we also get the tree.
     %tested_transformations = (
-     'protect_comma' => \&Texinfo::Common::protect_comma_in_tree,
-     'protect_colon' => \&Texinfo::Common::protect_colon_in_tree,
+     'protect_comma' => \&Texinfo::ManipulateTree::protect_comma_in_tree,
+     'protect_colon' => \&Texinfo::ManipulateTree::protect_colon_in_tree,
      'protect_node_after_label'
-        => \&Texinfo::Common::protect_node_after_label_in_tree,
+        => \&Texinfo::ManipulateTree::protect_node_after_label_in_tree,
      'protect_first_parenthesis'
       => \&Texinfo::Transformations::protect_first_parenthesis_in_targets,
      'protect_hashchar_at_line_beginning'
@@ -1095,11 +1096,12 @@ sub test($$)
   }
 
   if ($tree_transformations{'relate_index_entries_to_items'}) {
-    Texinfo::Common::relate_index_entries_to_table_items_in_tree($document);
+    Texinfo::ManipulateTree::relate_index_entries_to_table_items_in_tree(
+                                                                   $document);
   }
 
   if ($tree_transformations{'move_index_entries_after_items'}) {
-    Texinfo::Common::move_index_entries_after_items_in_tree($tree);
+    Texinfo::ManipulateTree::move_index_entries_after_items_in_tree($tree);
   }
 
   if ($tree_transformations{'insert_nodes_for_sectioning_commands'}) {
diff --git a/tp/texi2any.pl b/tp/texi2any.pl
index ac04b714ff..c55916c608 100755
--- a/tp/texi2any.pl
+++ b/tp/texi2any.pl
@@ -1643,12 +1643,13 @@ while(@input_files) {
 
   if 
($formats_table{$converted_format}->{'relate_index_entries_to_table_items'}
       or $tree_transformations{'relate_index_entries_to_table_items'}) {
-    Texinfo::Common::relate_index_entries_to_table_items_in_tree($document);
+    Texinfo::ManipulateTree::relate_index_entries_to_table_items_in_tree(
+                                                                 $document);
   }
 
   if ($formats_table{$converted_format}->{'move_index_entries_after_items'}
       or $tree_transformations{'move_index_entries_after_items'}) {
-    Texinfo::Common::move_index_entries_after_items_in_tree($tree);
+    Texinfo::ManipulateTree::move_index_entries_after_items_in_tree($tree);
   }
 
   if ($tree_transformations{'insert_nodes_for_sectioning_commands'}) {



reply via email to

[Prev in Thread] Current Thread [Next in Thread]