code:
/* makeinfo.h -- declarations for Makeinfo.
   $Id: makeinfo.h,v 1.32 2011-04-06 21:21:33 gray Exp $

   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
   2005, 2006, 2007, 2008 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/>.

   Written by Brian Fox (bfox@ai.mit.edu). */

#ifndef MAKEINFO_H
#define MAKEINFO_H

#ifdef COMPILING_MAKEINFO
#  define DECLARE(type,var,init) type var = init
#else
#  define DECLARE(type,var,init) extern type var
#endif

/* Hardcoded per GNU standards, not dependent on argv[0].  */
DECLARE (char *, progname, "makeinfo");

/* Nonzero means a string is in execution, as opposed to a file. */
DECLARE (int, executing_string, 0);

/* Nonzero means to inhibit writing macro expansions to the output
   stream, because it has already been written. */
DECLARE (int, me_inhibit_expansion, 0);

/* Current output stream. */
DECLARE (FILE *, output_stream, NULL);

DECLARE (char *, pretty_output_filename, NULL);

/* Current output file name.  */
DECLARE (char *, current_output_filename, NULL);

/* Output paragraph buffer and its length. */
#define INITIAL_PARAGRAPH_BUFFER_LEN 5000
DECLARE (unsigned char *, output_paragraph, NULL);
DECLARE (int, paragraph_buffer_len, INITIAL_PARAGRAPH_BUFFER_LEN);

/* Offset into OUTPUT_PARAGRAPH. */
DECLARE (int, output_paragraph_offset, 0);

/* Position in the output file. */
DECLARE (int, output_position, 0);

/* Number of lines in the output.  */
DECLARE (int, output_line_number, 1);
DECLARE (int, node_line_number, 0);

/* The offset into OUTPUT_PARAGRAPH where we have a meta character
   produced by a markup such as @code or @dfn.  */
DECLARE (int, meta_char_pos, -1);

/* Nonzero means output_paragraph contains text. */
DECLARE (int, paragraph_is_open, 0);

/* Nonzero means that `start_paragraph' MUST be called before we pay
   any attention to `close_paragraph' calls. */
DECLARE (int, must_start_paragraph, 0);

/* Nonzero if we have output the topmatter of the output file.  */
DECLARE (int, output_head_p, 0);

/* Nonzero means that we have seen "@top" once already. */
DECLARE (int, top_node_seen, 0);

/* Nonzero means that we have seen a non-"@top" node already. */
DECLARE (int, non_top_node_seen, 0);

/* Nonzero indicates that indentation is temporarily turned off. */
DECLARE (int, no_indent, 1);

/* The amount of indentation to apply at the start of each line. */
DECLARE (int, current_indent, 0);

/* Nonzero means that we suppress the indentation of the first paragraph
   following any section heading.  */
DECLARE (int, do_first_par_indent, 0);

/* Amount by which @example indentation increases/decreases. */
DECLARE (int, example_indentation_increment, 5);

/* Amount by which @table, @defun, etc. indentation increases/decreases.  */
DECLARE (int, default_indentation_increment, 5);

/* Amount by which xml indentation increases/decreases.
   Zero means unnecessary whitespace is compressed.  */
DECLARE (int, xml_indentation_increment, 2);

/* Nonzero indicates that filling a line also indents the new line. */
DECLARE (int, indented_fill, 0);

/* Nonzero means forcing output text to be flushright. */
DECLARE (int, force_flush_right, 0);

/* The column at which long lines are broken. */
DECLARE (int, fill_column, 72);

/* Nonzero means we're doing one space after sentences (@frenchspacing).  */
DECLARE (int, french_spacing, 0);

/* Nonzero means that words are not to be split, even in long lines.  This
   gets changed for cm_w (). */
DECLARE (int, non_splitting_words, 0);

/* Nonzero means that we are currently hacking the insides of an
   insertion which would use a fixed width font. */
DECLARE (int, in_fixed_width_font, 0);

/* Nonzero if we are currently processing a multitable command */
DECLARE (int, multitable_active, 0);

/* Nonzero means that we're generating HTML. (--html) */
DECLARE (int, html, 0);

/* Nonzero means that we're generating XML. (--xml) */
DECLARE (int, xml, 0);

/* Nonzero means that we're generating DocBook. (--docbook) */
DECLARE (int, docbook, 0);

/* Nonzero means 8-bit output for Info and plain text, according to
   @documentencoding.  (--enable-encoding) */
DECLARE (int, enable_encoding, 1);

/* Nonzero means escape characters in HTML output. */
DECLARE (int, escape_html, 1);

/* Access key number for next menu entry to be generated (1 to 9, or 10 to
   mean no access key)  */
DECLARE (int, next_menu_item_number, 1);

/* Nonzero means that the use of paragraph_start_indent is inhibited.
   @example uses this to line up the left columns of the example text.
   A negative value for this variable is incremented each time it is used.
   @noindent uses this to inhibit indentation for a single paragraph.  */
DECLARE (int, inhibit_paragraph_indentation, 0);

/* Nonzero indicates that filling will take place on long lines. */
DECLARE (int, filling_enabled, 1);

/* The current node's node name. */
DECLARE (char *, current_node, NULL);

/* Command name in the process of being hacked. */
DECLARE (char *, command, NULL);

/* Nonzero if we have seen an @titlepage command.  */
DECLARE (int, titlepage_cmd_present, 0);

/* @copying ... @end copying. */
DECLARE (char *, copying_text, NULL);

/* @documentdescription ... @end documentdescription. */
DECLARE (const char *, document_description, NULL);

/* Nonzero if the last character inserted has the syntax class of NEWLINE. */
DECLARE (int, last_char_was_newline, 1);

/* The current input file state. */
DECLARE (char *, input_filename, (char *)NULL);
DECLARE (char *, input_text, (char *)NULL);
DECLARE (int, input_text_length, 0);
DECLARE (int, input_text_offset, 0);
DECLARE (int, line_number, 0);
DECLARE (char *, toplevel_output_filename, NULL);
#define curchar() input_text[input_text_offset]

/* A colon separated list of directories to search for files included
   with @include.  This can be controlled with the `-I' option to makeinfo. */
DECLARE (char *, include_files_path, NULL);

/* The filename of the current input file.  This is never freed. */
DECLARE (char *, node_filename, NULL);

/* Name of CSS file to include, if any.  (--css-include).  */
DECLARE (char *, css_include, NULL);

/* Name of CSS to reference, if any.  (--css-ref).  */
DECLARE (char *, css_ref, NULL);

/* Transliterate file names into ASCII */
DECLARE (int, transliterate_file_names, 0);

/* Nonzero means do not output "Node: Foo" for node separations, that
   is, generate plain text.  (--no-headers) */
DECLARE (int, no_headers, 0);

/* Nonzero means that we process @docbook and @ifdocbook.  (--ifdocbook) */
DECLARE (int, process_docbook, 0);

/* Nonzero means that we process @html and @rawhtml even when not
   generating HTML.  (--ifhtml) */
DECLARE (int, process_html, 0);

/* Positive means process @ifinfo (even if not generating Info);
   zero means don't process @ifinfo (even if we are);
   -1 means we don't know yet.  (--ifinfo) */
DECLARE (int, process_info, -1);

/* Positive means process @ifplaintext (even if not generating plain text);
   zero means we don't process @ifplaintext (even if we are);
   -1 means we don't know yet.  (--ifplaintext) */
DECLARE (int, process_plaintext, -1);

/* Nonzero means that we process @tex and @iftex.  (--iftex) */
DECLARE (int, process_tex, 0);

/* Nonzero means that we process @xml and @ifxml.  (--ifxml) */
DECLARE (int, process_xml, 0);

/* Default is to check node references.  (--no-validate) */
DECLARE (int, validating, 1);

/* Nonzero means print information about what is going on.  (--verbose) */
DECLARE (int, verbose_mode, 0);

/* Nonzero means prefix each @chapter, ... with a number like
   1, 1.1, etc.  (--number-sections) */
DECLARE (int, number_sections, 1);

/* Nonzero means split size.  When zero, DEFAULT_SPLIT_SIZE is used. */
DECLARE (int, split_size, 0);

/* Nonzero means expand node names and references while validating.
   This will avoid errors when the Texinfo document uses features
   like @@ and @value inconsistently in node names, but will slow
   the program by about 80%.  You HAVE been warned.  */
DECLARE (int, expensive_validation, 0);

/* C's standard macros don't check to make sure that the characters being
   changed are within range.  So I have to check explicitly. */

#define coerce_to_upper(c) ((islower(c) ? toupper(c) : (c)))
#define coerce_to_lower(c) ((isupper(c) ? tolower(c) : (c)))

#define whitespace(c)       ((c) == '\t' || (c) == ' ')
#define sentence_ender(c)   ((c) == '.'  || (c) == '?' || (c) == '!')
#define cr_or_whitespace(c) (whitespace(c) || (c) == '\r' || (c) == '\n')

#ifndef isletter
#define isletter(c) (((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z'))
#endif

#ifndef isupper
#define isupper(c) ((c) >= 'A' && (c) <= 'Z')
#endif

#ifndef isdigit
#define isdigit(c)  ((c) >= '0' && (c) <= '9')
#endif

#ifndef digit_value
#define digit_value(c) ((c) - '0')
#endif

/* These characters are not really HTML-safe (with strict XHTML),
   and also there are possible collisions.  That's the whole reason we
   designed a new conversion scheme in the first place.  But we
   nevertheless need to generate the old names.  See
   `add_escaped_anchor_name' in html.c.  */
#define OLD_HTML_SAFE "$-_.+!*'()"
#define OLD_URL_SAFE_CHAR(ch) (strchr (OLD_HTML_SAFE, ch))

/* For the current/stable scheme.  */
#define URL_SAFE_CHAR(ch) (((unsigned char)ch)<128 && isalnum (ch))

#define COMMAND_PREFIX '@'

/* A byte value to represent a non-breaking space until flush_output (). */
#define NON_BREAKING_SPACE 036

#define END_VERBATIM "end verbatim"

/* Stuff for splitting large files.  The numbers for Emacs
   texinfo-format-buffer are much smaller, but memory capacities have
   increased so much, 50k info files seem a bit tiny these days.  */
#define DEFAULT_SPLIT_SIZE 300000
DECLARE (int, splitting, 1);    /* Defaults to true for now. */

#define skip_whitespace() \
     while ((input_text_offset != input_text_length) && \
             whitespace (curchar())) \
       input_text_offset++

#define skip_whitespace_and_newlines() \
  do { \
   while (input_text_offset != input_text_length \
          && cr_or_whitespace (curchar ())) \
      { \
         if (curchar () == '\n') \
           line_number++; \
         input_text_offset++; \
      } \
   } while (0)

/* Return nonzero if STRING is the text at input_text + input_text_offset,
   else zero. */
#define looking_at(string) \
  (strncmp (input_text + input_text_offset, string, strlen (string)) == 0)

/* Any list with a member named `next'.  */
typedef struct generic_list {
  struct generic_list *next;
} GENERIC_LIST;

/* Use `gdt' instead of `_' to translate strings that end up in the
   output document.  */
extern char *getdocumenttext (const char *msgid);
#define gdt(s) getdocumenttext(s)

/* Reverse the order of a list.  */
extern GENERIC_LIST * reverse_list (GENERIC_LIST *list);

/* Possibly return Local Variables trailer for Info output.  */
extern char *info_trailer (void),
  *expansion (char *str, int implicit_code),
  *text_expansion (char *str),
  *maybe_escaped_expansion (char *str, int implicit_code, int do_escape_html),
  *full_expansion (char *str, int implicit_code);

extern void free_and_clear (char **pointer),
  add_word (char *string),
  add_char (int character),
  add_meta_char (int character),
  close_single_paragraph (void),
  insert_string (const char *),
  insert (int character),
  get_rest_of_line (int expand, char **string),
  add_html_block_elt (char *string),
  get_until_in_braces (char *match, char **string),
  get_until_in_line (int expand, char *match, char **string),
  canon_white (char *string),
  discard_until (char *string),
  indent (int amount),
  kill_self_indent (int count),
  backup_input_pointer (void),
  inhibit_output_flushing (void),
  uninhibit_output_flushing (void),
  flush_output (void),
  start_paragraph (void),
  close_paragraph (void),
  close_insertion_paragraph (void),
  init_paragraph (void),
  ignore_blank_line (void),
  reader_loop (void),
  discard_braces (void),
  replace_with_expansion (int from, int *to),
  fix_whitespace (char *string),
  output_head (void),
  add_html_elt (char *string);

extern int get_until (char *match, char **string),
  set_paragraph_indent (char *string),
  self_delimiting (int character),
  search_forward (const char *string, int from),
  search_forward_until_pos (char *string, int from, int end_pos),
  next_nonwhitespace_character (void),
  current_output_column (void),
  fs_error (char *filename);

extern void add_word_args (const char *, ...) TEXINFO_PRINTFLIKE(1,2);
extern void add_html_block_elt_args (const char *, ...) TEXINFO_PRINTFLIKE(1,2);
extern void execute_string (char *, ...) TEXINFO_PRINTFLIKE(1,2);
extern void warning (const char *format, ...) TEXINFO_PRINTFLIKE(1,2);
extern void error (const char *format, ...) TEXINFO_PRINTFLIKE(1,2);
extern void line_error (const char *format, ...) TEXINFO_PRINTFLIKE(1,2);
extern void file_line_error (char *infile, int lno, const char *format, ...)
  TEXINFO_PRINTFLIKE(3,4);

#endif /* not MAKEINFO_H */


code:
/* multi.c -- multiple-column tables (@multitable) for makeinfo.
   $Id: multi.c,v 1.18 2007-07-01 21:20:33 karl Exp $

   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2004, 2005, 2007
   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/>.

   Originally written by phr@gnu.org (Paul Rubin).  */

#include "system.h"
#include "mbswidth.h"
#include "cmds.h"
#include "insertion.h"
#include "makeinfo.h"
#include "multi.h"
#include "xml.h"

#define MAXCOLS 100             /* remove this limit later @@ */

/*
 * Output environments.  This is a hack grafted onto existing
 * structure.  The "output environment" used to consist of the
 * global variables `output_paragraph', `fill_column', etc.
 * Routines like add_char would manipulate these variables.
 *
 * Now, when formatting a multitable, we maintain separate environments
 * for each column.  That way we can build up the columns separately
 * and write them all out at once.  The "current" output environment"
 * is still kept in those global variables, so that the old output
 * routines don't have to change.  But we provide routines to save
 * and restore these variables in an "environment table".  The
 * `select_output_environment' function switches from one output
 * environment to another.
 *
 * Environment #0 (i.e., element #0 of the table) is the regular
 * environment that is used when we're not formatting a multitable.
 *
 * Environment #N (where N = 1,2,3,...) is the env. for column #N of
 * the table, when a multitable is active.
 */

/* contents of an output environment */
/* some more vars may end up being needed here later @@ */
static struct env
{
  unsigned char *output_paragraph;
  int output_paragraph_offset;
  int paragraph_buffer_len;
  int paragraph_is_open;
  int meta_char_pos;
  int current_indent;
  int fill_column;
} envs[MAXCOLS];                /* the environment table */

/* index in environment table of currently selected environment */
static int current_env_no;

/* current column number */
static int current_column_no;

/* We need to make a difference between template based widths and
   @columnfractions for HTML tables' sake.  Sigh.  */
static int seen_column_fractions;

/* column number of last column in current multitable */
static int last_column;

/* flags indicating whether horizontal and vertical separators need
   to be drawn, separating rows and columns in the current multitable. */
static int hsep, vsep;

/* whether this is the first row. */
static int first_row;

/* Called to handle a {...} template on the @multitable line.
   We're at the { and our first job is to find the matching }; as a side
   effect, we change *PARAMS to point to after it.  Our other job is to
   expand the template text and return the width of that string.  */
static unsigned
find_template_width (char **params)
{
  char *template, *xtemplate;
  unsigned len;
  char *start = *params;
  int brace_level = 0;

  /* The first character should be a {.  */
  if (!params || !*params || **params != '{')
    {
      line_error ("find_template width internal error: passed %s",
                  params ? *params : "null");
      return 0;
    }

  do
    {
      if (**params == '{' && (*params == start || (*params)[-1] != '@'))
        brace_level++;
      else if (**params == '}' && (*params)[-1] != '@')
        brace_level--;
      else if (**params == 0)
        {
          line_error (_("Missing } in @multitable template"));
          return 0;
        }
      (*params)++;
    }
  while (brace_level > 0);

  template = substring (start + 1, *params - 1); /* omit braces */
  xtemplate = expansion (template, 0);
  len = strlen (xtemplate);

  free (template);
  free (xtemplate);

  return len;
}

/* Direct current output to environment number N.  Used when
   switching work from one column of a multitable to the next.
   Returns previous environment number. */
static int
select_output_environment (int n)
{
  struct env *e = &envs[current_env_no];
  int old_env_no = current_env_no;

  /* stash current env info from global vars into the old environment */
  e->output_paragraph = output_paragraph;
  e->output_paragraph_offset = output_paragraph_offset;
  e->paragraph_buffer_len = paragraph_buffer_len;
  e->paragraph_is_open = paragraph_is_open;
  e->meta_char_pos = meta_char_pos;
  e->current_indent = current_indent;
  e->fill_column = fill_column;

  /* now copy new environment into global vars */
  current_env_no = n;
  e = &envs[current_env_no];
  output_paragraph = e->output_paragraph;
  output_paragraph_offset = e->output_paragraph_offset;
  /* All the elements in the output environment structures start out as
     zeros from the static declaration.  However, we don't want to try
     to malloc zero bytes when init_paragraph is called just below,
     after we return.  */
  paragraph_buffer_len = e->paragraph_buffer_len ? e->paragraph_buffer_len
                         : INITIAL_PARAGRAPH_BUFFER_LEN;
  paragraph_is_open = e->paragraph_is_open;
  meta_char_pos = e->meta_char_pos;
  current_indent = e->current_indent;
  fill_column = e->fill_column;

  return old_env_no;
}

/* Initialize environment number ENV_NO, of width WIDTH.
   The idea is that we're going to use one environment for each column of
   a multitable, so we can build them up separately and print them
   all out at the end. */
static int
setup_output_environment (int env_no, int width)
{
  int old_env = select_output_environment (env_no);

  /* clobber old environment and set width of new one */
  init_paragraph ();

  /* make our change */
  fill_column = width;

  /* Save new environment and restore previous one. */
  select_output_environment (old_env);

  return env_no;
}

/* Read the parameters for a multitable from the current command
   line, save the parameters away, and return the
   number of columns. */
static int
setup_multitable_parameters (void)
{
  char *params = insertion_stack->item_function;
  int nchars;
  float columnfrac;
  char command[200]; /* xx no fixed limits */
  int i = 1;

  /* We implement @hsep and @vsep even though TeX doesn't.
     We don't get mixing of @columnfractions and templates right,
     but TeX doesn't either.  */
  hsep = vsep = 0;

  /* Assume no @columnfractions per default.  */
  seen_column_fractions = 0;

  while (*params) {
    while (whitespace (*params))
      params++;

    if (*params == '@') {
      sscanf (params, "%200s", command);
      nchars = strlen (command);
      params += nchars;
      if (strcmp (command, "@hsep") == 0)
        hsep++;
      else if (strcmp (command, "@vsep") == 0)
        vsep++;
      else if (strcmp (command, "@columnfractions") == 0) {
        seen_column_fractions = 1;
        /* Clobber old environments and create new ones, starting at #1.
           Environment #0 is the normal output, so don't mess with it. */
        for ( ; i <= MAXCOLS; i++) {
          if (sscanf (params, "%f", &columnfrac) < 1)
            goto done;
          /* Unfortunately, can't use %n since m68k-hp-bsd libc (at least)
             doesn't support it.  So skip whitespace (preceding the
             number) and then non-whitespace (the number).  */
          while (*params && (*params == ' ' || *params == '\t'))
            params++;
          /* Hmm, but what about @columnfractions 3foo.  Oh well,
             it's invalid input anyway.  */
          while (*params && *params != ' ' && *params != '\t'
                 && *params != '\n' && *params != '@')
            params++;

          {
            /* For html/xml/docbook, translate fractions into integer
               percentages, adding .005 to avoid rounding problems.  For
               info, we want the character width.  */
            int width = xml || html ? (columnfrac + .005) * 100
                        : (columnfrac * (fill_column - current_indent) + .5);
            setup_output_environment (i, width);
          }
        }
      }

    } else if (*params == '{') {
      unsigned template_width = find_template_width (&params);

      /* This gives us two spaces between columns.  Seems reasonable.
         How to take into account current_indent here?  */
      setup_output_environment (i++, template_width + 2);

    } else {
      warning (_("ignoring stray text `%s' after @multitable"), params);
      break;
    }
  }

done:
  flush_output ();
  inhibit_output_flushing ();

  last_column = i - 1;
  return last_column;
}

/* Output a row.  Calls insert, but also flushes the buffered output
   when we see a newline, since in multitable every line is a separate
   paragraph.  */
static void
out_char (int ch)
{
  if (html || xml)
    add_char (ch);
  else
    {
      int env = select_output_environment (0);
      insert (ch);
      if (ch == '\n')
	{
	  uninhibit_output_flushing ();
	  flush_output ();
	  inhibit_output_flushing ();
	}
      select_output_environment (env);
    }
}

static void
draw_horizontal_separator (void)
{
  int i, j, s;

  if (html)
    {
      add_word ("
");
      return;
    }
  if (xml)
    return;

  for (s = 0; s < envs[0].current_indent; s++)
    out_char (' ');
  if (vsep)
    out_char ('+');
  for (i = 1; i <= last_column; i++) {
    for (j = 0; j <= envs[i].fill_column; j++)
      out_char ('-');
    if (vsep)
      out_char ('+');
  }
  out_char (' ');
  out_char ('\n');
}

/* multitable strategy:
    for each item {
       for each column in an item {
        initialize a new paragraph
        do ordinary formatting into the new paragraph
        save the paragraph away
        repeat if there are more paragraphs in the column
      }
      dump out the saved paragraphs and free the storage
    }

   For HTML we construct a simple HTML 3.2 table with
s inserted
   to help non-tables browsers.  `@item' inserts a <tr> and `@tab'
   inserts ; we also try to close
.  The only real
   alternative is to rely on the info formatting engine and present
   preformatted text.  */

void
do_multitable (void)
{
  int ncolumns;

  if (multitable_active)
    {
      line_error ("Multitables cannot be nested");
      return;
    }

  close_single_paragraph ();

  if (xml)
    {
      xml_no_para = 1;
      if (output_paragraph_offset > 0
	  && output_paragraph[output_paragraph_offset-1] == '\n')
        output_paragraph_offset--;
    }

  /* scan the current item function to get the field widths
     and number of columns, and set up the output environment list
     accordingly. */
  ncolumns = setup_multitable_parameters ();
  first_row = 1;

  /*
 for non-tables browsers.  @multitable implicitly ends the
     current paragraph, so this is ok.  */
  if (html)
    add_html_block_elt ("

");
  /*  else if (docbook)*/ /* 05-08 */
  else if (xml)
    {
      int *widths = xmalloc (ncolumns * sizeof (int));
      int i;
      for (i=0; i<ncolumns; i++)
	widths[i] = envs[i+1].fill_column;
      xml_begin_multitable (ncolumns, widths);
      free (widths);
    }

  if (hsep)
    draw_horizontal_separator ();

  /* The next @item command will direct stdout into the first column
     and start processing.  @tab will then switch to the next column,
     and @item will flush out the saved output and return to the first
     column.  Environment #1 is the first column.  (Environment #0 is
     the normal output) */

  ++multitable_active;
}

/* advance to the next environment number */
static void
nselect_next_environment (void)
{
  if (current_env_no >= last_column) {
    line_error (_("Too many columns in multitable item (max %d)"), last_column);
    return;
  }
  select_output_environment (current_env_no + 1);
}

/* do anything needed at the beginning of processing a
   multitable column. */
static void
init_column (void)
{
  /* don't indent 1st paragraph in the item */
  cm_noindent ();

  /* throw away possible whitespace after @item or @tab command */
  skip_whitespace ();
}

static void
output_multitable_row (void)
{
  /* offset in the output paragraph of the next char needing
     to be output for that column. */
  int offset[MAXCOLS];
  int i, j, s, remaining;
  int had_newline = 0;

  for (i = 0; i <= last_column; i++)
    offset[i] = 0;

  /* select the current environment, to make sure the env variables
     get updated */
  select_output_environment (current_env_no);

#define CHAR_ADDR(n) (offset[i] + (n))
#define CHAR_AT(n) (envs[i].output_paragraph[CHAR_ADDR(n)])

  /* remove trailing whitespace from each column */
  for (i = 1; i <= last_column; i++) {
    while (envs[i].output_paragraph_offset
           && cr_or_whitespace (CHAR_AT (envs[i].output_paragraph_offset - 1)))
      envs[i].output_paragraph_offset--;

    if (i == current_env_no)
      output_paragraph_offset = envs[i].output_paragraph_offset;
  }

  /* read the current line from each column, outputting them all
     pasted together.  Do this til all lines are output from all
     columns.  */
  for (;;) {
    remaining = 0;
    /* first, see if there is any work to do */
    for (i = 1; i <= last_column; i++) {
      if (CHAR_ADDR (0) < envs[i].output_paragraph_offset) {
        remaining = 1;
        break;
      }
    }
    if (!remaining)
      break;

    for (s = 0; s < envs[0].current_indent; s++)
      out_char (' ');

    if (vsep)
      out_char ('|');

    for (i = 1; i <= last_column; i++) {
      for (s = 0; s < envs[i].current_indent; s++)
        out_char (' ');
      for (j = 0; CHAR_ADDR (j) < envs[i].output_paragraph_offset; j++) {
        if (CHAR_AT (j) == '\n')
          break;
        out_char (CHAR_AT (j));
      }
      /* Do not output trailing blanks if we're in the last column and
         there will be no trailing |.  */
      if (i < last_column && !vsep)
        for (s = mbsnwidth ((char *)&CHAR_AT (0), j, 0);
	     s <= envs[i].fill_column; s++)
          out_char (' ');
      if (vsep)
        out_char ('|'); /* draw column separator */

      offset[i] += j + 1;       /* skip last text plus skip the newline */
    }
    out_char ('\n');    /* end of line */
    had_newline = 1;
  }

  /* If completely blank item, get blank line despite no other output.  */
  if (!had_newline)
    out_char ('\n');    /* end of line */

  if (hsep)
    draw_horizontal_separator ();

  /* Now dispose of the buffered output. */
  for (i = 1; i <= last_column; i++) {
    select_output_environment (i);
    init_paragraph ();
  }
}

int after_headitem = 0;
int headitem_row = 0;

/* start a new item (row) of a multitable */
int
multitable_item (void)
{
  if (!multitable_active) {
    line_error ("multitable_item internal error: no active multitable");
    xexit (1);
  }

  current_column_no = 1;

  if (html)
    {
      if (!first_row)
        /*
 for non-tables browsers. */
	add_word_args ("
</%s>
", after_headitem ? "th" : "td");

      if (seen_column_fractions)
        add_word_args ("
<%s valign=\"top\" width=\"%d%%\">",
            headitem_flag ? "th" : "td",
            envs[current_column_no].fill_column);
      else
        add_word_args ("
<%s valign=\"top\">",
            headitem_flag ? "th" : "td");

      if (headitem_flag)
        after_headitem = 1;
      else
        after_headitem = 0;
      first_row = 0;
      headitem_row = headitem_flag;
      headitem_flag = 0;
      return 0;
    }
  /*  else if (docbook)*/ /* 05-08 */
  else if (xml)
    {
      xml_end_multitable_row (first_row);
      if (headitem_flag)
        after_headitem = 1;
      else
        after_headitem = 0;
      first_row = 0;
      headitem_flag = 0;
      return 0;
    }
  first_row = 0;

  if (current_env_no > 0) {
    output_multitable_row ();
  }
  /* start at column 1 */
  select_output_environment (1);
  if (!output_paragraph) {
    line_error (_("[unexpected] cannot select column #%d in multitable"),
                current_env_no);
    xexit (1);
  }

  init_column ();

  if (headitem_flag)
    hsep = 1;
  else
    hsep = 0;

  if (headitem_flag)
    after_headitem = 1;
  else
    after_headitem = 0;
  headitem_flag = 0;

  return 0;
}

#undef CHAR_AT
#undef CHAR_ADDR

/* select a new column in current row of multitable */
void
cm_tab (void)
{
  if (!multitable_active)
    error (_("ignoring @tab outside of multitable"));

  current_column_no++;

  if (html)
    {
      if (seen_column_fractions)
        add_word_args ("</%s><%s valign=\"top\" width=\"%d%%\">",
            headitem_row ? "th" : "td",
            headitem_row ? "th" : "td",
            envs[current_column_no].fill_column);
      else
        add_word_args ("</%s><%s valign=\"top\">",
            headitem_row ? "th" : "td",
            headitem_row ? "th" : "td");
    }
  /*  else if (docbook)*/ /* 05-08 */
  else if (xml)
    xml_end_multitable_column ();
  else
    nselect_next_environment ();

  init_column ();
}

/* close a multitable, flushing its output and resetting
   whatever needs resetting */
void
end_multitable (void)
{
  if (!html && !docbook)
    output_multitable_row ();

  /* Multitables cannot be nested.  Otherwise, we'd have to save the
     previous output environment number on a stack somewhere, and then
     restore to that environment.  */
  select_output_environment (0);
  multitable_active = 0;
  uninhibit_output_flushing ();
  close_insertion_paragraph ();

  if (html)
    add_word_args ("
</%s>

\n", headitem_row ? "th" : "td");
  /*  else if (docbook)*/ /* 05-08 */
  else if (xml)
    xml_end_multitable ();

#if 0
  printf (_("** Multicolumn output from last row:\n"));
  for (i = 1; i <= last_column; i++) {
    select_output_environment (i);
    printf (_("* column #%d: output = %s\n"), i, output_paragraph);
  }
#endif
}

repo_name: mthl/texinfo

license: mit


code:
/* multi.h -- declarations for multi.c.
   $Id: multi.h,v 1.4 2007-07-01 21:20:33 karl Exp $

   Copyright (C) 2004, 2007 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/>.  */

#ifndef MULTI_H
#define MULTI_H

extern void do_multitable (void);
extern void end_multitable (void);
extern int multitable_item (void);

#endif /* !MULTI_H */


code:
/* node.c -- nodes for Texinfo.
   $Id: node.c,v 1.41 2008-07-05 23:57:29 karl Exp $

   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007, 2008
   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/>.  */

#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 (mbscasecmp (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 && i > 0 && 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 || mbscasecmp (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 ("\n");
	      close_paragraph ();
	      if (epilogue_len == 0)
		epilogue_len = ftell (output_stream) - pos1;
	      fclose (output_stream);
	      output_stream = NULL;
              output_position = 0;
	      tag = find_node_by_fname (fname_for_this_node);
	    }
	  free (fname_for_prev_node);
	}
    }

  filling_enabled = indented_fill = 0;
  if (!html || (html && splitting))
    current_footnote_number = 1;

  if (verbose_mode)
    printf (_("Formatting node %s...\n"), node);

  if (macro_expansion_output_stream && !executing_string)
    remember_itext (input_text, input_text_offset);

  /* Reset the line number in each node for Info output, so that
     index entries will save the line numbers of parent node.  */
  node_line_number = 0;

  no_indent = 1;
  if (xml)
    {
      xml_begin_node ();
      if (!docbook)
	{
	  xml_insert_element (NODENAME, START);
	  if (macro_expansion_output_stream && !executing_string)
	    me_execute_string (node);
	  else
	    execute_string ("%s", node);
	  xml_insert_element (NODENAME, END);
	}
      else
	xml_node_id = xml_id (node);
    }
  else if (!no_headers && !html)
    {
      /* Emacs Info reader cannot grok indented escape sequence.  */
      kill_self_indent (-1);

      add_word_args ("\037\nFile: %s,  Node: ", pretty_output_filename);

      if (macro_expansion_output_stream && !executing_string)
        me_execute_string (node);
      else
        execute_string ("%s", node);
      filling_enabled = indented_fill = 0;
    }

  /* Check for defaulting of this node's next, prev, and up fields. */
  defaulting = (*next == 0 && *prev == 0 && *up == 0);

  this_section = what_section (input_text + input_text_offset, NULL);

  /* If we are defaulting, then look at the immediately following
     sectioning command (error if none) to determine the node's
     level.  Find the node that contains the menu mentioning this node
     that is one level up (error if not found).  That node is the "Up"
     of this node.  Default the "Next" and "Prev" from the menu. */
  if (defaulting)
    {
      NODE_REF *last_ref = NULL;
      NODE_REF *ref = node_references;

      if (this_section < 0 && !STREQ (node, "Top"))
        {
          char *polite_section_name = "top";
          int i;

          for (i = 0; section_alist[i].name; i++)
            if (section_alist[i].level == current_section + 1)
              {
                polite_section_name = section_alist[i].name;
                break;
              }

          line_error
            (_("Node `%s' requires a sectioning command (e.g., %c%s)"),
             node, COMMAND_PREFIX, polite_section_name);
        }
      else
        {
          if (strcmp (node, "Top") == 0)
            {
              /* Default the NEXT pointer to be the first menu item in
                 this node, if there is a menu in this node.  We have to
                 try very hard to find the menu, as it may be obscured
                 by execution_strings which are on the filestack.  For
                 every member of the filestack which has a FILENAME
                 member which is identical to the current INPUT_FILENAME,
                 search forward from that offset. */
              int saved_input_text_offset = input_text_offset;
              int saved_input_text_length = input_text_length;
              char *saved_input_text = input_text;
              FSTACK *next_file = filestack;

              int orig_offset, orig_size;

              int bye_offset = search_forward ("\n@bye", input_text_offset);

              /* No matter what, make this file point back at `(dir)'. */
              free (up);
              up = xstrdup ("(dir)"); /* html fixxme */

              while (1)
                {
                  orig_offset = input_text_offset;
                  orig_size =
                    search_forward (node_search_string, orig_offset);

                  if (orig_size < 0)
                    orig_size = input_text_length;

                  input_text_offset = search_forward ("\n@menu", orig_offset);
                  if (input_text_offset > -1
                      && (bye_offset > -1 && input_text_offset < bye_offset)
                      && cr_or_whitespace (input_text[input_text_offset + 6]))
                    {
                      char *nodename_from_menu = NULL;

                      input_text_offset =
                        search_forward ("\n* ", input_text_offset);

                      if (input_text_offset != -1)
                        nodename_from_menu = glean_node_from_menu (0,
						     (enum reftype) 0);

                      if (nodename_from_menu)
                        {
                          free (next);
                          next = nodename_from_menu;
                          break;
                        }
                    }

                  /* We got here, so it hasn't been found yet.  Try
                     the next file on the filestack if there is one. */
                  if (next_file
                      && FILENAME_CMP (next_file->filename, input_filename)
                          == 0)
                    {
                      input_text = next_file->text;
                      input_text_offset = next_file->offset;
                      input_text_length = next_file->size;
                      next_file = next_file->next;
                    }
                  else
                    { /* No more input files to check. */
                      break;
                    }
                }

              input_text = saved_input_text;
              input_text_offset = saved_input_text_offset;
              input_text_length = saved_input_text_length;
            }
        }

      /* Fix the level of the menu references in the Top node, iff it
         was declared with @top, and no subsequent reference was found. */
      if (top_node_seen && !non_top_node_seen)
        {
          /* Then this is the first non-@top node seen. */
          int level;

          level = set_top_section_level (this_section - 1);
          non_top_node_seen = 1;

          while (ref)
            {
              if (ref->section == level)
                ref->section = this_section - 1;
              ref = ref->next;
            }

          ref = node_references;
        }

      while (ref)
        {
          if (ref->section == (this_section - 1)
              && ref->type == menu_reference
              && strcmp (ref->node, node) == 0)
            {
              char *containing_node = ref->containing_node;

              free (up);
              up = xstrdup (containing_node);

              if (last_ref
                  && last_ref->type == menu_reference
                  && strcmp (last_ref->containing_node, containing_node) == 0)
                {
                  free (next);
                  next = xstrdup (last_ref->node);
                }

              while (ref->section == this_section - 1
                     && ref->next
                     && ref->next->type != menu_reference)
                ref = ref->next;

              if (ref->next && ref->type == menu_reference
                  && strcmp (ref->next->containing_node, containing_node) == 0)
                {
                  free (prev);
                  prev = xstrdup (ref->next->node);
                }
              else if (!ref->next
                       && mbscasecmp (ref->containing_node, "Top") == 0)
                {
                  free (prev);
                  prev = xstrdup (ref->containing_node);
                }
              break;
            }
          last_ref = ref;
          ref = ref->next;
        }
    }

  /* Insert the correct args if we are expanding macros, and the node's
     pointers weren't defaulted. */
  if (macro_expansion_output_stream && !executing_string && !defaulting)
    {
      char *temp;
      int op_orig = output_paragraph_offset;
      int meta_pos_orig = meta_char_pos;
      int extra = html ? strlen (node) : 0;

      temp = xmalloc (7 + extra + strlen (next) + strlen (prev) + strlen (up));
      sprintf (temp, "%s, %s, %s, %s", html ? node : "", next, prev, up);
      me_execute_string (temp);
      free (temp);

      output_paragraph_offset = op_orig;
      meta_char_pos = meta_pos_orig;
    }

  if (!*node)
    {
      line_error (_("No node name specified for `%c%s' command"),
                  COMMAND_PREFIX, command);
      free (node);
      free (next); next = NULL;
      free (prev); prev= NULL;
      free (up);   up = NULL;
      node_number++;            /* else it doesn't get bumped */
    }
  else
    {
      if (!*next) { free (next); next = NULL; }
      if (!*prev) { free (prev); prev = NULL; }
      if (!*up)   { free (up);   up = NULL;   }
      remember_node (node, prev, next, up, new_node_pos, line_number,
		     fname_for_this_node, no_warn);
      outstanding_node = 1;
    }

  if (html)
    {
      if (splitting && *node && output_stream == NULL)
        {
	  char *dirname;
	  char filename[PATH_MAX];

	  dirname = pathname_part (current_output_filename);
	  strcpy (filename, dirname);
	  strcat (filename, fname_for_this_node);
	  free (dirname);

	  /* See if the node name converted to a file name clashes
	     with other nodes or anchors.  If it clashes with an
	     anchor, we complain and nuke that anchor's file.  */
	  if (!tag)
	    {
	      output_stream = fopen (filename, "w");
	      output_head_p = 0; /* so that we generate HTML preamble */
	      output_head ();
	    }
	  else if ((tag->flags & TAG_FLAG_ANCHOR) != 0)
	    {
	      line_error (_("Anchor `%s' and node `%s' map to the same file name"),
			  tag->node, node);
	      file_line_error (tag->filename, tag->line_no,
			       _("This @anchor command ignored; references to it will not work"));
	      file_line_error (tag->filename, tag->line_no,
			       _("Rename this anchor or use the `--no-split' option"));
	      /* Nuke the file name recorded in anchor's tag.
		 Since we are about to nuke the file itself, we
		 don't want find_node_by_fname to consider this
		 anchor anymore.  */
	      free (tag->html_fname);
	      tag->html_fname = NULL;
	      output_stream = fopen (filename, "w");
	      output_head_p = 0; /* so that we generate HTML preamble */
	      output_head ();
	    }
	  else
	    {
	      /* This node's file name clashes with another node.
		 We put them both on the same file.  */
	      output_stream = fopen (filename, "r+");
	      if (output_stream)
		{
		  static char html_end[] = "\n";
		  char end_line[sizeof(html_end)];
		  int fpos = fseek (output_stream, -epilogue_len,
				    SEEK_END);

		  if (fpos < 0
		      || fgets (end_line, sizeof (html_end),
				output_stream) == NULL
		      /* Paranoia: did someone change the way HTML
			 files are finished up?  */
		      || mbscasecmp (end_line, html_end) != 0)
		    {
		      line_error (_("Unexpected string at end of split-HTML file `%s'"),
				  fname_for_this_node);
		      fclose (output_stream);
		      xexit (1);
		    }
		  fseek (output_stream, -epilogue_len, SEEK_END);
		}
	    }
          if (output_stream == NULL)
            {
              fs_error (filename);
              xexit (1);
            }
          set_current_output_filename (filename);
        }

      if (!splitting && no_headers)
	{ /* cross refs need a name="#anchor" even if not writing headers */
          add_html_names (node);
	}

      if (splitting || !no_headers)
        { /* Navigation bar. */
          add_html_block_elt ("
\n");

          /* In the split HTML case, the filename is wrong for the
             old-style converted names, but we'll add them anyway, for
             consistency.  (And we need them in the normal (not
             no_headers) nonsplit case.)  */
          add_html_names (node);

          /* Do this after adding the anchors, so the browser rendering
             can be better.  The
 avoids the links area running on
             with old Lynxen.  */
          add_word_args ("
%s\n", splitting ? "" : "
");

          if (next)
            {
              tem = expansion (next, 0);
	      add_word ((char *) gdt("Next:"));
              add_word (" ");

	      add_word ("%s", tem);

              free (tem);

	      if (prev || up)
		add_word (",\n");
            }
          if (prev)
            {
              tem = expansion (prev, 0);
	      add_word ((char *) gdt("Previous:"));
              add_word (" ");
	      add_word ("%s", tem);
              free (tem);

	      if (up)
		add_word (",\n");
            }
          if (up)
            {
              tem = expansion (up, 0);
	      add_word ((char *) gdt("Up:"));
              add_word (" ");
	      add_word ("%s", tem);
              free (tem);
            }
          /* html fixxme: we want a `top' or `contents' link here.  */

          add_word_args ("\n%s\n", splitting ? "
" : "");
      	  add_word ("
\n");
        }
    }
  else if (docbook)
    ;
  else if (xml)
    {
      if (next)
	{
	  xml_insert_element (NODENEXT, START);
	  execute_string ("%s", next);
	  xml_insert_element (NODENEXT, END);
	}
      if (prev)
	{
	  xml_insert_element (NODEPREV, START);
	  execute_string ("%s", prev);
	  xml_insert_element (NODEPREV, END);
	}
      if (up)
	{
	  xml_insert_element (NODEUP, START);
	  execute_string ("%s", up);
	  xml_insert_element (NODEUP, END);
	}
    }
  else if (!no_headers)
    {
      if (macro_expansion_output_stream)
        me_inhibit_expansion++;

      /* These strings are not translatable.  */
      if (next)
        {
          execute_string (",  Next: %s", next);
          filling_enabled = indented_fill = 0;
        }
      if (prev)
        {
          execute_string (",  Prev: %s", prev);
          filling_enabled = indented_fill = 0;
        }
      if (up)
        {
          execute_string (",  Up: %s", up);
          filling_enabled = indented_fill = 0;
        }
      if (macro_expansion_output_stream)
        me_inhibit_expansion--;
    }

  close_paragraph ();
  no_indent = 0;

  /* Change the section only if there was a sectioning command. */
  if (this_section >= 0)
    current_section = this_section;

  if (current_node && STREQ (current_node, "Top"))
    top_node_seen = 1;

  filling_enabled = 1;
  in_fixed_width_font--;
}

/* Cross-reference target at an arbitrary spot.  */
void
cm_anchor (int arg)
{
  char *anchor;
  char *fname_for_anchor = NULL;

  if (arg == END)
    {
      /* We want to ignore whitespace following @anchor a la
         texinfo.tex, but we're sitting at the }.  So advance past it,
         ignore the whitespace, and then go back one character.  When we
         return, reader_loop will increment input_text_offset again (see
         the '}' case).  Sorry.  */
      input_text_offset++;
      skip_whitespace_and_newlines ();
      input_text_offset--;
      return;
    }

  /* Parse the anchor text.  */
  anchor = get_xref_token (1);

  /* Force all versions of "top" to be "Top". */
  normalize_node_name (anchor);

  /* In HTML mode, need to actually produce some output.  */
  if (html)
    {
      /* If this anchor is at the beginning of a new paragraph, make
	 sure a new paragraph is indeed started.  */
      if (!paragraph_is_open)
	{
	  if (!executing_string && html)
	    output_head ();
	  start_paragraph ();
	  if (!in_fixed_width_font || in_menu || in_detailmenu)
	    {
	      insert_string ("
");
	      in_paragraph = 1;
	    }
	}
      add_word ("");
      if (splitting)
	{
	  /* If we are splitting, cm_xref will produce a reference to
	     a file whose name is derived from the anchor name.  So we
	     must create a file when we see an @anchor, otherwise
	     xref's to anchors won't work.  The file we create simply
	     redirects to the file of this anchor's node.  */
	  TAG_ENTRY *tag;

	  fname_for_anchor = nodename_to_filename (anchor);
	  /* See if the anchor name converted to a file name clashes
	     with other anchors or nodes.  */
	  tag = find_node_by_fname (fname_for_anchor);
	  if (tag)
	    {
	      if ((tag->flags & TAG_FLAG_ANCHOR) != 0)
		line_error (_("Anchors `%s' and `%s' map to the same file name"),
			    anchor, tag->node);
	      else
		line_error (_("Anchor `%s' and node `%s' map to the same file name"),
			    anchor, tag->node);
	      line_error (_("@anchor command ignored; references to it will not work"));
	      line_error (_("Rename this anchor or use the `--no-split' option"));
	      free (fname_for_anchor);
	      /* We will not be creating a file for this anchor, so
		 set its name to NULL, so that remember_node stores a
		 NULL and find_node_by_fname won't consider this
		 anchor for clashes.  */
	      fname_for_anchor = NULL;
	    }
	  else
	    {
	      char *dirname, *p;
	      char filename[PATH_MAX];
	      FILE *anchor_stream;

	      dirname = pathname_part (current_output_filename);
	      strcpy (filename, dirname);
	      strcat (filename, fname_for_anchor);
	      free (dirname);

	      anchor_stream = fopen (filename, "w");
	      if (anchor_stream == NULL)
		{
		  fs_error (filename);
		  xexit (1);
		}
	      /* The HTML magic below will cause the browser to
		 immediately go to the anchor's node's file.  Lynx
		 seems not to support this redirection, but it looks
		 like a bug in Lynx, and they can work around it by
		 clicking on the link once more.  */
	      fputs ("<meta http-equiv=\"refresh\" content=\"0; url=",
		     anchor_stream);
	      /* Make the indirect link point to the current node's
		 file and anchor's "<a name" label.  If we don't have
		 a valid node name, refer to the current output file
		 instead.  */
	      if (current_node && *current_node)
		{
		  char *fn, *tem;

		  tem = expand_node_name (current_node);
		  fn = nodename_to_filename (tem);
		  free (tem);
		  fputs (fn, anchor_stream);
		  free (fn);
		}
	      else
		{
		  char *base = filename_part (current_output_filename);

		  fputs (base, anchor_stream);
		  free (base);
		}
	      fputs ("#", anchor_stream);
	      for (p = anchor; *p; p++)
		{
		  if (*p == '&')
		    fputs ("&", anchor_stream);
		  else if (!URL_SAFE_CHAR (*p))
		    fprintf (anchor_stream, "%%%x", (unsigned char) *p);
		  else
		    fputc (*p, anchor_stream);
		}
	      fputs ("\">\n", anchor_stream);
	      fclose (anchor_stream);
	    }
	}
    }
  else if (xml || docbook)
    {
      xml_insert_element_with_attribute (ANCHOR, START,
					 docbook ? "id=\"%s\"" : "name=\"%s\"",
					 anchor);
      xml_insert_element (ANCHOR, END);
    }

  /* Save it in the tag table.  */
  remember_node (anchor, NULL, NULL, NULL,
                 output_position + output_paragraph_offset,
                 line_number, fname_for_anchor, TAG_FLAG_ANCHOR);
}

/* Find NODE in REF_LIST. */
static NODE_REF *
find_node_reference (char *node, NODE_REF *ref_list)
{
  NODE_REF *orig_ref_list = ref_list;
  char *expanded_node;

  while (ref_list)
    {
      if (strcmp (node, ref_list->node) == 0)
        break;
      ref_list = ref_list->next;
    }

  if (ref_list || !expensive_validation)
    return ref_list;

  /* Maybe NODE is not expanded yet.  This may be SLOW.  */
  expanded_node = expand_node_name (node);
  for (ref_list = orig_ref_list; ref_list; ref_list = ref_list->next)
    {
      if (STREQ (expanded_node, ref_list->node))
        break;
      if (strchr (ref_list->node, COMMAND_PREFIX))
        {
          char *expanded_ref = expand_node_name (ref_list->node);

          if (STREQ (expanded_node, expanded_ref))
            {
              free (expanded_ref);
              break;
            }
          free (expanded_ref);
        }
    }
  free (expanded_node);
  return ref_list;
}

void
free_node_references (void)
{
  NODE_REF *list, *temp;

  list = node_references;

  while (list)
    {
      temp = list;
      free (list->node);
      free (list->containing_node);
      list = list->next;
      free (temp);
    }
  node_references = NULL;
}

void
free_node_node_references (void)
{
  NODE_REF *list, *temp;

  list = node_references;

  while (list)
    {
      temp = list;
      free (list->node);
      list = list->next;
      free (temp);
    }
  node_node_references = NULL;
}

/* Return the number assigned to a named node in either the tag_table
   or node_references list or zero if no number has been assigned. */
int
number_of_node (char *node)
{
  NODE_REF *temp_ref;
  TAG_ENTRY *temp_node = find_node (node);

  if (temp_node)
    return temp_node->number;
  else if ((temp_ref = find_node_reference (node, node_references)))
    return temp_ref->number;
  else if ((temp_ref = find_node_reference (node, node_node_references)))
    return temp_ref->number;
  else
    return 0;
}

/* validation */

/* Return 1 if TAG (at LINE) correctly validated, or 0 if not.
   LABEL is the (translated) description of the type of reference --
   Menu, Cross, Next, etc.  */

static int
validate (char *tag, int line, const char *label)
{
  TAG_ENTRY *result;

  /* If there isn't a tag to verify, or if the tag is in another file,
     then it must be okay. */
  if (!tag || !*tag || *tag == '(')
    return 1;

  /* Otherwise, the tag must exist. */
  result = find_node (tag);

  if (!result)
    {
      line_number = line;
      line_error (_("%s reference to nonexistent node `%s' (perhaps incorrect sectioning?)"), label, tag);
      return 0;
    }
  result->touched++;
  return 1;
}

/* The strings here are followed in the message by `reference to...' in
   the `validate' routine.  They are only used in messages, thus are
   translated.  */
static const char *
reftype_type_string (enum reftype type)
{
  switch (type)
    {
    case menu_reference:
      return gdt("Menu");
    case followed_reference:
      return gdt("Cross");
    default:
      return "Internal-bad-reference-type";
    }
}

static void
validate_other_references (NODE_REF *ref_list)
{
  char *old_input_filename = input_filename;

  while (ref_list)
    {
      input_filename = ref_list->filename;
      validate (ref_list->node, ref_list->line_no,
                reftype_type_string (ref_list->type));
      ref_list = ref_list->next;
    }
  input_filename = old_input_filename;
}

/* Validation of an info file.
   Scan through the list of tag entries touching the Prev, Next, and Up
   elements of each.  It is an error not to be able to touch one of them,
   except in the case of external node references, such as "(DIR)".

   If the Prev is different from the Up,
   then the Prev node must have a Next pointing at this node.

   Every node except Top must have an Up.
   The Up node must contain some sort of reference, other than a Next,
   to this node.

   If the Next is different from the Next of the Up,
   then the Next node must have a Prev pointing at this node. */
void
validate_file (TAG_ENTRY *tag_table)
{
  char *old_input_filename = input_filename;
  TAG_ENTRY *tags = tag_table;

  while (tags)
    {
      TAG_ENTRY *temp_tag;
      char *tem1, *tem2;

      input_filename = tags->filename;
      line_number = tags->line_no;

      /* If this is a "no warn" node, don't validate it in any way. */
      if (tags->flags & TAG_FLAG_NO_WARN)
        {
          tags = tags->next_ent;
          continue;
        }

      /* If this node has a Next, then make sure that the Next exists. */
      if (tags->next)
        {
          validate (tags->next, tags->line_no, gdt("Next"));

          /* If the Next node exists, and there is no Up, then make sure
             that the Prev of the Next points back.  But do nothing if
             we aren't supposed to issue warnings about this node. */
          temp_tag = find_node (tags->next);
          if (temp_tag && !(temp_tag->flags & TAG_FLAG_NO_WARN))
            {
              char *prev = temp_tag->prev;
              int you_lose = !prev || !STREQ (prev, tags->node);

              if (you_lose && expensive_validation)
                {
                  tem1 = expand_node_name (prev);
                  tem2 = expand_node_name (tags->node);

                  if (tem1 && tem2 && STREQ (tem1, tem2))
                    you_lose = 0;
                  free (tem1);
                  free (tem2);
                }
              if (you_lose)
                {
                  line_error (_("Next field of node `%s' not pointed to (perhaps incorrect sectioning?)"),
                              tags->node);
                  file_line_error (temp_tag->filename, temp_tag->line_no,
				   _("This node (%s) has the bad Prev"),
				   temp_tag->node);
                  temp_tag->flags |= TAG_FLAG_PREV_ERROR;
                }
            }
        }

      /* Validate the Prev field if there is one, and we haven't already
         complained about it in some way.  You don't have to have a Prev
         field at this stage. */
      if (!(tags->flags & TAG_FLAG_PREV_ERROR) && tags->prev)
        {
          int valid_p = validate (tags->prev, tags->line_no, gdt("Prev"));

          if (!valid_p)
            tags->flags |= TAG_FLAG_PREV_ERROR;
          else
            { /* If the Prev field is not the same as the Up field,
                 then the node pointed to by the Prev field must have
                 a Next field which points to this node. */
              int prev_equals_up = !tags->up || STREQ (tags->prev, tags->up);

              if (!prev_equals_up && expensive_validation)
                {
                  tem1 = expand_node_name (tags->prev);
                  tem2 = expand_node_name (tags->up);
                  prev_equals_up = STREQ (tem1, tem2);
                  free (tem1);
                  free (tem2);
                }
              if (!prev_equals_up)
                {
                  temp_tag = find_node (tags->prev);

                  /* If we aren't supposed to issue warnings about the
                     target node, do nothing. */
                  if (!temp_tag || (temp_tag->flags & TAG_FLAG_NO_WARN))
                    /* Do nothing. */ ;
                  else
                    {
                      int you_lose = !temp_tag->next
                        || !STREQ (temp_tag->next, tags->node);

                      if (temp_tag->next && you_lose && expensive_validation)
                        {
                          tem1 = expand_node_name (temp_tag->next);
                          tem2 = expand_node_name (tags->node);
                          if (STREQ (tem1, tem2))
                            you_lose = 0;
                          free (tem1);
                          free (tem2);
                        }
                      if (you_lose)
                        {
                          line_error
                            (_("Prev field of node `%s' not pointed to"),
                             tags->node);
                          file_line_error (temp_tag->filename,
					   temp_tag->line_no,
					   _("This node (%s) has the bad Next"),
					   temp_tag->node);
                          temp_tag->flags |= TAG_FLAG_NEXT_ERROR;
                        }
                    }
                }
            }
        }

      if (!tags->up
          && !(tags->flags & TAG_FLAG_ANCHOR)
          && mbscasecmp (tags->node, "Top") != 0)
        line_error (_("`%s' has no Up field (perhaps incorrect sectioning?)"), tags->node);
      else if (tags->up)
        {
          int valid_p = validate (tags->up, tags->line_no, gdt("Up"));

          /* If node X has Up: Y, then warn if Y fails to have a menu item
             or note pointing at X, if Y isn't of the form "(Y)". */
          if (valid_p && *tags->up != '(')
            {
              NODE_REF *nref;
              NODE_REF *tref = NULL;
              NODE_REF *list = node_references;

              for (;;)
                {
                  nref = find_node_reference (tags->node, list);
                  if (!nref)
                    break;

                  if (strcmp (nref->containing_node, tags->up) == 0)
                    {
                      if (nref->type != menu_reference)
                        {
                          tref = nref;
                          list = nref->next;
                        }
                      else
                        break;
                    }
                  list = nref->next;
                }

              if (!nref)
                {
		  if (!tref && expensive_validation)
		    {
		      /* Sigh...  This might be AWFULLY slow, but if
		         they want this feature, they'll have to pay!
		         We do all the loop again expanding each
		         containing_node reference as we go.  */
		      char *tags_up = expand_node_name (tags->up);
		      char *tem;

		      list = node_references;

		      for (;;)
			{
			  nref = find_node_reference (tags->node, list);
			  if (!nref)
			    break;
			  tem = expand_node_name (nref->containing_node);
			  if (STREQ (tem, tags_up))
			    {
			      if (nref->type != menu_reference)
				tref = nref;
			      else
				{
				  free (tem);
				  break;
				}
			    }
			  free (tem);
			  list = nref->next;
			}
		    }
                  if (!nref && !tref)
                    {
                      temp_tag = find_node (tags->up);
                      file_line_error (temp_tag->filename, temp_tag->line_no,
           _("Node `%s' lacks menu item for `%s' despite being its Up target"),
                                  tags->up, tags->node);
                    }
                }
            }
        }
      tags = tags->next_ent;
    }

  validate_other_references (node_references);
  /* We have told the user about the references which didn't exist.
     Now tell him about the nodes which aren't referenced. */

  for (tags = tag_table; tags; tags = tags->next_ent)
    {
      /* If this node is a "no warn" node, do nothing. */
      if (tags->flags & TAG_FLAG_NO_WARN)
        {
          tags = tags->next_ent;
          continue;
        }

      if (tags->touched == 0)
        {
          input_filename = tags->filename;
          line_number = tags->line_no;

          /* Notice that the node "Top" is special, and doesn't have to
             be referenced.   Anchors don't have to be referenced
             either, you might define them for another document.  */
          if (mbscasecmp (tags->node, "Top") != 0
              && !(tags->flags & TAG_FLAG_ANCHOR))
            warning (_("unreferenced node `%s'"), tags->node);
        }
    }
  input_filename = old_input_filename;
}

/* Splitting */

/* Return true if the tag entry pointed to by TAGS is the last node.
   This means only anchors follow.  */

static int
last_node_p (TAG_ENTRY *tags)
{
  int last = 1;
  while (tags->next_ent) {
    tags = tags->next_ent;
    if (tags->flags & TAG_FLAG_ANCHOR)
      ;
    else
      {
        last = 0;
        break;
      }
  }

  return last;
}

static char *
enumerate_filename (char *pathname, char *basename, int number)
{
  /* Do we need to generate names of subfiles which don't exceed 8+3 limits? */
  const int dos_file_names = !HAVE_LONG_FILENAMES (pathname ? pathname : ".");
  unsigned name_len = strlen (basename);
  char *filename = xmalloc (10 + strlen (pathname) + name_len);
  char *base_filename = xmalloc (10 + name_len);

  sprintf (base_filename, "%s-%d", basename, number);

  if (dos_file_names)
    {
      char *dot = strchr (base_filename, '.');
      unsigned base_len = strlen (base_filename);

      if (dot)
        { /* Make foobar.i1, .., foobar.i99, foobar.100, ... */
          dot[1] = 'i';
          memmove (number <= 99 ? dot + 2 : dot + 1,
              base_filename + name_len + 1,
              strlen (base_filename + name_len + 1) + 1);
        }
      else if (base_len > 8)
        {
          /* Make foobar-1, .., fooba-10, .., foob-100, ... */
          unsigned numlen = base_len - name_len;

          memmove (base_filename + 8 - numlen, base_filename + name_len, numlen + 1);
        }
    }

  sprintf (filename, "%s%s", pathname, base_filename);

  return filename;
}

/* Remove previously split files, to avoid
   lingering parts of shrinked documents.  */
void
clean_old_split_files (char *filename)
{
  char *root_filename = filename_part (filename);
  char *root_pathname = pathname_part (filename);
  int i;

  /* We break as soon as we hit an inexistent file,
     so looping until large numbers is harmless.  */
  for (i = 1; i < 1000; i++)
    {
      struct stat st;
      char *check_file = enumerate_filename (root_pathname, root_filename, i);

      if (stat (check_file, &st) != 0)
        break;
      else if (!S_ISDIR (st.st_mode))
        {
          /* Give feedback if requested, removing a file is important.  */
          if (verbose_mode)
            printf (_("Removing %s\n"), check_file);

          /* Warn user that we cannot remove the file.  */
          if (unlink (check_file) != 0)
            warning (_("Can't remove file `%s': %s"), check_file, strerror (errno));
        }

      free (check_file);
    }
}

/* Split large output files into a series of smaller files.  Each file
   is pointed to in the tag table, which then gets written out as the
   original file.  The new files have the same name as the original file
   with a "-num" attached.  SIZE is the largest number of bytes to allow
   in any single split file. */
void
split_file (char *filename, int size)
{
  char *root_filename, *root_pathname;
  char *the_file;
  struct stat fileinfo;
  long file_size;
  char *the_header;
  int header_size;

  /* Can only do this to files with tag tables. */
  if (!tag_table)
    return;

  if (size == 0)
    size = DEFAULT_SPLIT_SIZE;

  if ((stat (filename, &fileinfo) != 0)
      || (((long) fileinfo.st_size) < size))
    return;
  file_size = (long) fileinfo.st_size;

  the_file = find_and_load (filename, 0);
  if (!the_file)
    return;

  root_filename = filename_part (filename);
  root_pathname = pathname_part (filename);

  if (!root_pathname)
    root_pathname = xstrdup ("");

  /* Start splitting the file.  Walk along the tag table
     outputting sections of the file.  When we have written
     all of the nodes in the tag table, make the top-level
     pointer file, which contains indirect pointers and
     tags for the nodes. */
  {
    int which_file = 1;
    TAG_ENTRY *tags = tag_table;
    char *indirect_info = NULL;

    /* Maybe we want a Local Variables section.  */
    char *trailer = info_trailer ();
    int trailer_len = trailer ? strlen (trailer) : 0;

    /* Remember the `header' of this file.  The first tag in the file is
       the bottom of the header; the top of the file is the start. */
    the_header = xmalloc (1 + (header_size = tags->position));
    memcpy (the_header, the_file, header_size);

    while (tags)
      {
        int file_top, file_bot, limit;

        /* Have to include the Control-_. */
        file_top = file_bot = tags->position;
        limit = file_top + size;

        /* If the rest of this file is only one node, then
           that is the entire subfile. */
        if (last_node_p (tags))
          {
            int i = tags->position + 1;
            char last_char = the_file[i];

            while (i < file_size)
              {
                if ((the_file[i] == '\037') &&
                    ((last_char == '\n') ||
                     (last_char == '\014')))
                  break;
                else
                  last_char = the_file[i];
                i++;
              }
            file_bot = i;
            tags = tags->next_ent;
            goto write_region;
          }

        /* Otherwise, find the largest number of nodes that can fit in
           this subfile. */
        for (; tags; tags = tags->next_ent)
          {
            if (last_node_p (tags))
              {
                /* This entry is the last node.  Search forward for the end
                   of this node, and that is the end of this file. */
                int i = tags->position + 1;
                char last_char = the_file[i];

                while (i < file_size)
                  {
                    if ((the_file[i] == '\037') &&
                        ((last_char == '\n') ||
                         (last_char == '\014')))
                      break;
                    else
                      last_char = the_file[i];
                    i++;
                  }
                file_bot = i;

                if (file_bot < limit)
                  {
                    tags = tags->next_ent;
                    goto write_region;
                  }
                else
                  {
                    /* Here we want to write out everything before the last
                       node, and then write the last node out in a file
                       by itself. */
                    file_bot = tags->position;
                    goto write_region;
                  }
              }

            /* Write region only if this was a node, not an anchor.  */
            if (tags->next_ent->position > limit
                && !(tags->flags & TAG_FLAG_ANCHOR))
              {
                if (tags->position == file_top)
                  tags = tags->next_ent;

                file_bot = tags->position;

              write_region:
                {
                  int fd;
                  char *split_filename = enumerate_filename (root_pathname,
                      root_filename, which_file);
                  char *split_basename = filename_part (split_filename);

                  fd = open (split_filename, O_WRONLY|O_TRUNC|O_CREAT, 0666);
                  if (fd < 0
                      || write (fd, the_header, header_size) != header_size
                      || write (fd, the_file + file_top, file_bot - file_top)
                         != (file_bot - file_top)
                      || (trailer_len
                          && write (fd, trailer, trailer_len) != trailer_len)
                      || close (fd) < 0)
                    {
                      perror (split_filename);
                      if (fd != -1)
                        close (fd);
                      xexit (1);
                    }

                  if (!indirect_info)
                    {
                      indirect_info = the_file + file_top;
                      sprintf (indirect_info, "\037\nIndirect:\n");
                      indirect_info += strlen (indirect_info);
                    }

                  sprintf (indirect_info, "%s: %d\n",
                           split_basename, file_top);

                  free (split_basename);
                  free (split_filename);
                  indirect_info += strlen (indirect_info);
                  which_file++;
                  break;
                }
              }
          }
      }

    /* We have sucessfully created the subfiles.  Now write out the
       original again.  We must use `output_stream', or
       write_tag_table_indirect () won't know where to place the output. */
    output_stream = fopen (filename, "w");
    if (!output_stream)
      {
        perror (filename);
        xexit (1);
      }

    {
      int distance = indirect_info - the_file;
      fwrite (the_file, 1, distance, output_stream);

      /* Inhibit newlines. */
      paragraph_is_open = 0;

      /* Write the indirect tag table.  */
      write_tag_table_indirect ();

      /* preserve local variables in info output.  */
      if (trailer)
        {
          fwrite (trailer, 1, trailer_len, output_stream);
          free (trailer);
        }

      fclose (output_stream);
      free (the_header);
      free (the_file);
      return;
    }
  }
}


code:
/* node.h -- declarations for Node.
   $Id: node.h,v 1.5 2007-07-01 21:20:33 karl Exp $

   Copyright (C) 1996, 1997, 1998, 1999, 2002, 2007
   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/>.

   Written by Brian Fox (bfox@ai.mit.edu). */

#ifndef NODE_H
#define NODE_H

#include "xref.h"

/* The various references that we know about. */
/* What we remember for each node. */
typedef struct tentry
{
  struct tentry *next_ent;
  char *node;           /* Name of this node. */
  char *prev;           /* Name of "Prev:" for this node. */
  char *next;           /* Name of "Next:" for this node. */
  char *up;             /* Name of "Up:" for this node.   */
  int position;         /* Output file position of this node. */
  int line_no;          /* Defining line in source file. */
  char *filename;       /* The file that this node was found in. */
  int touched;          /* Nonzero means this node has been referenced. */
  int flags;
  int number;           /* Number for this node, relevant for HTML
                           splitting -- from use+define order, not just
                           define. */
  int order;            /* The order of the tag, starting from zero.  */
  char *html_fname;	/* The HTML file to which this node is written
			   (non-NULL only for HTML splitting).  */
} TAG_ENTRY;

/* If node-a has a "Next" for node-b, but node-b has no "Prev" for node-a,
   we turn on this flag bit in node-b's tag entry.  This means that when
   it is time to validate node-b, we don't report an additional error
   if there was no "Prev" field. */
#define TAG_FLAG_PREV_ERROR  1
#define TAG_FLAG_NEXT_ERROR  2
#define TAG_FLAG_UP_ERROR    4
#define TAG_FLAG_NO_WARN     8
#define TAG_FLAG_IS_TOP     16
#define TAG_FLAG_ANCHOR     32

/* Menu reference, *note reference, and validation hacking. */

/* A structure to remember references with.  A reference to a node is
   either an entry in a menu, or a cross-reference made with [px]ref. */
typedef struct node_ref
{
  struct node_ref *next;
  char *node;                   /* Name of node referred to. */
  char *containing_node;        /* Name of node containing this reference. */
  int line_no;                  /* Line number where the reference occurs. */
  int section;                  /* Section level where the reference occurs. */
  char *filename;               /* Name of file where the reference occurs. */
  enum reftype type;            /* Type of reference, either menu or note. */
  int number;                   /* Number for this node, relevant for
                                   HTML splitting -- from use+define
                                   order, not just define. */
} NODE_REF;

/* The linked list of such structures. */
extern NODE_REF *node_references;

/* A similar list for references occuring in @node next
   and similar references, needed for HTML. */
extern NODE_REF *node_node_references;

/* List of all nodes.  */
extern TAG_ENTRY *tag_table;

/* Counter for setting node_ref.number; zero is Top. */
extern int node_number;

/* Node order counter.  */
extern int node_order;

/* The current node's section level. */
extern int current_section;

/* Nonzero when the next sectioning command should generate an anchor
   corresponding to the current node in HTML mode. */
extern int outstanding_node;

extern TAG_ENTRY *find_node (char *name);

/* A search string which is used to find a line defining a node. */
DECLARE (char *, node_search_string, "\n@node ");

/* Extract node name from a menu item. */
extern char *glean_node_from_menu (int remember_ref, enum reftype ref_type);

/* Remember a node for later validation.  */
extern void remember_node_reference (char *node, int line, enum reftype type);

/* Remember the name of the current output file.  */
extern void set_current_output_filename (const char *fname);

/* Expand macros and commands in the node name and canonicalize
   whitespace in the resulting expansion.  */
extern char *expand_node_name (char *node);

extern int number_of_node (char *node);

extern void init_tag_table (void);
extern void write_tag_table (char *filename);
extern void free_node_references (void);
extern void free_node_node_references (void);
extern void validate_file (TAG_ENTRY *tag_table);
extern void split_file (char *filename, int size);
extern void clean_old_split_files (char *filename);

#endif /* NODE_H */