1# Common.pm: definition of commands. Common code of other Texinfo modules.
2#
3# Copyright 2010-2020 Free Software Foundation, Inc.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 3 of the License,
8# or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17#
18# Original author: Patrice Dumas <pertusus@free.fr>
19# Parts (also from Patrice Dumas) come from texi2html.pl or texi2html.init.
20
21package Texinfo::Common;
22
23use strict;
24
25# for unicode/layer support in binmode
26use 5.006;
27
28# to determine the null file
29use Config;
30use File::Spec;
31
32use Texinfo::Documentlanguages;
33
34# debugging
35use Carp qw(cluck);
36
37require Exporter;
38use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
39@ISA = qw(Exporter);
40
41%EXPORT_TAGS = ( 'all' => [ qw(
42debug_hash
43debug_list
44definition_category
45expand_verbatiminclude
46expand_today
47float_name_caption
48is_content_empty
49move_index_entries_after_items_in_tree
50normalize_top_node_name
51numbered_heading
52protect_comma_in_tree
53protect_first_parenthesis
54protect_hashchar_at_line_beginning
55protect_colon_in_tree
56protect_node_after_label_in_tree
57trim_spaces_comment_from_content
58valid_tree_transformation
59) ] );
60
61@EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
62
63@EXPORT = qw(
64__ __p print_tree
65);
66
67$VERSION = '6.8';
68
69# i18n
70sub N__($)
71{
72  return $_[0];
73}
74
75# determine the null devices
76my $default_null_device = File::Spec->devnull();
77our %null_device_file = (
78  $default_null_device => 1
79);
80# special case, djgpp recognizes both null devices
81if ($Config{osname} eq 'dos' and $Config{osvers} eq 'djgpp') {
82  $null_device_file{'/dev/null'} = 1;
83  $null_device_file{'NUL'} = 1;
84}
85
86use Locale::Messages;
87
88my $messages_textdomain = 'texinfo';
89
90sub __($) {
91  my $msgid = shift;
92  return Locale::Messages::dgettext($messages_textdomain, $msgid);
93}
94
95sub __p($$) {
96  my $context = shift;
97  my $msgid = shift;
98  return Locale::Messages::dpgettext($messages_textdomain, $context, $msgid);
99}
100
101# these are the default values for the parser state that may be
102# initialized to values given by the user.
103# They are defined here, because they are used below and we
104# don't want Texinfo::Common to use Texinfo::Parser.
105our %default_parser_state_configuration = (
106  'expanded_formats' => [],
107  'include_directories' => [ '.' ],
108  # these are the user-added indices.  May be an array reference on names
109  # or an hash reference in the same format than %index_names below
110  'indices' => [],
111  # the following are dynamically modified during the document parsing.
112  'aliases' => {},            # key is a command name value is the alias
113  'documentlanguage' => undef,
114                              # Current documentlanguage set by
115                              # @documentlanguage
116  'explained_commands' => {}, # the key is a command name, either acronym
117                              # or abbr, the value is a hash.  The key hash
118                              # is a normalized first argument of the
119                              # corresponding command, the value is the
120                              # contents array of the previous command with
121                              # this first arg and a second arg.
122  'labels'          => {},    # keys are normalized label names, as described
123                              # in the `HTML Xref' node.  Value should be
124                              # a node/anchor or float in the tree.
125  'targets' => [],            # array of elements used to build 'labels'
126  'macros' => {},             # the key is the user-defined macro name.  The
127                              # value is the reference on a macro element
128                              # as obtained by parsing the @macro
129  'merged_indices' => {},     # the key is merged in the value
130  'sections_level' => 0,      # modified by raise/lowersections
131  'values' => {'txicommandconditionals' => 1},
132                              # the key is the name, the value the @set name
133                              # argument.  A Texinfo tree may also be used.
134  'info' => {
135    'novalidate' => 0,        # same as setting @novalidate.
136    'input_encoding_name' => 'utf-8',
137    'input_perl_encoding' => 'utf-8'
138  },
139  'in_gdt' => 0 # whether we are being called by gdt
140);
141
142
143# Customization variables obeyed by the parser, and the default values.
144our %default_parser_customization_values = (
145  'DEBUG' => 0,     # if >= 10, tree is printed in texi2any.pl after parsing.
146                    # If >= 100 tree is printed every line.
147  'FORMAT_MENU' => 'menu',           # if not 'menu' no menu error related.
148  'IGNORE_BEFORE_SETFILENAME' => 1,
149  'IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME' => 1,
150  'CPP_LINE_DIRECTIVES' => 1, # handle cpp like synchronization lines
151  'MAX_MACRO_CALL_NESTING' => 100000, # max number of nested macro calls
152  # This is not used directly, but passed to Convert::Text through
153  # Texinfo::Common::_convert_text_options
154  'ENABLE_ENCODING' => 1,     # output accented and special characters
155                              # based on @documentencoding
156);
157
158# Customization variables set in the parser for other modules, and the
159# default values.
160our %default_structure_customization_values = (
161  # following are used in Texinfo::Structuring
162  'USE_UP_NODE_FOR_ELEMENT_UP' => 0, # Use node up for Up if there is no
163                                     # section up.
164  'CHECK_NORMAL_MENU_STRUCTURE' => 0, # output warnings when node with
165            # automatic direction does directions in menu are not consistent
166            # with sectionning, and when node directions are not consistent
167            # with menu directions
168);
169
170
171# customization options
172our %document_settable_at_commands = (
173  'allowcodebreaks' => 'true',
174  'clickstyle' => '@arrow',
175  'codequotebacktick' => 'off',
176  'codequoteundirected' => 'off',
177  'contents' => 0,
178  'deftypefnnewline' => 'off',
179  'documentencoding' => 'us-ascii',
180  'documentlanguage' => 'en',
181  # is N ems in TeX, 0.4 in.
182  'exampleindent' => 5,
183  'firstparagraphindent' => 'none',
184  'frenchspacing' => 'off',
185  'headings' => 'on',
186  'kbdinputstyle' => 'distinct',
187  'paragraphindent' => 3,
188  'shortcontents' => 0,
189  'urefbreakstyle' => 'after',
190  'xrefautomaticsectiontitle' => 'off',
191);
192
193# those should be unique
194our %document_settable_unique_at_commands = (
195  # when passed through a configuration variable, documentdescription
196  # should be already formatted for HTML
197  'documentdescription' => undef,
198  'evenfootingmarks' => undef,
199  'evenheadingmarks' => undef,
200  'everyfootingmarks' => 'bottom',
201  'everyheadingmarks' => 'bottom',
202  'fonttextsize' => 11,
203  'footnotestyle' => 'end',
204  'novalidate' => 0,
205  'oddfootingmarks' => undef,
206  'oddheadingmarks' => undef,
207  # FIXME not clear here.
208  'pagesizes' => undef,
209  'setchapternewpage' => 'on',
210  'setfilename' => undef,
211  'everyheading'      => undef,
212  'everyfooting'      => undef,
213  'evenheading'       => undef,
214  'evenfooting'       => undef,
215  'oddheading'        => undef,
216  'oddfooting'        => undef,
217);
218
219my @command_line_settables = (
220  'CASE_INSENSITIVE_FILENAMES', 'ENABLE_ENCODING', 'ERROR_LIMIT',
221  'FILLCOLUMN', 'FORCE', 'HEADERS', 'INTERNAL_LINKS', 'MACRO_EXPAND',
222  'NODE_FILES', 'NO_WARN', 'NUMBER_FOOTNOTES', 'NUMBER_SECTIONS',
223  'OUTFILE', 'SPLIT', 'SPLIT_SIZE', 'SUBDIR', 'TRANSLITERATE_FILE_NAMES',
224  'VERBOSE'
225);
226
227# documented in the Texinfo::Parser pod section
228# all are lower cased in texi2any.pl
229my @parser_options = map {uc($_)} (keys(%default_parser_state_configuration));
230
231our @variable_string_settables = (
232'AFTER_ABOUT',
233'AFTER_BODY_OPEN',
234'AFTER_OVERVIEW',
235'AFTER_TOC_LINES',
236'AVOID_MENU_REDUNDANCY',
237'BASEFILENAME_LENGTH',
238'BEFORE_OVERVIEW',
239'BEFORE_TOC_LINES',
240'BIG_RULE',
241'BODYTEXT',
242'COPIABLE_ANCHORS',
243'CHAPTER_HEADER_LEVEL',
244'CHECK_HTMLXREF',
245'CHECK_NORMAL_MENU_STRUCTURE',
246'CLOSE_QUOTE_SYMBOL',
247'COMPLEX_FORMAT_IN_TABLE',
248'CONTENTS_OUTPUT_LOCATION',
249'CPP_LINE_DIRECTIVES',
250'CSS_LINES',
251'DATE_IN_HEADER',
252'DEBUG',
253'DEFAULT_RULE',
254'DEF_TABLE',
255'DO_ABOUT',
256'DOCTYPE',
257'DUMP_TEXI',
258'DUMP_TREE',
259'ENABLE_ENCODING_USE_ENTITY',
260'EXTENSION',
261'EXTERNAL_CROSSREF_SPLIT',
262'EXTERNAL_DIR',
263'EXTRA_HEAD',
264'FOOTNOTE_END_HEADER_LEVEL',
265'FOOTNOTE_SEPARATE_HEADER_LEVEL',
266'FRAMES',
267'FRAMESET_DOCTYPE',
268'HEADER_IN_TABLE',
269'HTML_MATH',
270'HTMLXREF',
271'ICONS',
272'IGNORE_BEFORE_SETFILENAME',
273'IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME',
274'IMAGE_LINK_PREFIX',
275'INDEX_ENTRY_COLON',
276'INDEX_SPECIAL_CHARS_WARNING',
277'INFO_JS_DIR',
278'INFO_SPECIAL_CHARS_QUOTE',
279'INFO_SPECIAL_CHARS_WARNING',
280'INLINE_CSS_STYLE',
281'JS_WEBLABELS',
282'JS_WEBLABELS_FILE',
283'KEEP_TOP_EXTERNAL_REF',
284'L2H',
285'L2H_CLEAN',
286'L2H_FILE',
287'L2H_HTML_VERSION',
288'L2H_L2H',
289'L2H_SKIP',
290'L2H_TMP',
291'MATHJAX_SCRIPT',
292'MATHJAX_SOURCE',
293'MAX_HEADER_LEVEL',
294'MAX_MACRO_CALL_NESTING',
295'MENU_ENTRY_COLON',
296'MENU_SYMBOL',
297'MONOLITHIC',
298'NO_CSS',
299'NODE_FILE_EXTENSION',
300'NODE_FILENAMES',
301'NODE_NAME_IN_INDEX',
302'NODE_NAME_IN_MENU',
303'NO_USE_SETFILENAME',
304'OPEN_QUOTE_SYMBOL',
305'OUTPUT_ENCODING_NAME',
306'OUTPUT_PERL_ENCODING',
307'OVERVIEW_LINK_TO_TOC',
308'PACKAGE',
309'PACKAGE_AND_VERSION',
310'PACKAGE_NAME',
311'PACKAGE_URL',
312'PACKAGE_VERSION',
313'PRE_ABOUT',
314'PRE_BODY_CLOSE',
315'PREFIX',
316'PROGRAM',
317'PROGRAM_NAME_IN_FOOTER',
318'SECTION_NAME_IN_TITLE',
319'SHORTEXTN',
320'FORMAT_MENU',
321'SHOW_TITLE',
322'SIMPLE_MENU',
323'SORT_ELEMENT_COUNT',
324'SORT_ELEMENT_COUNT_WORDS',
325'TEST',
326'TEXI2DVI',
327'TEXI2HTML',
328'TEXINFO_DTD_VERSION',
329'TEXINFO_OUTPUT_FORMAT',
330'TEXTCONTENT_COMMENT',
331'TOC_LINKS',
332'TOP_FILE',
333'TOP_NODE_FILE_TARGET',
334'TOP_NODE_UP',
335'TOP_NODE_UP_URL',
336'TREE_TRANSFORMATIONS',
337'USE_ACCESSKEY',
338'USE_ISO',
339'USE_LINKS',
340'USE_NODES',
341'USE_NODE_DIRECTIONS',
342'USE_NUMERIC_ENTITY',
343'USE_REL_REV',
344'USE_SETFILENAME_EXTENSION',
345'USE_TITLEPAGE_FOR_TITLE',
346'USE_UNIDECODE',
347'USE_UP_NODE_FOR_ELEMENT_UP',
348'VERTICAL_HEAD_NAVIGATION',
349'WORDS_IN_PAGE',
350'XREF_USE_FLOAT_LABEL',
351'XREF_USE_NODE_NAME_ARG',
352);
353
354# Not strings.
355# FIXME To be documented somewhere, but where?
356my @variable_other_settables = (
357  'LINKS_BUTTONS', 'TOP_BUTTONS', 'SECTION_BUTTONS', 'BUTTONS_TEXT',
358  'BUTTONS_ACCESSKEY', 'BUTTONS_REL', 'BUTTONS_GOTO',
359  'CHAPTER_FOOTER_BUTTONS', 'SECTION_FOOTER_BUTTONS',
360  'NODE_FOOTER_BUTTONS',
361  'MISC_BUTTONS', 'CHAPTER_BUTTONS', 'BUTTONS_NAME',
362  'BUTTONS_EXAMPLE', 'SPECIAL_ELEMENTS_NAME', 'SPECIAL_ELEMENTS_CLASS',
363  'ACTIVE_ICONS', 'PASSIVE_ICONS',
364  'CSS_FILES', 'CSS_REFS',
365  'GLOBAL_COMMANDS',
366);
367
368my %valid_options;
369foreach my $var (keys(%document_settable_at_commands),
370         keys(%document_settable_unique_at_commands),
371         @command_line_settables, @variable_string_settables,
372         @variable_other_settables, @parser_options) {
373  $valid_options{$var} = 1;
374}
375
376sub valid_option($)
377{
378  my $option = shift;
379  return $valid_options{$option};
380}
381
382sub add_valid_option($)
383{
384  my $option = shift;
385  if ($option =~ /^[A-Z][A-Z_]{2,}$/) {
386    $valid_options{$option} = 1;
387    return 1;
388  }
389  return 0;
390}
391
392my %customization_variable_classes = (
393  'document_settable_at_commands' => [ sort(keys(%document_settable_at_commands)) ],
394  'document_settable_unique_at_commands' => [ sort(keys(%document_settable_unique_at_commands)) ],
395  'command_line_settables' => \@command_line_settables,
396  'variable_string_settables' => \@variable_string_settables,
397  'variable_other_settables' => \@variable_other_settables,
398  'parser_options' => \@parser_options,
399);
400
401my %valid_tree_transformations;
402foreach my $valid_transformation ('simple_menus',
403    'fill_gaps_in_sectioning', 'move_index_entries_after_items',
404    'insert_nodes_for_sectioning_commands',
405    'complete_tree_nodes_menus', 'regenerate_master_menu',
406    'indent_menu_descriptions') {
407  $valid_tree_transformations{$valid_transformation} = 1;
408}
409
410sub valid_tree_transformation ($)
411{
412  my $transformation = shift;
413  return 1 if (defined($transformation)
414               and $valid_tree_transformations{$transformation});
415  return 0;
416}
417
418our %no_brace_commands;             # commands never taking braces
419%no_brace_commands = (
420           '*', "\n",
421           ' ', ' ',
422           "\t", ' ',
423           "\n", ' ',
424           '-', '',  # hyphenation hint
425           '|', '',  # used in formatting commands @evenfooting and friends
426           '/', '',
427           ':', '',
428           '!', '!',
429           '?', '?',
430           '.', '.',
431           '@', '@',
432           '}', '}',
433           '{', '{',
434           '&', '&',
435           '\\', '\\',  # should only appear in math
436);
437
438
439# commands taking a line as argument or no argument.
440# sectioning commands and def* commands are added below.
441# index commands are added dynamically.
442#
443# The values signification is:
444# special:     no value and macro expansion, all the line is used, and
445#              analysed during parsing (_parse_special_misc_command)
446# lineraw:     no value and macro expansion, the line is kept as-is, not
447#              analysed
448# skipline:    no argument, everything else on the line is skipped
449# text:        the line is parsed as texinfo, and the argument is converted
450#              to simple text (in _end_line)
451# line:        the line is parsed as texinfo
452# a number:    the line is parsed as texinfo and the result should be plain
453#              text maybe followed by a comment; the result is analysed
454#              during parsing (_parse_line_command_args).
455#              The number is an indication of the number of arguments of
456#              the command.
457#
458# Beware that @item may be a 'line' command or an 'other' command
459# depending on the context.
460our %line_commands = (
461  'node'              => 'line', # special arg
462  'bye'               => 'skipline', # no arg
463  'end'               => 'text',
464  # set, clear
465  'set'               => 'special', # special arg
466  'clear'             => 'special', # special arg
467  'unmacro'           => 'special',
468  # comments
469  'comment'           => 'lineraw',
470  'c'                 => 'lineraw',
471  # special
472  'definfoenclose'    => 3,
473  'alias'             => 2,
474  # number of arguments is not known in advance.
475  'columnfractions'   => 1,
476  # file names
477  'setfilename'       => 'text',
478  'verbatiminclude'   => 'text',
479  'include'           => 'text',
480
481  'raisesections'     => 'skipline',  # no arg
482  'lowersections'     => 'skipline', # no arg
483  'contents'          => 'skipline', # no arg
484  'shortcontents'     => 'skipline', # no arg
485  'summarycontents'   => 'skipline', # no arg
486  'insertcopying'     => 'skipline', # no arg
487  'clickstyle'        => 'special', # arg should be an @-command
488  # more relevant in preamble
489  'documentencoding'  => 'text', # or 1?
490  'novalidate'        => 'skipline', # no arg
491  'dircategory'       => 'line', # line. Position with regard
492                                 # with direntry is significant
493  'pagesizes'         => 'line', # can have 2 args
494                           # or one? 200mm,150mm 11.5in
495  'finalout'          => 'skipline', # no arg
496  'paragraphindent'   => 1, # arg none asis
497                       # or a number and forbids anything else on the line
498  'firstparagraphindent' => 1, # none insert
499  'frenchspacing'     => 1, # on off
500  'codequoteundirected'       => 1, # on off
501  'codequotebacktick'         => 1, # on off
502  'xrefautomaticsectiontitle' => 1, # on off
503  'deftypefnnewline'  => 1, # on off
504  'fonttextsize'      => 1, # 10 11
505  'allowcodebreaks'   => 1, # false or true
506  'exampleindent'     => 1, # asis or a number
507  'footnotestyle'     => 1, # end and separate, nothing else on the line
508  'urefbreakstyle'    => 1, # after|before|none
509  'afourpaper'        => 'skipline', # no arg
510  'afivepaper'        => 'skipline', # no arg
511  'afourlatex'        => 'skipline', # no arg
512  'afourwide'         => 'skipline', # no arg
513  'bsixpaper'         => 'skipline', # no arg
514  'headings'          => 1, #off on single double singleafter doubleafter
515                            # interacts with setchapternewpage
516  'setchapternewpage' => 1, # off on odd
517
518  # only relevant in TeX, and special
519  'everyheading'      => 'lineraw',  # @*heading @*footing use @|
520  'everyfooting'      => 'lineraw',  # + @thispage @thissectionname
521  'evenheading'       => 'lineraw',  # @thissectionnum @thissection
522  'evenfooting'       => 'lineraw',  # @thischaptername @thischapternum
523  'oddheading'        => 'lineraw',  # @thischapter @thistitle @thisfile
524  'oddfooting'        => 'lineraw',
525
526  'smallbook'         => 'skipline', # no arg
527  'syncodeindex'      => 2,   # args are index identifiers
528  'synindex'          => 2,
529  'defindex'          => 1, # one identifier arg
530  'defcodeindex'      => 1, # one identifier arg
531  'documentlanguage'  => 'text',     # language code arg
532  'kbdinputstyle'     => 1,          # code example distinct
533  'everyheadingmarks' => 1, # top bottom
534  'everyfootingmarks' => 1,
535  'evenheadingmarks'  => 1,
536  'oddheadingmarks'   => 1,
537  'evenfootingmarks'  => 1,
538  'oddfootingmarks'   => 1,
539
540  # formatting
541  'center'            => 'line',
542  'printindex'        => 1,
543  'listoffloats'      => 'line',
544  # especially in titlepage
545#  'shorttitle'        => 'line',
546  'shorttitlepage'    => 'line',
547  'settitle'          => 'line',
548  'author'            => 'line',
549  'subtitle'          => 'line',
550  'title'             => 'line',
551  'sp'                => 1, # numerical arg
552  'page'              => 'skipline', # no arg (pagebreak)
553  'need'              => 1, # one numerical/real arg
554  # formatting
555  'exdent'            => 'line',
556  'item'              => 'line', # or skipspace, depending on the context
557  'itemx'             => 'line',
558  # not valid for info (should be in @iftex)
559  'vskip'             => 'lineraw', # arg line in TeX
560  'subentry'          => 'line',
561);
562
563# commands that do not take the whole line as argument
564#
565# skipspace:   no argument, following spaces are skipped.
566# noarg:       no argument
567#
568our %other_commands = (
569  # formatting
570  'noindent'          => 'skipspace',
571  'indent'            => 'skipspace',
572  'headitem'          => 'skipspace',
573  'item'              => 'skipspace', # or line, depending on the context
574  'tab'               => 'skipspace',
575  'refill'            => 'noarg',     # obsolete
576);
577
578# only valid in heading or footing
579our %in_heading_commands;
580foreach my $in_heading_command ('thischapter', 'thischaptername',
581  'thischapternum', 'thisfile', 'thispage', 'thistitle') {
582  $in_heading_commands{$in_heading_command} = 1;
583
584  $other_commands{$in_heading_command} = 'noarg';
585}
586
587
588# only valid in index entries
589our %in_index_commands;
590foreach my $in_index_command ('sortas', 'seeentry', 'seealso', 'subentry') {
591  $in_index_commands{$in_index_command} = 1;
592}
593
594
595our %index_names = (
596 'cp' => {'in_code' => 0},
597 'fn' => {'in_code' => 1},
598 'vr' => {'in_code' => 1},
599 'ky' => {'in_code' => 1},
600 'pg' => {'in_code' => 1},
601 'tp' => {'in_code' => 1},
602);
603
604foreach my $index(keys(%index_names)) {
605  $index_names{$index}->{'name'} = $index;
606}
607
608our %default_index_commands;
609# all the commands are readded dynamically in the Parser.
610foreach my $index_name (keys (%index_names)) {
611  if ($index_name =~ /^(.).$/) {
612    my $index_prefix = $1;
613    # only put the one letter versions in the hash.
614    $line_commands{$index_prefix.'index'} = 'line';
615    $default_index_commands{$index_prefix.'index'} = 1;
616  }
617}
618
619# command with braces. Value is the max number of arguments.
620our %brace_commands;
621
622our %letter_no_arg_commands;
623foreach my $letter_no_arg_command ('aa','AA','ae','oe','AE','OE','o','O',
624                                   'ss','l','L','DH','dh','TH','th') {
625  $letter_no_arg_commands{$letter_no_arg_command} = 1;
626  $brace_commands{$letter_no_arg_command} = 0;
627}
628
629foreach my $no_arg_command ('TeX','LaTeX','bullet','copyright',
630  'registeredsymbol','dots','enddots','equiv','error','expansion','arrow',
631  'minus','point','print','result','today',
632  'exclamdown','questiondown','pounds','ordf','ordm',
633  'atchar', 'lbracechar', 'rbracechar', 'backslashchar', 'hashchar', 'comma',
634  'ampchar',
635  'euro', 'geq','leq','tie','textdegree','click',
636  'quotedblleft','quotedblright','quoteleft','quoteright','quotedblbase',
637  'quotesinglbase','guillemetleft','guillemetright','guillemotleft',
638  'guillemotright','guilsinglleft','guilsinglright') {
639  $brace_commands{$no_arg_command} = 0;
640}
641
642# accent commands. They may be called with and without braces.
643our %accent_commands;
644foreach my $accent_command ('"','~','^','`',"'",',','=',
645                           'ringaccent','H','dotaccent','u','ubaraccent',
646                           'udotaccent','v','ogonek','tieaccent', 'dotless') {
647  $accent_commands{$accent_command} = 1;
648  $brace_commands{$accent_command} = 'accent';
649}
650
651our %style_commands;
652foreach my $style_command ('asis','cite','clicksequence',
653  'dfn', 'emph',
654  'sc', 't', 'var',
655  'headitemfont', 'code', 'command', 'env', 'file', 'kbd',
656  'option', 'samp', 'strong', 'sub', 'sup') {
657  $brace_commands{$style_command} = 'style';
658  $style_commands{$style_command} = 1;
659}
660
661our %regular_font_style_commands;
662foreach my $command ('r', 'i', 'b', 'sansserif', 'slanted') {
663  $regular_font_style_commands{$command} = 1;
664  $brace_commands{$command} = 'style';
665  $style_commands{$command} = 1;
666}
667
668foreach my $one_arg_command ('U', 'dmn', 'key',
669    'titlefont', 'anchor', 'errormsg', 'sortas', 'seeentry', 'seealso') {
670  $brace_commands{$one_arg_command} = 1;
671}
672
673# FIXME: 'key', 'verb', 't'?
674foreach my $other_arg_command ('w', 'hyphenation') {
675  $brace_commands{$other_arg_command} = 'other';
676}
677
678our %code_style_commands;
679foreach my $command ('code', 'command', 'env', 'file', 'kbd', 'key', 'option',
680   'samp', 'verb', 't') {
681  $code_style_commands{$command} = 1;
682  $brace_commands{$command} = 'style';
683}
684
685# FIXME: a special case?
686$code_style_commands{'indicateurl'} = 1;
687$brace_commands{'indicateurl'} = 1;
688
689
690# Commands that enclose full texts, that can contain multiple paragraphs.
691our %context_brace_commands;
692foreach my $context_brace_command ('footnote', 'caption',
693    'shortcaption') {
694  $context_brace_commands{$context_brace_command} = $context_brace_command;
695  $brace_commands{$context_brace_command} = 'context';
696}
697
698our %math_commands;
699# Commands that enclose full texts, that can contain multiple paragraphs
700# and contain maths
701foreach my $math_brace_command ('math') {
702  $context_brace_commands{$math_brace_command} = $math_brace_command;
703  $brace_commands{$math_brace_command} = 'context';
704  $math_commands{$math_brace_command} = 1;
705}
706
707our %explained_commands;
708foreach my $explained_command ('abbr', 'acronym') {
709  $explained_commands{$explained_command} = 1;
710  $brace_commands{$explained_command} = 2;
711}
712
713
714our %inline_format_commands;
715our %inline_commands;
716foreach my $inline_format_command ('inlineraw', 'inlinefmt',
717        'inlinefmtifelse') {
718  $inline_format_commands{$inline_format_command} = 1;
719  $brace_commands{$inline_format_command} = 2;
720  $inline_commands{$inline_format_command} = 1;
721}
722
723$brace_commands{'inlinefmtifelse'} = 3;
724
725our %inline_conditional_commands;
726foreach my $inline_conditional_command ('inlineifclear', 'inlineifset') {
727  $inline_conditional_commands{$inline_conditional_command} = 1;
728  $brace_commands{$inline_conditional_command} = 2;
729  $inline_commands{$inline_conditional_command} = 1;
730}
731
732foreach my $two_arg_command('email') {
733  $brace_commands{$two_arg_command} = 2;
734}
735
736foreach my $three_arg_command('uref','url','inforef') {
737  $brace_commands{$three_arg_command} = 3;
738}
739
740foreach my $five_arg_command('xref','ref','pxref','image') {
741  $brace_commands{$five_arg_command} = 5;
742}
743
744
745# some classification to help converters
746our %ref_commands;
747foreach my $ref_command ('xref','ref','pxref','inforef') {
748  $ref_commands{$ref_command} = 1;
749}
750
751
752# brace command that is not replaced with text.
753my %unformatted_brace_commands;
754foreach my $unformatted_brace_command ('anchor', 'shortcaption',
755    'caption', 'hyphenation', 'errormsg') {
756  $unformatted_brace_commands{$unformatted_brace_command} = 1;
757}
758
759
760# commands delimiting blocks, with an @end.
761# Value is either the number of arguments on the line separated by
762# commas or the type of command, 'raw', 'def', 'conditional',
763# or 'multitable'.
764our %block_commands;
765
766# commands that have a possible content before an item
767our %block_item_commands;
768
769sub gdt($)
770{
771  return $_[0];
772}
773
774our %def_map = (
775    # basic commands.
776    # 'arg' and 'argtype' are for everything appearing after the other
777    # arguments.
778    'deffn',     [ 'category', 'name', 'arg' ],
779    'defvr',     [ 'category', 'name' ],
780    'deftypefn', [ 'category', 'type', 'name', 'argtype' ],
781    'deftypeop', [ 'category', 'class' , 'type', 'name', 'argtype' ],
782    'deftypevr', [ 'category', 'type', 'name' ],
783    'defcv',     [ 'category', 'class' , 'name' ],
784    'deftypecv', [ 'category', 'class' , 'type', 'name' ],
785    'defop',     [ 'category', 'class' , 'name', 'arg' ],
786    'deftp',     [ 'category', 'name', 'argtype' ],
787    # shortcuts
788    'defun',         {'deffn'     => gdt('Function')},
789    'defmac',        {'deffn'     => gdt('Macro')},
790    'defspec',       {'deffn'     => gdt('Special Form')},
791    'defvar',        {'defvr'     => gdt('Variable')},
792    'defopt',        {'defvr'     => gdt('User Option')},
793    'deftypefun',    {'deftypefn' => gdt('Function')},
794    'deftypevar',    {'deftypevr' => gdt('Variable')},
795    'defivar',       {'defcv'     => gdt('Instance Variable')},
796    'deftypeivar',   {'deftypecv' => gdt('Instance Variable')},
797    'defmethod',     {'defop'     => gdt('Method')},
798    'deftypemethod', {'deftypeop' => gdt('Method')},
799);
800
801# the type of index, fn: function, vr: variable, tp: type
802my %index_type_def = (
803 'fn' => ['deffn', 'deftypefn', 'deftypeop', 'defop'],
804 'vr' => ['defvr', 'deftypevr', 'defcv', 'deftypecv' ],
805 'tp' => ['deftp']
806);
807
808# Keys are commmands, values are names of indices.
809our %command_index;
810
811$command_index{'vtable'} = 'vr';
812$command_index{'ftable'} = 'fn';
813
814foreach my $index_type (keys %index_type_def) {
815  foreach my $def (@{$index_type_def{$index_type}}) {
816    $command_index{$def} = $index_type;
817  }
818}
819
820our %def_commands;
821our %def_aliases;
822foreach my $def_command(keys %def_map) {
823  if (ref($def_map{$def_command}) eq 'HASH') {
824    my ($real_command) = keys (%{$def_map{$def_command}});
825    $command_index{$def_command} = $command_index{$real_command};
826    $def_aliases{$def_command} = $real_command;
827  }
828  $block_commands{$def_command} = 'def';
829  $line_commands{$def_command.'x'} = 'line';
830  $def_commands{$def_command} = 1;
831  $def_commands{$def_command.'x'} = 1;
832  $command_index{$def_command.'x'} = $command_index{$def_command};
833}
834
835#print STDERR "".Data::Dumper->Dump([\%def_aliases]);
836#print STDERR "".Data::Dumper->Dump([\%def_prepended_content]);
837
838$block_commands{'multitable'} = 'multitable';
839$block_item_commands{'multitable'} = 1;
840
841# block commands in which menu entry and menu comments appear
842our %menu_commands;
843foreach my $menu_command ('menu', 'detailmenu', 'direntry') {
844  $menu_commands{$menu_command} = 1;
845  $block_commands{$menu_command} = 0;
846};
847
848our %align_commands;
849foreach my $align_command('raggedright', 'flushleft', 'flushright') {
850  $block_commands{$align_command} = 0;
851  $align_commands{$align_command} = 1;
852}
853$align_commands{'center'} = 1;
854
855foreach my $block_command(
856    'cartouche', 'group', 'indentedblock', 'smallindentedblock') {
857  $block_commands{$block_command} = 0;
858}
859
860our %region_commands;
861foreach my $block_command('titlepage', 'copying', 'documentdescription') {
862  $block_commands{$block_command} = 0;
863  $region_commands{$block_command} = 1;
864}
865
866our %preformatted_commands;
867our %preformatted_code_commands;
868foreach my $preformatted_command(
869    'example', 'smallexample', 'lisp', 'smalllisp') {
870  $block_commands{$preformatted_command} = 0;
871  $preformatted_commands{$preformatted_command} = 1;
872  $preformatted_code_commands{$preformatted_command} = 1;
873}
874$block_commands{'example'} = 'variadic'; # unlimited arguments
875
876foreach my $preformatted_command(
877    'display', 'smalldisplay', 'format', 'smallformat') {
878  $block_commands{$preformatted_command} = 0;
879  $preformatted_commands{$preformatted_command} = 1;
880}
881
882foreach my $block_math_command('displaymath') {
883  $block_commands{$block_math_command} = 0;
884  $math_commands{$block_math_command} = 1;
885}
886
887our %format_raw_commands;
888foreach my $format_raw_command('html', 'tex', 'xml', 'docbook') {
889  $block_commands{$format_raw_command} = 0;
890  $format_raw_commands{$format_raw_command} = 1;
891}
892
893our %raw_commands;
894# macro/rmacro are special
895foreach my $raw_command ('verbatim',
896                         'ignore', 'macro', 'rmacro') {
897  $block_commands{$raw_command} = 'raw';
898  $raw_commands{$raw_command} = 1;
899}
900
901our %texinfo_output_formats;
902foreach my $command (keys(%format_raw_commands), 'info', 'plaintext') {
903  $block_commands{'if' . $command} = 'conditional';
904  $block_commands{'ifnot' . $command} = 'conditional';
905  $texinfo_output_formats{$command} = $command;
906}
907
908$block_commands{'ifset'} = 'conditional';
909$block_commands{'ifclear'} = 'conditional';
910
911$block_commands{'ifcommanddefined'} = 'conditional';
912$block_commands{'ifcommandnotdefined'} = 'conditional';
913
914# 'macro' ?
915foreach my $block_command_one_arg('table', 'ftable', 'vtable',
916  'itemize', 'enumerate', 'quotation', 'smallquotation') {
917  $block_commands{$block_command_one_arg} = 1;
918  $block_item_commands{$block_command_one_arg} = 1
919    unless ($block_command_one_arg =~ /quotation/);
920}
921
922$block_commands{'float'} = 2;
923
924# commands that forces closing an opened paragraph.
925our %close_paragraph_commands;
926
927foreach my $block_command (keys(%block_commands)) {
928  $close_paragraph_commands{$block_command} = 1
929     unless ($block_commands{$block_command} eq 'raw' or
930             $block_commands{$block_command} eq 'conditional'
931             or $format_raw_commands{$block_command});
932}
933
934$close_paragraph_commands{'verbatim'} = 1;
935
936foreach my $close_paragraph_command ('titlefont', 'insertcopying', 'sp',
937  'verbatiminclude', 'page', 'item', 'itemx', 'tab', 'headitem',
938  'printindex', 'listoffloats', 'center', 'dircategory', 'contents',
939  'shortcontents', 'summarycontents', 'caption', 'shortcaption',
940  'setfilename', 'exdent') {
941  $close_paragraph_commands{$close_paragraph_command} = 1;
942}
943
944foreach my $close_paragraph_command (keys(%def_commands)) {
945  $close_paragraph_commands{$close_paragraph_command} = 1;
946}
947
948our %item_container_commands;
949foreach my $item_container_command ('itemize', 'enumerate') {
950  $item_container_commands{$item_container_command} = 1;
951}
952our %item_line_commands;
953foreach my $item_line_command ('table', 'ftable', 'vtable') {
954  $item_line_commands{$item_line_command} = 1;
955}
956
957our %deprecated_commands = (
958  'definfoenclose' => '',
959  'refill' => '',
960  'inforef' => '',
961  'centerchap' => '',
962);
963
964my %unformatted_block_commands;
965foreach my $unformatted_block_command ('ignore', 'macro', 'rmacro') {
966  $unformatted_block_commands{$unformatted_block_command} = 1;
967}
968
969
970# commands that should only appear at the root level and contain up to
971# the next root command.  @node and sectioning commands.
972our %root_commands;
973
974our %command_structuring_level = (
975              'top', 0,
976              'chapter', 1,
977              'unnumbered', 1,
978              'chapheading', 1,
979              'appendix', 1,
980              'section', 2,
981              'unnumberedsec', 2,
982              'heading', 2,
983              'appendixsec', 2,
984              'subsection', 3,
985              'unnumberedsubsec', 3,
986              'subheading', 3,
987              'appendixsubsec', 3,
988              'subsubsection', 4,
989              'unnumberedsubsubsec', 4,
990              'subsubheading', 4,
991              'appendixsubsubsec', 4,
992         );
993
994our %level_to_structuring_command;
995
996{
997  my $sections = [ ];
998  my $appendices = [ ];
999  my $unnumbered = [ ];
1000  my $headings = [ ];
1001  foreach my $command (keys (%command_structuring_level)) {
1002    if ($command =~ /^appendix/) {
1003      $level_to_structuring_command{$command} = $appendices;
1004    } elsif ($command =~ /^unnumbered/ or $command eq 'top') {
1005      $level_to_structuring_command{$command} = $unnumbered;
1006    } elsif ($command =~ /section$/ or $command eq 'chapter') {
1007      $level_to_structuring_command{$command} = $sections;
1008    } else {
1009      $level_to_structuring_command{$command} = $headings;
1010    }
1011    $level_to_structuring_command{$command}->[$command_structuring_level{$command}]
1012      = $command;
1013  }
1014  $level_to_structuring_command{'appendixsection'} = $appendices;
1015  $level_to_structuring_command{'majorheading'} = $headings;
1016  $level_to_structuring_command{'centerchap'} = $unnumbered;
1017}
1018
1019
1020# out of the main hierarchy
1021$command_structuring_level{'part'} = 0;
1022# this are synonyms
1023$command_structuring_level{'appendixsection'} = 2;
1024# command_structuring_level{'majorheading'} is also 1 and not 0
1025$command_structuring_level{'majorheading'} = 1;
1026$command_structuring_level{'centerchap'} = 1;
1027
1028our %sectioning_commands;
1029
1030foreach my $sectioning_command (keys (%command_structuring_level)) {
1031  $line_commands{$sectioning_command} = 'line';
1032  if ($sectioning_command =~ /heading/) {
1033    $close_paragraph_commands{$sectioning_command} = 1;
1034  } else {
1035    $root_commands{$sectioning_command} = 1;
1036  }
1037  $sectioning_commands{$sectioning_command} = 1;
1038}
1039
1040
1041# misc commands that may be formatted as text.
1042# index commands may be too, but index command may be added with
1043# @def*index so they are not added here.
1044my %formatted_misc_commands;
1045foreach my $formatted_misc_command ('insertcopying', 'contents',
1046   'shortcontents', 'summarycontents', 'center', 'printindex',
1047   'listoffloats', 'shorttitlepage', 'settitle',
1048   'author', 'subtitle', 'title', 'sp', 'exdent', 'headitem', 'item',
1049   'itemx', 'tab', 'node', keys(%sectioning_commands)) {
1050  $formatted_misc_commands{$formatted_misc_command} = 1;
1051}
1052
1053
1054our %misc_commands = (%line_commands, %other_commands);
1055
1056$root_commands{'node'} = 1;
1057
1058our %all_commands;
1059foreach my $command (
1060  keys(%Texinfo::Common::block_commands),
1061  keys(%Texinfo::Common::brace_commands),
1062  keys(%Texinfo::Common::misc_commands),
1063  keys(%Texinfo::Common::no_brace_commands),
1064  qw(value),
1065 ) {
1066  $all_commands{$command} = 1;
1067}
1068
1069our @MONTH_NAMES =
1070    (
1071     'January', 'February', 'March', 'April', 'May',
1072     'June', 'July', 'August', 'September', 'October',
1073     'November', 'December'
1074    );
1075
1076# file:        file name to locate. It can be a file path.
1077# directories: a reference on a array containing a list of directories to
1078#              search the file in.
1079# all_files:   if true collect all the files with that name, otherwise stop
1080#              at first match.
1081sub locate_init_file($$$)
1082{
1083  my $file = shift;
1084  my $directories = shift;
1085  my $all_files = shift;
1086
1087  if (File::Spec->file_name_is_absolute($file)) {
1088    return $file if (-e $file and -r $file);
1089  } else {
1090    my @files;
1091    foreach my $dir (@$directories) {
1092      next unless (-d $dir);
1093      my $possible_file = File::Spec->catfile($dir, $file);
1094      if ($all_files) {
1095        push (@files, $possible_file)
1096          if (-e $possible_file and -r $possible_file);
1097      } else {
1098        return $possible_file if (-e $possible_file and -r $possible_file);
1099      }
1100    }
1101    return @files if ($all_files);
1102  }
1103  return undef;
1104}
1105
1106sub locate_include_file($$)
1107{
1108  my $self = shift;
1109  my $text = shift;
1110  my $file;
1111
1112  my $ignore_include_directories = 0;
1113
1114  my ($volume, $directories, $filename) = File::Spec->splitpath($text);
1115  my @directories = File::Spec->splitdir($directories);
1116
1117  #print STDERR "$self $text @{$self->{'include_directories'}}\n";
1118  # If the path is absolute or begins with . or .., do not search in
1119  # include directories.
1120  if (File::Spec->file_name_is_absolute($text)) {
1121    $ignore_include_directories = 1;
1122  } else {
1123    foreach my $dir (@directories) {
1124      if ($dir eq File::Spec->updir() or $dir eq File::Spec->curdir()) {
1125        $ignore_include_directories = 1;
1126        last;
1127      } elsif ($dir ne '') {
1128        last;
1129      }
1130    }
1131  }
1132
1133  #if ($text =~ m,^(/|\./|\.\./),) {
1134  if ($ignore_include_directories) {
1135    $file = $text if (-e $text and -r $text);
1136  } else {
1137    my @dirs;
1138    if ($self) {
1139      @dirs = @{$self->{'include_directories'}};
1140    } else {
1141      # no object with directory list and not an absolute path, never succeed
1142      return undef;
1143    }
1144    foreach my $include_dir (@{$self->{'include_directories'}}) {
1145      my ($include_volume, $include_directories, $include_filename)
1146         = File::Spec->splitpath($include_dir, 1);
1147
1148      my $possible_file = File::Spec->catpath($include_volume,
1149        File::Spec->catdir(File::Spec->splitdir($include_directories),
1150                           @directories), $filename);
1151      #$file = "$include_dir/$text" if (-e "$include_dir/$text" and -r "$include_dir/$text");
1152      $file = "$possible_file" if (-e "$possible_file" and -r "$possible_file");
1153      last if (defined($file));
1154    }
1155  }
1156  return $file;
1157}
1158
1159sub open_out($$;$$)
1160{
1161  my $self = shift;
1162  my $file = shift;
1163  my $encoding = shift;
1164  my $use_binmode = shift;
1165
1166  if (!defined($encoding) and $self
1167      and defined($self->get_conf('OUTPUT_PERL_ENCODING'))) {
1168    $encoding = $self->get_conf('OUTPUT_PERL_ENCODING');
1169  }
1170
1171  if ($file eq '-') {
1172    binmode(STDOUT) if $use_binmode;
1173    binmode(STDOUT, ":encoding($encoding)") if ($encoding);
1174    if ($self) {
1175      $self->{'unclosed_files'}->{$file} = \*STDOUT;
1176    }
1177    return \*STDOUT;
1178  }
1179  my $filehandle = do { local *FH };
1180  if (!open ($filehandle, '>', $file)) {
1181    return undef;
1182  }
1183  # We run binmode to turn off outputting LF as CR LF under MS-Windows,
1184  # so that Info tag tables will have correct offsets.  This must be done
1185  # before setting the encoding filters with binmode.
1186  binmode($filehandle) if $use_binmode;
1187  if ($encoding) {
1188    if ($encoding eq 'utf8'
1189        or $encoding eq 'utf-8'
1190        or $encoding eq 'utf-8-strict') {
1191      binmode($filehandle, ':utf8');
1192    } else { # FIXME also right for shiftijs or similar encodings?
1193      binmode($filehandle, ':bytes');
1194    }
1195    binmode($filehandle, ":encoding($encoding)");
1196  }
1197  if ($self) {
1198    push @{$self->{'opened_files'}}, $file;
1199    $self->{'unclosed_files'}->{$file} = $filehandle;
1200  }
1201  return $filehandle;
1202}
1203
1204sub warn_unknown_language($) {
1205  my $lang = shift;
1206
1207  my @messages = ();
1208  my $lang_code = $lang;
1209  my $region_code;
1210
1211  if ($lang =~ /^([a-z]+)_([A-Z]+)/) {
1212    $lang_code = $1;
1213    $region_code = $2;
1214  }
1215
1216  if (! $Texinfo::Documentlanguages::language_codes{$lang_code}) {
1217    push @messages, sprintf(__("%s is not a valid language code"),
1218                            $lang_code);
1219  }
1220  if (defined($region_code)
1221       and ! $Texinfo::Documentlanguages::region_codes{$region_code}) {
1222    push @messages, sprintf(__("%s is not a valid region code"),
1223                            $region_code);
1224  }
1225  return @messages;
1226}
1227
1228my %possible_split = (
1229  'chapter' => 1,
1230  'section' => 1,
1231  'node' => 1,
1232);
1233
1234sub warn_unknown_split($) {
1235  my $split = shift;
1236
1237  my @messages = ();
1238  if ($split and !$possible_split{$split}) {
1239    push @messages, sprintf(__("%s is not a valid split possibility"), $split);
1240  }
1241  return @messages;
1242}
1243
1244# This should do the job, or at least don't do wrong if $self
1245# is not defined, as could be the case if called from
1246# Texinfo::Convert::Text.
1247sub expand_verbatiminclude($$)
1248{
1249  my $self = shift;
1250  my $current = shift;
1251
1252  return unless ($current->{'extra'} and defined($current->{'extra'}->{'text_arg'}));
1253  my $text = $current->{'extra'}->{'text_arg'};
1254  my $file = locate_include_file($self, $text);
1255
1256  my $verbatiminclude;
1257
1258  if (defined($file)) {
1259    if (!open(VERBINCLUDE, $file)) {
1260      if ($self) {
1261        $self->line_error(sprintf(__("could not read %s: %s"), $file, $!),
1262                            $current->{'line_nr'});
1263      }
1264    } else {
1265      if (defined $current->{'extra'}->{'input_perl_encoding'}) {
1266        binmode(VERBINCLUDE, ":encoding("
1267                             . $current->{'extra'}->{'input_perl_encoding'}
1268                             . ")");
1269      }
1270      $verbatiminclude = { 'cmdname' => 'verbatim',
1271                           'parent' => $current->{'parent'},
1272                           'extra' =>
1273                        {'text_arg' => $current->{'extra'}->{'text_arg'}} };
1274      while (<VERBINCLUDE>) {
1275        push @{$verbatiminclude->{'contents'}},
1276                  {'type' => 'raw', 'text' => $_ };
1277      }
1278      if (!close (VERBINCLUDE)) {
1279        if ($self) {
1280          $self->document_warn(sprintf(__(
1281                      "error on closing \@verbatiminclude file %s: %s"),
1282                             $file, $!));
1283        }
1284      }
1285    }
1286  } elsif ($self) {
1287    $self->line_error(sprintf(__("\@%s: could not find %s"),
1288                    $current->{'cmdname'}, $text), $current->{'line_nr'});
1289  }
1290  return $verbatiminclude;
1291}
1292
1293sub definition_category($$)
1294{
1295  my $self = shift;
1296  my $current = shift;
1297
1298  return undef if (!$current->{'extra'}
1299      or !$current->{'extra'}->{'def_parsed_hash'});
1300
1301  my $arg_category = $current->{'extra'}->{'def_parsed_hash'}->{'category'};
1302  my $arg_class = $current->{'extra'}->{'def_parsed_hash'}->{'class'};
1303
1304  return $arg_category
1305    if (!defined($arg_class));
1306
1307  my $style = $command_index{$current->{'extra'}->{'def_command'}};
1308  if ($style eq 'fn') {
1309    if ($self) {
1310      return $self->gdt('{category} on {class}', { 'category' => $arg_category,
1311                                          'class' => $arg_class });
1312    } else {
1313      return {'contents' => [$arg_category, {'text' => ' on '}, $arg_class]};
1314    }
1315  } elsif ($style eq 'vr') {
1316    if ($self) {
1317      return $self->gdt('{category} of {class}', { 'category' => $arg_category,
1318                                          'class' => $arg_class });
1319    } else {
1320      return {'contents' => [$arg_category, {'text' => ' of '}, $arg_class]};
1321    }
1322  }
1323}
1324
1325sub expand_today($)
1326{
1327  my $self = shift;
1328  if ($self->get_conf('TEST')) {
1329    return {'text' => 'a sunny day'};
1330  }
1331
1332  my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst)
1333    = ($ENV{SOURCE_DATE_EPOCH}
1334        ? gmtime($ENV{SOURCE_DATE_EPOCH})
1335        : localtime(time));
1336  # See https://reproducible-builds.org/specs/source-date-epoch/.
1337
1338  $year += ($year < 70) ? 2000 : 1900;
1339  return $self->gdt('{month} {day}, {year}',
1340          { 'month' => $self->gdt($MONTH_NAMES[$mon]),
1341            'day' => $mday, 'year' => $year });
1342}
1343
1344sub translated_command_tree($$)
1345{
1346  my $self = shift;
1347  my $cmdname = shift;
1348  if ($self->{'translated_commands'}->{$cmdname}) {
1349    return $self->gdt($self->{'translated_commands'}->{$cmdname});
1350  }
1351  return undef;
1352}
1353
1354sub numbered_heading($$$;$)
1355{
1356  my $self = shift;
1357  my $current = shift;
1358  my $text = shift;
1359  my $numbered = shift;
1360
1361  my $number;
1362  if (defined($current->{'number'}) and ($numbered or !defined($numbered))) {
1363    $number = $current->{'number'};
1364  }
1365
1366  my $result;
1367  if ($self) {
1368    if (defined($number)) {
1369      if ($current->{'cmdname'} eq 'appendix' and $current->{'level'} == 1) {
1370        $result = $self->gdt('Appendix {number} {section_title}',
1371                   {'number' => $number, 'section_title' => $text},
1372                   'translated_text');
1373      } else {
1374        $result = $self->gdt('{number} {section_title}',
1375                   {'number' => $number, 'section_title' => $text},
1376                   'translated_text');
1377      }
1378    } else {
1379      $result = $text;
1380    }
1381  } else {
1382    $result = $text;
1383    $result = $number.' '.$result if (defined($number));
1384    if ($current->{'cmdname'} eq 'appendix' and $current->{'level'} == 1) {
1385      $result = 'Appendix '.$result;
1386    }
1387  }
1388  chomp ($result);
1389  return $result;
1390}
1391
1392sub definition_arguments_content($)
1393{
1394  my $root = shift;
1395  my $result;
1396
1397  return undef if (!defined($root->{'extra'})
1398                    or !defined($root->{'extra'}->{'def_parsed_hash'}));
1399  my @args = @{$root->{'args'}->[0]->{'contents'}};
1400  while (@args) {
1401    last if (defined($args[0]->{'extra'})
1402             and defined($args[0]->{'extra'}->{'def_role'})
1403             and $args[0]->{'extra'}->{'def_role'} ne 'spaces'
1404             and !$root->{'extra'}->{'def_parsed_hash'}
1405                       ->{$args[0]->{'extra'}->{'def_role'}});
1406    shift @args;
1407  }
1408  if (scalar(@args) > 0) {
1409    return \@args;
1410  } else {
1411    return undef;
1412  }
1413}
1414
1415# find the accent commands stack and the innermost text contents
1416sub find_innermost_accent_contents($;$)
1417{
1418  my $current = shift;
1419  my $encoding = shift;
1420  my @accent_commands = ();
1421  my $debug = 0;
1422 ACCENT:
1423  while (1) {
1424    # the following can happen if called with a bad tree
1425    if (!$current->{'cmdname'}
1426        or !$accent_commands{$current->{'cmdname'}}) {
1427      #print STDERR "BUG: Not an accent command in accent\n";
1428      cluck "BUG: Not an accent command in accent\n";
1429      #print STDERR Texinfo::Convert::Texinfo::convert($current)."\n";
1430      #print STDERR Data::Dumper->Dump([$current]);
1431      last;
1432    }
1433    push @accent_commands, $current;
1434    # A bogus accent, that may happen
1435    if (!$current->{'args'}) {
1436      return ([], \@accent_commands);
1437    }
1438    my $arg = $current->{'args'}->[0];
1439    if (!$arg->{'contents'}) {
1440      print STDERR "BUG: No content in accent command\n";
1441      #print STDERR Data::Dumper->Dump([$current]);
1442      #print STDERR Texinfo::Convert::Texinfo::convert($current)."\n";
1443      return ([], \@accent_commands);
1444    }
1445    # inside the argument of an accent
1446    my $text_contents = [];
1447    foreach my $content (@{$arg->{'contents'}}) {
1448      if (!($content->{'cmdname'} and ($content->{'cmdname'} eq 'c'
1449                                  or $content->{'cmdname'} eq 'comment'))) {
1450        if ($content->{'cmdname'} and $accent_commands{$content->{'cmdname'}}) {
1451          $current = $content;
1452          next ACCENT;
1453        } else {
1454          push @$text_contents, $content;
1455        }
1456      }
1457    }
1458    # we go here if there was no nested accent
1459    return ($text_contents, \@accent_commands);
1460  }
1461}
1462
1463sub trim_spaces_comment_from_content($)
1464{
1465  my $contents = shift;
1466  shift @$contents
1467    if ($contents->[0] and $contents->[0]->{'type'}
1468       and ($contents->[0]->{'type'} eq 'empty_line_after_command'
1469            or $contents->[0]->{'type'} eq 'empty_spaces_after_command'
1470            or $contents->[0]->{'type'} eq 'empty_spaces_before_argument'
1471            or $contents->[0]->{'type'} eq 'empty_spaces_after_close_brace'));
1472
1473  while (@$contents
1474         and (($contents->[-1]->{'cmdname'}
1475               and ($contents->[-1]->{'cmdname'} eq 'c'
1476                    or $contents->[-1]->{'cmdname'} eq 'comment'))
1477              or ($contents->[-1]->{'type'}
1478                  and ($contents->[-1]->{'type'} eq 'spaces_at_end'
1479                       or $contents->[-1]->{'type'} eq 'space_at_end_block_command')))) {
1480    pop @$contents;
1481  }
1482}
1483
1484sub _find_end_brace($$)
1485{
1486  my $text = shift;
1487  my $braces_count = shift;
1488
1489  my $before = '';
1490  while ($braces_count > 0 and length($text)) {
1491    if ($text =~ s/([^()]*)([()])//) {
1492      $before .= $1.$2;
1493      my $brace = $2;
1494      if ($brace eq '(') {
1495        $braces_count++;
1496      } else {
1497        $braces_count--;
1498        if ($braces_count == 0) {
1499          return ($before, $text, 0);
1500        }
1501      }
1502    } else {
1503      $before .= $text;
1504      $text = '';
1505    }
1506  }
1507  return ($before, undef, $braces_count);
1508}
1509
1510# This only counts opening braces, and returns 0 once all the parentheses
1511# are closed
1512sub _count_opened_tree_braces($$);
1513sub _count_opened_tree_braces($$)
1514{
1515  my $current = shift;
1516  my $braces_count = shift;
1517  if (defined($current->{'text'})) {
1518    my ($before, $after);
1519    ($before, $after, $braces_count) = _find_end_brace($current->{'text'},
1520                                                          $braces_count);
1521  }
1522  return $braces_count;
1523}
1524
1525# $NODE->{'contents'} is the Texinfo for the specification of a node.
1526# Returned object is a hash with two fields:
1527#
1528#     manual_content - Texinfo tree for a manual name extracted from the
1529#                      node specification.
1530#     node_content - Texinfo tree for the node name on its own
1531#
1532# retrieve a leading manual name in parentheses, if there is one.
1533sub parse_node_manual($)
1534{
1535  my $node = shift;
1536  my @contents = @{$node->{'contents'}};
1537
1538  my $manual;
1539  my $result;
1540  my ($end_paren, $spaces_after);
1541
1542  if ($contents[0] and $contents[0]->{'text'} and $contents[0]->{'text'} =~ /^\(/) {
1543    my $braces_count = 1;
1544    if ($contents[0]->{'text'} !~ /^\($/) {
1545      my $brace = shift @contents;
1546      my $brace_text = $brace->{'text'};
1547      $brace_text =~ s/^\(//;
1548      unshift @contents, { 'text' => $brace_text, 'type' => $brace->{'type'},
1549                           'parent' => $brace->{'parent'} } if $brace_text ne '';
1550    } else {
1551      shift @contents;
1552    }
1553    while(@contents) {
1554      my $content = shift @contents;
1555      if (!defined($content->{'text'}) or $content->{'text'} !~ /\)/) {
1556        push @$manual, $content;
1557        $braces_count = _count_opened_tree_braces($content, $braces_count);
1558        # This is an error, braces were closed in a command
1559        if ($braces_count == 0) {
1560          last;
1561        }
1562      } else {
1563        my ($before, $after);
1564        ($before, $after, $braces_count) = _find_end_brace($content->{'text'},
1565                                                              $braces_count);
1566        if ($braces_count == 0) {
1567          $before =~ s/(\))$//;
1568          $end_paren = $1;
1569          push @$manual, { 'text' => $before, 'parent' => $content->{'parent'} }
1570            if ($before ne '');
1571          $after =~ s/^(\s*)//;
1572          $spaces_after = $1;
1573          unshift @contents,  { 'text' => $after, 'parent' => $content->{'parent'} }
1574            if ($after ne '');
1575          last;
1576        } else {
1577          push @$manual, $content;
1578        }
1579      }
1580    }
1581    if ($braces_count == 0) {
1582      $result->{'manual_content'} = $manual if (defined($manual));
1583    } else {
1584      @contents = ({ 'text' => '(', 'parent' => $node }, @$manual);
1585    }
1586  }
1587  if (@contents) {
1588    $result->{'node_content'} = \@contents;
1589  }
1590
1591  # Overwrite the contents array so that all the elements in 'manual_content'
1592  # and 'node_content' are in the main tree.
1593  my $new_contents = [];
1594  if (defined($result) and defined($result->{'manual_content'})) {
1595    @$new_contents = ({ 'text' => '(', 'parent' => $node },
1596                      @$manual);
1597    push @$new_contents, {  'text' => ')', 'parent' => $node }
1598      if $end_paren;
1599    push @$new_contents, { 'text' => $spaces_after, 'parent' => $node }
1600      if $spaces_after;
1601  }
1602  if (@contents) {
1603    @$new_contents = (@$new_contents, @contents);
1604  }
1605  $node->{'contents'} = $new_contents;
1606
1607  return $result;
1608}
1609
1610sub float_name_caption($$)
1611{
1612  my $self = shift;
1613  my $root = shift;
1614
1615  my $caption;
1616  if ($root->{'extra'}->{'caption'}) {
1617    $caption = $root->{'extra'}->{'caption'};
1618  } elsif ($root->{'extra'}->{'shortcaption'}) {
1619    $caption = $root->{'extra'}->{'shortcaption'};
1620  }
1621  #if ($self->get_conf('DEBUG')) {
1622  #  my $caption_texi =
1623  #    Texinfo::Convert::Texinfo::convert({ 'contents' => $caption->{'contents'}});
1624  #  print STDERR "  CAPTION: $caption_texi\n";
1625  #}
1626  my $type;
1627  if ($root->{'extra'}->{'type'}->{'normalized'} ne '') {
1628    $type = {'contents' => $root->{'extra'}->{'type'}->{'content'}};
1629  }
1630
1631  my $prepended;
1632  if ($type) {
1633    if ($caption) {
1634      if (defined($root->{'number'})) {
1635        $prepended = $self->gdt('{float_type} {float_number}: ',
1636            {'float_type' => $type,
1637             'float_number' => $root->{'number'}});
1638      } else {
1639        $prepended = $self->gdt('{float_type}: ',
1640          {'float_type' => $type});
1641      }
1642    } else {
1643      if (defined($root->{'number'})) {
1644        $prepended = $self->gdt("{float_type} {float_number}\n",
1645            {'float_type' => $type,
1646              'float_number' => $root->{'number'}});
1647      } else {
1648        $prepended = $self->gdt("{float_type}\n",
1649            {'float_type' => $type});
1650      }
1651    }
1652  } elsif (defined($root->{'number'})) {
1653    if ($caption) {
1654      $prepended = $self->gdt('{float_number}: ',
1655          {'float_number' => $root->{'number'}});
1656    } else {
1657      $prepended = $self->gdt("{float_number}\n",
1658           {'float_number' => $root->{'number'}});
1659    }
1660  }
1661  return ($caption, $prepended);
1662}
1663
1664# decompose a decimal number on a given base.
1665sub _decompose_integer($$)
1666{
1667  my $number = shift;
1668  my $base = shift;
1669  my @result = ();
1670
1671  while ($number >= 0) {
1672    my $factor = $number % $base;
1673    push (@result, $factor);
1674    $number = int(($number - $factor) / $base) - 1;
1675  }
1676  return @result;
1677}
1678
1679sub enumerate_item_representation($$)
1680{
1681  my $specification = shift;
1682  my $number = shift;
1683
1684  if ($specification =~ /^[0-9]+$/) {
1685    return $specification + $number -1;
1686  }
1687
1688  my $result = '';
1689  my $base_letter = ord('a');
1690  $base_letter = ord('A') if (ucfirst($specification) eq $specification);
1691  my @letter_ords = _decompose_integer(ord($specification) - $base_letter + $number - 1, 26);
1692  foreach my $ord (@letter_ords) {
1693    $result = chr($base_letter + $ord) . $result;
1694  }
1695  return $result;
1696}
1697
1698sub is_content_empty($;$);
1699sub is_content_empty($;$)
1700{
1701  my $tree = shift;
1702  my $do_not_ignore_index_entries = shift;
1703  if (!defined($tree) or !exists($tree->{'contents'})) {
1704    return 1;
1705  }
1706  foreach my $content (@{$tree->{'contents'}}) {
1707    #print STDERR _print_current($content);
1708    if ($content->{'cmdname'}) {
1709      if ($content->{'type'} and $content->{'type'} eq 'index_entry_command') {
1710        if ($do_not_ignore_index_entries) {
1711          return 0;
1712        } else {
1713          next;
1714        }
1715      }
1716      if (exists($misc_commands{$content->{'cmdname'}})) {
1717        my @truc = keys(%formatted_misc_commands);
1718        if ($formatted_misc_commands{$content->{'cmdname'}}) {
1719          return 0;
1720        } else {
1721          next;
1722        }
1723      } elsif ($unformatted_brace_commands{$content->{'cmdname'}}
1724               or $unformatted_block_commands{$content->{'cmdname'}}) {
1725        next;
1726      } else {
1727        return 0;
1728      }
1729    }
1730    if ($content->{'type'}) {
1731      if ($content->{'type'} eq 'paragraph') {
1732        return 0;
1733      }
1734    }
1735    if ($content->{'text'} and $content->{'text'} =~ /\S/) {
1736      return 0;
1737    }
1738    if (not is_content_empty($content, $do_not_ignore_index_entries)) {
1739      return 0;
1740    }
1741  }
1742  return 1;
1743}
1744sub normalize_top_node_name($)
1745{
1746  my $node = shift;
1747  if ($node =~ /^top$/i) {
1748    return 'Top';
1749  }
1750  return $node;
1751}
1752
1753# Argument is a converter object
1754sub _convert_text_options($)
1755{
1756  my $self = shift;
1757  my %options;
1758  if ($self->get_conf('ENABLE_ENCODING')) {
1759    if ($self->get_conf('OUTPUT_ENCODING_NAME')) {
1760      $options{'enabled_encoding'} = $self->get_conf('OUTPUT_ENCODING_NAME');
1761    }
1762  }
1763  $options{'TEST'} = 1 if ($self->get_conf('TEST'));
1764  $options{'NUMBER_SECTIONS'} = $self->get_conf('NUMBER_SECTIONS');
1765  $options{'converter'} = $self;
1766  $options{'expanded_formats_hash'} = $self->{'expanded_formats_hash'};
1767  return %options;
1768}
1769
1770# Used in count_bytes
1771my $Encode_encoding_object;
1772my $last_encoding;
1773
1774sub count_bytes($$;$)
1775{
1776  my $self = shift;
1777  my $string = shift;
1778  my $encoding = shift;
1779
1780  if (!defined($encoding) and $self and $self->get_conf('OUTPUT_PERL_ENCODING')) {
1781    $encoding = $self->get_conf('OUTPUT_PERL_ENCODING');
1782  }
1783
1784  if ($encoding eq 'utf-8'
1785      or $encoding eq 'utf-8-strict') {
1786    if (Encode::is_utf8($string)) {
1787      # Get the number of bytes in the underlying storage.  This may
1788      # be slightly faster than calling Encode::encode_utf8.
1789      use bytes;
1790      return length($string);
1791
1792      # Here's another way of doing it.
1793      #Encode::_utf8_off($string);
1794      #my $length = length($string);
1795      #Encode::_utf8_on($string);
1796      #return $length
1797    } else {
1798      return length(Encode::encode_utf8($string));
1799    }
1800  } elsif ($encoding and $encoding ne 'ascii') {
1801    if (!defined($last_encoding) or $last_encoding ne $encoding) {
1802      # Look up and save encoding object for next time.  This is
1803      # slightly faster than calling Encode::encode.
1804      $last_encoding = $encoding;
1805      $Encode_encoding_object = Encode::find_encoding($encoding);
1806      if (!defined($Encode_encoding_object)) {
1807        Carp::croak "Unknown encoding '$encoding'";
1808      }
1809    }
1810    return length($Encode_encoding_object->encode($string));
1811  } else {
1812    return length($string);
1813    #my $length = length($string);
1814    #$string =~ s/\n/\\n/g;
1815    #$string =~ s/\f/\\f/g;
1816    #print STDERR "Count($length): $string\n";
1817    #return $length;
1818  }
1819  # FIXME is the following required for correct count of end of lines?
1820  #if ($encoding) {
1821  #  return length(Encode::encode($encoding, $string));
1822  #} else {
1823  #  return length(Encode::encode('ascii', $string));
1824  #}
1825}
1826
1827# TODO
1828# also recurse into
1829# extra->misc_args, extra->args_index
1830# extra->index_entry extra->type
1831#
1832# extra that should point to other elements:
1833# command_as_argument end_command
1834# associated_section part_associated_section associated_node associated_part
1835# @prototypes @columnfractions titlepage quotation @author command
1836# menu_entry_description menu_entry_name
1837#
1838# should point to other elements, or be copied.  And some should be recursed
1839# into too.
1840# extra->type->content
1841# extra->nodes_manuals->[]
1842# extra->node_content
1843# extra->node_argument
1844# extra->explanation_contents
1845# extra->menu_entry_node
1846
1847
1848sub _copy_tree($$$);
1849sub _copy_tree($$$)
1850{
1851  my $current = shift;
1852  my $parent = shift;
1853  my $reference_associations = shift;
1854  my $new = {};
1855  $reference_associations->{$current} = $new;
1856  $new->{'parent'} = $parent if ($parent);
1857  foreach my $key ('type', 'cmdname', 'text') {
1858    $new->{$key} = $current->{$key} if (exists($current->{$key}));
1859  }
1860  foreach my $key ('args', 'contents') {
1861    if ($current->{$key}) {
1862      if (ref($current->{$key}) ne 'ARRAY') {
1863        my $command_or_type = '';
1864        if ($new->{'cmdname'}) {
1865          $command_or_type = '@'.$new->{'cmdname'};
1866        } elsif ($new->{'type'}) {
1867          $command_or_type = $new->{'type'};
1868        }
1869        print STDERR "Not an array [$command_or_type] $key ".ref($current->{$key})."\n";
1870      }
1871      $new->{$key} = [];
1872      $reference_associations->{$current->{$key}} = $new->{$key};
1873      foreach my $child (@{$current->{$key}}) {
1874        push @{$new->{$key}}, _copy_tree($child, $new, $reference_associations);
1875      }
1876    }
1877  }
1878  if ($current->{'extra'}) {
1879    $new->{'extra'} = {};
1880    foreach my $key (keys %{$current->{'extra'}}) {
1881      if ($current->{'cmdname'} and $current->{'cmdname'} eq 'multitable'
1882          and $key eq 'prototypes') {
1883        $new->{'extra'}->{$key} = [];
1884        $reference_associations->{$current->{'extra'}->{$key}} = $new->{$key};
1885        foreach my $child (@{$current->{'extra'}->{$key}}) {
1886          push @{$new->{'extra'}->{$key}},
1887                  _copy_tree($child, $new, $reference_associations);
1888        }
1889      } elsif (!ref($current->{'extra'}->{$key})) {
1890        $new->{'extra'}->{$key} = $current->{'extra'}->{$key};
1891      }
1892    }
1893  }
1894  return $new;
1895}
1896
1897# for user-defined code
1898sub collect_commands_in_tree($$)
1899{
1900  my $root = shift;
1901  my $commands_list = shift;
1902
1903  my $commands_hash = {};
1904  foreach my $command_name (@$commands_list) {
1905    $commands_hash->{$command_name} = [];
1906  }
1907  _collect_commands_in_tree($root, $commands_hash);
1908  return $commands_hash;
1909}
1910
1911sub _collect_commands_in_tree($$);
1912sub _collect_commands_in_tree($$)
1913{
1914  my $current = shift;
1915  my $commands_hash = shift;
1916
1917  if (defined($current->{'cmdname'})
1918      and defined($commands_hash->{$current->{'cmdname'}})) {
1919    push @{$commands_hash->{$current->{'cmdname'}}}, $current;
1920  }
1921  foreach my $key ('args', 'contents') {
1922    if ($current->{$key}) {
1923      foreach my $child (@{$current->{$key}}) {
1924        _collect_commands_in_tree($child, $commands_hash);
1925      }
1926    }
1927  }
1928}
1929
1930# Not used.
1931sub _collect_references($$);
1932sub _collect_references($$)
1933{
1934  my $current = shift;
1935  my $references = shift;
1936  foreach my $key ('args', 'contents') {
1937    if ($current->{$key}) {
1938      $references->{$current->{$key}} = $current->{$key};
1939      foreach my $child (@{$current->{$key}}) {
1940        $references->{$child} = $child;
1941        _collect_references($child, $references);
1942      }
1943    }
1944  }
1945}
1946
1947sub _substitute_references_in_array($$$);
1948sub _substitute_references_in_array($$$)
1949{
1950  my $array = shift;
1951  my $reference_associations = shift;
1952  my $context = shift;
1953
1954  my $result = [];
1955  my $index = 0;
1956  foreach my $item (@{$array}) {
1957    if (!ref($item)) {
1958      push @{$result}, $item;
1959    } elsif ($reference_associations->{$item}) {
1960      push @{$result}, $reference_associations->{$item};
1961    } elsif (ref($item) eq 'ARRAY') {
1962      push @$result,
1963        _substitute_references_in_array($item, $reference_associations,
1964                                        "$context [$index]");
1965    } elsif (defined($item->{'text'})) {
1966      my $new_text = _copy_tree($item, undef, $reference_associations);
1967      substitute_references($item, $new_text, $reference_associations);
1968      push @{$result}, $new_text;
1969    } else {
1970      print STDERR "Trouble with $context [$index] (".ref($item).")\n";
1971      push @{$result}, undef;
1972    }
1973    $index++;
1974  }
1975  return $result;
1976}
1977
1978sub substitute_references($$$);
1979sub substitute_references($$$)
1980{
1981  my $current = shift;
1982  my $new = shift;
1983  my $reference_associations = shift;
1984
1985  foreach my $key ('args', 'contents') {
1986    if ($new->{$key}) {
1987      my $index = 0;
1988      foreach my $child (@{$new->{$key}}) {
1989        substitute_references($child, $current->{$key}->[$index],
1990                              $reference_associations);
1991        $index++;
1992      }
1993    }
1994  }
1995  if ($current->{'extra'}) {
1996    foreach my $key (keys %{$current->{'extra'}}) {
1997      if (ref($current->{'extra'}->{$key})) {
1998        my $command_or_type = '';
1999        if ($new->{'cmdname'}) {
2000          $command_or_type = '@'.$new->{'cmdname'};
2001        } elsif ($new->{'type'}) {
2002          $command_or_type = $new->{'type'};
2003        }
2004
2005        if ($current->{'cmdname'} and $current->{'cmdname'} eq 'multitable'
2006            and $key eq 'prototypes') {
2007          my $index = 0;
2008          foreach my $child (@{$new->{'extra'}->{$key}}) {
2009            substitute_references($child, $current->{'extra'}->{$key}->[$index],
2010                                  $reference_associations);
2011            $index++;
2012          }
2013        } elsif ($reference_associations->{$current->{'extra'}->{$key}}) {
2014          $new->{'extra'}->{$key}
2015            = $reference_associations->{$current->{'extra'}->{$key}};
2016          #print STDERR "Done [$command_or_type]: $key\n";
2017        } else {
2018          if (ref($current->{'extra'}->{$key}) eq 'ARRAY') {
2019
2020            #print STDERR "Array $command_or_type -> $key\n";
2021            $new->{'extra'}->{$key} = _substitute_references_in_array(
2022              $current->{'extra'}->{$key}, $reference_associations,
2023              "[$command_or_type]{$key}");
2024          } else {
2025            if (($current->{'cmdname'}
2026                 and ($current->{'cmdname'} eq 'listoffloats'
2027                     or $current->{'cmdname'} eq 'float')
2028                 and $key eq 'type')
2029                 or ($key eq 'index_entry')
2030                 or ($current->{'type'}
2031                     and $current->{'type'} eq 'menu_entry'
2032                     and $key eq 'menu_entry_node')) {
2033              foreach my $type_key (keys(%{$current->{'extra'}->{$key}})) {
2034                if (!ref($current->{'extra'}->{$key}->{$type_key})) {
2035                  $new->{'extra'}->{$key}->{$type_key}
2036                    = $current->{'extra'}->{$key}->{$type_key};
2037                } elsif ($reference_associations->{$current->{'extra'}->{$key}->{$type_key}}) {
2038                  $new->{'extra'}->{$key}->{$type_key}
2039                    = $reference_associations->{$current->{'extra'}->{$key}->{$type_key}};
2040                } elsif (ref($current->{'extra'}->{$key}->{$type_key}) eq 'ARRAY') {
2041                  $new->{'extra'}->{$key}->{$type_key}
2042                    = _substitute_references_in_array(
2043                      $current->{'extra'}->{$key}->{$type_key},
2044                      $reference_associations,
2045                      "[$command_or_type]{$key}{$type_key}");
2046                } else {
2047                  print STDERR "Not substituting [$command_or_type]{$key}: $type_key\n";
2048                }
2049              }
2050            } else {
2051              print STDERR "Not substituting [$command_or_type]: $key ($current->{'extra'}->{$key})\n";
2052            }
2053          }
2054        }
2055      }
2056    }
2057  }
2058}
2059
2060sub copy_tree($;$)
2061{
2062  my $current = shift;
2063  my $parent = shift;
2064  my $reference_associations = {};
2065  my $copy = _copy_tree($current, $parent, $reference_associations);
2066  substitute_references($current, $copy, $reference_associations);
2067  return $copy;
2068}
2069
2070sub modify_tree($$$;$);
2071sub modify_tree($$$;$)
2072{
2073  my $self = shift;
2074  my $tree = shift;
2075  my $operation = shift;
2076  my $argument = shift;
2077  #print STDERR "modify_tree tree: $tree\n";
2078
2079  if ($tree->{'args'}) {
2080    my @args = @{$tree->{'args'}};
2081    for (my $i = 0; $i <= $#args; $i++) {
2082      my @new_args = &$operation($self, 'arg', $args[$i], $argument);
2083      modify_tree($self, $args[$i], $operation, $argument);
2084      # this puts the new args at the place of the old arg using the
2085      # offset from the end of the array
2086      splice (@{$tree->{'args'}}, $i - $#args -1, 1, @new_args);
2087      #foreach my $arg (@new_args) {
2088      #  modify_tree($self, $arg, $operation);
2089      #}
2090    }
2091  }
2092  if ($tree->{'contents'}) {
2093    my @contents = @{$tree->{'contents'}};
2094    for (my $i = 0; $i <= $#contents; $i++) {
2095      my @new_contents = &$operation($self, 'content', $contents[$i], $argument);
2096      modify_tree($self, $contents[$i], $operation, $argument);
2097      # this puts the new contents at the place of the old content using the
2098      # offset from the end of the array
2099      splice (@{$tree->{'contents'}}, $i - $#contents -1, 1, @new_contents);
2100      #foreach my $content (@new_contents) {
2101      #  modify_tree($self, $content, $operation);
2102      #}
2103    }
2104  }
2105  return $tree;
2106}
2107
2108sub _protect_comma($$$)
2109{
2110  my $self = shift;
2111  my $type = shift;
2112  my $current = shift;
2113
2114  return _protect_text($current, quotemeta(','));
2115}
2116
2117sub protect_comma_in_tree($)
2118{
2119  my $tree = shift;
2120  return modify_tree(undef, $tree, \&_protect_comma);
2121}
2122
2123sub _new_asis_command_with_text($$;$)
2124{
2125  my $text = shift;
2126  my $parent = shift;
2127  my $text_type = shift;
2128  my $new_command = {'cmdname' => 'asis', 'parent' => $parent };
2129  push @{$new_command->{'args'}}, {'type' => 'brace_command_arg',
2130                                   'parent' => $new_command};
2131  push @{$new_command->{'args'}->[0]->{'contents'}}, {
2132    'text' => $text,
2133    'parent' => $new_command->{'args'}->[0]};
2134  if (defined($text_type)) {
2135    $new_command->{'args'}->[0]->{'contents'}->[0]->{'type'} = $text_type;
2136  }
2137  return $new_command;
2138}
2139
2140sub _protect_text($$)
2141{
2142  my $current = shift;
2143  my $to_protect = shift;
2144
2145  #print STDERR "$to_protect: $current "._print_current($current)."\n";
2146  if (defined($current->{'text'}) and $current->{'text'} =~ /$to_protect/
2147      and !(defined($current->{'type'}) and $current->{'type'} eq 'raw')) {
2148    my @result = ();
2149    my $remaining_text = $current->{'text'};
2150    while ($remaining_text) {
2151      if ($remaining_text =~ s/^(.*?)(($to_protect)+)//) {
2152        if ($1 ne '') {
2153          push @result, {'text' => $1, 'parent' => $current->{'parent'}};
2154          $result[-1]->{'type'} = $current->{'type'}
2155            if defined($current->{'type'});
2156        }
2157        if ($to_protect eq quotemeta(',')) {
2158          for (my $i = 0; $i < length($2); $i++) {
2159            push @result, {'cmdname' => 'comma', 'parent' => $current->{'parent'},
2160                           'args' => [{'type' => 'brace_command_arg'}]};
2161          }
2162        } else {
2163          push @result, _new_asis_command_with_text($2, $current->{'parent'},
2164                                                    $current->{'type'});
2165        }
2166      } else {
2167        push @result, {'text' => $remaining_text, 'parent' => $current->{'parent'}};
2168        $result[-1]->{'type'} = $current->{'type'}
2169          if defined($current->{'type'});
2170        last;
2171      }
2172    }
2173    #print STDERR "Result: @result\n";
2174    return @result;
2175  } else {
2176    #print STDERR "No change: $current\n";
2177    return ($current);
2178  }
2179}
2180
2181sub _protect_colon($$$)
2182{
2183  my $self = shift;
2184  my $type = shift;
2185  my $current = shift;
2186
2187  return _protect_text ($current, quotemeta(':'));
2188}
2189
2190sub protect_colon_in_tree($)
2191{
2192  my $tree = shift;
2193  return modify_tree(undef, $tree, \&_protect_colon);
2194}
2195
2196sub _protect_node_after_label($$$)
2197{
2198  my $self = shift;
2199  my $type = shift;
2200  my $current = shift;
2201
2202  return _protect_text ($current, '['. quotemeta(".\t,") .']');
2203}
2204
2205sub protect_node_after_label_in_tree($)
2206{
2207  my $tree = shift;
2208  return modify_tree(undef, $tree, \&_protect_node_after_label);
2209}
2210
2211sub _is_cpp_line($)
2212{
2213  my $text = shift;
2214  return 1 if ($text =~ /^\s*#\s*(line)? (\d+)(( "([^"]+)")(\s+\d+)*)?\s*$/);
2215  return 0;
2216}
2217
2218sub _protect_hashchar_at_line_beginning($$$)
2219{
2220  my $self = shift;
2221  my $type = shift;
2222  my $current = shift;
2223
2224  #print STDERR "$type $current "._print_current($current)."\n";
2225  # if the next is a hash character at line beginning, mark it
2226  if (defined($current->{'text'}) and $current->{'text'} =~ /\n$/
2227      and $current->{'parent'} and $current->{'parent'}->{'contents'}) {
2228    my $parent = $current->{'parent'};
2229    #print STDERR "End of line in $current, parent $parent: (@{$parent->{'contents'}})\n";
2230    my $current_found = 0;
2231    foreach my $content (@{$parent->{'contents'}}) {
2232      if ($current_found) {
2233        #print STDERR "after $current: $content $content->{'text'}\n";
2234        if ($content->{'text'} and _is_cpp_line($content->{'text'})) {
2235          $content->{'extra'}->{'_protect_hashchar'} = 1;
2236        }
2237        last;
2238      } elsif ($content eq $current) {
2239        $current_found = 1;
2240      }
2241    }
2242  }
2243
2244  my $protect_hash = 0;
2245  # if marked, or first and a cpp_line protect a leading hash character
2246  if ($current->{'extra'} and $current->{'extra'}->{'_protect_hashchar'}) {
2247    delete $current->{'extra'}->{'_protect_hashchar'};
2248    if (!scalar(keys(%{$current->{'extra'}}))) {
2249      delete $current->{'extra'};
2250    }
2251    $protect_hash = 1;
2252  } elsif ($current->{'parent'} and $current->{'parent'}->{'contents'}
2253           and $current->{'parent'}->{'contents'}->[0]
2254           and $current->{'parent'}->{'contents'}->[0] eq $current
2255           and $current->{'text'}
2256           and _is_cpp_line($current->{'text'})) {
2257    $protect_hash = 1;
2258  }
2259  if ($protect_hash) {
2260    my @result = ();
2261    if ($current->{'type'} and $current->{'type'} eq 'raw') {
2262      if ($self) {
2263        my $parent = $current->{'parent'};
2264        while ($parent) {
2265          if ($parent->{'cmdname'} and $parent->{'line_nr'}) {
2266            $self->line_warn(sprintf(__(
2267                  "could not protect hash character in \@%s"),
2268                             $parent->{'cmdname'}), $parent->{'line_nr'});
2269            last;
2270          }
2271          $parent = $parent->{'parent'};
2272        }
2273      }
2274    } else {
2275      $current->{'text'} =~ s/^(\s*)#//;
2276      if ($1 ne '') {
2277        push @result, {'text' => $1, 'parent' => $current->{'parent'}};
2278      }
2279      push @result, {'cmdname' => 'hashchar', 'parent' => $current->{'parent'},
2280                     'args' => [{'type' => 'brace_command_arg'}]};
2281    }
2282    push @result, $current;
2283    return @result;
2284  } else {
2285    return ($current);
2286  }
2287}
2288
2289sub protect_hashchar_at_line_beginning($$)
2290{
2291  my $self = shift;
2292  my $tree = shift;
2293  return modify_tree($self, $tree, \&_protect_hashchar_at_line_beginning);
2294}
2295
2296sub protect_first_parenthesis($)
2297{
2298  my $contents = shift;
2299  return undef if (!defined ($contents));
2300  my @contents = @$contents;
2301  my $brace;
2302  if ($contents[0] and $contents->[0]{'text'} and $contents[0]->{'text'} =~ /^\(/) {
2303    if ($contents[0]->{'text'} !~ /^\($/) {
2304      $brace = shift @contents;
2305      my $brace_text = $brace->{'text'};
2306      $brace_text =~ s/^\(//;
2307      unshift @contents, { 'text' => $brace_text, 'type' => $brace->{'type'},
2308                           'parent' => $brace->{'parent'} } if $brace_text ne '';
2309    } else {
2310      $brace = shift @contents;
2311    }
2312    unshift @contents, _new_asis_command_with_text('(', $brace->{'parent'},
2313                                                    $brace->{'type'});
2314  }
2315  return \@contents;
2316}
2317
2318sub find_parent_root_command($$)
2319{
2320  my $parser = shift;
2321  my $current = shift;
2322
2323  my $root_command;
2324  while (1) {
2325    if ($current->{'cmdname'}) {
2326      if ($root_commands{$current->{'cmdname'}}) {
2327        return $current;
2328      } elsif ($region_commands{$current->{'cmdname'}}) {
2329        if ($current->{'cmdname'} eq 'copying' and $parser
2330            and $parser->{'extra'} and $parser->{'extra'}->{'insertcopying'}) {
2331          foreach my $insertcopying(@{$parser->{'extra'}->{'insertcopying'}}) {
2332            my $root_command
2333              = $parser->find_parent_root_command($insertcopying);
2334            return $root_command if (defined($root_command));
2335          }
2336        } else {
2337          return undef;
2338        }
2339      }
2340    }
2341    if ($current->{'parent'}) {
2342      $current = $current->{'parent'};
2343    } else {
2344      return undef;
2345    }
2346  }
2347  # Should never get there
2348  return undef;
2349}
2350
2351# for debugging
2352sub _print_current($)
2353{
2354  my $current = shift;
2355  if (ref($current) ne 'HASH') {
2356    return  "_print_current: $current not a hash\n";
2357  }
2358  my $type = '';
2359  my $cmd = '';
2360  my $parent_string = '';
2361  my $text = '';
2362  $type = "($current->{'type'})" if (defined($current->{'type'}));
2363  $cmd = "\@$current->{'cmdname'}" if (defined($current->{'cmdname'}));
2364  $cmd .= "($current->{'level'})" if (defined($current->{'level'}));
2365  $text = "[text: $current->{'text'}]" if (defined($current->{'text'}));
2366  if ($current->{'parent'}) {
2367    my $parent = $current->{'parent'};
2368    my $parent_cmd = '';
2369    my $parent_type = '';
2370    $parent_cmd = "\@$parent->{'cmdname'}" if (defined($parent->{'cmdname'}));
2371    $parent_type = "($parent->{'type'})" if (defined($parent->{'type'}));
2372    $parent_string = " <- $parent_cmd$parent_type\n";
2373  }
2374  my $args = '';
2375  my $contents = '';
2376  $args = "args(".scalar(@{$current->{'args'}}).')' if $current->{'args'};
2377  $contents = "contents(".scalar(@{$current->{'contents'}}).')'
2378    if $current->{'contents'};
2379  if ("$cmd$type" ne '') {
2380    return "$cmd$type : $text $args $contents\n$parent_string";
2381  } else {
2382    return "$text $args $contents\n$parent_string";
2383  }
2384}
2385
2386sub move_index_entries_after_items($) {
2387  # enumerate or itemize
2388  my $current = shift;
2389
2390  return unless ($current->{'contents'});
2391
2392  my $previous;
2393  foreach my $item (@{$current->{'contents'}}) {
2394    #print STDERR "Before proceeding: $previous $item->{'cmdname'} (@{$previous->{'contents'}})\n" if ($previous and $previous->{'contents'});
2395    if (defined($previous) and $item->{'cmdname'}
2396        and $item->{'cmdname'} eq 'item'
2397        and $previous->{'contents'} and scalar(@{$previous->{'contents'}})) {
2398
2399      my $previous_ending_container;
2400      if ($previous->{'contents'}->[-1]->{'type'}
2401          and ($previous->{'contents'}->[-1]->{'type'} eq 'paragraph'
2402               or $previous->{'contents'}->[-1]->{'type'} eq 'preformatted')) {
2403        $previous_ending_container = $previous->{'contents'}->[-1];
2404      } else {
2405        $previous_ending_container = $previous;
2406      }
2407
2408      my @gathered_index_entries;
2409
2410      #print STDERR "Gathering for item $item in previous $previous ($previous_ending_container)\n";
2411      while ($previous_ending_container->{'contents'}->[-1]
2412             and (($previous_ending_container->{'contents'}->[-1]->{'type'}
2413                   and $previous_ending_container->{'contents'}->[-1]->{'type'} eq 'index_entry_command')
2414                  or ($previous_ending_container->{'contents'}->[-1]->{'cmdname'}
2415                      and ($previous_ending_container->{'contents'}->[-1]->{'cmdname'} eq 'c'
2416                           or $previous_ending_container->{'contents'}->[-1]->{'cmdname'} eq 'comment')))) {
2417        unshift @gathered_index_entries, pop @{$previous_ending_container->{'contents'}};
2418      }
2419      #print STDERR "Gathered: @gathered_index_entries\n";
2420      if (scalar(@gathered_index_entries)) {
2421        # put back leading comments
2422        while ($gathered_index_entries[0]
2423               and (!$gathered_index_entries[0]->{'type'}
2424                    or $gathered_index_entries[0]->{'type'} ne 'index_entry_command')) {
2425          #print STDERR "Putting back $gathered_index_entries[0] $gathered_index_entries[0]->{'cmdname'}\n";
2426          push @{$previous_ending_container->{'contents'}},
2427             shift @gathered_index_entries;
2428        }
2429
2430        # We have the index entries of the previous @item or before item.
2431        # Now put them right after the current @item command.
2432        if (scalar(@gathered_index_entries)) {
2433          my $item_container;
2434          if ($item->{'contents'} and $item->{'contents'}->[0]
2435              and $item->{'contents'}->[0]->{'type'}
2436              and $item->{'contents'}->[0]->{'type'} eq 'preformatted') {
2437            $item_container = $item->{'contents'}->[0];
2438          } else {
2439            $item_container = $item;
2440          }
2441          foreach my $entry(@gathered_index_entries) {
2442            $entry->{'parent'} = $item_container;
2443          }
2444          if ($item->{'extra'}
2445              and $item->{'extra'}->{'spaces_before_argument'}
2446       and $item->{'extra'}->{'spaces_before_argument'} !~ /\n$/) {
2447            $item->{'extra'}->{'spaces_before_argument'} .= "\n";
2448          # TODO: could we delete all these cases down here?
2449          } elsif ($item_container->{'contents'}
2450              and $item_container->{'contents'}->[0]
2451              and $item_container->{'contents'}->[0]->{'type'}) {
2452            if ($item_container->{'contents'}->[0]->{'type'} eq 'empty_line_after_command') {
2453              unshift @gathered_index_entries, shift @{$item_container->{'contents'}};
2454            } elsif ($item_container->{'contents'}->[0]->{'type'} eq 'empty_spaces_after_command') {
2455               unshift @gathered_index_entries, shift @{$item_container->{'contents'}};
2456               $gathered_index_entries[0]->{'type'} = 'empty_line_after_command';
2457               $gathered_index_entries[0]->{'text'} .= "\n";
2458            }
2459          }
2460          unshift @{$item_container->{'contents'}}, @gathered_index_entries;
2461        }
2462      }
2463    }
2464    $previous = $item;
2465  }
2466}
2467
2468sub _move_index_entries_after_items($$$)
2469{
2470  my $self = shift;
2471  my $type = shift;
2472  my $current = shift;
2473
2474  if ($current->{'cmdname'} and ($current->{'cmdname'} eq 'enumerate'
2475                                 or $current->{'cmdname'} eq 'itemize')) {
2476    move_index_entries_after_items($current);
2477  }
2478  return ($current);
2479}
2480
2481sub move_index_entries_after_items_in_tree($)
2482{
2483  my $tree = shift;
2484  return modify_tree(undef, $tree, \&_move_index_entries_after_items);
2485}
2486
2487sub _relate_index_entry_to_table_entry($)
2488{
2489  my $current = shift; # table_entry
2490
2491  my ($table_term, $table_item, $item);
2492
2493  if ($current->{'contents'}
2494        and $current->{'contents'}->[0]
2495        and $current->{'contents'}->[0]->{'type'} eq 'table_term') {
2496    $table_term = $current->{'contents'}->[0];
2497  }
2498
2499  if ($current->{'contents'}
2500        and $current->{'contents'}->[1]
2501        and $current->{'contents'}->[1]->{'type'} eq 'table_item') {
2502    $table_item = $current->{'contents'}->[1];
2503  }
2504
2505  if ($table_term->{'contents'}
2506    and $table_term->{'contents'}->[0]
2507    and (!$table_term->{'contents'}->[0]->{'extra'}
2508          or !$table_term->{'contents'}->[0]->{'extra'}->{'index_entry'})) {
2509    $item = $table_term->{'contents'}->[0];
2510  }
2511
2512  return if !$table_term or !$table_item or !$item;
2513
2514  if ($table_item->{'contents'}
2515    and $table_item->{'contents'}->[0]
2516    and $table_item->{'contents'}->[0]->{'type'}
2517    and $table_item->{'contents'}->[0]->{'type'} eq 'index_entry_command') {
2518      my $index_command = shift @{$table_item->{'contents'}};
2519      delete $index_command->{'parent'};
2520      $item->{'extra'}->{'index_entry'}
2521        = $index_command->{'extra'}->{'index_entry'};
2522      $item->{'extra'}->{'index_entry'}->{'command'} = $item;
2523  }
2524}
2525
2526sub _relate_index_entries_to_table_entries_in_tree($$$)
2527{
2528  my ($self, $type, $current) = @_;
2529
2530  if ($current->{'type'} and ($current->{'type'} eq 'table_entry')) {
2531    _relate_index_entry_to_table_entry($current);
2532  }
2533  return ($current);
2534}
2535
2536sub relate_index_entries_to_table_entries_in_tree($)
2537{
2538  my $tree = shift;
2539  return modify_tree(undef, $tree,
2540                     \&_relate_index_entries_to_table_entries_in_tree);
2541}
2542
2543
2544sub debug_list
2545{
2546  my ($label) = shift;
2547  my (@list) = (ref $_[0] && $_[0] =~ /.*ARRAY.*/) ? @{$_[0]} : @_;
2548
2549  my $str = "$label: [";
2550  my @items = ();
2551  for my $item (@list) {
2552    $item = "" if ! defined ($item);
2553    $item =~ s/\n/\\n/g;
2554    push (@items, $item);
2555  }
2556  $str .= join (" ", @items);
2557  $str .= "]";
2558
2559  warn "$str\n";
2560}
2561#
2562sub debug_hash
2563{
2564  my ($label) = shift;
2565  my (%hash) = (ref $_[0] && $_[0] =~ /.*HASH.*/) ? %{$_[0]} : @_;
2566
2567  my $str = "$label: {";
2568  my @items = ();
2569  for my $key (sort keys %hash) {
2570    my $val = $hash{$key} || ""; # no undef
2571    $key =~ s/\n/\\n/g;
2572    $val =~ s/\n/\\n/g;
2573    push (@items, "$key:$val");
2574  }
2575  $str .= join (",", @items);
2576  $str .= "}";
2577
2578  warn "$str\n";
2579}
2580
2581use Data::Dumper;
2582
2583my @kept_keys = ('contents', 'cmdname', 'type', 'text', 'args',
2584  'extra', 'def_role', 'spaces_before_argument',
2585  'spaces_after_argument', 'comment_at_end', 'index_entry'
2586);
2587my %kept_keys;
2588foreach my $key (@kept_keys) {
2589  $kept_keys{$key} = 1;
2590}
2591sub _filter_print_keys { [grep {$kept_keys{$_}} ( sort keys %{$_[0]} )] };
2592sub print_tree($)
2593{
2594  my $tree = shift;
2595  local $Data::Dumper::Sortkeys = \&_filter_print_keys;
2596  local $Data::Dumper::Purity = 1;
2597  local $Data::Dumper::Indent = 1;
2598
2599  return Data::Dumper->Dump([$tree]);
2600}
2601
2602# common parser functions
2603
2604sub _non_bracketed_contents {
2605  my $current = shift;
2606
2607  if ($current->{'type'} and $current->{'type'} eq 'bracketed') {
2608    my $new = {};
2609    $new->{'contents'} = $current->{'contents'} if ($current->{'parent'});
2610    $new->{'parent'} = $current->{'parent'} if ($current->{'parent'});
2611    return $new;
2612  } else {
2613    return $current;
2614  }
2615}
2616
2617# In a handful of cases, we delay storing the contents of the
2618# index entry until now to avoid needing Texinfo::Report::gdt
2619# in the main code of Parser.pm.  Also set 'in_code' value on
2620# index entries.
2621
2622sub complete_indices {
2623  my $self = shift;
2624
2625  my ($index_entry, $index_contents_normalized);
2626
2627  my $save_lang = $self->get_conf('documentlanguage');
2628
2629  foreach my $index_name (keys(%{$self->{'index_names'}})) {
2630    next if !defined $self->{'index_names'}->{$index_name}->{'index_entries'};
2631    foreach my $entry (@{$self->{'index_names'}->{$index_name}->{'index_entries'}}) {
2632      $entry->{'in_code'} = $self->{'index_names'}->{$index_name}->{'in_code'};
2633
2634      if (!defined $entry->{'content'}) {
2635        my $def_command = $entry->{'command'}->{'extra'}->{'def_command'};
2636
2637        my $def_parsed_hash = $entry->{'command'}->{'extra'}->{'def_parsed_hash'};
2638        if ($def_parsed_hash and $def_parsed_hash->{'class'}
2639            and $def_command) {
2640          # Use the document language that was current when the command was
2641          # used for getting the translation.
2642          $self->{'documentlanguage'} = $entry->{'command'}->{'extra'}->{'documentlanguage'};
2643          delete $entry->{'command'}->{'extra'}->{'documentlanguage'};
2644          if ($def_command eq 'defop'
2645              or $def_command eq 'deftypeop'
2646              or $def_command eq 'defmethod'
2647              or $def_command eq 'deftypemethod') {
2648            $index_entry = $self->gdt('{name} on {class}',
2649                                  {'name' => $def_parsed_hash->{'name'},
2650                                   'class' => $def_parsed_hash->{'class'}});
2651           $index_contents_normalized
2652             = [_non_bracketed_contents($def_parsed_hash->{'name'}),
2653                { 'text' => ' on '},
2654                _non_bracketed_contents($def_parsed_hash->{'class'})];
2655          } elsif ($def_command eq 'defivar'
2656                   or $def_command eq 'deftypeivar'
2657                   or $def_command eq 'deftypecv') {
2658            $index_entry = $self->gdt('{name} of {class}',
2659                                     {'name' => $def_parsed_hash->{'name'},
2660                                     'class' => $def_parsed_hash->{'class'}});
2661            $index_contents_normalized
2662              = [_non_bracketed_contents($def_parsed_hash->{'name'}),
2663                 { 'text' => ' of '},
2664                 _non_bracketed_contents($def_parsed_hash->{'class'})];
2665          }
2666        }
2667        # 'root_line' is the container returned by gdt.
2668        if ($index_entry->{'type'} and $index_entry->{'type'} eq 'root_line') {
2669          for my $child (@{$index_entry->{'contents'}}) {
2670            delete $child->{'parent'};
2671          }
2672        }
2673        if ($index_entry->{'contents'}) {
2674          $entry->{'content'} = [@{$index_entry->{'contents'}}];
2675          $entry->{'content_normalized'} = $index_contents_normalized;
2676        }
2677      }
2678    }
2679  }
2680  $self->{'documentlanguage'} = $save_lang;
2681}
2682
2683# Called from Texinfo::Parser and Texinfo::XS::parsetexi::Parsetexi.
2684sub labels_information
2685{
2686  my $self = shift;
2687  if (defined $self->{'targets'}) {
2688    my %labels = ();
2689    for my $target (@{$self->{'targets'}}) {
2690      if ($target->{'cmdname'} eq 'node') {
2691        if ($target->{'extra'}->{'nodes_manuals'}) {
2692          for my $node_manual (@{$target->{'extra'}{'nodes_manuals'}}) {
2693            if (defined $node_manual
2694                  and defined $node_manual->{'node_content'}) {
2695              my $normalized = Texinfo::Convert::NodeNameNormalization::normalize_node({'contents' => $node_manual->{'node_content'}});
2696              $node_manual->{'normalized'} = $normalized;
2697            }
2698          }
2699        }
2700      }
2701      if (defined $target->{'extra'}
2702            and defined $target->{'extra'}->{'node_content'}) {
2703        my $normalized = Texinfo::Convert::NodeNameNormalization::normalize_node({'contents' => $target->{'extra'}->{'node_content'}});
2704
2705        if ($normalized !~ /[^-]/) {
2706          $self->line_error (sprintf(__("empty node name after expansion `%s'"),
2707                Texinfo::Convert::Texinfo::convert({'contents'
2708                               => $target->{'extra'}->{'node_content'}})),
2709                $target->{'line_nr'});
2710          delete $target->{'extra'}->{'node_content'};
2711        } else {
2712          if (defined $labels{$normalized}) {
2713            $self->line_error(
2714              sprintf(__("\@%s `%s' previously defined"),
2715                         $target->{'cmdname'},
2716                   Texinfo::Convert::Texinfo::convert({'contents' =>
2717                       $target->{'extra'}->{'node_content'}})),
2718                           $target->{'line_nr'});
2719            $self->line_error(
2720              sprintf(__("here is the previous definition as \@%s"),
2721                               $labels{$normalized}->{'cmdname'}),
2722                       $labels{$normalized}->{'line_nr'});
2723            delete $target->{'extra'}->{'node_content'};
2724          } else {
2725            $labels{$normalized} = $target;
2726            $target->{'extra'}->{'normalized'} = $normalized;
2727            if ($target->{'cmdname'} eq 'node') {
2728              if ($target->{'extra'}
2729                  and $target->{'extra'}{'node_argument'}) {
2730                $target->{'extra'}{'node_argument'}{'normalized'}
2731                  = $normalized;
2732              }
2733              push @{$self->{'nodes'}}, $target;
2734            }
2735          }
2736        }
2737      } else {
2738        if ($target->{'cmdname'} eq 'node') {
2739          $self->line_error (sprintf(__("empty argument in \@%s"),
2740                  $target->{'cmdname'}), $target->{'line_nr'});
2741          delete $target->{'extra'}->{'node_content'};
2742        }
2743      }
2744    }
2745    $self->{'labels'} = \%labels;
2746    delete $self->{'targets'};
2747  }
2748  return $self->{'labels'};
2749}
2750
27511;
2752
2753__END__
2754
2755=head1 NAME
2756
2757Texinfo::Common - Classification of commands and miscellaneous methods
2758
2759=head1 SYNOPSIS
2760
2761  use Texinfo::Common qw(expand_today expand_verbatiminclude);
2762  if ($Texinfo::Common::accent_commands{$a_command}) {
2763    print STDERR "$a_command is an accent command\n";
2764  }
2765
2766  my $today_tree = expand_today($converter);
2767  my $verbatiminclude_tree
2768     = expand_verbatiminclude(undef, $verbatiminclude);
2769
2770=head1 DESCRIPTION
2771
2772Texinfo::Common holds interesting hashes classifying Texinfo @-commands,
2773as well as miscellaneous methods that may be useful for any backend
2774converting texinfo trees.
2775
2776It also defines, as our variable a hash for default indices,
2777named C<%index_names>.  The format of this hash is described in
2778L<Texinfo::Parser/indices_information>.
2779
2780=head1 COMMAND CLASSES
2781
2782Hashes are defined as C<our> variables, and are therefore available
2783outside of the module.
2784
2785The key of the hashes are @-command names without the @.  The
2786following hashes are available:
2787
2788=over
2789
2790=item %all_commands
2791
2792All the @-commands.
2793
2794=item %no_brace_commands
2795
2796Commands without brace with a single character as name, like C<*>
2797or C<:>.  The value is an ascii representation of the command.  It
2798may be an empty string.
2799
2800=item %misc_commands
2801
2802Command that do not take braces and are not block commands either, like
2803C<@node>, C<@chapter>, C<@cindex>, C<@deffnx>, C<@end>, C<@footnotestyle>,
2804C<@set>, C<@settitle>, C<@indent>, C<@definfoenclose>, C<@comment> and many
2805others.
2806
2807=item %default_index_commands
2808
2809Index entry commands corresponding to default indices. For example
2810C<@cindex>.
2811
2812=item %root_commands
2813
2814Commands that are at the root of a Texinfo document, namely
2815C<@node> and sectioning commands, except heading commands.
2816
2817=item %sectioning_commands
2818
2819All the sectioning and heading commands.
2820
2821=item %brace_commands
2822
2823The commands that take braces.  The associated value is the maximum
2824number of arguments.
2825
2826=item %letter_no_arg_commands
2827
2828@-commands with braces but no argument corresponding to letters,
2829like C<@AA{}> or C<@ss{}> or C<@o{}>.
2830
2831=item %accent_commands
2832
2833Accent @-commands taking an argument, like C<@'> or C<@ringaccent>
2834including C<@dotless> and C<@tieaccent>.
2835
2836=item %style_commands
2837
2838Commands that mark a fragment of texinfo, like C<@strong>,
2839C<@cite>, C<@code> or C<@asis>.
2840
2841=item %code_style_commands
2842
2843I<style_commands> that have their argument in code style, like
2844C<@code>.
2845
2846=item %regular_font_style_commands
2847
2848I<style_commands> that have their argument in regular font, like
2849C<@r> or C<@slanted>.
2850
2851=item %context_brace_commands
2852
2853@-commands with brace like C<@footnote>, C<@caption> and C<@math>
2854whose argument is outside of the main text flow in one way or another.
2855
2856=item %ref_commands
2857
2858Cross reference @-command referencing nodes, like C<@xref>.
2859
2860=item %explained_commands
2861
2862@-commands whose second argument explain first argument and further
2863@-command call without first argument, as C<@abbr> and C<@acronym>.
2864
2865=item %block commands
2866
2867Commands delimiting a block with a closing C<@end>.  The value
2868is I<conditional> for C<@if> commands, I<def> for definition
2869commands like C<@deffn>, I<raw> for @-commands that have no expansion
2870of @-commands in their bodies and I<multitable> for C<@multitable>.
2871Otherwise it is set to the number of arguments separated by commas
2872that may appear on the @-command line. That means 0 in most cases,
28731 for C<@quotation> and 2 for C<@float>.
2874
2875=item %raw_commands
2876
2877@-commands that have no expansion of @-commands in their bodies,
2878as C<@macro>, C<@verbatim> or C<@ignore>.
2879
2880=item %format_raw_commands
2881
2882@-commands associated with raw output format, like C<@html>, or
2883C<@docbook>.
2884
2885=item %math_commands
2886
2887@-commands which contains math, like C<@math> or C<@displaymath>.
2888
2889=item %texinfo_output_formats
2890
2891Cannonical output formats that have associated conditionals.  In
2892practice C<%format_raw_commands> plus C<info> and C<plaintext>.
2893
2894=item %def_commands
2895
2896=item %def_aliases
2897
2898Definition commands.  C<%def_aliases> associates an aliased command
2899to the original command, for example C<defun> is associated to C<deffn>.
2900
2901=item %menu_commands
2902
2903@-commands with menu entries.
2904
2905=item %align_commands
2906
2907@-commands related with alignement of text.
2908
2909=item %region_commands
2910
2911Block @-commands that enclose full text regions, like C<@titlepage>.
2912
2913=item %preformatted_commands
2914
2915=item %preformatted_code_commands
2916
2917I<%preformatted_commands> is for commands whose content should not
2918be filled, like C<@example> or C<@display>.  If the command is meant
2919for code, it is also in I<%preformatted_code_commands>, like C<@example>.
2920
2921=item %item_container_commands
2922
2923Commands holding C<@item> with C<@item> that contains blocks of text,
2924like C<@itemize>.
2925
2926=item %item_line_commands
2927
2928Commands with C<@item> that have their arguments on their lines, like
2929C<@ftable>.
2930
2931=back
2932
2933=head1 METHODS
2934
2935No method is exported in the default case.
2936
2937Most methods takes a I<$converter> as argument, sometime optionally,
2938to get some information and use methods for error reporting,
2939see L<Texinfo::Convert::Converter> and L<Texinfo::Report>.
2940
2941=over
2942
2943=item $tree = expand_today($converter)
2944
2945Expand today's date, as a texinfo tree with translations.
2946
2947=item $tree = expand_verbatiminclude($converter, $verbatiminclude)
2948
2949The I<$converter> argument may be undef.  I<$verbatiminclude> is a
2950C<@verbatiminclude> tree element.  This function returns a
2951C<@verbatim> tree elements after finding the included file and
2952reading it.  If I<$converter> is not defined, the document encoding
2953is not taken into account when reading the file.
2954
2955=item $tree = definition_category($converter, $def_line)
2956
2957The I<$converter> argument may be undef.  I<$def_line> is a
2958C<def_line> texinfo tree container.  This function
2959returns a texinfo tree corresponding to the category of the
2960I<$def_line> taking the class into account, if there is one.
2961If I<$converter> is not defined, the resulting string won't be
2962translated.
2963
2964=item $result = is_content_empty($tree, $do_not_ignore_index_entries)
2965
2966Return true if the C<$tree> has content that could be formatted.
2967C<$do_not_ignore_index_entries> is optional.  If set, index entries
2968are considered to be formatted.
2969
2970=item $result = numbered_heading ($converter, $heading_element, $heading_text, $do_number)
2971
2972The I<$converter> argument may be undef.  I<$heading_element> is
2973a heading command tree element.  I<$heading_text> is the already
2974formatted heading text.  if the I<$do_number> optional argument is
2975defined and false, no number is used and the text is returned as is.
2976This function returns the heading with a number and the appendix
2977part if needed.  If I<$converter> is not defined, the resulting
2978string won't be translated.
2979
2980=item ($caption, $prepended) = float_name_caption ($converter, $float)
2981
2982I<$float> is a texinfo tree C<@float> element.  This function
2983returns the caption that should be used for the float formatting
2984and the I<$prepended> texinfo tree combining the type and label
2985of the float.
2986
2987=item $text = enumerate_item_representation($specification, $number)
2988
2989This function returns the number or letter correponding to item
2990number I<$number> for an C<@enumerate> specification I<$specification>,
2991appearing on an C<@enumerate> line.  For example
2992
2993  enumerate_item_representation('c', 3)
2994
2995is C<e>.
2996
2997=item trim_spaces_comment_from_content($contents)
2998
2999Remove empty spaces after commands or braces at begin and
3000spaces and comments at end from a content array, modifying it.
3001
3002=item $normalized_name = normalize_top_node_name ($node_string)
3003
3004Normalize the node name string given in argument, by normalizing
3005Top node case.
3006
3007=item protect_comma_in_tree($tree)
3008
3009Protect comma characters, replacing C<,> with @comma{} in tree.
3010
3011=item protect_colon_in_tree($tree)
3012
3013=item protect_node_after_label_in_tree($tree)
3014
3015Protect colon with C<protect_colon_in_tree> and characters that
3016are special in node names after a label in menu entries (tab
3017dot and comma) with C<protect_node_after_label_in_tree>.
3018The protection is achieved by putting protected characters
3019in C<@asis{}>.
3020
3021=item $contents_result = protect_first_parenthesis ($contents)
3022
3023Return a contents array reference with first parenthesis in the
3024contents array reference protected.
3025
3026=item protect_hashchar_at_line_beginning($parser, $tree)
3027
3028Protect hash character at beginning of line if the line is a cpp
3029line directive.  The I<$parser> argument maybe undef, if it is
3030defined it is used for error reporting in case an hash character
3031could not be protected because it appeared in a raw environment.
3032
3033=item move_index_entries_after_items_in_tree($tree)
3034
3035In C<@enumerate> and C<@itemize> from the tree, move index entries
3036appearing just before C<@item> after the C<@item>.  Comment lines
3037between index entries are moved too.
3038
3039=item $command = find_parent_root_command($parser, $tree_element)
3040
3041Find the parent root command of a tree element (sectioning command or node).
3042The C<$parser> argument is optional, it is used to continue
3043through C<@insertcopying> if in a C<@copying>.
3044
3045=item valid_tree_transformation($name)
3046
3047Return true if the I<$name> is a known tree transformation name
3048that may be passed with C<TREE_TRANSFORMATIONS> to modify a texinfo
3049tree.
3050
3051=item collect_commands_in_tree($tree, $commands_list)
3052
3053Returns a hash reference with keys @-commands names specified
3054in the I<$commands_list> array reference and values arrays of
3055tree elements corresponding to those @-command found in I<$tree>
3056by traversing the tree.
3057
3058=back
3059
3060=head1 SEE ALSO
3061
3062L<Texinfo::Parser>, L<Texinfo::Convert::Converter> and L<Texinfo::Report>.
3063
3064=head1 AUTHOR
3065
3066Patrice Dumas, E<lt>pertusus@free.frE<gt>
3067
3068=cut
3069