blob: 318dd9fc555a4ca60b856f7530916243e211753a [file]
/* Copyright 2010-2025 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* corresponds to Texinfo::Structuring code related to output units and
used in converters */
#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <inttypes.h>
#include "text.h"
#include "command_ids.h"
#include "element_types.h"
#include "tree_types.h"
#include "document_types.h"
/* for fatal */
#include "base_utils.h"
#include "tree.h"
#include "builtin_commands.h"
#include "extra.h"
#include "command_stack.h"
/* for xasprintf */
#include "utils.h"
#include "debug.h"
#include "errors.h"
#include "targets.h"
#include "manipulate_tree.h"
#include "convert_to_texinfo.h"
#include "api_to_perl.h"
#include "output_unit.h"
const char *relative_unit_direction_name[] = {
#define rud_type(name) #name,
RUD_DIRECTIONS_TYPES_LIST
RUD_FILE_DIRECTIONS_TYPES
#undef rud_type
#define rud_type(name) "FirstInFile" #name,
RUD_DIRECTIONS_TYPES_LIST
#undef rud_type
};
OUTPUT_UNIT_LIST *
retrieve_output_units (const DOCUMENT *document, size_t output_units_descriptor)
{
const OUTPUT_UNIT_LISTS *output_units_lists = &document->output_units_lists;
/* the list can still be uninitialized and .list be 0 */
if (output_units_descriptor > 0
&& output_units_descriptor <= output_units_lists->number)
return &output_units_lists->output_units_lists[output_units_descriptor -1];
return 0;
}
static void
reallocate_output_unit_list (OUTPUT_UNIT_LIST *list)
{
if (list->number >= list->space)
{
list->space += 10;
list->list = realloc (list->list, list->space * sizeof (OUTPUT_UNIT *));
if (!list->list)
fatal ("realloc failed");
}
}
/* descriptor starts at 1, 0 is an error */
size_t
new_output_units_descriptor (DOCUMENT *document)
{
size_t output_units_index;
int slot_found = 0;
size_t i;
OUTPUT_UNIT_LISTS *output_units_lists = &document->output_units_lists;
for (i = 0; i < output_units_lists->number; i++)
{
if (output_units_lists->output_units_lists[i].list == 0)
{
slot_found = 1;
output_units_index = i;
}
}
if (!slot_found)
{
if (output_units_lists->number == output_units_lists->space)
{
output_units_lists->output_units_lists
= realloc (output_units_lists->output_units_lists,
(output_units_lists->space += 1) * sizeof (OUTPUT_UNIT_LIST));
if (!output_units_lists->output_units_lists)
fatal ("realloc failed");
}
output_units_index = output_units_lists->number;
output_units_lists->number++;
}
memset (&output_units_lists->output_units_lists[output_units_index],
0, sizeof (OUTPUT_UNIT_LIST));
/* immediately allocate, even if the list will remain empty, such
that the slot is reserved */
reallocate_output_unit_list
(&output_units_lists->output_units_lists[output_units_index]);
/*
fprintf (stderr, "Register output units (%d): %d\n", slot_found,
output_units_index);
*/
return output_units_index +1;
}
OUTPUT_UNIT *
new_output_unit (enum output_unit_type unit_type)
{
OUTPUT_UNIT *output_unit = (OUTPUT_UNIT *) malloc (sizeof (OUTPUT_UNIT));
memset (output_unit, 0, sizeof (OUTPUT_UNIT));
output_unit->unit_type = unit_type;
return output_unit;
}
void
add_to_output_unit_list (OUTPUT_UNIT_LIST *list, OUTPUT_UNIT *output_unit)
{
reallocate_output_unit_list (list);
list->list[list->number] = output_unit;
output_unit->index = list->number;
list->number++;
}
/* in addition to splitting, register the output_units list */
size_t
split_by_node (DOCUMENT *document)
{
const ELEMENT *root = document->tree;
size_t output_units_descriptor = new_output_units_descriptor (document);
OUTPUT_UNIT_LIST *output_units
= retrieve_output_units (document, output_units_descriptor);
OUTPUT_UNIT *current = new_output_unit (OU_unit);
ELEMENT_LIST *pending_parts = new_list ();
size_t i;
add_to_output_unit_list (output_units, current);
if (root->e.c->contents.number > 0)
document->modified_information |= F_DOCM_tree;
for (i = 0; i < root->e.c->contents.number; i++)
{
ELEMENT *content = root->e.c->contents.list[i];
enum command_id data_cmd = element_builtin_data_cmd (content);
if (data_cmd == CM_part)
{
add_to_element_list (pending_parts, content);
continue;
}
if (data_cmd == CM_node)
{
const char *normalized
= lookup_extra_string (content, AI_key_normalized);
if (normalized)
{
int status;
size_t node_number
= lookup_extra_integer (content,
AI_key_node_number, &status);
const NODE_RELATIONS *node_relations
= document->nodes_list.list[node_number -1];
if (!current->uc.unit_command)
current->uc.unit_command = content;
else
{
OUTPUT_UNIT *last
= output_units->list[output_units->number -1];
current = new_output_unit (OU_unit);
current->uc.unit_command = content;
current->tree_unit_directions[D_prev] = last;
last->tree_unit_directions[D_next] = current;
add_to_output_unit_list (output_units, current);
}
current->unit_node = node_relations;
if (node_relations->associated_section)
current->unit_section = node_relations->associated_section;
}
}
if (pending_parts->number > 0)
{
size_t j;
for (j = 0; j < pending_parts->number; j++)
{
ELEMENT *part = pending_parts->list[j];
add_to_element_list (&current->unit_contents, part);
part->e.c->associated_unit = current;
}
pending_parts->number = 0;
}
add_to_element_list (&current->unit_contents, content);
content->e.c->associated_unit = current;
}
if (pending_parts->number > 0)
{
size_t j;
for (j = 0; j < pending_parts->number; j++)
{
ELEMENT *part = pending_parts->list[j];
add_to_element_list (&current->unit_contents, part);
part->e.c->associated_unit = current;
}
pending_parts->number = 0;
}
destroy_list (pending_parts);
return output_units_descriptor;
}
/* in addition to splitting, register the output_units list */
size_t
split_by_section (DOCUMENT *document)
{
const ELEMENT *root = document->tree;
size_t output_units_descriptor = new_output_units_descriptor (document);
OUTPUT_UNIT_LIST *output_units
= retrieve_output_units (document, output_units_descriptor);
OUTPUT_UNIT *current = new_output_unit (OU_unit);
size_t i;
if (root->e.c->contents.number > 0)
document->modified_information |= F_DOCM_tree;
add_to_output_unit_list (output_units, current);
for (i = 0; i < root->e.c->contents.number; i++)
{
int status;
ELEMENT *content = root->e.c->contents.list[i];
enum command_id data_cmd = element_builtin_data_cmd (content);
unsigned long flags = builtin_command_data[data_cmd].flags;
const NODE_RELATIONS *node_relations = 0;
const SECTION_RELATIONS *new_section_relations = 0;
if (data_cmd == CM_node)
{
size_t node_number
= lookup_extra_integer (content,
AI_key_node_number, &status);
if (node_number)
{
node_relations
= document->nodes_list.list[node_number -1];
if (node_relations->associated_section)
new_section_relations = node_relations->associated_section;
}
}
else if (flags & CF_root)
{
size_t section_number
= lookup_extra_integer (content,
AI_key_section_number, &status);
const SECTION_RELATIONS *section_relations
= document->sections_list.list[section_number -1];
if (data_cmd == CM_part)
{
if (section_relations->part_associated_section)
new_section_relations
= section_relations->part_associated_section;
}
if (!new_section_relations)
new_section_relations = section_relations;
if (new_section_relations->associated_node)
node_relations = new_section_relations->associated_node;
}
if (new_section_relations)
{
if (!current->uc.unit_command)
{
current->uc.unit_command = new_section_relations->element;
current->unit_section = new_section_relations;
if (node_relations)
current->unit_node = node_relations;
}
else if (new_section_relations != current->unit_section)
{
OUTPUT_UNIT *last = output_units->list[output_units->number -1];
current = new_output_unit (OU_unit);
current->uc.unit_command = new_section_relations->element;
current->unit_section = new_section_relations;
if (node_relations)
current->unit_node = node_relations;
current->tree_unit_directions[D_prev] = last;
last->tree_unit_directions[D_next] = current;
add_to_output_unit_list (output_units, current);
}
}
add_to_element_list (&current->unit_contents, content);
content->e.c->associated_unit = current;
}
return output_units_descriptor;
}
int
unsplit (DOCUMENT *document)
{
const ELEMENT *root = document->tree;
int unsplit_needed = 0;
size_t i;
if (root->type != ET_document_root || root->e.c->contents.number <= 0)
return 0;
for (i = 0; i < root->e.c->contents.number; i++)
{
ELEMENT *content = root->e.c->contents.list[i];
if (content->e.c->associated_unit)
{
content->e.c->associated_unit = 0;
unsplit_needed = 1;
}
}
if (unsplit_needed)
document->modified_information |= F_DOCM_tree;
return unsplit_needed;
}
void
destroy_output_unit (OUTPUT_UNIT *output_unit)
{
/* need to destroy elements associated with special output units
as they are not in the document Texinfo tree */
if (output_unit->special_unit_variety)
destroy_element (output_unit->uc.special_unit_command);
else if (output_unit->unit_contents.number)
{
size_t i;
for (i = 0; i < output_unit->unit_contents.number; i++)
{
ELEMENT *element = output_unit->unit_contents.list[i];
if (element->e.c->associated_unit
&& element->e.c->associated_unit == output_unit)
element->e.c->associated_unit = 0;
}
}
free (output_unit->unit_contents.list);
free (output_unit->unit_filename);
if (output_unit->hv)
{
ERROR_MESSAGE_LIST *error_messages = 0;
unregister_perl_data (output_unit->hv);
error_messages = get_check_element_interpreter_refcount ();
if (error_messages)
{
/* for debugging */
/* For this check to be silent, the output units should have gone
through codes, release_output_units_list in Perl or similar in Perl C,
that removes the directions and other keys that lead to cycles,
and also through codes that remove other references to output units,
typically when releasing conversion data. This is only done if
TEST > 1 in the main program, but set_check_element_interpreter_refcount,
and therefore this code is also only called in that case.
*/
int hv_refcount = get_refcount (output_unit->hv);
/*
if (1)
*/
if (hv_refcount != 0)
{
const char *msg = "DEBUG Output unit refcounts %p: %d\n";
fprintf (stderr, msg,
output_unit->hv, hv_refcount);
message_list_document_warn (error_messages, 0, 0, msg,
output_unit->hv, hv_refcount);
}
}
}
free (output_unit);
}
void
free_output_unit_list (OUTPUT_UNIT_LIST *output_units_list)
{
size_t i;
for (i = 0; i < output_units_list->number; i++)
{
destroy_output_unit (output_units_list->list[i]);
}
free (output_units_list->list);
memset (output_units_list, 0, sizeof (OUTPUT_UNIT_LIST));
}
void
free_output_units_lists (OUTPUT_UNIT_LISTS *output_units_lists)
{
size_t i;
for (i = 0; i < output_units_lists->number; i++)
{
OUTPUT_UNIT_LIST *output_units_list
= &output_units_lists->output_units_lists[i];
free_output_unit_list (output_units_list);
}
free (output_units_lists->output_units_lists);
memset (output_units_lists, 0, sizeof (OUTPUT_UNIT_LISTS));
}
typedef struct LEVEL_SPLIT_STRING {
int level;
char *split;
} LEVEL_SPLIT_STRING;
static LEVEL_SPLIT_STRING split_level_table[3] = {
{-1, "node"},
{1, "chapter"},
{2, "section"}
};
/*
Associate top-level units with pages according to the splitting
specification. Set 'first_in_page' on each unit to the unit
that is the first in the output page.
*/
void
split_pages (OUTPUT_UNIT_LIST *output_units,
const NODE_RELATIONS_LIST *nodes_list, const char *split)
{
int split_level = -2;
int i;
size_t j;
OUTPUT_UNIT *current_first_in_page = 0;
if (!output_units || !output_units->number)
return;
if (!split || !strlen (split))
{
for (j = 0; j < output_units->number; j++)
{
OUTPUT_UNIT *output_unit = output_units->list[j];
output_unit->first_in_page = output_units->list[0];
}
return;
}
for (i = 0; i < 3; i++)
{
if (!strcmp (split, split_level_table[i].split))
{
split_level = split_level_table[i].level;
break;
}
}
if (split_level == -2)
{
fprintf (stderr, "Unknown split specification: %s\n", split);
split_level = -1; /* split by node */
}
for (j = 0; j < output_units->number; j++)
{
OUTPUT_UNIT *output_unit = output_units->list[j];
int level = -3;
if (output_unit->unit_section)
{
int status;
level = lookup_extra_integer (output_unit->unit_section->element,
AI_key_section_level, &status);
if (status < 0)
level = -3;
}
if ((split_level == -1) || (level != -3 && split_level >= level)
|| !current_first_in_page)
current_first_in_page = output_unit;
output_unit->first_in_page = current_first_in_page;
}
}
/* return to be freed by the caller */
char *
output_unit_texi (const OUTPUT_UNIT *output_unit)
{
const ELEMENT *unit_command;
if (!output_unit)
return strdup ("UNDEF OUTPUT UNIT");
if (output_unit->unit_type == OU_external_node_unit)
{
char *result;
char *reference_texi
= convert_contents_to_texinfo (output_unit->uc.unit_command);
xasprintf (&result, "_EXT_NODE: %s", reference_texi);
free (reference_texi);
return result;
}
else if (output_unit->unit_type == OU_special_unit)
{
char *result;
xasprintf (&result, "_SPECIAL_UNIT: %s",
output_unit->special_unit_variety);
return result;
}
unit_command = output_unit->uc.unit_command;
if (!unit_command)
{
/* happens when there are only nodes and sections are used as elements */
char *result;
xasprintf (&result, "No associated command (type %s)",
output_unit_type_names[output_unit->unit_type]);
return result;
}
return root_heading_command_to_texinfo (unit_command);
}
static OUTPUT_UNIT *
label_target_unit_element (const ELEMENT *label,
OUTPUT_UNIT_LIST *external_node_target_units)
{
const ELEMENT *manual_content
= lookup_extra_container (label, AI_key_manual_content);
if (manual_content)
{
/* setup an output_unit for consistency with regular output units */
OUTPUT_UNIT *external_node_unit
= new_output_unit (OU_external_node_unit);
external_node_unit->uc.unit_command = label;
add_to_output_unit_list (external_node_target_units,
external_node_unit);
return external_node_unit;
}
else if (label->e.c->cmd == CM_node)
return label->e.c->associated_unit;
else
/* case of a @float or an @*anchor, no target element defined at this stage */
return 0;
}
/* Used for debugging and possibly in test suite, but not generally
useful. Not documented in pod section as it should not, in
general, be used. */
char *
print_output_unit_directions (const OUTPUT_UNIT *output_unit)
{
TEXT result;
int i;
int with_direction = 0;
char *output_unit_text = output_unit_texi (output_unit);
text_init (&result);
text_printf (&result, "output unit: %s\n", output_unit_text);
free (output_unit_text);
for (i = 0; i < RUD_type_FirstInFileNodeBack+1; i++)
{
const OUTPUT_UNIT *direction = output_unit->directions[i];
if (direction)
{
char *direction_text = output_unit_texi (direction);
text_printf (&result, " %s: %s\n", relative_unit_direction_name[i],
direction_text);
free (direction_text);
with_direction++;
}
}
if (!with_direction)
text_append (&result, " NO DIRECTION\n");
return result.text;
}
static enum relative_unit_direction_type node_unit_directions[]
= {RUD_type_NodeNext,
RUD_type_NodePrev,
RUD_type_NodeUp};
static enum relative_unit_direction_type section_unit_directions[]
= {RUD_type_Next,
RUD_type_Prev,
RUD_type_Up};
/* Do output units directions (like in texi2html) and store them
in 'directions'.
The directions are only created if pointing to other output units.
*/
void
units_directions (const C_HASHMAP *identifiers_target,
const NODE_RELATIONS_LIST *nodes_list,
OUTPUT_UNIT_LIST *output_units,
OUTPUT_UNIT_LIST *external_node_target_units,
int print_debug)
{
size_t i;
ELEMENT_STACK up_list;
ELEMENT *node_top;
if (!output_units || !output_units->number)
return;
memset (&up_list, 0, sizeof (ELEMENT_STACK));
node_top = find_identifier_target (identifiers_target, "Top");
for (i = 0; i < output_units->number; i++)
{
OUTPUT_UNIT *output_unit = output_units->list[i];
const OUTPUT_UNIT **directions = output_unit->directions;
const ELEMENT * const *node_directions;
directions[RUD_type_This] = output_unit;
if (output_unit->tree_unit_directions[D_next]
&& output_unit->tree_unit_directions[D_next]->unit_type == OU_unit)
directions[RUD_type_Forward]
= output_unit->tree_unit_directions[D_next];
if (output_unit->tree_unit_directions[D_prev]
&& output_unit->tree_unit_directions[D_prev]->unit_type == OU_unit)
directions[RUD_type_Back]
= output_unit->tree_unit_directions[D_prev];
if (output_unit->unit_node)
{
const NODE_RELATIONS *node_relations = output_unit->unit_node;
const ELEMENT *node = node_relations->element;
const ELEMENT *menu_child
= first_menu_node (node_relations, identifiers_target);
enum directions d;
node_directions = node_relations->node_directions;
if (node_directions)
{
for (d = 0; d < directions_length; d++)
{
const ELEMENT *node_direction = node_directions[d];
if (node_direction)
directions[node_unit_directions[d]]
= label_target_unit_element (node_direction,
external_node_target_units);
}
}
/* Now do NodeForward which is something like the following node. */
if (menu_child)
{
directions[RUD_type_NodeForward]
= label_target_unit_element (menu_child,
external_node_target_units);
}
else
{
const ELEMENT *argument = node->e.c->contents.list[0];
int automatic_directions = (argument->e.c->contents.number <= 1);
const SECTION_RELATIONS_LIST *section_children = 0;
if (node_relations->associated_section)
{
const SECTION_RELATIONS *associated_relations
= node_relations->associated_section;
section_children = associated_relations->section_children;
}
if (automatic_directions
&& section_children && section_children->number > 0)
{
directions[RUD_type_NodeForward]
= section_children->list[0]->element->e.c->associated_unit;
}
else if (node_directions
&& node_directions[D_next])
directions[RUD_type_NodeForward]
= label_target_unit_element (
node_directions[D_next],
external_node_target_units);
else if (node_directions && node_directions[D_up])
{
const ELEMENT *up = node_directions[D_up];
push_stack_element (&up_list, node);
while (1)
{
size_t i;
int status;
size_t up_node_number;
const NODE_RELATIONS *up_node_relations = 0;
int in_up = 0;
for (i = 0; i < up_list.top; i++)
if (up == up_list.stack[i])
{
in_up = 1;
break;
}
if (in_up || (node_top && node_top == up))
break;
if (up->e.c->cmd == CM_node)
{
up_node_number
= lookup_extra_integer (up,
AI_key_node_number, &status);
up_node_relations
= nodes_list->list[up_node_number -1];
}
if (up_node_relations
&& up_node_relations->node_directions
&& up_node_relations->node_directions[D_next])
{
directions[RUD_type_NodeForward]
= label_target_unit_element (
up_node_relations->node_directions[D_next],
external_node_target_units);
break;
}
push_stack_element (&up_list, up);
if (up_node_relations
&& up_node_relations->node_directions
&& up_node_relations->node_directions[D_up])
up = up_node_relations->node_directions[D_up];
else
break;
}
up_list.top = 0;
}
}
if (directions[RUD_type_NodeForward]
&& directions[RUD_type_NodeForward]->unit_type == OU_unit
&& !directions[RUD_type_NodeForward]
->directions[RUD_type_NodeBack])
{
/* to modify the NodeForward element direction, we remove
the const by casting */
OUTPUT_UNIT *forward_unit
= (OUTPUT_UNIT *)directions[RUD_type_NodeForward];
forward_unit->directions[RUD_type_NodeBack] = output_unit;
}
}
if (! output_unit->unit_section)
{
/* If there is no associated section, find the previous element section.
Use the FastForward of this element.
Use it as FastBack if the section is top level, or use the FastBack. */
OUTPUT_UNIT *section_output_unit = 0;
OUTPUT_UNIT *current_unit = output_unit;
while (current_unit->tree_unit_directions[D_prev])
{
current_unit = current_unit->tree_unit_directions[D_prev];
if (current_unit->unit_section)
{
section_output_unit = current_unit;
break;
}
}
if (section_output_unit)
{
const ELEMENT *section = current_unit->unit_section->element;
int section_level;
int status;
if (section_output_unit->directions[RUD_type_FastForward])
directions[RUD_type_FastForward]
= section_output_unit->directions[RUD_type_FastForward];
section_level = lookup_extra_integer (section,
AI_key_section_level, &status);
/* status should always be ok */
if (status >= 0 && section_level <= 1)
directions[RUD_type_FastBack] = section_output_unit;
else if (section_output_unit->directions[RUD_type_FastBack])
directions[RUD_type_FastBack]
= section_output_unit->directions[RUD_type_FastBack];
}
}
else
{
int status;
const SECTION_RELATIONS *section_relations
= output_unit->unit_section;
const ELEMENT *section = section_relations->element;
enum directions d;
const SECTION_RELATIONS * const *section_directions
= section_relations->section_directions;
const SECTION_RELATIONS_LIST *up_section_children;
int up_section_level;
const SECTION_RELATIONS *up_relations
= section_relations;
const ELEMENT *up = section;
if (section_directions)
{
for (d = 0; d < directions_length; d++)
{
/* in most cases $section_directions
->{$direction->[1]}
->{'associated_unit'} is defined
but it may not be the case for the up of @top.
The section may be its own up in cases like
@part part
@chapter chapter
in that cas the direction is not set up */
if (section_directions[d]
&& section_directions[d]->element->e.c->associated_unit
&& (!section->e.c->associated_unit
|| section->e.c->associated_unit
!= section_directions[d]->element->e.c->associated_unit))
directions[section_unit_directions[d]]
= section_directions[d]->element->e.c->associated_unit;
}
}
/* fastforward is the next element on same level than the upper parent
element. */
while (1)
{
up_section_level
= lookup_extra_integer (up, AI_key_section_level, &status);
if (status >= 0 && up_section_level > 1
&& up_relations->section_directions
&& up_relations->section_directions[D_up])
{
up_relations = up_relations->section_directions[D_up];
up = up_relations->element;
}
else
break;
}
up_section_children = up_relations->section_children;
if (status >= 0 && up_section_level < 1
&& up->e.c->cmd == CM_top && up_section_children
&& up_section_children->number > 0)
{
directions[RUD_type_FastForward]
= up_section_children->list[0]->element->e.c->associated_unit;
}
else
{
if (up_relations->toplevel_directions
&& up_relations->toplevel_directions[D_next])
directions[RUD_type_FastForward]
= up_relations->toplevel_directions[D_next]
->element->e.c->associated_unit;
else
{
if (up_relations->section_directions
&& up_relations->section_directions[D_next])
directions[RUD_type_FastForward]
= up_relations->section_directions[D_next]
->element->e.c->associated_unit;
}
}
/* if the element isn't at the highest level, fastback is the
highest parent element */
if (up && up != section && up->e.c->associated_unit)
directions[RUD_type_FastBack] = up->e.c->associated_unit;
else
{
int status;
int section_level
= lookup_extra_integer (section, AI_key_section_level, &status);
if (status >= 0 && section_level <= 1
&& directions[RUD_type_FastForward])
{
/* the element is a top level element, we adjust the next
toplevel element fastback */
/* to modify the FastForward element direction, we remove
the const by casting */
OUTPUT_UNIT *fastf_unit
= (OUTPUT_UNIT *)directions[RUD_type_FastForward];
fastf_unit->directions[RUD_type_FastBack] = output_unit;
}
}
}
}
free (up_list.stack);
if (print_debug > 0)
{
size_t i;
for (i = 0; i < output_units->number; i++)
{
const OUTPUT_UNIT *output_unit = output_units->list[i];
char *element_directions
= print_output_unit_directions (output_unit);
fprintf (stderr, "Directions: %s\n", element_directions);
free (element_directions);
}
}
}
void
units_file_directions (OUTPUT_UNIT_LIST *output_units)
{
size_t i;
char *current_filename = 0;
OUTPUT_UNIT *first_unit_in_file = 0;
if (!output_units || !output_units->number)
return;
for (i = 0; i < output_units->number; i++)
{
OUTPUT_UNIT *output_unit = output_units->list[i];
if (output_unit->unit_filename)
{
char *filename = output_unit->unit_filename;
OUTPUT_UNIT *current_output_unit = output_unit;
if (!current_filename || strcmp (filename, current_filename))
{
first_unit_in_file = output_unit;
current_filename = filename;
}
while (1)
{
if (current_output_unit->tree_unit_directions[D_prev])
{
current_output_unit
= current_output_unit->tree_unit_directions[D_prev];
if (current_output_unit->unit_filename)
{
if (strcmp (current_output_unit->unit_filename, filename))
{
output_unit->directions[RUD_type_PrevFile]
= current_output_unit;
break;
}
}
else
break;
}
else
break;
}
current_output_unit = output_unit;
while (1)
{
if (current_output_unit->tree_unit_directions[D_next])
{
current_output_unit
= current_output_unit->tree_unit_directions[D_next];
if (current_output_unit->unit_filename)
{
if (strcmp (current_output_unit->unit_filename, filename))
{
output_unit->directions[RUD_type_NextFile]
= current_output_unit;
break;
}
}
else
break;
}
else
break;
}
}
/* set the directions of the first elements in file, to
be used in footers for example */
if (first_unit_in_file)
{
memcpy (&output_unit->directions[RUD_type_FirstInFileThis],
&first_unit_in_file->directions[RUD_type_This],
(RUD_type_NodeBack - RUD_type_This +1) * sizeof (OUTPUT_UNIT *));
}
}
}
/* called for tests. (From Perl only as tests are in Perl) */
void
do_units_directions_pages (DOCUMENT *document,
enum units_split_type units_split,
const char *split_pages_string, int debug)
{
size_t output_units_descriptor = 0;
OUTPUT_UNIT_LIST *output_units = 0;
if (units_split == UST_node)
output_units_descriptor = split_by_node (document);
else if (units_split == UST_section)
output_units_descriptor = split_by_section (document);
if (output_units_descriptor)
{
OUTPUT_UNIT_LIST *external_node_target_units;
size_t external_nodes_units_descriptor
= new_output_units_descriptor (document);
/* Note that we may overwrite previous descriptors. Since this code
is only used from tests and called once per test max, this should
not be an issue */
document->output_units_descriptors[OUDT_units] = output_units_descriptor;
document->output_units_descriptors[OUDT_external_nodes_units]
= external_nodes_units_descriptor;
output_units = retrieve_output_units (document, output_units_descriptor);
external_node_target_units = retrieve_output_units (document,
external_nodes_units_descriptor);
units_directions (&document->identifiers_target,
&document->nodes_list,
output_units,
external_node_target_units, debug);
if (split_pages_string)
split_pages (output_units, &document->nodes_list, split_pages_string);
}
}
static char *
output_unit_name_string (const OUTPUT_UNIT *output_unit)
{
char *output_unit_name;
if (output_unit->unit_type == OU_unit)
xasprintf (&output_unit_name, "[U%zu]", output_unit->index);
else if (output_unit->unit_type == OU_external_node_unit)
output_unit_name
= convert_contents_to_texinfo (output_unit->uc.unit_command);
else if (output_unit->unit_type == OU_special_unit)
xasprintf (&output_unit_name, "[S:%s]",
output_unit->special_unit_variety);
else /* should never happen */
output_unit_name = strdup ("");
return output_unit_name;
}
static void
add_ou_direction (STRING_LIST *ou_directions,
const OUTPUT_UNIT *output_unit,
const char *direction_name)
{
if (output_unit)
{
char *direction_text = output_unit_name_string (output_unit);
char *ou_direction_text;
xasprintf (&ou_direction_text, "%s->%s",
direction_name, direction_text);
free (direction_text);
add_string (ou_direction_text, ou_directions);
free (ou_direction_text);
}
}
/* the caller should have prepared the Texinfo tree elements for detailed
printing, namely by calling functions to number elements. The argument
CURRENT_NR should consistently be the numbering function return value
*/
static uintptr_t
print_output_units_details (OUTPUT_UNIT_LIST *output_units,
uintptr_t current_nr, TEXT *result,
const char *fname_encoding, int use_filename)
{
TEXT directions_text;
/* those not in the directions field */
STRING_LIST ou_directions;
size_t i;
text_init (&directions_text);
memset (&ou_directions, 0, sizeof (STRING_LIST));
for (i = 0; i < output_units->number; i++)
{
size_t j;
OUTPUT_UNIT *output_unit = output_units->list[i];
text_printf (result, "U%zu %s", output_unit->index,
output_unit_type_names[output_unit->unit_type]);
if (output_unit->special_unit_variety)
text_printf (result, "-%s", output_unit->special_unit_variety);
if (output_unit->unit_type != OU_special_unit
&& output_unit->uc.unit_command)
{
char *additional_info = 0;
char *root_command_texi
= root_command_element_string (output_unit->uc.unit_command);
/* determine the kind of command by comparing with
unit node or unit section. Also show the texinfo code
of the node or section relations not associated to unit_command.
*/
if (output_unit->unit_node)
{
if (output_unit->unit_node->element
== output_unit->uc.unit_command)
{
text_append_n (result, "{N:", 3);
if (root_command_texi)
text_append (result, root_command_texi);
}
else
{
char *node_texi
= root_command_element_string (
output_unit->unit_node->element);
if (node_texi)
{
xasprintf (&additional_info, "{n:%s}", node_texi);
free (node_texi);
}
else
xasprintf (&additional_info, "{n:}");
}
}
if (output_unit->unit_section)
{
if (output_unit->unit_section->element
== output_unit->uc.unit_command)
{
text_append_n (result, "{S:", 3);
if (root_command_texi)
text_append (result, root_command_texi);
}
else
{
char *section_texi
= root_command_element_string (
output_unit->unit_section->element);
if (section_texi)
{
xasprintf (&additional_info, "{s:%s}", section_texi);
free (section_texi);
}
else
xasprintf (&additional_info, "{s:}");
}
}
text_append_n (result, "}", 1);
if (additional_info)
{
text_append (result, additional_info);
free (additional_info);
}
free (root_command_texi);
}
if (output_unit->unit_filename)
{
text_append_n (result, " ", 1);
if (use_filename)
{
char *file_name_and_directory[2];
parse_file_path (output_unit->unit_filename,
file_name_and_directory);
text_append (result, file_name_and_directory[0]);
free (file_name_and_directory[0]);
free (file_name_and_directory[1]);
}
else
text_append (result, output_unit->unit_filename);
}
text_append_n (result, "\n", 1);
if (output_unit->tree_unit_directions[D_prev]
|| output_unit->tree_unit_directions[D_next])
{
size_t directions_nr = sizeof (output_unit->tree_unit_directions)
/ sizeof (output_unit->tree_unit_directions[0]);
for (j = 0; j < directions_nr; j++)
{
const OUTPUT_UNIT *direction
= output_unit->tree_unit_directions[j];
add_ou_direction (&ou_directions, direction,
direction_names[j]);
}
}
if (output_unit->first_in_page)
{
add_ou_direction (&ou_directions, output_unit->first_in_page,
"page");
}
if (output_unit->associated_document_unit)
{
add_ou_direction (&ou_directions,
output_unit->associated_document_unit, "doc unit");
}
if (ou_directions.number)
{
char *joined_string = join_strings_list (&ou_directions);
text_printf (result, "unit_directions:D[%s]\n",
joined_string);
free (joined_string);
}
clear_strings_list (&ou_directions);
/* TODO add global directions, included added and special units
directions? */
for (j = 0; j < RUD_type_FirstInFileNodeBack+1; j++)
{
const OUTPUT_UNIT *direction = output_unit->directions[j];
if (direction)
{
char *direction_text = output_unit_name_string (direction);
text_printf (&directions_text, "%s: %s\n",
relative_unit_direction_name[j],
direction_text);
free (direction_text);
}
}
if (directions_text.end > 0)
{
text_append (result, "UNIT_DIRECTIONS\n");
text_append (result, directions_text.text);
text_reset (&directions_text);
}
if (output_unit->unit_contents.number)
{
for (j = 0; j < output_unit->unit_contents.number; j++)
{
ELEMENT *element = output_unit->unit_contents.list[j];
current_nr = print_tree_details (element, 1, 0, current_nr,
result, fname_encoding,
use_filename);
}
}
}
free_strings_list (&ou_directions);
free (directions_text.text);
return current_nr;
}
/* for debugging */
char *
output_units_print_details (OUTPUT_UNIT_LIST *output_units,
const char *fname_encoding, int use_filename)
{
TEXT result;
uintptr_t current_nr = 0;
text_init (&result);
text_append (&result, "");
print_output_units_details (output_units, current_nr, &result,
fname_encoding, use_filename);
return result.text;
}
char *
print_output_units_tree_details (OUTPUT_UNIT_LIST *output_units, ELEMENT *tree,
const char *fname_encoding, int use_filename)
{
uintptr_t current_nr = 0;
TEXT result;
text_init (&result);
text_append (&result, "");
/*
current_nr = set_element_tree_numbers (tree, 0);
*/
current_nr = print_output_units_details (output_units,
current_nr, &result, fname_encoding, use_filename);
/*
remove_element_tree_numbers (tree);
*/
return result.text;
}