}; 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_container', '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'}}; my $brace_container = {'type' => 'brace_container', 'parent' => $e}; $e->{'args'} = [$brace_container]; $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'} and ($content->{'cmdname'} eq 'c' or $content->{'cmdname'} eq 'comment' # subentry is not within the index entry in the tree or $content->{'cmdname'} eq 'subentry'))) { 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); }