Transformations.pm 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. # Transformations.pm: some transformations of the document tree
  2. #
  3. # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free Software Foundation,
  4. # Inc.
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 3 of the License,
  9. # or (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. # Original author: Patrice Dumas <pertusus@free.fr>
  20. # Parts (also from Patrice Dumas) come from texi2html.pl.
  21. package Texinfo::Transformations;
  22. use 5.00405;
  23. use strict;
  24. use Texinfo::Common;
  25. use Texinfo::Structuring;
  26. # Add raise/lowersections to be back at the normal level
  27. sub _correct_level($$;$)
  28. {
  29. my $section = shift;
  30. my $parent = shift;
  31. my $modifier = shift;
  32. $modifier = 1 if (!defined($modifier));
  33. my @result;
  34. if ($section->{'extra'} and $section->{'extra'}->{'sections_level'}) {
  35. my $level_to_remove = $modifier * $section->{'extra'}->{'sections_level'};
  36. my $command;
  37. if ($level_to_remove < 0) {
  38. $command = 'raisesection';
  39. } else {
  40. $command = 'lowersection';
  41. }
  42. my $remaining_level = abs($level_to_remove);
  43. while ($remaining_level) {
  44. push @result, {'cmdname' => $command,
  45. 'parent' => $parent};
  46. push @result, {'type' => 'empty_line', 'text' => "\n",
  47. 'parent' => $parent};
  48. $remaining_level--;
  49. }
  50. }
  51. return @result;
  52. }
  53. sub fill_gaps_in_sectioning($)
  54. {
  55. my $root = shift;
  56. if (!$root->{'type'} or $root->{'type'} ne 'document_root'
  57. or !$root->{'contents'}) {
  58. return undef;
  59. }
  60. my @sections_list;
  61. foreach my $content (@{$root->{'contents'}}) {
  62. if ($content->{'cmdname'} and $content->{'cmdname'} ne 'node'
  63. and $content->{'cmdname'} ne 'bye') {
  64. push @sections_list, $content;
  65. }
  66. }
  67. return (undef, undef) if (!scalar(@sections_list));
  68. my @added_sections;
  69. my @contents;
  70. my $previous_section;
  71. foreach my $content(@{$root->{'contents'}}) {
  72. push @contents, $content;
  73. if (!@sections_list or $sections_list[0] ne $content) {
  74. next;
  75. }
  76. my $current_section = shift @sections_list;
  77. my $current_section_level = $current_section->{'level'};
  78. my $next_section = $sections_list[0];
  79. if (defined($next_section)) {
  80. my $next_section_level = $next_section->{'level'};
  81. if ($next_section_level - $current_section_level > 1) {
  82. my @correct_level_offset_commands = _correct_level($next_section,
  83. $contents[-1]);
  84. if (@correct_level_offset_commands) {
  85. push @{$contents[-1]->{'contents'}}, @correct_level_offset_commands;
  86. }
  87. #print STDERR "* $current_section_level "._print_root_command_texi($current_section)."\n";
  88. #print STDERR " $next_section_level "._print_root_command_texi($next_section)."\n";
  89. while ($next_section_level - $current_section_level > 1) {
  90. $current_section_level++;
  91. my $new_section = {'cmdname' =>
  92. $Texinfo::Common::level_to_structuring_command{'unnumbered'}->[$current_section_level],
  93. 'parent' => $root,
  94. };
  95. $new_section->{'contents'} = [{'type' => 'empty_line',
  96. 'text' => "\n",
  97. 'parent' => $new_section}];
  98. $new_section->{'args'} = [{'type' => 'misc_line_arg',
  99. 'parent' => $new_section}];
  100. $new_section->{'args'}->[0]->{'contents'} = [
  101. {'type' => 'empty_spaces_after_command',
  102. 'text' => " ",
  103. 'extra' => {'command' => $new_section},
  104. 'parent' => $new_section->{'args'}->[0]
  105. },
  106. {'cmdname' => 'asis',
  107. 'parent' => $new_section->{'args'}->[0]
  108. },
  109. {'type' => 'spaces_at_end',
  110. 'text' => "\n",
  111. 'parent' => $new_section->{'args'}->[0]
  112. }];
  113. $new_section->{'args'}->[0]->{'contents'}->[1]->{'args'}
  114. = [{'type' => 'brace_command_arg',
  115. 'contents' => [],
  116. 'parent' => $new_section->{'args'}->[0]->{'contents'}->[1]}];
  117. my @misc_contents = @{$new_section->{'args'}->[0]->{'contents'}};
  118. Texinfo::Common::trim_spaces_comment_from_content(\@misc_contents);
  119. $new_section->{'extra'}->{'misc_content'} = \@misc_contents;
  120. push @contents, $new_section;
  121. push @added_sections, $new_section;
  122. #print STDERR " -> "._print_root_command_texi($new_section)."\n";
  123. }
  124. my @set_level_offset_commands = _correct_level($next_section,
  125. $contents[-1], -1);
  126. if (@set_level_offset_commands) {
  127. push @{$contents[-1]->{'contents'}}, @set_level_offset_commands;
  128. }
  129. }
  130. }
  131. }
  132. return (\@contents, \@added_sections);
  133. }
  134. sub _reference_to_arg($$$)
  135. {
  136. my $self = shift;
  137. my $type = shift;
  138. my $current = shift;
  139. if ($current->{'cmdname'} and
  140. $Texinfo::Common::ref_commands{$current->{'cmdname'}}
  141. and $current->{'extra'}
  142. and $current->{'extra'}->{'brace_command_contents'}) {
  143. my @args_try_order;
  144. if ($current->{'cmdname'} eq 'inforef') {
  145. @args_try_order = (0, 1, 2);
  146. } else {
  147. @args_try_order = (0, 1, 2, 4, 3);
  148. }
  149. foreach my $index (@args_try_order) {
  150. if (defined($current->{'args'}->[$index])
  151. and defined($current->{'extra'}->{'brace_command_contents'}->[$index])) {
  152. # This a double checking that there is some content.
  153. # Not sure that it is useful.
  154. my $text = Texinfo::Convert::Text::convert($current->{'args'}->[$index]);
  155. if (defined($text) and $text =~ /\S/) {
  156. my $result = {'contents' =>
  157. $current->{'extra'}->{'brace_command_contents'}->[$index],
  158. 'parent' => $current->{'parent'}};
  159. return ($result);
  160. }
  161. }
  162. }
  163. return {'text' => '', 'parent' => $current->{'parent'}};
  164. } else {
  165. return ($current);
  166. }
  167. }
  168. sub reference_to_arg_in_tree($$)
  169. {
  170. my $self = shift;
  171. my $tree = shift;
  172. return Texinfo::Common::modify_tree($self, $tree, \&_reference_to_arg);
  173. }
  174. # prepare a new node and register it
  175. sub _new_node($$)
  176. {
  177. my $self = shift;
  178. my $node_tree = shift;
  179. $node_tree = Texinfo::Common::protect_comma_in_tree($node_tree);
  180. $node_tree->{'contents'}
  181. = Texinfo::Common::protect_first_parenthesis($node_tree->{'contents'});
  182. $node_tree = reference_to_arg_in_tree($self, $node_tree);
  183. my $empty_node = 0;
  184. if (!$node_tree->{'contents'}
  185. or !scalar(@{$node_tree->{'contents'}})) {
  186. $node_tree->{'contents'} = [{'text' => ''}];
  187. $empty_node = 1;
  188. }
  189. unless (($node_tree->{'contents'}->[-1]->{'cmdname'}
  190. and ($node_tree->{'contents'}->[-1]->{'cmdname'} eq 'c'
  191. or $node_tree->{'contents'}->[-1]->{'cmdname'} eq 'comment'))
  192. or (defined($node_tree->{'contents'}->[-1]->{'text'})
  193. and $node_tree->{'contents'}->[-1]->{'text'} =~ /\n/)) {
  194. push @{$node_tree->{'contents'}},
  195. {'type' => 'spaces_at_end', 'text' => "\n"};
  196. }
  197. my $appended_number = 0 +$empty_node;
  198. my ($node, $parsed_node);
  199. while (!defined($node)
  200. or ($self->{'labels'}
  201. and $self->{'labels'}->{$parsed_node->{'normalized'}})) {
  202. $node = {'cmdname' => 'node', 'args' => [{}]};
  203. my $node_arg = $node->{'args'}->[0];
  204. $node_arg->{'parent'} = $node;
  205. @{$node_arg->{'contents'}} = (
  206. {'extra' => {'command' => $node},
  207. 'text' => ' ',
  208. 'type' => 'empty_spaces_after_command'},
  209. @{$node_tree->{'contents'}});
  210. if ($appended_number) {
  211. splice (@{$node_arg->{'contents'}}, -1, 0,
  212. {'text' => " $appended_number"});
  213. }
  214. foreach my $content (@{$node_arg->{'contents'}}) {
  215. $content->{'parent'} = $node_arg;
  216. }
  217. $parsed_node = Texinfo::Parser::_parse_node_manual($node_arg);
  218. if ($parsed_node and $parsed_node->{'node_content'}) {
  219. $parsed_node->{'normalized'} =
  220. Texinfo::Convert::NodeNameNormalization::normalize_node (
  221. { 'contents' => $parsed_node->{'node_content'} });
  222. }
  223. if (!defined($parsed_node) or !$parsed_node->{'node_content'}
  224. or $parsed_node->{'normalized'} !~ /[^-]/) {
  225. if ($appended_number) {
  226. return undef;
  227. } else {
  228. $node = undef;
  229. }
  230. }
  231. $appended_number++;
  232. }
  233. push @{$node->{'extra'}->{'nodes_manuals'}}, $parsed_node;
  234. if ($parsed_node->{'normalized'} ne '') {
  235. $self->{'labels'}->{$parsed_node->{'normalized'}} = $node;
  236. $node->{'extra'}->{'normalized'} = $parsed_node->{'normalized'};
  237. }
  238. if (!Texinfo::Parser::_register_label($self, $node, $parsed_node, undef)) {
  239. print STDERR "BUG: node unique, register failed: $parsed_node->{'normalized'}\n";
  240. }
  241. push @{$self->{'nodes'}}, $node;
  242. return $node;
  243. }
  244. # reassociate a tree element to the new node, from previous node
  245. sub _reassociate_to_node($$$$)
  246. {
  247. my $self = shift;
  248. my $type = shift;
  249. my $current = shift;
  250. my $nodes = shift;
  251. my ($new_node, $previous_node) = @{$nodes};
  252. if ($current->{'cmdname'} and $current->{'cmdname'} eq 'menu') {
  253. if ($previous_node) {
  254. if (!$previous_node->{'menus'} or !@{$previous_node->{'menus'}}
  255. or !grep {$current eq $_} @{$previous_node->{'menus'}}) {
  256. print STDERR "Bug: menu $current not in previous node $previous_node\n";
  257. } else {
  258. @{$previous_node->{'menus'}} = grep {$_ ne $current} @{$previous_node->{'menus'}};
  259. delete $previous_node->{'menus'} if !(@{$previous_node->{'menus'}});
  260. }
  261. } else {
  262. my $info = $self->global_informations();
  263. if (!$info or !$info->{'unassociated_menus'}
  264. or !@{$info->{'unassociated_menus'}}
  265. or !grep {$current eq $_} @{$info->{'unassociated_menus'}}) {
  266. print STDERR "Bug: menu $current not in unassociated menus\n";
  267. } else {
  268. @{$info->{'unassociated_menus'}}
  269. = grep {$_ ne $current} @{$info->{'unassociated_menus'}};
  270. delete $info->{'unassociated_menus'} if !(@{$info->{'unassociated_menus'}});
  271. }
  272. }
  273. push @{$new_node->{'menus'}}, $current;
  274. } elsif ($current->{'extra'} and $current->{'extra'}->{'index_entry'}) {
  275. if ($previous_node
  276. and (!$current->{'extra'}->{'index_entry'}->{'node'}
  277. or $current->{'extra'}->{'index_entry'}->{'node'} ne $previous_node)) {
  278. print STDERR "Bug: index entry $current (".
  279. Texinfo::Convert::Texinfo::convert ({'contents' => $current->{'extra'}->{'index_entry'}->{'content'}})
  280. .") not in previous node $previous_node\n";
  281. print STDERR " previous node: "._print_root_command_texi($previous_node)."\n";
  282. if ($current->{'extra'}->{'index_entry'}->{'node'}) {
  283. print STDERR " current node: ".
  284. _print_root_command_texi($current->{'extra'}->{'index_entry'}->{'node'})."\n";
  285. } else {
  286. print STDERR " current node not set\n";
  287. }
  288. }
  289. $current->{'extra'}->{'index_entry'}->{'node'} = $new_node;
  290. }
  291. return ($current);
  292. }
  293. sub insert_nodes_for_sectioning_commands($$)
  294. {
  295. my $self = shift;
  296. my $root = shift;
  297. if (!$root->{'type'} or $root->{'type'} ne 'document_root'
  298. or !$root->{'contents'}) {
  299. return (undef, undef);
  300. }
  301. my @added_nodes;
  302. my @contents;
  303. my $previous_node;
  304. foreach my $content (@{$root->{'contents'}}) {
  305. if ($content->{'cmdname'} and $content->{'cmdname'} ne 'node'
  306. and $content->{'cmdname'} ne 'bye'
  307. and $content->{'cmdname'} ne 'part'
  308. and not ($content->{'extra'}
  309. and $content->{'extra'}->{'associated_node'})) {
  310. my $new_node_tree;
  311. if ($content->{'cmdname'} eq 'top') {
  312. $new_node_tree = {'contents' => [{'text' => 'Top'}]};
  313. } else {
  314. $new_node_tree = Texinfo::Common::copy_tree({'contents'
  315. => $content->{'extra'}->{'misc_content'}});
  316. }
  317. my $new_node = _new_node($self, $new_node_tree);
  318. if (defined($new_node)) {
  319. push @contents, $new_node;
  320. push @added_nodes, $new_node;
  321. $new_node->{'extra'}->{'associated_section'} = $content;
  322. $content->{'extra'}->{'associated_node'} = $new_node;
  323. $new_node->{'parent'} = $content->{'parent'};
  324. # reassociate index entries and menus
  325. Texinfo::Common::modify_tree($self, $content, \&_reassociate_to_node,
  326. [$new_node, $previous_node]);
  327. }
  328. }
  329. # check normalized to avoid erroneous nodes, such as duplicates
  330. $previous_node = $content
  331. if ($content->{'cmdname'}
  332. and $content->{'cmdname'} eq 'node'
  333. and $content->{'extra'}->{'normalized'});
  334. push @contents, $content;
  335. }
  336. return (\@contents, \@added_nodes);
  337. }
  338. sub complete_node_menu($$)
  339. {
  340. my $self = shift;
  341. my $node = shift;
  342. my @node_childs;
  343. if ($node->{'extra'}->{'associated_section'}->{'section_childs'}) {
  344. foreach my $child (@{$node->{'extra'}->{'associated_section'}->{'section_childs'}}) {
  345. if ($child->{'extra'} and $child->{'extra'}->{'associated_node'}) {
  346. push @node_childs, $child->{'extra'}->{'associated_node'};
  347. }
  348. }
  349. }
  350. # Special case for @top. Gather all the children of the @part following
  351. # @top.
  352. if ($node->{'extra'}->{'associated_section'}->{'cmdname'} eq 'top') {
  353. my $current = $node->{'extra'}->{'associated_section'};
  354. while ($current->{'section_next'}) {
  355. $current = $current->{'section_next'};
  356. if ($current->{'cmdname'} and $current->{'cmdname'} eq 'part'
  357. and $current->{'section_childs'}) {
  358. foreach my $child (@{$current->{'section_childs'}}) {
  359. if ($child->{'extra'} and $child->{'extra'}->{'associated_node'}) {
  360. push @node_childs, $child->{'extra'}->{'associated_node'};
  361. }
  362. }
  363. } elsif ($current->{'extra'}->{'associated_node'}) {
  364. # for @appendix, and what follows, as it stops a @part, but is
  365. # not below @top
  366. push @node_childs, $current->{'extra'}->{'associated_node'};
  367. }
  368. }
  369. }
  370. if (scalar(@node_childs)) {
  371. my %existing_entries;
  372. if ($node->{'menus'} and @{$node->{'menus'}}) {
  373. foreach my $menu (@{$node->{'menus'}}) {
  374. foreach my $entry (@{$menu->{'contents'}}) {
  375. if ($entry->{'type'} and $entry->{'type'} eq 'menu_entry') {
  376. my $entry_node = $entry->{'extra'}->{'menu_entry_node'};
  377. if (! $entry_node->{'manual_content'}
  378. and defined($entry_node->{'normalized'})) {
  379. $existing_entries{$entry_node->{'normalized'}}
  380. = [$menu, $entry];
  381. }
  382. }
  383. }
  384. }
  385. }
  386. #print STDERR join('|', keys(%existing_entries))."\n";
  387. my @pending;
  388. my $current_menu;
  389. foreach my $node_entry (@node_childs) {
  390. if ($existing_entries{$node_entry->{'extra'}->{'normalized'}}) {
  391. my $entry;
  392. ($current_menu, $entry)
  393. = @{$existing_entries{$node_entry->{'extra'}->{'normalized'}}};
  394. if (@pending) {
  395. my $index;
  396. for ($index = 0; $index < scalar(@{$current_menu->{'contents'}}); $index++) {
  397. #print STDERR "$index, ".scalar(@{$current_menu->{'contents'}})."\n";
  398. last if ($current_menu->{'contents'}->[$index] eq $entry);
  399. }
  400. splice (@{$current_menu->{'contents'}}, $index, 0, @pending);
  401. foreach my $entry (@pending) {
  402. $entry->{'parent'} = $current_menu;
  403. }
  404. @pending = ();
  405. }
  406. } else {
  407. my $entry = Texinfo::Structuring::new_node_menu_entry($self,
  408. $node_entry->{'extra'}->{'node_content'});
  409. push @pending, $entry;
  410. }
  411. }
  412. if (scalar(@pending)) {
  413. if (!$current_menu) {
  414. my $section = $node->{'extra'}->{'associated_section'};
  415. $current_menu =
  416. Texinfo::Structuring::new_block_command (\@pending, $section, 'menu');
  417. push @{$section->{'contents'}}, $current_menu;
  418. push @{$section->{'contents'}}, {'type' => 'empty_line',
  419. 'text' => "\n",
  420. 'parent' => $section};
  421. push @{$node->{'menus'}}, $current_menu;
  422. } else {
  423. foreach my $entry (@pending) {
  424. $entry->{'parent'} = $current_menu;
  425. }
  426. my $end;
  427. if ($current_menu->{'contents'}->[-1]->{'cmdname'}
  428. and $current_menu->{'contents'}->[-1]->{'cmdname'} eq 'end') {
  429. $end = pop @{$current_menu->{'contents'}};
  430. }
  431. push @{$current_menu->{'contents'}}, @pending;
  432. push @{$current_menu->{'contents'}}, $end if ($end);
  433. }
  434. }
  435. }
  436. }
  437. # This should be called after Texinfo::Structuring::sectioning_structure.
  438. sub complete_tree_nodes_menus($$)
  439. {
  440. my $self = shift;
  441. my $root = shift;
  442. if (!$root->{'type'} or $root->{'type'} ne 'document_root'
  443. or !$root->{'contents'}) {
  444. return undef;
  445. }
  446. foreach my $content (@{$root->{'contents'}}) {
  447. if ($content->{'cmdname'} and $content->{'cmdname'} eq 'node'
  448. and (scalar(@{$content->{'extra'}->{'nodes_manuals'}}) == 1)
  449. and $content->{'extra'}
  450. and $content->{'extra'}->{'associated_section'}) {
  451. complete_node_menu($self, $content);
  452. }
  453. }
  454. }
  455. sub _copy_contents($)
  456. {
  457. my $contents = shift;
  458. my $copy = Texinfo::Common::copy_tree({'contents' => $contents});
  459. return $copy->{'contents'};
  460. }
  461. sub _print_down_menus($$;$);
  462. sub _print_down_menus($$;$)
  463. {
  464. my $self = shift;
  465. my $node = shift;
  466. my $labels = shift;
  467. $labels = $self->labels_information() if (!defined($labels));
  468. my @master_menu_contents;
  469. if ($node->{'menus'} and scalar(@{$node->{'menus'}})) {
  470. my @node_children;
  471. foreach my $menu (@{$node->{'menus'}}) {
  472. foreach my $entry (@{$menu->{'contents'}}) {
  473. if ($entry->{'type'} and $entry->{'type'} eq 'menu_entry') {
  474. push @master_menu_contents, Texinfo::Common::copy_tree($entry);
  475. # gather node children to recusrsively print their menus
  476. my $entry_node = $entry->{'extra'}->{'menu_entry_node'};
  477. if (! $entry_node->{'manual_content'}
  478. and defined($entry_node->{'normalized'})) {
  479. my $node = $labels->{$entry_node->{'normalized'}};
  480. if (defined($node) and $node->{'extra'}) {
  481. push @node_children, $node;
  482. }
  483. }
  484. }
  485. }
  486. }
  487. if (scalar(@master_menu_contents)) {
  488. # Prepend node title
  489. my $node_title_contents;
  490. if ($node->{'extra'}->{'associated_section'}
  491. and $node->{'extra'}->{'associated_section'}->{'extra'}
  492. and $node->{'extra'}->{'associated_section'}->{'extra'}->{'misc_content'}) {
  493. $node_title_contents
  494. = _copy_contents($node->{'extra'}->{'associated_section'}->{'extra'}->{'misc_content'});
  495. } else {
  496. $node_title_contents = _copy_contents($node->{'extra'}->{'node_content'});
  497. }
  498. my $menu_comment = {'type' => 'menu_comment'};
  499. $menu_comment->{'contents'}->[0] = {'type' => 'preformatted',
  500. 'parent' => $menu_comment};
  501. $menu_comment->{'contents'}->[0]->{'contents'}
  502. = [{'text' => "\n", 'type' => 'empty_line'}, @$node_title_contents,
  503. {'text' => "\n", 'type' => 'empty_line'},
  504. {'text' => "\n", 'type' => 'empty_line'}];
  505. foreach my $content (@{$menu_comment->{'contents'}->[0]->{'contents'}}) {
  506. $content->{'parent'} = $menu_comment->{'contents'}->[0];
  507. }
  508. unshift @master_menu_contents, $menu_comment;
  509. # now recurse in the children
  510. foreach my $child (@node_children) {
  511. push @master_menu_contents, _print_down_menus($self, $child, $labels);
  512. }
  513. }
  514. }
  515. return @master_menu_contents;
  516. }
  517. sub new_master_menu($;$)
  518. {
  519. my $self = shift;
  520. my $labels = shift;
  521. $labels = $self->labels_information() if (!defined($labels));
  522. my $node = $labels->{'Top'};
  523. return undef if (!defined($node));
  524. my @master_menu_contents;
  525. if ($node->{'menus'} and scalar(@{$node->{'menus'}})) {
  526. foreach my $menu (@{$node->{'menus'}}) {
  527. foreach my $entry (@{$menu->{'contents'}}) {
  528. if ($entry->{'type'} and $entry->{'type'} eq 'menu_entry') {
  529. my $entry_node = $entry->{'extra'}->{'menu_entry_node'};
  530. if (! $entry_node->{'manual_content'}
  531. and defined($entry_node->{'normalized'})) {
  532. my $node = $labels->{$entry_node->{'normalized'}};
  533. if (defined($node) and $node->{'extra'}) {
  534. push @master_menu_contents, _print_down_menus($self,
  535. $node, $labels);
  536. }
  537. }
  538. }
  539. }
  540. }
  541. }
  542. if (scalar(@master_menu_contents)) {
  543. my $first_preformatted = $master_menu_contents[0]->{'contents'}->[0];
  544. my $master_menu_title = $self->gdt(' --- The Detailed Node Listing ---');
  545. my @master_menu_title_contents;
  546. foreach my $content (@{$master_menu_title->{'contents'}}, {'text' => "\n"}) {
  547. $content->{'parent'} = $first_preformatted;
  548. push @master_menu_title_contents, $content;
  549. }
  550. unshift @{$first_preformatted->{'contents'}}, @master_menu_title_contents;
  551. return Texinfo::Structuring::new_block_command(\@master_menu_contents, undef, 'detailmenu');
  552. } else {
  553. return undef;
  554. }
  555. }
  556. sub regenerate_master_menu($;$)
  557. {
  558. my $self = shift;
  559. my $labels = shift;
  560. $labels = $self->labels_information() if (!defined($labels));
  561. my $top_node = $labels->{'Top'};
  562. return undef if (!defined($top_node));
  563. my $new_master_menu = new_master_menu($self, $labels);
  564. return undef if (!defined($new_master_menu) or !$top_node->{'menus'}
  565. or !scalar(@{$top_node->{'menus'}}));
  566. foreach my $menu (@{$top_node->{'menus'}}) {
  567. my $detailmenu_index = 0;
  568. foreach my $entry (@{$menu->{'contents'}}) {
  569. if ($entry->{'cmdname'} and $entry->{'cmdname'} eq 'detailmenu') {
  570. # replace existing detailmenu by the master menu
  571. $new_master_menu->{'parent'} = $menu;
  572. splice (@{$menu->{'contents'}}, $detailmenu_index, 1,
  573. $new_master_menu);
  574. return 1;
  575. }
  576. $detailmenu_index++;
  577. }
  578. }
  579. my $last_menu = $top_node->{'menus'}->[-1];
  580. my $index = scalar(@{$last_menu->{'contents'}});
  581. if ($index
  582. and $last_menu->{'contents'}->[$index-1]->{'cmdname'}
  583. and $last_menu->{'contents'}->[$index-1]->{'cmdname'} eq 'end') {
  584. $index --;
  585. }
  586. $new_master_menu->{'parent'} = $last_menu;
  587. if ($index
  588. and $last_menu->{'contents'}->[$index-1]->{'type'}
  589. and $last_menu->{'contents'}->[$index-1]->{'type'} eq 'menu_comment'
  590. and $last_menu->{'contents'}->[$index-1]->{'contents'}->[-1]->{'type'}
  591. and $last_menu->{'contents'}->[$index-1]->{'contents'}->[-1]->{'type'}
  592. eq 'preformatted') {
  593. my $empty_line = {'type' => 'empty_line', 'text' => "\n", 'parent' =>
  594. $last_menu->{'contents'}->[$index-1]->{'contents'}->[-1]};
  595. push @{$last_menu->{'contents'}->[$index-1]->{'contents'}}, $empty_line;
  596. } elsif ($index
  597. and $last_menu->{'contents'}->[$index-1]->{'type'}
  598. and $last_menu->{'contents'}->[$index-1]->{'type'} eq 'menu_entry') {
  599. my $menu_comment = {'type' => 'menu_comment', 'parent' => $last_menu};
  600. splice (@{$last_menu->{'contents'}}, $index, 0, $menu_comment);
  601. $index++;
  602. my $preformatted = {'type' => 'preformatted', 'parent' => $menu_comment};
  603. push @{$menu_comment->{'contents'}}, $preformatted;
  604. my $empty_line = {'type' => 'after_description_line', 'text' => "\n",
  605. 'parent' => $preformatted};
  606. push @{$preformatted->{'contents'}}, $empty_line;
  607. }
  608. splice (@{$last_menu->{'contents'}}, $index, 0, $new_master_menu);
  609. return 1;
  610. }
  611. # modify the menu tree to put description and menu comment content
  612. # together directly in the menu. Put the menu_entry in a preformatted.
  613. # last merge preformatted.
  614. sub menu_to_simple_menu($);
  615. sub menu_to_simple_menu($)
  616. {
  617. my $menu = shift;
  618. my @contents;
  619. foreach my $content (@{$menu->{'contents'}}) {
  620. if ($content->{'type'} and $content->{'type'} eq 'menu_comment') {
  621. push @contents, @{$content->{'contents'}};
  622. } elsif ($content->{'type'} and $content->{'type'} eq 'menu_entry') {
  623. my $preformatted = {'type' => 'preformatted', 'contents' => [$content]};
  624. push @contents, $preformatted;
  625. $content->{'parent'} = $preformatted;
  626. my $in_description;
  627. my @args = @{$content->{'args'}};
  628. @{$content->{'args'}} = ();
  629. while (@args) {
  630. if ($args[0]->{'type'} and $args[0]->{'type'} eq 'menu_entry_description') {
  631. my $description = shift @args;
  632. push @contents, @{$description->{'contents'}};
  633. push @contents, @args;
  634. last;
  635. } else {
  636. my $arg = shift @args;
  637. push @{$content->{'args'}}, $arg;
  638. }
  639. }
  640. } elsif ($content->{'cmdname'}
  641. and $Texinfo::Common::menu_commands{$content->{'cmdname'}}) {
  642. menu_to_simple_menu($content);
  643. push @contents, $content;
  644. } else {
  645. push @contents, $content;
  646. }
  647. }
  648. # reset parent, put in menu and merge preformatted.
  649. @{$menu->{'contents'}} = ();
  650. my $current_preformatted;
  651. foreach my $content (@contents) {
  652. $content->{'parent'} = $menu;
  653. if ($content->{'type'} and $content->{'type'} eq 'preformatted') {
  654. if (!defined($current_preformatted)) {
  655. $current_preformatted = $content;
  656. push @{$menu->{'contents'}}, $content;
  657. } else {
  658. foreach my $preformatted_content (@{$content->{'contents'}}) {
  659. push @{$current_preformatted->{'contents'}}, $preformatted_content;
  660. $preformatted_content->{'parent'} = $current_preformatted;
  661. }
  662. }
  663. } else {
  664. $current_preformatted = undef;
  665. push @{$menu->{'contents'}}, $content;
  666. }
  667. }
  668. }
  669. sub set_menus_to_simple_menu($)
  670. {
  671. my $self = shift;
  672. if ($self->{'info'} and $self->{'info'}->{'unassociated_menus'}) {
  673. foreach my $menu (@{$self->{'info'}->{'unassociated_menus'}}) {
  674. menu_to_simple_menu($menu);
  675. }
  676. }
  677. if ($self->{'nodes'} and @{$self->{'nodes'}}) {
  678. foreach my $node (@{$self->{'nodes'}}) {
  679. if ($node->{'menus'}) {
  680. foreach my $menu (@{$node->{'menus'}}) {
  681. menu_to_simple_menu($menu);
  682. }
  683. }
  684. }
  685. }
  686. }
  687. 1;
  688. __END__
  689. =head1 NAME
  690. Texinfo::Transformations - transformations of Texinfo::Parser.pm tree
  691. =head1 SYNOPSIS
  692. use Texinfo:Transformations;
  693. =head1 DESCRIPTION
  694. Includes miscellaneous methods C<set_menus_to_simple_menu> and
  695. C<menu_to_simple_menu> to change the menu texinfo tree, as well
  696. as C<insert_nodes_for_sectioning_commands> that adds nodes for
  697. sectioning commands without nodes and C<complete_tree_nodes_menus>
  698. that completes the node menus based on the sectioning tree.
  699. =head1 METHODS
  700. No method is exported in the default case.
  701. Most of those function references takes a Texinfo::Parser object
  702. as argument, see L<Texinfo::Parser>.
  703. =over
  704. =item ($root_content, $added_sections) = fill_gaps_in_sectioning ($root)
  705. This function adds empty C<@unnumbered> and similar commands in a tree
  706. to fill gaps in sectioning. This may be used, for example, when converting
  707. from a format that can handle gaps in sectioning. I<$root> is the tree
  708. root. An array reference is returned, containing the root contents
  709. with added sectioning commands, as well as an array reference containing
  710. the added sectioning commands.
  711. If the sectioning commands are lowered or raised (with C<@raisesections>,
  712. C<@lowersection>) the tree may be modified with C<@raisesections> or
  713. C<@lowersection> added to some tree elements.
  714. =item menu_to_simple_menu ($menu)
  715. =item set_menus_to_simple_menu ($parser)
  716. C<menu_to_simple_menu> transforms the tree of a menu tree element.
  717. C<set_menus_to_simple_menu> calls C<menu_to_simple_menu> for all the
  718. menus of the document.
  719. A simple menu has no I<menu_comment>, I<menu_entry> or I<menu_entry_description>
  720. container anymore, their content are merged directly in the menu in
  721. I<preformatted> container.
  722. =item ($root_content, $added_nodes) = insert_nodes_for_sectioning_commands ($parser, $tree)
  723. Insert nodes for sectioning commands without node in C<$tree>.
  724. An array reference is returned, containing the root contents
  725. with added nodes, as well as an array reference containing the
  726. added nodes.
  727. =item complete_tree_nodes_menus ($parser, $tree)
  728. Add menu entries or whole menus for nodes associated with sections,
  729. based on the sectioning tree. This function should therefore be
  730. called after L<sectioning_structure>.
  731. =item $detailmenu = new_master_menu ($parser)
  732. Returns a detailmenu tree element formatted as a master node.
  733. =item regenerate_master_menu ($parser)
  734. Regenerate the Top node master menu, replacing the first detailmenu
  735. in Top node menus or appending at the end of the Top node menu.
  736. =back
  737. =head1 SEE ALSO
  738. L<Texinfo manual|http://www.gnu.org/s/texinfo/manual/texinfo/>,
  739. L<Texinfo::Parser>.
  740. =head1 AUTHOR
  741. Patrice Dumas, E<lt>pertusus@free.frE<gt>
  742. =head1 COPYRIGHT AND LICENSE
  743. Copyright 2010, 2011, 2012 Free Software Foundation, Inc.
  744. This library is free software; you can redistribute it and/or modify
  745. it under the terms of the GNU General Public License as published by
  746. the Free Software Foundation; either version 3 of the License,
  747. or (at your option) any later version.
  748. =cut