# TexinfoXML.pm: output tree as Texinfo XML.
#
# Copyright 2011, 2012, 2013, 2016, 2017, 2018 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::Convert::TexinfoXML;

use 5.00405;
use strict;

use Texinfo::Convert::Converter;
use Texinfo::Common;
use Texinfo::Convert::Unicode;
# for debugging and adding the original line for some commands
use Texinfo::Convert::Texinfo;
use Data::Dumper;
use Carp qw(cluck);

require Exporter;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
@ISA = qw(Exporter Texinfo::Convert::Converter);

%EXPORT_TAGS = ( 'all' => [ qw(
  convert
  convert_tree
  output
) ] );

@EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );

@EXPORT = qw(
);

$VERSION = '6.6';

# XML specific
my %defaults = (
  'ENABLE_ENCODING'      => 0,
  'SHOW_MENU'            => 1,
  'EXTENSION'            => 'xml',
  #'output_perl_encoding' => 'utf8',
  'OUTPUT_ENCODING_NAME' => 'utf-8',
  'TEXINFO_DTD_VERSION'  => '5.0',
  'OUTFILE'              => undef,
  'SUBDIR'               => undef,
  'output_format'        => 'xml',
  'SPLIT'                => 0,
  'documentlanguage'     => 'en',
);


# our because it is used in the xml to texi translator
our %commands_formatting = (
           '*' => 'linebreak',
           ' ' => ['spacecmd', 'type', 'spc'],
           "\t" => ['spacecmd', 'type', 'tab'],
           "\n" => ['spacecmd', 'type', 'nl'],
           '-' => 'hyphenbreak',  # hyphenation hint
           '|' => '',  # used in formatting commands @evenfooting and friends
           '/' => 'slashbreak',
           ':' => 'noeos',
           '!' => 'eosexcl',
           '?' => 'eosquest',
           '.' => 'eosperiod',
           '@' => 'arobase',
           '{' => 'lbrace',
           '}' => 'rbrace',
           '&' => 'amp',
           '\\' => 'backslash',  # should only appear in math

           'TeX' => 'tex',
           'LaTeX' => 'latex',
           'bullet' => 'bullet',
           'copyright'    => 'copyright',
           'registeredsymbol'   => 'registered',
           'dots'    => 'dots',
           'enddots'    => 'enddots',
           'error'        => 'errorglyph',
           'expansion'     => 'expansion',
           'arrow'        => 'rarr',
           'click'        => ['click', 'command', 'arrow'],
           'minus'        => 'minus',
           'point'        => 'point',
           'print'        => 'printglyph',
           'result'       => 'result',
           'l'            => 'lslash',
           'L'            => 'Lslash',
           'today'        => ['today'],
           'comma'        => 'comma',
           'atchar'       => 'atchar',
           'ampchar'      => 'ampchar',
           'lbracechar'   => 'lbracechar',
           'rbracechar'   => 'rbracechar',
           'backslashchar' => 'backslashchar',
           'hashchar'      => 'hashchar',
);

# use default XML formatting to complete the hash, removing XML
# specific formatting.  This avoids some code duplication.
my %default_xml_commands_formatting = 
    %{$Texinfo::Convert::Converter::default_xml_commands_formatting{'normal'}};

foreach my $command (keys(%default_xml_commands_formatting)) {
  if (!exists($commands_formatting{$command})) {
    if ($default_xml_commands_formatting{$command} ne '') {
      if ($default_xml_commands_formatting{$command} =~ /^&(.*);$/) {
        $commands_formatting{$command} = $1;
      } else {
        die "BUG: Strange xml_commands_formatting: $default_xml_commands_formatting{$command}\n";
      }
    } else {
      $commands_formatting{$command} = '';
    }
  }
}

# Following are XML specific formatting functions.

# format specific.  Used in few places where plain text is used outside
# of attributes.
sub protect_text($$)
{
  my $self = shift;
  my $string = shift;
  return $self->_protect_text($string);
}

sub _xml_attributes($$)
{
  my $self = shift;
  my $attributes = shift;
  if (ref($attributes) ne 'ARRAY') {
    cluck "attributes not an array($attributes).";
  }
  my $result = '';
  for (my $i = 0; $i < scalar(@$attributes); $i += 2) {
    # this cannot be used, because of formfeed, as in 
    # attribute < which is substituted from &formfeed; is not allowed
    #my $text = $self->_protect_text($attributes->[$i+1]);
    my $text = $self->xml_protect_text($attributes->[$i+1]);
    # in fact form feed is not allowed at all in XML, even protected
    # and even in xml 1.1 in contrast to what is said on internet.
    # maybe this is a limitation of libxml?
    #$text =~ s/\f/&#12;/g;
    if ($attributes->[$i] ne 'spaces' 
        and $attributes->[$i] ne 'trailingspaces') {
      $text =~ s/\f/&attrformfeed;/g;
      # &attrformfeed; resolves to \f so \ are doubled
      $text =~ s/\\/\\\\/g;
    }
    $result .= " $attributes->[$i]=\"".$text."\"";
  }
  return $result;
}

# format specific
sub element($$$)
{
  my $self = shift;
  my $element_name = shift;
  my $attributes = shift;
  my $result= '<'.$element_name;
  $result .= $self->_xml_attributes($attributes) if ($attributes);
  $result .= '/>';
  return $result;
}

# format specific
sub open_element($$$)
{
  my $self = shift;
  my $element_name = shift;
  my $attributes = shift;
  my $result= '<'."$element_name";
  $result .= $self->_xml_attributes($attributes) if ($attributes);
  $result .= '>';
  return $result;
}

# format specific
sub close_element($$)
{
  my $self = shift;
  my $element_name = shift;
  my $result= "</$element_name>";
  return $result;
}

# format specific
sub format_atom($$)
{
  my $self = shift;
  my $atom = shift;
  if ($commands_formatting{$atom} ne '') {
    return '&'.$commands_formatting{$atom}.';';
  } else {
    return '';
  }
}

# format specific
sub format_comment($$)
{
  my $self = shift;
  my $string = shift;

  return $self->xml_comment($string);
}

# form feed is not accepted in xml, replace it.
sub _protect_text($$)
{
  my $self = shift;
  my $text = shift;
  my $result = $self->xml_protect_text($text);
  $result =~ s/\f/&formfeed;/g;
  return $result;
}

# format specific
sub format_text($$)
{
  my $self = shift;
  my $root = shift;
  my $result = $self->_protect_text($root->{'text'});
  if (! defined($root->{'type'}) or $root->{'type'} ne 'raw') {
    if (!$self->{'document_context'}->[-1]->{'monospace'}->[-1]) {
      $result =~ s/``/&textldquo;/g;
      $result =~ s/\'\'/&textrdquo;/g;
      $result =~ s/---/&textmdash;/g;
      $result =~ s/--/&textndash;/g;
      $result =~ s/'/&textrsquo;/g;
      $result =~ s/`/&textlsquo;/g;
    }
  }
  return $result;
}

# output format specific
sub format_header($)
{
  my $self = shift;
  my $encoding = '';
  if ($self->get_conf('OUTPUT_ENCODING_NAME')
      and $self->get_conf('OUTPUT_ENCODING_NAME') ne 'utf-8') {
    $encoding = " encoding=\"".$self->get_conf('OUTPUT_ENCODING_NAME')."\" ";
  }
  my $texinfo_dtd_version = $self->get_conf('TEXINFO_DTD_VERSION');
  if (!defined($texinfo_dtd_version)) {
    $texinfo_dtd_version = '1.00';
  }

  my $header =  "<?xml version=\"1.0\"${encoding}?>".'
<!DOCTYPE texinfo PUBLIC "-//GNU//DTD TexinfoML V'.$texinfo_dtd_version.'//EN" "http://www.gnu.org/software/texinfo/dtd/'.$texinfo_dtd_version.'/texinfo.dtd">
'. $self->open_element('texinfo', ['xml:lang', $self->get_conf('documentlanguage')])."\n";
  if ($self->{'output_file'} ne '') {
    my $output_filename = $self->{'output_filename'};
    $header .= $self->open_element('filename',['file', $output_filename])
             .$self->close_element('filename')."\n";
  }
  return $header;
}

# following is not format specific.  Some infos are taken from generic XML, but 
# XML specific formatting is stripped.

my %accents = (
 '=' => 'macr',
# following are not entities
 'H' => 'doubleacute',
 'u' => 'breve',
 'v' => 'caron',
);

# our because it is used in the xml to texi translator
our %accent_types = (%Texinfo::Convert::Converter::xml_accent_entities, %accents);

# no entity
my @other_accents = ('dotaccent', 'tieaccent', 'ubaraccent', 'udotaccent');
foreach my $accent (@other_accents) {
  $accent_types{$accent} = $accent;
}

my %misc_command_line_attributes = (
  'setfilename' => 'file',
  'documentencoding' => 'encoding',
  'verbatiminclude' => 'file',
  'documentlanguage' => 'xml:lang',
);

my %misc_command_numbered_arguments_attributes = (
  'definfoenclose' => [ 'command', 'open', 'close' ],
  'alias' => [ 'new', 'existing' ],
  'syncodeindex' => [ 'from', 'to' ],
  'synindex' => [ 'from', 'to' ],
);

my %misc_commands = %Texinfo::Common::misc_commands;

foreach my $command ('item', 'headitem', 'itemx', 'tab', 
                      keys %Texinfo::Common::def_commands) {
  delete $misc_commands{$command};
}

my %default_args_code_style
  = %Texinfo::Convert::Converter::default_args_code_style;
my %regular_font_style_commands = %Texinfo::Common::regular_font_style_commands;

# our because it is used in the xml to texi translator
our %commands_args_elements = (
  'email' => ['emailaddress', 'emailname'],
  'uref' => ['urefurl', 'urefdesc', 'urefreplacement'],
  'url' => ['urefurl', 'urefdesc', 'urefreplacement'],
  'inforef' => ['inforefnodename', 'inforefrefname', 'inforefinfoname'],
  'image' => ['imagefile', 'imagewidth', 'imageheight', 
              'alttext', 'imageextension'],
  'quotation' => ['quotationtype'],
  'float' => ['floattype', 'floatname'],
  'itemize' => ['itemprepend'],
  'enumerate' => ['enumeratefirst'],
);

foreach my $ref_cmd ('pxref', 'xref', 'ref') {
  $commands_args_elements{$ref_cmd} 
    = ['xrefnodename', 'xrefinfoname', 'xrefprinteddesc', 'xrefinfofile', 
       'xrefprintedname'];
}

foreach my $explained_command (keys(%Texinfo::Common::explained_commands)) {
  $commands_args_elements{$explained_command} = ["${explained_command}word",
                                                 "${explained_command}desc"];
}

foreach my $inline_command (keys(%Texinfo::Common::inline_commands)) {
  $commands_args_elements{$inline_command} = ["${inline_command}format",
                                              "${inline_command}content"];
}

my $inline_command = 'inlinefmtifelse';
$commands_args_elements{$inline_command} = ["${inline_command}format",
             "${inline_command}contentif", "${inline_command}contentelse"];

my %commands_elements;
foreach my $command (keys(%Texinfo::Common::brace_commands)) {
  $commands_elements{$command} = [$command];
  if ($commands_args_elements{$command}) {
    push @{$commands_elements{$command}}, @{$commands_args_elements{$command}};
  }
}

my %defcommand_name_type = (
 'deffn'     => 'function',
 'defvr'     => 'variable',
 'deftypefn' => 'function',
 'deftypeop' => 'operation',
 'deftypevr' => 'variable',
 'defcv'     => 'classvar',
 'deftypecv' => 'classvar',
 'defop'     => 'operation',
 'deftp'     => 'datatype',
);

my %ignored_types;
foreach my $type (
            # those are put as spaces in the corresponding @-command
            'empty_spaces_after_command',
            'empty_spaces_before_argument',
  ) {
  $ignored_types{$type} = 1;
}

# this is used in IXIN, to ignore everything before first node.
sub _set_ignored_type($$)
{
  my $self = shift;
  my $type = shift;

  $ignored_types{$type} = 1;
}

my %type_elements = (
  'paragraph' => 'para',
  'preformatted' => 'pre',
  'menu_entry' => 'menuentry',
  'menu_entry_node' => 'menunode',
  'menu_comment' => 'menucomment',
  'menu_entry_description' => 'menudescription',
  'menu_entry_name' => 'menutitle',
  'preamble' => 'preamble',
  'table_item' => 'tableitem',
  'table_entry' => 'tableentry',
  'table_term' => 'tableterm',
  'row' => 'row',
  'multitable_head' => 'thead',
  'multitable_body' => 'tbody',
  'def_item' => 'definitionitem',
  'before_item' => 'beforefirstitem',
);

my %default_context_block_commands = (
  'float' => 1,
);

sub converter_defaults($$)
{
  return %defaults;
}

sub converter_initialize($)
{
  my $self = shift;

  $self->{'document_context'} = [{'monospace' => [0]}];
  $self->{'context_block_commands'} = {%default_context_block_commands};
  foreach my $raw (keys (%Texinfo::Common::format_raw_commands)) {
    $self->{'context_block_commands'}->{$raw} = 1
         if $self->{'expanded_formats_hash'}->{$raw};
  } 
  if ($self->{'parser'}) {
    $self->{'index_names'} = $self->{'parser'}->indices_information();
  }
}

# Main output function for the XML file.
sub output($$)
{
  my $self = shift;
  my $root = shift;

  $self->_set_outfile();
  return undef unless $self->_create_destination_directory();

  my $fh;
  if (! $self->{'output_file'} eq '') {
    $fh = $self->Texinfo::Common::open_out($self->{'output_file'});
    if (!$fh) {
      $self->document_error(sprintf(__("could not open %s for writing: %s"),
                                    $self->{'output_file'}, $!));
      return undef;
    }
  }

  $self->_set_global_multiple_commands(-1);

  my $result = '';
  $result .= $self->_output_text($self->format_header(), $fh);
  if ($self->get_conf('USE_NODES')) {
    $result .= $self->convert_document_nodes($root, $fh);
  } else {
    $result .= $self->convert_document_sections($root, $fh);
  }
  $result .= $self->_output_text($self->close_element('texinfo')."\n", $fh);
  if ($fh and $self->{'output_file'} ne '-') {
    $self->register_close_file($self->{'output_file'});
    if (!close ($fh)) {
      $self->document_error(sprintf(__("error on closing %s: %s"),
                                    $self->{'output_file'}, $!));
    }
  }

  return $result;
}

sub _format_command($$)
{
  my $self = shift;
  my $command = shift;

  if (! ref($commands_formatting{$command})) {
    return $self->format_atom($command);
  } else {
    my @spec = @{$commands_formatting{$command}};
    my $element_name = shift @spec;
    return $self->element($element_name, \@spec);
  }
}

sub _index_entry($$)
{
  my $self = shift;
  my $root = shift;
  if ($root->{'extra'} and $root->{'extra'}->{'index_entry'}) {
    my $index_entry = $root->{'extra'}->{'index_entry'};
    my $attribute = ['index', $index_entry->{'index_name'}];
    push @$attribute, ('number', $index_entry->{'number'})
        if (defined($index_entry->{'number'}));
    # in case the index is not a default index, or the style of the
    # entry (in code or not) is not the default for this index
    if ($self->{'index_names'}) {
      my $in_code = $self->{'index_names'}->{$index_entry->{'index_name'}}->{'in_code'};
      if (!$Texinfo::Common::index_names{$index_entry->{'index_name'}}
          or $in_code != $Texinfo::Common::index_names{$index_entry->{'index_name'}}->{'in_code'}) {
        push @$attribute, ('incode', $in_code);
      }
      if ($self->{'index_names'}->{$index_entry->{'index_name'}}->{'merged_in'}) {
        push @$attribute, ('mergedindex', 
         $self->{'index_names'}->{$index_entry->{'index_name'}}->{'merged_in'});
      }
    }
    my $result = $self->open_element('indexterm', $attribute);
    push @{$self->{'document_context'}}, {'monospace' => [0]};
    $self->{'document_context'}->[-1]->{'monospace'}->[-1] = 1
      if ($index_entry->{'in_code'});
    $result .= $self->_convert({'contents' => $index_entry->{'content'}});
    pop @{$self->{'document_context'}};
    $result .= $self->close_element('indexterm');
    return $result;
  }
  return '';
}

sub _infoenclose_attribute($$) {
  my $self = shift;
  my $root = shift;
  my @attribute = ();
  return @attribute if (!$root->{'extra'});
  push @attribute, ('begin', $root->{'extra'}->{'begin'})
    if (defined($root->{'extra'}->{'begin'}));
  push @attribute, ('end', $root->{'extra'}->{'end'})
    if (defined($root->{'extra'}->{'end'}));
  return @attribute;
}

sub _accent($$;$$$)
{
  my $self = shift;
  my $text = shift;
  my $root = shift;
  my $in_upper_case = shift;
  my $attributes = shift;
  $attributes = [] if (!defined($attributes));

  unshift @$attributes, ('type', $accent_types{$root->{'cmdname'}});
  my $result = $self->open_element('accent', $attributes);
  $result .= $text;
  $result .= $self->close_element('accent');
  return $result;
}

sub convert($$;$)
{
  my $self = shift;
  my $root = shift;
  my $fh = shift;
  
  return $self->convert_document_sections($root, $fh);
}

sub convert_tree($$)
{
  my $self = shift;
  my $root = shift;

  return $self->_convert($root);
}

sub _protect_in_spaces($)
{
  my $text = shift;
  $text =~ s/\n/\\n/g;
  $text =~ s/\f/\\f/g;
  return $text;
}

sub _leading_spaces($)
{
  my $root = shift;
  if ($root->{'extra'} and $root->{'extra'}->{'spaces_before_argument'}
      and $root->{'extra'}->{'spaces_before_argument'} ne '') {
    return ('spaces', _protect_in_spaces(
         $root->{'extra'}->{'spaces_before_argument'}));
  } else {
    return ();
  }
}

sub _leading_spaces_before_argument($)
{
  my $root = shift;
  if ($root->{'extra'} and $root->{'extra'}->{'spaces_before_argument'}
      and $root->{'extra'}->{'spaces_before_argument'} ne '') {
    return ('spaces', _protect_in_spaces(
                 $root->{'extra'}->{'spaces_before_argument'}));
  } else {
    return ();
  }
}

sub _end_line_spaces
{
  my $root = shift;

  my $end_spaces = undef;
  if ($root->{'args'}->[-1]
      and $root->{'args'}->[-1]->{'extra'}
      and $root->{'args'}->[-1]->{'extra'}->{'spaces_after_argument'}) {
    $end_spaces = $root->{'args'}->[-1]->{'extra'}->{'spaces_after_argument'};
    chomp $end_spaces;
  }
  return $end_spaces;
}

sub _arg_line($)
{
  my $self = shift;
  my $root = shift;
  if ($root->{'extra'} and defined($root->{'extra'}->{'arg_line'})) {
    my $line = $root->{'extra'}->{'arg_line'};
    chomp($line);
    if ($line ne '') {
      return ('line', $line);
    }
  } 
  return ();
}

sub _trailing_spaces_arg($$)
{
  my $self = shift;
  my $root = shift;
  
  my @spaces = $self->_collect_leading_trailing_spaces_arg($root);
  if (defined($spaces[1])) {
    chomp($spaces[1]);
    if ($spaces[1] ne '') {
      return ('trailingspaces', _protect_in_spaces($spaces[1]));
    }
  }
  return ();
}

sub _leading_trailing_spaces_arg($$)
{
  my $self = shift;
  my $root = shift;

  my @result;
  @result = _leading_spaces_before_argument($root);

  my @spaces = $self->_collect_leading_trailing_spaces_arg($root);
  if (defined($spaces[1])) {
    chomp($spaces[1]);
    if ($spaces[1] ne '') {
      push @result, ('trailingspaces', _protect_in_spaces($spaces[1]));
    }
  }
  return @result;
}

sub _texinfo_line($$)
{
  my $self = shift;
  my $root = shift;

  my $line = Texinfo::Convert::Texinfo::convert($root->{'args'}->[-1]);
  chomp($line);
  if ($line ne '') {
    return ('line', $line);
  } else {
    return ();
  }
}

sub _convert_argument_and_end_line($$)
{
  my $self = shift;
  my $root = shift;

  my $converted = $self->convert_tree($root->{'args'}->[-1]);

  my $end_line = '';

  if ($root->{'args'}->[-1]->{'extra'} and $root->{'args'}->[-1]->{'extra'}->{'spaces_after_argument'}) {
    $converted .= $root->{'args'}->[-1]->{'extra'}->{'spaces_after_argument'};
  }

  if ($root->{'args'}->[-1]->{'extra'} and $root->{'args'}->[-1]->{'extra'}->{'comment_at_end'}) {
    $end_line = $self->convert_tree($root->{'args'}->[-1]->{'extra'}->{'comment_at_end'});
  } else {
    if (chomp($converted)) {
      $end_line = "\n";
    } else {
      $end_line = "";
    }
  }
  return ($converted, $end_line);
}

my @node_directions = ('Next', 'Prev', 'Up');

# not used here, but it is consistent with other %commands_args_elements
# entries and may be used by XML to Texinfo converters
$commands_args_elements{'node'} = ['nodename'];
foreach my $direction (@node_directions) {
  push @{$commands_args_elements{'node'}}, 'node'.lc($direction);
}

sub _convert($$;$);

sub _convert($$;$)
{
  my $self = shift;
  my $root = shift;

  if (0) {
  #if (1) { #}
    print STDERR "root\n";
    print STDERR "  Command: $root->{'cmdname'}\n" if ($root->{'cmdname'});
    print STDERR "  Type: $root->{'type'}\n" if ($root->{'type'});
    print STDERR "  Text: $root->{'text'}\n" if (defined($root->{'text'}));
    #print STDERR "  Special def_command: $root->{'extra'}->{'def_command'}\n"
    #  if (defined($root->{'extra'}) and $root->{'extra'}->{'def_command'});
  }

  return '' if ($root->{'type'} and $ignored_types{$root->{'type'}});
  my $result = '';
  if (defined($root->{'text'})) {
    if ($self->{'document_context'}->[-1]->{'raw'}) {
      # ignore the newline at the end of the @xml line, and the last in xml
      if ($root->{'type'} and ($root->{'type'} eq 'empty_line_after_command'
                               or $root->{'type'} eq 'last_raw_newline')) {
        return '';
      } else {
        return $root->{'text'};
      }
    }
    $result = $self->format_text($root);
    return $result;
  }
  my @close_elements;
  if ($root->{'cmdname'}) {
    if (defined($commands_formatting{$root->{'cmdname'}})) {
      if ($root->{'cmdname'} eq 'click' 
          and $root->{'extra'} 
          and defined($root->{'extra'}->{'clickstyle'})) {
        return $self->element('click', ['command', $root->{'extra'}->{'clickstyle'}]);;
      }
      if ($self->{'itemize_line'} and $root->{'type'} 
          and $root->{'type'} eq 'command_as_argument'
          and !$root->{'args'}) {
        return $self->element('formattingcommand', ['command', $root->{'cmdname'}]);
      }
      return $self->_format_command($root->{'cmdname'});
    } elsif ($accent_types{$root->{'cmdname'}}) {
      if ($self->get_conf('ENABLE_ENCODING')) {
        return $self->convert_accents($root, \&_accent);
      } else {
        my $attributes = [];
        if (!$root->{'args'}) {
          $result = '';
        } else {
          $result = $self->_convert($root->{'args'}->[0]);
          if ($root->{'extra'} and $root->{'extra'}->{'spaces'}) {
            push @$attributes,  ('spaces', $root->{'extra'}->{'spaces'});
          }
          if ($root->{'args'}->[0]->{'type'} eq 'following_arg') {
             push @$attributes, ('bracketed', 'off');
          }
        }
        return $self->_accent($result, $root,  undef, $attributes);
      }
    } elsif ($root->{'cmdname'} eq 'item' or $root->{'cmdname'} eq 'itemx'
             or $root->{'cmdname'} eq 'headitem' or $root->{'cmdname'} eq 'tab') {
      if ($root->{'cmdname'} eq 'item'
          and $root->{'parent'}->{'cmdname'}
          and ($root->{'parent'}->{'cmdname'} eq 'itemize'
               or $root->{'parent'}->{'cmdname'} eq 'enumerate')) {
        $result .= $self->open_element('listitem', [_leading_spaces($root)]);
        if ($root->{'parent'}->{'cmdname'} eq 'itemize'
            and $root->{'parent'}->{'args'}
            and @{$root->{'parent'}->{'args'}}) {
          $result .= $self->open_element('prepend')
            .$self->_convert($root->{'parent'}->{'args'}->[0])
            .$self->close_element('prepend');
        }
        unshift @close_elements, 'listitem';
      } elsif (($root->{'cmdname'} eq 'item' or $root->{'cmdname'} eq 'itemx')
               and $root->{'parent'}->{'type'} 
               and $root->{'parent'}->{'type'} eq 'table_term') {
        my $table_command = $root->{'parent'}->{'parent'}->{'parent'};
        my $format_item_command;
        my $attribute = [];
        if ($table_command->{'extra'} 
            and $table_command->{'extra'}->{'command_as_argument'}) {
          $format_item_command 
            = $table_command->{'extra'}->{'command_as_argument'}->{'cmdname'};
          $attribute 
           = [$self->_infoenclose_attribute($table_command->{'extra'}->{'command_as_argument'})];
        }
        $result .= $self->open_element($root->{'cmdname'}, [_leading_spaces($root)]);
        if ($format_item_command) {
          $result .=  $self->open_element('itemformat', ['command', $format_item_command, @$attribute]);
        }
        $result .= $self->_index_entry($root);
        my $in_code;
        $in_code = 1
          if ($format_item_command 
              and defined($default_args_code_style{$format_item_command})
              and $default_args_code_style{$format_item_command}->[0]);
        my $in_monospace_not_normal;
        if ($format_item_command) {
          if (defined($default_args_code_style{$format_item_command})
              and $default_args_code_style{$format_item_command}->[0]) {
            $in_monospace_not_normal = 1;
          } elsif ($regular_font_style_commands{$format_item_command}) {
            $in_monospace_not_normal = 0;
          }
        }
        push @{$self->{'document_context'}->[-1]->{'monospace'}}, 
          $in_monospace_not_normal
            if (defined($in_monospace_not_normal));

        $result .= $self->_convert($root->{'args'}->[0]);
        if ($root->{'args'}->[0]->{'extra'} and $root->{'args'}->[0]->{'extra'}->{'spaces_after_argument'}) {
          $result .= $root->{'args'}->[0]->{'extra'}->{'spaces_after_argument'};
         }
        if ($root->{'args'}->[-1]->{'extra'} and $root->{'args'}->[-1]->{'extra'}->{'comment_at_end'}) {
          $result .= $self->_convert($root->{'args'}->[-1]->{'extra'}->{'comment_at_end'});
        }
        pop @{$self->{'document_context'}->[-1]->{'monospace'}} 
          if (defined($in_monospace_not_normal));
        chomp ($result);
        if ($format_item_command) {
          $result .= $self->close_element('itemformat');
        }
        $result .= $self->close_element($root->{'cmdname'})."\n";
      } else {
        unless (($root->{'cmdname'} eq 'item' 
                     or $root->{'cmdname'} eq 'headitem'
                     or $root->{'cmdname'} eq 'tab')
                    and $root->{'parent'}->{'type'}
                    and $root->{'parent'}->{'type'} eq 'row') {
          print STDERR "BUG: multitable cell command not in a row "
            .Texinfo::Common::_print_current($root);
        }
        
        $result .= $self->open_element('entry', ['command', 
               $root->{'cmdname'}, _leading_spaces($root)]);
        unshift @close_elements, 'entry';
      }
    } elsif ($root->{'type'} and $root->{'type'} eq 'index_entry_command') {
      my $element;
      my $attribute = [];
      if (exists $Texinfo::Common::misc_commands{$root->{'cmdname'}}) {
        $element = $root->{'cmdname'};
      } else {
        $element = 'indexcommand';
        $attribute = ['command', $root->{'cmdname'}];
      }
      push @$attribute, ('index', $root->{'extra'}->{'index_entry'}->{'index_name'});
      push @$attribute, _leading_spaces($root);
      my $end_line;
      if ($root->{'args'}->[0]) {
        $end_line = $self->_end_line_or_comment($root);
      } else {
        # May that happen?
        $end_line = '';
      }
      return $self->open_element($element, ${attribute}).
        $self->_index_entry($root).$self->close_element($element).${end_line};
    } elsif (exists($misc_commands{$root->{'cmdname'}})) {
      my $command = $root->{'cmdname'};
      my $type = $misc_commands{$root->{'cmdname'}};
      if ($type eq 'text') {
        return '' if ($root->{'cmdname'} eq 'end');
        my $attribute;
        if ($misc_command_line_attributes{$root->{'cmdname'}}) {
          if ($root->{'extra'} and defined($root->{'extra'}->{'text_arg'})) {
            push @$attribute, ($misc_command_line_attributes{$root->{'cmdname'}},
                  $root->{'extra'}->{'text_arg'});
          }
        }
        my ($arg, $end_line) = $self->_convert_argument_and_end_line($root);
        push @$attribute, _leading_spaces($root);
        return $self->open_element($command, $attribute).$arg
                .$self->close_element($command).${end_line};
      } elsif ($type eq 'line') {
        if ($root->{'cmdname'} eq 'node') {
          my $nodename;
          if (defined($root->{'extra'}->{'normalized'})) {
            $nodename = $root->{'extra'}->{'normalized'};
          } else {
            $nodename = '';
          }
          # FIXME avoid protection, here?
          $result .= $self->open_element('node', ['name', $nodename, _leading_spaces($root)]);
          push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1;
          $result .= $self->open_element('nodename', 
            [$self->_trailing_spaces_arg($root->{'args'}->[0])])
             .$self->_convert({'contents' => $root->{'extra'}->{'node_content'}})
             .$self->close_element('nodename');
          # first arg is the node name.
          my $direction_index = 1;
          my $pending_empty_directions = '';
          foreach my $direction(@node_directions) {
            my $element = 'node'.lc($direction);
            if ($root->{'node_'.lc($direction)}) {
              my $node_direction = $root->{'node_'.lc($direction)};
              my $node_name = '';
              my $attribute = [];
              if (! defined($root->{'extra'}->{'nodes_manuals'}->[$direction_index])) {
                push @$attribute, ('automatic', 'on');
              }
              if ($root->{'args'}->[$direction_index]) {
                push @$attribute, $self->_leading_trailing_spaces_arg(
                                 $root->{'args'}->[$direction_index]);
              }
              if ($node_direction->{'extra'}->{'manual_content'}) {
                $node_name .= $self->_convert({
                             'contents' => [{'text' => '('},
                             @{$node_direction->{'extra'}->{'manual_content'}},
                                          {'text' => ')'}]});
              }
              if ($node_direction->{'extra'}->{'node_content'}) {
                $node_name .= Texinfo::Common::normalize_top_node_name($self->_convert({
                  'contents' => $node_direction->{'extra'}->{'node_content'}}));
              }
              $result .= "$pending_empty_directions".
                $self->open_element($element, ${attribute}).$node_name.
                $self->close_element($element);
              $pending_empty_directions = '';
            } else {
              if ($root->{'args'}->[$direction_index]) {
                my $spaces_attribute = $self->_leading_trailing_spaces_arg(
                                 $root->{'args'}->[$direction_index]);
                $pending_empty_directions .= $self->open_element($element,
                    [$self->_leading_trailing_spaces_arg(
                                 $root->{'args'}->[$direction_index])])
                            .$self->close_element($element);
              }
            }
            $direction_index++;
          }
          my $end_line;
          if ($root->{'args'}->[0]) {
            $end_line 
              = $self->_end_line_or_comment($root);
          } else {
            $end_line = "\n";
          }
          if (! $self->get_conf('USE_NODES')) {
            $result .= $self->close_element('node');
          }
          $result .= ${end_line};
          pop @{$self->{'document_context'}->[-1]->{'monospace'}};
        } elsif ($Texinfo::Common::root_commands{$root->{'cmdname'}}) {
          my $attribute = [_leading_spaces($root)];
          $command = $self->_level_corrected_section($root);
          if ($command ne $root->{'cmdname'}) {
            unshift @$attribute, ('originalcommand', $root->{'cmdname'});
          }
          $result .= $self->open_element($command, $attribute);
          my $closed_section_element;
          if ($self->get_conf('USE_NODES')) {
            $closed_section_element = $self->close_element($command);
          } else {
            $closed_section_element = '';
          }

          if ($root->{'args'} and $root->{'args'}->[0]) {
            my ($arg, $end_line) = $self->_convert_argument_and_end_line($root);
            $result .= $self->open_element('sectiontitle').$arg
                      .$self->close_element('sectiontitle')
                      .$closed_section_element.$end_line;
          } else {
            $result .= $closed_section_element;
          }
        } else {
          my $attribute = [_leading_spaces($root)];
          if ($root->{'cmdname'} eq 'listoffloats' and $root->{'extra'} 
              and $root->{'extra'}->{'type'} 
              and defined($root->{'extra'}->{'type'}->{'normalized'})) {
            unshift @$attribute, ('type', $root->{'extra'}->{'type'}->{'normalized'});
          }
          my ($arg, $end_line) = $self->_convert_argument_and_end_line($root);
          return $self->open_element($command, ${attribute}).$arg
               .$self->close_element($command).$end_line;
        }
      } elsif ($type eq 'skipline') {
        # the command associated with an element is closed at the end of the
        # element. @bye is withing the element, but we want it to appear after
        # the comand closing.  So we delay the output of @bye, and store it.
        if ($root->{'cmdname'} eq 'bye' and $root->{'parent'}
            and $root->{'parent'}->{'type'}
            and $root->{'parent'}->{'type'} eq 'element'
            and !($root->{'parent'}->{'extra'}
                  and ($root->{'parent'}->{'extra'}->{'no_section'}
                       or $root->{'parent'}->{'extra'}->{'no_node'}))) {
          $self->{'pending_bye'} = $self->open_element($command)
                    .$self->close_element($command)."\n";
          return '';
        }
        my $attribute = [];
        if ($root->{'args'} and $root->{'args'}->[0] 
            and defined($root->{'args'}->[0]->{'text'})) {
          my $line = $root->{'args'}->[0]->{'text'};
          chomp($line);
          $attribute = ['line', $line]
             if ($line ne '');
        }
        return $self->open_element($command, $attribute)
                 .$self->close_element($command)."\n";
      } elsif ($type eq 'noarg' or $type eq 'skipspace') {
        my $spaces = '';
        $spaces = $root->{'extra'}->{'spaces_after_command'}
          if ($root->{'extra'} and $root->{'extra'}->{'spaces_after_command'}
              and $root->{'extra'}->{'spaces_after_command'} ne '');
        return $self->open_element($command)
                .$self->close_element($command).$spaces;
      } elsif ($type eq 'special') {
        if ($root->{'cmdname'} eq 'clear' or $root->{'cmdname'} eq 'set') {
          my $attribute = [];
          if ($root->{'args'} and $root->{'args'}->[0]
              and defined($root->{'args'}->[0]->{'text'})) {
            push @$attribute, ('name', $root->{'args'}->[0]->{'text'});
          }
          my $value = '';
          if ($root->{'cmdname'} eq 'set' and $root->{'args'} and $root->{'args'}->[1]
              and defined($root->{'args'}->[1]->{'text'})) {
            $value = $self->protect_text($root->{'args'}->[1]->{'text'});
          }
          push @$attribute, $self->_arg_line($root);
          return $self->open_element($command, $attribute)
                         .$value.$self->close_element($command)."\n";
        } elsif ($root->{'cmdname'} eq 'clickstyle') {
          my $attribute = [$self->_arg_line($root)];
          my $value = '';
          if ($root->{'args'} and $root->{'args'}->[0]
              and defined($root->{'args'}->[0]->{'text'})) {
            my $click_command = $root->{'args'}->[0]->{'text'};
            $click_command =~ s/^\@//;
            unshift @$attribute, ('command', $click_command);
            $value = $self->protect_text($root->{'args'}->[0]->{'text'});
          };
          return $self->open_element($command, $attribute)
                         .$value.$self->close_element($command)."\n";
        } else {
          # should only be unmacro
          my $attribute = [$self->_arg_line($root)];
          if ($root->{'args'} and $root->{'args'}->[0]
              and defined($root->{'args'}->[0]->{'text'})) {
            unshift @$attribute, ('name', $root->{'args'}->[0]->{'text'});
          }
          return $self->open_element($command, $attribute)
                    .$self->close_element($command)."\n";
        }
      } elsif ($type eq 'lineraw') {
        if ($root->{'cmdname'} eq 'c' or $root->{'cmdname'} eq 'comment') {
          return $self->format_comment(" $root->{'cmdname'}".$root->{'args'}->[0]->{'text'})
        } else {
          my $value = '';
          if ($root->{'args'} and $root->{'args'}->[0]
              and defined($root->{'args'}->[0]->{'text'})) {
            $value = $self->protect_text($root->{'args'}->[0]->{'text'});
          }
          chomp ($value);
          return $self->open_element($command).$value
                    .$self->close_element($command)."\n";
        }
      } else {
        print STDERR "BUG: unknown misc_command style $type\n" if ($type !~ /^\d$/);
        my $args_attributes;
        if ($misc_command_numbered_arguments_attributes{$root->{'cmdname'}}) {
          $args_attributes = $misc_command_numbered_arguments_attributes{$root->{'cmdname'}};
        } else {
          $args_attributes = ['value'];
        }
        my $attribute = [];
        my $arg_index = 0;
        if (defined($root->{'extra'}) 
            and defined($root->{'extra'}->{'misc_args'})) {
          foreach my $arg_attribute (@{$args_attributes}) {
            if (defined ($root->{'extra'}->{'misc_args'}->[$arg_index])) {
              push @$attribute, ( $arg_attribute, 
                        $root->{'extra'}->{'misc_args'}->[$arg_index]);
            }
            $arg_index++;
          }
        }
        my $end_line;
        if ($root->{'args'}->[0]) {
          $end_line = $self->_end_line_or_comment($root);
          push @$attribute, $self->_texinfo_line($root);
        } else {
          $end_line = "\n";
        }
        return $self->open_element($command, $attribute)
                    .$self->close_element($command).$end_line;
      }
    } elsif ($root->{'type'}
             and $root->{'type'} eq 'definfoenclose_command') {
      my $in_monospace_not_normal;
      if (defined($default_args_code_style{$root->{'cmdname'}})
          and $default_args_code_style{$root->{'cmdname'}}->[0]) {
        $in_monospace_not_normal = 1;
      } elsif ($regular_font_style_commands{$root->{'cmdname'}}) {
        $in_monospace_not_normal = 0;
      }
      push @{$self->{'document_context'}->[-1]->{'monospace'}}, 
        $in_monospace_not_normal
          if (defined($in_monospace_not_normal));
      my $arg = $self->_convert($root->{'args'}->[0]);
      $result .= $self->open_element('infoenclose', ['command', $root->{'cmdname'},
                                        $self->_infoenclose_attribute($root)])
                 .$arg.$self->close_element('infoenclose');
      pop @{$self->{'document_context'}->[-1]->{'monospace'}}
        if (defined($in_monospace_not_normal));
    } elsif ($root->{'args'}
             and exists($Texinfo::Common::brace_commands{$root->{'cmdname'}})) {
      if ($Texinfo::Common::context_brace_commands{$root->{'cmdname'}}) {
        push @{$self->{'document_context'}}, {'monospace' => [0]};
      }
      if ($Texinfo::Common::inline_format_commands{$root->{'cmdname'}}
          and $root->{'extra'} and $root->{'extra'}->{'format'}
          and $self->{'expanded_formats_hash'}->{$root->{'extra'}->{'format'}}) {
        if ($root->{'cmdname'} eq 'inlineraw') {
          push @{$self->{'document_context'}}, {'monospace' => [0]};
          $self->{'document_context'}->[-1]->{'raw'} = 1;
        }
        if (scalar (@{$root->{'args'}}) == 2
              and defined($root->{'args'}->[-1])
              and @{$root->{'args'}->[-1]->{'contents'}}) {
          $result .= $self->_convert({'contents' 
                        => $root->{'args'}->[-1]->{'contents'}});
        }
        if ($root->{'cmdname'} eq 'inlineraw') {
          pop @{$self->{'document_context'}};
        }
        return $result;
      }
      my @elements = @{$commands_elements{$root->{'cmdname'}}};
      my $command;
      if (scalar(@elements) > 1) {
        $command = shift @elements;
      }
      # this is used for commands without args, or associated to the
      # first argument
      my $attribute = [];
      if ($root->{'cmdname'} eq 'verb') {
        push @$attribute, ('delimiter', $root->{'extra'}->{'delimiter'});
      } elsif ($root->{'cmdname'} eq 'anchor') {
        my $anchor_name;
        if (defined($root->{'extra'}->{'normalized'})) {
          $anchor_name = $root->{'extra'}->{'normalized'};
        } else {
          $anchor_name = '';
        }
        push @$attribute, ('name', $anchor_name);
      }
      my $arg_index = 0;
      foreach my $element (@elements) {
        if (defined($root->{'args'}->[$arg_index])) {
          my $in_monospace_not_normal;
          if (defined($default_args_code_style{$root->{'cmdname'}})
              and $default_args_code_style{$root->{'cmdname'}}->[$arg_index]) {
            $in_monospace_not_normal = 1;
          } elsif ($regular_font_style_commands{$root->{'cmdname'}}) {
            $in_monospace_not_normal = 0;
          }
          push @{$self->{'document_context'}->[-1]->{'monospace'}},
            $in_monospace_not_normal
              if (defined($in_monospace_not_normal));
          my $arg = $self->_convert($root->{'args'}->[$arg_index]);
          if ($root->{'args'}->[$arg_index]->{'extra'}
              and $root->{'args'}->[$arg_index]->{'extra'}->{'spaces_after_argument'}) {
            $arg .= $root->{'args'}->[$arg_index]
                   ->{'extra'}->{'spaces_after_argument'};
          }
          if (!$Texinfo::Common::context_brace_commands{$root->{'cmdname'}}
              and $root->{'cmdname'} ne 'verb') {
            push @$attribute, 
              _leading_spaces_before_argument($root->{'args'}->[$arg_index]);
          }
          if (!defined($command) or $arg ne '' or scalar(@$attribute) > 0) {
            # ${attribute} is only set for @verb
            push @$attribute, _leading_spaces_before_argument($root)
               if (!defined($command));
            $result .= $self->open_element($element, $attribute).$arg
                      .$self->close_element($element);
          }
          $attribute = [];
          pop @{$self->{'document_context'}->[-1]->{'monospace'}}
            if (defined($in_monospace_not_normal));
        } else {
          last;
        }
        $arg_index++;
      }
      # This is for the main command
      $attribute = [];
      if ($root->{'cmdname'} eq 'image') {
        if ($self->_is_inline($root)) {
          push @$attribute, ('where', 'inline');
        }
      } elsif ($Texinfo::Common::ref_commands{$root->{'cmdname'}}) {
        if ($root->{'args'}) {
          my $normalized;
          if ($root->{'extra'}->{'node_argument'}
              and $root->{'extra'}->{'node_argument'}->{'node_content'}) {
            my $normalized;
            if (defined($root->{'extra'}->{'node_argument'}->{'normalized'})) {
              $normalized = $root->{'extra'}->{'node_argument'}->{'normalized'};
            } else {
              $normalized = Texinfo::Convert::NodeNameNormalization::normalize_node( {'contents' => $root->{'extra'}->{'node_argument'}->{'node_content'} } );
            }
            if ($normalized) {
              push @$attribute, ('label', $normalized);
            }
          }
          my $manual;
          my $manual_arg_index = 3;
          if ($root->{'cmdname'} eq 'inforef') {
            $manual_arg_index = 2;
          }
          if (defined($root->{'args'}->[$manual_arg_index])
              and @{$root->{'args'}->[$manual_arg_index]->{'contents'}}) {
            $manual = Texinfo::Convert::Text::convert({'contents'
                     => $root->{'args'}->[$manual_arg_index]->{'contents'}},
                      {'code' => 1,
                       Texinfo::Common::_convert_text_options($self)});
          }
          if (!defined($manual) and $root->{'extra'}->{'node_argument'}
              and $root->{'extra'}->{'node_argument'}->{'manual_content'}) {
            $manual = Texinfo::Convert::Text::convert({'contents' 
                 => $root->{'extra'}->{'node_argument'}->{'manual_content'}},
              {'code' => 1, Texinfo::Common::_convert_text_options($self)});
          }
          if (defined($manual)) {
            my $manual_base = $manual;
            $manual_base =~ s/\.[^\.]*$//;
            $manual_base =~ s/^.*\///;
            
            push @$attribute, ('manual', $manual_base)
                  if ($manual_base ne '');
          }
        }
      }
      if (defined($command)) {
        push @$attribute, _leading_spaces_before_argument($root);
        $result = $self->open_element($command, $attribute).$result
                  .$self->close_element($command);
      }
      if ($Texinfo::Common::context_brace_commands{$root->{'cmdname'}}) {
        pop @{$self->{'document_context'}};
      }
    } elsif (exists($Texinfo::Common::block_commands{$root->{'cmdname'}})) {
      if ($self->{'context_block_commands'}->{$root->{'cmdname'}}) {
        push @{$self->{'document_context'}}, {'monospace' => [0]};
      }
      my $prepended_elements = '';
      my $attribute = [];
      $self->{'itemize_line'} = 1 if ($root->{'cmdname'} eq 'itemize');
      if ($root->{'extra'} and $root->{'extra'}->{'command_as_argument'}) {
        my $command_as_arg = $root->{'extra'}->{'command_as_argument'};
        push @$attribute,
         ('commandarg', $command_as_arg->{'cmdname'},
             $self->_infoenclose_attribute($command_as_arg));
      } elsif ($root->{'extra'}
               and $root->{'extra'}->{'enumerate_specification'}) {
        push @$attribute,('first', $root->{'extra'}->{'enumerate_specification'});
      } elsif ($root->{'cmdname'} eq 'float' and $root->{'extra'}) {
        if (defined($root->{'extra'}->{'node_content'})) {
          my $normalized =
            Texinfo::Convert::NodeNameNormalization::normalize_node (
                   { 'contents' => $root->{'extra'}->{'node_content'} });
          push @$attribute, ('name', $normalized);
        }
        if ($root->{'extra'}->{'type'} and 
            defined($root->{'extra'}->{'type'}->{'normalized'})) {
          push @$attribute, ('type', $root->{'extra'}->{'type'}->{'normalized'});
        }
        if (defined($root->{'number'})) {
          push @$attribute, ('number', $root->{'number'});
        }
      } elsif ($root->{'cmdname'} eq 'verbatim') {
        push @$attribute, ('xml:space', 'preserve');
      } elsif ($root->{'cmdname'} eq 'macro' 
               or $root->{'cmdname'} eq 'rmacro') {
        if (defined($root->{'args'})) {
          my @args = @{$root->{'args'}};
          my $name_arg = shift @args;
          if (defined($name_arg) and defined($name_arg->{'text'})) {
            push @$attribute, ('name', $name_arg->{'text'});
          }
          
          while (@args) {
            my $formal_arg = shift @args;
            $prepended_elements .= $self->open_element('formalarg')
                .$self->protect_text($formal_arg->{'text'})
                .$self->close_element('formalarg');
          }
        }
        push @$attribute, $self->_arg_line($root);
      }
      if ($self->{'expanded_formats_hash'}->{$root->{'cmdname'}}) {
        $self->{'document_context'}->[-1]->{'raw'} = 1;
      } else {
        my $end_command = $root->{'extra'}->{'end_command'};
        my $end_command_space = [_leading_spaces($end_command)];
        if (scalar(@$end_command_space)) {
          $end_command_space->[0] = 'endspaces';
        }
        $result .= $self->open_element($root->{'cmdname'}, [@$attribute, 
                              _leading_spaces($root), @$end_command_space])
                      .${prepended_elements};
        my $end_line = '';
        if ($root->{'args'}) {
          if ($commands_args_elements{$root->{'cmdname'}}) {
            my $arg_index = 0;
            foreach my $element (@{$commands_args_elements{$root->{'cmdname'}}}) {
              if (defined($root->{'args'}->[$arg_index])) {
                my $in_code;
                 $in_code = 1
                  if (defined($default_args_code_style{$root->{'cmdname'}})
                    and $default_args_code_style{$root->{'cmdname'}}->[$arg_index]);
                push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1 
                  if ($in_code);
                my $arg;
                if ($arg_index+1 eq scalar(@{$root->{'args'}})) {
                  # last argument
                  ($arg, $end_line) 
                    = $self->_convert_argument_and_end_line($root);
                } else {
                  $arg = $self->_convert($root->{'args'}->[$arg_index]);
                }
                my $spaces = [];
                if ($arg_index != 0) {
                  push @$spaces, _leading_spaces_before_argument(
                                              $root->{'args'}->[$arg_index]);
                }
                if ($arg ne '' or scalar(@$spaces)) {
                  $result .= $self->open_element($element, $spaces).$arg
                           .$self->close_element($element);
                }
                pop @{$self->{'document_context'}->[-1]->{'monospace'}} 
                  if ($in_code);
              } else {
                last;
              }
              $arg_index++;
            }
          } else {
            my $contents_possible_comment;
            # in that case the end of line is in the columnfractions line
            # or in the columnprototypes.  

            if ($root->{'cmdname'} eq 'multitable') {
              if (not $root->{'extra'}->{'columnfractions'}) {
                # Like 'prototypes' extra value, but keeping spaces information
                my @prototype_line;
                if (defined $root->{'args'}[0]
                    and defined $root->{'args'}[0]->{'type'}
                    and $root->{'args'}[0]->{'type'} eq 'block_line_arg') {
                  foreach my $content (@{$root->{'args'}[0]{'contents'}}) {
                    if ($content->{'type'} and $content->{'type'} eq 'bracketed') {
                      push @prototype_line, $content;
                    } elsif ($content->{'text'}) {
                      # The regexp breaks between characters, with a non space followed
                      # by a space or a space followed by non space.  It is like \b, but
                      # for \s \S, and not \w \W.
                      foreach my $prototype_or_space (split /(?<=\S)(?=\s)|(?=\S)(?<=\s)/, 
                        $content->{'text'}) {
                        if ($prototype_or_space =~ /\S/) {
                          push @prototype_line, {'text' => $prototype_or_space,
                            'type' => 'row_prototype' };
                        } elsif ($prototype_or_space =~ /\s/) {
                          push @prototype_line, {'text' => $prototype_or_space,
                            'type' => 'prototype_space' };
                        }
                      }
                    } else {
                      # FIXME could this happen?  Should be a debug message?
                      if (!$content->{'cmdname'}) { 
                      } elsif ($content->{'cmdname'} eq 'c' 
                          or $content->{'cmdname'} eq 'comment') {
                      } else {
                        push @prototype_line, $content;
                      }
                    }
                  }
                  $root->{'extra'}->{'prototypes_line'} = \@prototype_line;
                }
              }

              if ($root->{'extra'}
                    and $root->{'extra'}->{'prototypes_line'}) {
                $result .= $self->open_element('columnprototypes');
                my $first_proto = 1;
                foreach my $prototype (@{$root->{'extra'}->{'prototypes_line'}}) {
                  if ($prototype->{'text'} and $prototype->{'text'} !~ /\S/) {
                    if (!$first_proto) {
                      my $spaces = $prototype->{'text'};
                      chomp($spaces);
                      $result .= $spaces;
                    }
                  } else {
                    my $attribute = [];
                    if ($prototype->{'type'} 
                        and $prototype->{'type'} eq 'bracketed') {
                      push @$attribute, ('bracketed', 'on');
                      push @$attribute, _leading_spaces_before_argument($prototype);
                    }
                    $result .= $self->open_element('columnprototype', $attribute)
                           .$self->_convert($prototype)
                           .$self->close_element('columnprototype');
                  }
                  $first_proto = 0;
                }
                $result .= $self->close_element('columnprototypes');
                $contents_possible_comment = $root;
              } elsif ($root->{'extra'}
                         and $root->{'extra'}->{'columnfractions'}) {
                my $cmd;
                foreach my $content (@{$root->{'args'}->[0]->{'contents'}}) {
                  if ($content->{'cmdname'}
                      and $content->{'cmdname'} eq 'columnfractions') {
                    $cmd = $content;
                    last;
                  }
                }
                my $attribute = [$self->_texinfo_line($cmd)];
                $result .= $self->open_element('columnfractions', $attribute);
                foreach my $fraction (@{$root->{'extra'}->{'columnfractions'}
                                             ->{'extra'}->{'misc_args'}}) {
                  $result .= $self->open_element('columnfraction', 
                                                ['value', $fraction])
                             .$self->close_element('columnfraction');
                }
                $result .= $self->close_element('columnfractions');
                $contents_possible_comment = $cmd;
              } else { # bogus multitable
                $result .= "\n";
              }
            } else {
              # get end of lines from @*table.
              my $end_spaces = _end_line_spaces($root);
              if (defined($end_spaces)) {
                $end_line .= $end_spaces 
                # This also catches block @-commands with no argument that
                # have a bogus argument, such as text on @example line
                #print STDERR "NOT xtable: $root->{'cmdname'}\n" 
                #  if (!$Texinfo::Common::item_line_commands{$root->{'cmdname'}});
              }
              $contents_possible_comment = $root;
            }
            $end_line .= $self->_end_line_or_comment($contents_possible_comment);
          }
        }
        $result .= $end_line;
        unshift @close_elements, $root->{'cmdname'};
      }
      delete $self->{'itemize_line'} if ($self->{'itemize_line'});
    }
  }
  if ($root->{'type'}) {
    if (defined($type_elements{$root->{'type'}})) {
      my $attribute = [];
      if ($root->{'type'} eq 'preformatted') {
        push @$attribute, ('xml:space', 'preserve');
      } elsif ($root->{'type'} eq 'menu_entry') {
        push @$attribute, ('leadingtext', $self->_convert($root->{'args'}->[0]));
      } elsif (($root->{'type'} eq 'menu_entry_node' 
                or $root->{'type'} eq 'menu_entry_name')
               and $self->{'pending_menu_entry_separator'}) {
        push @$attribute, ('separator',
               $self->_convert($self->{'pending_menu_entry_separator'}));
        delete $self->{'pending_menu_entry_separator'};
      }
      $result .= $self->open_element($type_elements{$root->{'type'}}, $attribute);
    }
    if ($root->{'type'} eq 'def_line') {
      if ($root->{'cmdname'}) {
        $result .= $self->open_element($root->{'cmdname'}, [_leading_spaces($root)]);
      }
      $result .= $self->open_element('definitionterm');
      $result .= $self->_index_entry($root);
      push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1;
      if ($root->{'args'} and @{$root->{'args'}}
          and $root->{'args'}->[0]->{'contents'}) {
        my $main_command;
        my $alias;
        if ($Texinfo::Common::def_aliases{$root->{'extra'}->{'def_command'}}) {
          $main_command = $Texinfo::Common::def_aliases{$root->{'extra'}->{'def_command'}};
          $alias = 1;
        } else {
          $main_command = $root->{'extra'}->{'def_command'};
          $alias = 0;
        }
        foreach my $arg (@{$root->{'args'}->[0]->{'contents'}}) {
          next if $arg->{'type'}
                   and ($arg->{'type'} eq 'empty_spaces_after_command'
                         or $arg->{'type'} eq 'empty_line_after_command');
          my $type = $arg->{'extra'}->{'def_role'};
          next if !$type and $arg->{'type'} eq 'spaces';
          my $content = $self->_convert($arg);
          if ($type eq 'spaces') {
            $content =~ s/\n$//;
            $result .= $content;
          } else {
            my $attribute = [];
            if ($type eq 'category' and $alias) {
              push @$attribute, ('automatic', 'on');
            }
            my $element;
            if ($type eq 'name') {
              $element = $defcommand_name_type{$main_command};
            } elsif ($type eq 'arg') {
              $element = 'param';
            } elsif ($type eq 'typearg') {
              $element = 'paramtype';
            } else {
              $element = $type;
            }
            if ($arg->{'type'}
                and ($arg->{'type'} eq 'bracketed_def_content'
                  or ($arg->{'type'} eq 'bracketed_inserted'))) {
              push @$attribute, ('bracketed', 'on');
              push @$attribute, _leading_spaces_before_argument($arg);
            }
            $result .= $self->open_element("def$element", $attribute).$content
                      .$self->close_element("def$element");
          }
        }
      }
      pop @{$self->{'document_context'}->[-1]->{'monospace'}};
      $result .= $self->close_element('definitionterm');
      if ($root->{'cmdname'}) {
        $result .= $self->close_element($root->{'cmdname'});
      }
      chomp ($result);
      $result .= "\n";
    }
  }
  if ($root->{'contents'}) {
    my $in_code;
    if ($root->{'cmdname'} 
        and $Texinfo::Common::preformatted_code_commands{$root->{'cmdname'}}) {
      $in_code = 1;
    }
    push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1 
      if ($in_code);
    if (ref($root->{'contents'}) ne 'ARRAY') {
      cluck "contents not an array($root->{'contents'}).";
    }
    foreach my $content (@{$root->{'contents'}}) {
      $result .= $self->_convert($content);
    }
    pop @{$self->{'document_context'}->[-1]->{'monospace'}}
      if ($in_code);
  }
  my $arg_nr = -1;
  if ($root->{'type'} and $root->{'type'} eq 'menu_entry') {
    foreach my $arg (@{$root->{'args'}}) {
      $arg_nr++;
      # menu_entry_leading_text is added as attribute leadingtext of menu_entry
      # menu_entry_separator is recorded here and then added ass attribute
      # separator
      next if ($arg->{'type'} eq 'menu_entry_leading_text'
               or $arg->{'type'} eq 'menu_entry_separator');
      if ($root->{'args'}->[$arg_nr +1]
          and $root->{'args'}->[$arg_nr +1]->{'type'}
          and $root->{'args'}->[$arg_nr +1]->{'type'} eq 'menu_entry_separator') {
        $self->{'pending_menu_entry_separator'} = $root->{'args'}->[$arg_nr +1];
      }
      my $in_code;
      if ($arg->{'type'} eq 'menu_entry_node') {
        $in_code = 1;
      }
      push @{$self->{'document_context'}->[-1]->{'monospace'}}, 1 
        if ($in_code);
      $result .= $self->_convert($arg);
      pop @{$self->{'document_context'}->[-1]->{'monospace'}}
        if ($in_code);
    }
  }
  if ($root->{'type'}) {
    if (defined($type_elements{$root->{'type'}})) {
      $result .= $self->close_element($type_elements{$root->{'type'}});
    }
  }
  $result = '{'.$result.'}' 
     if ($root->{'type'} and $root->{'type'} eq 'bracketed'
         and (!$root->{'parent'}->{'type'} or
              ($root->{'parent'}->{'type'} ne 'block_line_arg'
               and $root->{'parent'}->{'type'} ne 'line_arg')));
  foreach my $element (@close_elements) {
    $result .= $self->close_element($element);
  }
  if ($root->{'cmdname'} 
      and exists($Texinfo::Common::block_commands{$root->{'cmdname'}})) {
    my $end_command = $root->{'extra'}->{'end_command'}; 
    if ($self->{'expanded_formats_hash'}->{$root->{'cmdname'}}) {
    } else {
      my $end_line = '';
      if ($end_command) {
        my $end_spaces = _end_line_spaces($end_command);
        $end_line .= $end_spaces if (defined($end_spaces));
        $end_line 
         .= $self->_end_line_or_comment($end_command)
          if ($end_command->{'args'});
      } else {
        #$end_line = "\n";
      }
      $result .= $end_line;
    }
    if ($self->{'context_block_commands'}->{$root->{'cmdname'}}) {
      pop @{$self->{'document_context'}};
    }
  # The command is closed either when the corresponding tree element
  # is done, and the command is not associated to an element, or when
  # the element is closed.
  } elsif ((($root->{'type'} and $root->{'type'} eq 'element'
             and $root->{'extra'} and $root->{'extra'}->{'element_command'}
             and !($root->{'extra'}->{'element_command'}->{'cmdname'}
                   and $root->{'extra'}->{'element_command'}->{'cmdname'} eq 'node'))
            or ($root->{'cmdname'} 
                and $Texinfo::Common::root_commands{$root->{'cmdname'}}
                and $root->{'cmdname'} ne 'node'
                and !($root->{'parent'} and $root->{'parent'}->{'type'}
                     and $root->{'parent'}->{'type'} eq 'element'
                     and $root->{'parent'}->{'extra'} 
                     and $root->{'parent'}->{'extra'}->{'element_command'}
                     and $root->{'parent'}->{'extra'}->{'element_command'} eq $root)))
           and !$self->get_conf('USE_NODES')) {
    if ($root->{'type'} and $root->{'type'} eq 'element') {
      $root = $root->{'extra'}->{'element_command'};
    }
    my $command = $self->_level_corrected_section($root);
    if (!($root->{'section_childs'} and scalar(@{$root->{'section_childs'}}))
        or $command eq 'top') {
      $result .= $self->close_element($command)."\n";
      my $current = $root;
      while ($current->{'section_up'}
             # the most up element is a virtual sectioning root element, this
             # condition avoids getting into it
             and $current->{'section_up'}->{'cmdname'}
             and !$current->{'section_next'}
             and $self->_level_corrected_section($current->{'section_up'}) ne 'top') {
        $current = $current->{'section_up'};
        $result .= $self->close_element($self->_level_corrected_section($current)) ."\n";
      }
    }
    if ($self->{'pending_bye'}) {
      $result .= $self->{'pending_bye'};
      delete $self->{'pending_bye'};
    }
  } elsif ((($root->{'type'} and $root->{'type'} eq 'element'
             and $root->{'extra'} and $root->{'extra'}->{'element_command'}
             and $root->{'extra'}->{'element_command'}->{'cmdname'}
             and $root->{'extra'}->{'element_command'}->{'cmdname'} eq 'node')
            or ($root->{'cmdname'} 
                and $root->{'cmdname'} eq 'node'
                and !($root->{'parent'} and $root->{'parent'}->{'type'}
                     and $root->{'parent'}->{'type'} eq 'element'
                     and $root->{'parent'}->{'extra'} 
                     and $root->{'parent'}->{'extra'}->{'element_command'}
                     and $root->{'parent'}->{'extra'}->{'element_command'} eq $root)))
           and $self->get_conf('USE_NODES')) {
    #if ($root->{'type'} and $root->{'type'} eq 'element') {
    #  $root = $root->{'extra'}->{'element_command'};
    #}
    $result .= $self->close_element('node');
    
    if ($self->{'pending_bye'}) {
      $result .= $self->{'pending_bye'};
      delete $self->{'pending_bye'};
    }
  }
  return $result;
}

1;

__END__
# $Id: template.pod 6140 2015-02-22 23:34:38Z karl $
# Automatically generated from maintain/template.pod

=head1 NAME

Texinfo::Convert::TexinfoXML - Convert Texinfo tree to TexinfoXML

=head1 SYNOPSIS

  my $converter 
    = Texinfo::Convert::TexinfoXML->converter({'parser' => $parser});

  $converter->output($tree);
  $converter->convert($tree);
  $converter->convert_tree($tree);

=head1 DESCRIPTION

Texinfo::Convert::TexinfoXML converts a Texinfo tree to TexinfoXML.

=head1 METHODS

=over

=item $converter = Texinfo::Convert::TexinfoXML->converter($options)

Initialize converter from Texinfo to TexinfoXML.  

The I<$options> hash reference holds options for the converter.  In
this option hash reference a parser object may be associated with the 
I<parser> key.  The other options should be configuration options
described in the Texinfo manual.  Those options, when appropriate,
override the document content.

See L<Texinfo::Convert::Converter> for more informations.

=item $converter->output($tree)

Convert a Texinfo tree I<$tree> and output the result in files as
described in the Texinfo manual.

=item $result = $converter->convert($tree)

Convert a Texinfo tree I<$tree> or tree portion and return 
the resulting output.

=item $result = $converter->convert_tree($tree)

Convert a Texinfo tree portion I<$tree> and return the resulting 
output.  This function does not try to output a full document but only
portions.  For a full document use C<convert>.

=back

=head1 AUTHOR

Patrice Dumas, E<lt>pertusus@free.frE<gt>

=cut
