/* node.c -- nodes for Texinfo.
$Id: node.c,v 1.27 2004/12/20 23:56:07 karl Exp $
Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004 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 2, 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, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
#include "system.h"
#include "cmds.h"
#include "files.h"
#include "float.h"
#include "footnote.h"
#include "macro.h"
#include "makeinfo.h"
#include "node.h"
#include "html.h"
#include "sectioning.h"
#include "insertion.h"
#include "xml.h"
/* See comments in node.h. */
NODE_REF *node_references = NULL;
NODE_REF *node_node_references = NULL;
TAG_ENTRY *tag_table = NULL;
int node_number = -1;
int node_order = 0;
int current_section = 0;
int outstanding_node = 0;
/* Adding nodes, and making tags. */
/* Start a new tag table. */
void
init_tag_table (void)
{
while (tag_table)
{
TAG_ENTRY *temp = tag_table;
free (temp->node);
free (temp->prev);
free (temp->next);
free (temp->up);
tag_table = tag_table->next_ent;
free (temp);
}
}
/* Write out the contents of the existing tag table.
INDIRECT_P says how to format the output (it depends on whether the
table is direct or indirect). */
static void
write_tag_table_internal (int indirect_p)
{
TAG_ENTRY *node;
int old_indent = no_indent;
if (xml)
{
flush_output ();
return;
}
no_indent = 1;
filling_enabled = 0;
must_start_paragraph = 0;
close_paragraph ();
if (!indirect_p)
{
no_indent = 1;
insert ('\n');
}
add_word_args ("\037\nTag Table:\n%s", indirect_p ? "(Indirect)\n" : "");
/* Do not collapse -- to -, etc., in node names. */
in_fixed_width_font++;
for (node = tag_table; node; node = node->next_ent)
{
if (node->flags & TAG_FLAG_ANCHOR)
{ /* This reference is to an anchor. */
execute_string ("Ref: %s", node->node);
}
else
{ /* This reference is to a node. */
execute_string ("Node: %s", node->node);
}
add_word_args ("\177%d\n", node->position);
}
add_word ("\037\nEnd Tag Table\n");
/* Do not collapse -- to -, etc., in node names. */
in_fixed_width_font--;
flush_output ();
no_indent = old_indent;
}
void
write_tag_table (char *filename)
{
output_stream = fopen (filename, "a");
if (!output_stream)
{
fs_error (filename);
return;
}
write_tag_table_internal (0); /* Not indirect. */
if (fclose (output_stream) != 0)
fs_error (filename);
}
static void
write_tag_table_indirect (void)
{
write_tag_table_internal (1);
}
/* Convert "top" and friends into "Top". */
static void
normalize_node_name (char *string)
{
if (strcasecmp (string, "Top") == 0)
strcpy (string, "Top");
}
static char *
get_node_token (int expand)
{
char *string;
get_until_in_line (expand, ",", &string);
if (curchar () == ',')
input_text_offset++;
fix_whitespace (string);
/* Force all versions of "top" to be "Top". */
normalize_node_name (string);
return string;
}
/* Expand any macros and other directives in a node name, and
return the expanded name as an malloc'ed string. */
char *
expand_node_name (char *node)
{
char *result = node;
if (node)
{
/* Don't expand --, `` etc., in case somebody will want
to print the result. */
in_fixed_width_font++;
result = expansion (node, 0);
in_fixed_width_font--;
fix_whitespace (result);
normalize_node_name (result);
}
return result;
}
/* Look up NAME in the tag table, and return the associated
tag_entry. If the node is not in the table return NULL. */
TAG_ENTRY *
find_node (char *name)
{
TAG_ENTRY *tag = tag_table;
char *expanded_name;
char n1 = name[0];
while (tag)
{
if (tag->node[0] == n1 && strcmp (tag->node, name) == 0)
return tag;
tag = tag->next_ent;
}
if (!expensive_validation)
return NULL;
/* Try harder. Maybe TAG_TABLE has the expanded NAME, or maybe NAME
is expanded while TAG_TABLE has its unexpanded form. This may
slow down the search, but if they want this feature, let them
pay! If they want it fast, they should write every node name
consistently (either always expanded or always unexpaned). */
expanded_name = expand_node_name (name);
for (tag = tag_table; tag; tag = tag->next_ent)
{
if (STREQ (tag->node, expanded_name))
break;
/* If the tag name doesn't have the command prefix, there's no
chance it could expand into anything but itself. */
if (strchr (tag->node, COMMAND_PREFIX))
{
char *expanded_node = expand_node_name (tag->node);
if (STREQ (expanded_node, expanded_name))
{
free (expanded_node);
break;
}
free (expanded_node);
}
}
free (expanded_name);
return tag;
}
/* Look in the tag table for a node whose file name is FNAME, and
return the associated tag_entry. If there's no such node in the
table, return NULL. */
static TAG_ENTRY *
find_node_by_fname (char *fname)
{
TAG_ENTRY *tag = tag_table;
while (tag)
{
if (tag->html_fname && FILENAME_CMP (tag->html_fname, fname) == 0)
return tag;
tag = tag->next_ent;
}
return tag;
}
/* Remember next, prev, etc. references in a @node command, where we
don't care about most of the entries. */
static void
remember_node_node_reference (char *node)
{
NODE_REF *temp = xmalloc (sizeof (NODE_REF));
int number;
if (!node) return;
temp->next = node_node_references;
temp->node = xstrdup (node);
temp->type = followed_reference;
number = number_of_node (node);
if (number)
temp->number = number; /* Already assigned. */
else
{
node_number++;
temp->number = node_number;
}
node_node_references = temp;
}
/* Remember NODE and associates. */
static void
remember_node (char *node, char *prev, char *next, char *up,
int position, int line_no, char *fname, int flags)
{
/* Check for existence of this tag already. */
if (validating)
{
TAG_ENTRY *tag = find_node (node);
if (tag)
{
line_error (_("Node `%s' previously defined at line %d"),
node, tag->line_no);
return;
}
}
if (!(flags & TAG_FLAG_ANCHOR))
{
/* Make this the current node. */
current_node = node;
}
/* Add it to the list. */
{
int number = number_of_node (node);
TAG_ENTRY *new = xmalloc (sizeof (TAG_ENTRY));
new->node = node;
new->prev = prev;
new->next = next;
new->up = up;
new->position = position;
new->line_no = line_no;
new->filename = node_filename;
new->touched = 0;
new->flags = flags;
if (number)
new->number = number; /* Already assigned. */
else
{
node_number++;
new->number = node_number;
}
if (fname)
new->html_fname = fname;
else
/* This happens for Top node under split-HTML, for example. */
new->html_fname
= normalize_filename (filename_part (current_output_filename));
new->next_ent = tag_table;
/* Increment the order counter, and save it. */
node_order++;
new->order = node_order;
tag_table = new;
}
if (html)
{ /* Note the references to the next etc. nodes too. */
remember_node_node_reference (next);
remember_node_node_reference (prev);
remember_node_node_reference (up);
}
}
/* Remember this node name for later validation use. This is used to
remember menu references while reading the input file. After the
output file has been written, if validation is on, then we use the
contents of `node_references' as a list of nodes to validate. */
void
remember_node_reference (char *node, int line, enum reftype type)
{
NODE_REF *temp = xmalloc (sizeof (NODE_REF));
int number = number_of_node (node);
temp->next = node_references;
temp->node = xstrdup (node);
temp->line_no = line;
temp->section = current_section;
temp->type = type;
temp->containing_node = xstrdup (current_node ? current_node : "");
temp->filename = node_filename;
if (number)
temp->number = number; /* Already assigned. */
else
{
node_number++;
temp->number = node_number;
}
node_references = temp;
}
static void
isolate_nodename (char *nodename)
{
int i, c;
int paren_seen, paren;
if (!nodename)
return;
canon_white (nodename);
paren_seen = paren = i = 0;
if (*nodename == '.' || !*nodename)
{
*nodename = 0;
return;
}
if (*nodename == '(')
{
paren++;
paren_seen++;
i++;
}
for (; (c = nodename[i]); i++)
{
if (paren)
{
if (c == '(')
paren++;
else if (c == ')')
paren--;
continue;
}
/* If the character following the close paren is a space, then this
node has no more characters associated with it. */
if (c == '\t' ||
c == '\n' ||
c == ',' ||
((paren_seen && nodename[i - 1] == ')') &&
(c == ' ' || c == '.')) ||
(c == '.' &&
((!nodename[i + 1] ||
(cr_or_whitespace (nodename[i + 1])) ||
(nodename[i + 1] == ')')))))
break;
}
nodename[i] = 0;
}
/* This function gets called at the start of every line while inside a
menu. It checks to see if the line starts with "* ", and if so and
REMEMBER_REF is nonzero, remembers the node reference as type
REF_TYPE that this menu refers to. input_text_offset is at the \n
just before the menu line. If REMEMBER_REF is zero, REF_TYPE is unused. */
#define MENU_STARTER "* "
char *
glean_node_from_menu (int remember_ref, enum reftype ref_type)
{
int i, orig_offset = input_text_offset;
char *nodename;
char *line, *expanded_line;
char *old_input = input_text;
int old_size = input_text_length;
if (strncmp (&input_text[input_text_offset + 1],
MENU_STARTER,
strlen (MENU_STARTER)) != 0)
return NULL;
else
input_text_offset += strlen (MENU_STARTER) + 1;
/* The menu entry might include macro calls, so we need to expand them. */
get_until ("\n", &line);
only_macro_expansion++; /* only expand macros in menu entries */
expanded_line = expansion (line, 0);
only_macro_expansion--;
free (line);
input_text = expanded_line;
input_text_offset = 0;
input_text_length = strlen (expanded_line);
get_until_in_line (0, ":", &nodename);
if (curchar () == ':')
input_text_offset++;
if (curchar () != ':')
{
free (nodename);
get_until_in_line (0, "\n", &nodename);
isolate_nodename (nodename);
}
input_text = old_input;
input_text_offset = orig_offset;
input_text_length = old_size;
free (expanded_line);
fix_whitespace (nodename);
normalize_node_name (nodename);
i = strlen (nodename);
if (i && nodename[i - 1] == ':')
nodename[i - 1] = 0;
if (remember_ref)
remember_node_reference (nodename, line_number, ref_type);
return nodename;
}
/* Set the name of the current output file. */
void
set_current_output_filename (const char *fname)
{
if (current_output_filename)
free (current_output_filename);
current_output_filename = xstrdup (fname);
}
/* Output the constructs for NODE. We output both
the new-style conversion and the old-style, if they are different.
See comments at `add_escaped_anchor_name' in html.c. */
static void
add_html_names (char *node)
{
char *tem = expand_node_name (node);
char *otem = xstrdup (tem);
/* Determine if the old and new schemes come up with different names;
only output the old scheme if that is so. We don't want to output
the same name twice. */
canon_white (otem);
{
char *optr = otem;
int need_old = 0;
for (; *optr; optr++)
{
if (!cr_or_whitespace (*optr) && !URL_SAFE_CHAR (*optr))
{
need_old = 1;
break;
}
}
if (need_old)
{
add_word ("\n");
}
free (otem);
}
/* Always output the new scheme. */
canon_white (tem);
add_word ("\n");
free (tem);
}
/* The order is: nodename, nextnode, prevnode, upnode.
If all of the NEXT, PREV, and UP fields are empty, they are defaulted.
You must follow a node command which has those fields defaulted
with a sectioning command (e.g., @chapter) giving the "level" of that node.
It is an error not to do so.
The defaults come from the menu in this node's parent. */
void
cm_node (void)
{
static long epilogue_len = 0L;
char *node, *prev, *next, *up;
int new_node_pos, defaulting, this_section;
int no_warn = 0;
char *fname_for_this_node = NULL;
char *tem;
TAG_ENTRY *tag = NULL;
if (strcmp (command, "nwnode") == 0)
no_warn = TAG_FLAG_NO_WARN;
/* Get rid of unmatched brace arguments from previous commands. */
discard_braces ();
/* There also might be insertions left lying around that haven't been
ended yet. Do that also. */
discard_insertions (1);
if (!html && !already_outputting_pending_notes)
{
close_paragraph ();
output_pending_notes ();
}
new_node_pos = output_position;
if (macro_expansion_output_stream && !executing_string)
append_to_expansion_output (input_text_offset + 1);
/* Do not collapse -- to -, etc., in node names. */
in_fixed_width_font++;
/* While expanding the @node line, leave any non-macros
intact, so that the macro-expanded output includes them. */
only_macro_expansion++;
node = get_node_token (1);
only_macro_expansion--;
next = get_node_token (0);
prev = get_node_token (0);
up = get_node_token (0);
if (html && splitting
/* If there is a Top node, it always goes into index.html. So
don't start a new HTML file for Top. */
&& (top_node_seen || strcasecmp (node, "Top") != 0))
{
/* We test *node here so that @node without a valid name won't
start a new file name with a bogus name such as ".html".
This could happen if we run under "--force", where we cannot
simply bail out. Continuing to use the same file sounds like
the best we can do in such cases. */
if (current_output_filename && output_stream && *node)
{
char *fname_for_prev_node;
if (current_node)
{
/* NOTE: current_node at this point still holds the name
of the previous node. */
tem = expand_node_name (current_node);
fname_for_prev_node = nodename_to_filename (tem);
free (tem);
}
else /* could happen if their top node isn't named "Top" */
fname_for_prev_node = filename_part (current_output_filename);
tem = expand_node_name (node);
fname_for_this_node = nodename_to_filename (tem);
free (tem);
/* Don't close current output file, if next output file is
to have the same name. This may happen at top level, or
if two nodes produce the same file name under --split. */
if (FILENAME_CMP (fname_for_this_node, fname_for_prev_node) != 0)
{
long pos1 = 0;
/* End the current split output file. */
close_paragraph ();
output_pending_notes ();
start_paragraph ();
/* Compute the length of the HTML file's epilogue. We
cannot know the value until run time, due to the
text/binary nuisance on DOS/Windows platforms, where
2 `\r' characters could be added to the epilogue when
it is written in text mode. */
if (epilogue_len == 0)
{
flush_output ();
pos1 = ftell (output_stream);
}
add_word ("