code:
/* cmds.h -- declarations for cmds.c.
   $Id: cmds.h,v 1.16 2009-01-01 07:24:58 olegkat Exp $

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

#ifndef CMDS_H
#define CMDS_H

/* The three arguments a command can get are a flag saying whether it is
   before argument parsing (START) or after (END), the starting position
   of the arguments, and the ending position.  */
typedef void COMMAND_FUNCTION (); /* So we can say COMMAND_FUNCTION *foo; */

/* Each command has an associated function.  When the command is
   encountered in the text, the associated function is called with START
   as the argument.  If the function expects arguments in braces, it
   remembers itself on the stack.  When the corresponding close brace is
   encountered, the function is called with END as the argument. */
#define START 0
#define END 1

/* Does the command expect braces?  */
#define NO_BRACE_ARGS 0
#define BRACE_ARGS 1
#define MAYBE_BRACE_ARGS 2

typedef struct
{
  char *name;
  COMMAND_FUNCTION *proc;
  int argument_in_braces;
} COMMAND;

extern COMMAND command_table[];

typedef struct acronym_desc
{
  struct acronym_desc *next;
  char *acronym;
  char *description;
} ACRONYM_DESC;

/* Texinfo commands.  */
extern void insert_self (int arg),
  insert_space (int arg),
  cm_ignore_line (void),
  cm_ignore_arg (int arg, int start_pos, int end_pos),
  cm_comment (void),
  cm_no_op (void);

/* Document structure and meta information.  */
extern void cm_setfilename (void),
  cm_settitle (void),
  cm_documentdescription (void),
  cm_node (void),
  cm_menu (void),
  cm_detailmenu (void),
  cm_dircategory (void),
  cm_direntry (void),
  cm_bye (void);

/* File inclusion.  */
extern void cm_include (void),
  cm_verbatiminclude (void);

/* Cross referencing commands.  */
extern void cm_anchor (int arg),
  cm_xref (int arg),
  cm_pxref (int arg),
  cm_ref (int arg),
  cm_inforef (int arg),
  cm_uref (int arg);

/* Special insertions.  */
extern void cm_LaTeX (int arg),
  cm_TeX (int arg),
  cm_arrow (int arg),
  cm_bullet (int arg),
  cm_click (int arg),
  cm_clicksequence (int arg),
  cm_clickstyle (void),
  cm_colon (void),
  cm_comma (int arg),
  cm_copyright (int arg),
  cm_dots (int arg),
  cm_enddots (int arg),
  cm_equiv (int arg),
  cm_error (int arg),
  cm_expansion (int arg),
  cm_image (int arg),
  cm_insert_copying (void),
  cm_guilsinglleft (int arg),
  cm_guilsinglright (int arg),
  cm_point (int arg),
  cm_print (int arg),
  cm_punct (int arg),
  cm_quotedblbase (int arg),
  cm_quotedblleft (int arg),
  cm_quotedblright (int arg),
  cm_quoteleft (int arg),
  cm_quoteright (int arg),
  cm_quotesinglbase (int arg),
  cm_registeredsymbol (int arg),
  cm_result (int arg);

/* Emphasis and markup.  */
extern void cm_acronym (int arg),
  cm_abbr (int arg),
  cm_b (int arg),
  cm_cite (int arg, int position),
  cm_code (int arg),
  cm_dfn (int arg, int position),
  cm_dmn (int arg),
  cm_email (int arg),
  cm_emph (int arg),
  cm_i (int arg),
  cm_kbd (int arg),
  cm_key (int arg),
  cm_math (int arg),
  cm_not_fixed_width (int arg, int start, int end),
  cm_r (int arg),
  cm_sansserif (int arg),
  cm_sc (int arg, int start_pos, int end_pos),
  cm_slanted (int arg),
  cm_strong (int arg, int start_pos, int end_pos),
  cm_tt (int arg),
  cm_indicate_url (int arg, int start, int end),
  cm_var (int arg, int start_pos, int end_pos),
  cm_verb (int arg);

/* Block environments.  */
extern void cm_cartouche (void),
  cm_group (void),
  cm_display (void),
  cm_smalldisplay (void),
  cm_example (void),
  cm_smallexample (void),
  cm_smalllisp (void),
  cm_lisp (void),
  cm_format (void),
  cm_smallformat (void),
  cm_quotation (void),
  cm_copying (void),
  cm_flushleft (void),
  cm_flushright (void),
  cm_raggedcenter (void),
  cm_raggedleft (void),
  cm_raggedright (void),
  cm_verbatim (void),
  cm_end (void);

/* Tables, lists, enumerations.  */
extern void cm_table (void),
  cm_ftable (void),
  cm_vtable (void),
  cm_itemize (void),
  cm_enumerate (void),
  cm_multitable (void),
  cm_headitem (void),
  cm_item (void),
  cm_itemx (void),
  cm_tab (void);

extern void cm_center (void),
  cm_exdent (void),
  cm_indent (void),
  cm_noindent (void),
  cm_noindent_cmd (void);

/* Line and page breaks.  */
extern void cm_asterisk (void),
  cm_sp (void),
  cm_page (void);

/* Non breaking words.  */
extern void cm_tie (int arg),
  cm_w (int arg);

/* Title page creation.  */
extern void cm_titlepage (void),
  cm_author (void),
  cm_titlepage_cmds (void),
  cm_titlefont (int arg),
  cm_today (int arg);

/* Floats.  */
extern void cm_float (void),
  cm_caption (int arg),
  cm_shortcaption (void),
  cm_listoffloats (void);

/* Indices.  */
extern void cm_kindex (void),
  cm_cindex (void),
  cm_findex (void),
  cm_pindex (void),
  cm_vindex (void),
  cm_tindex (void),
  cm_defindex (void),
  cm_defcodeindex (void),
  cm_synindex (void),
  cm_printindex (void);

/* Conditionals. */
extern void cm_set (void),
  cm_clear (void),
  cm_ifset (void),
  cm_ifclear (void),
  cm_ifeq (void),
  cm_value (int arg, int start_pos, int end_pos);

#endif /* !CMDS_H */


code:
/* defun.c -- @defun and friends.
   $Id: defun.c,v 1.18 2007-09-15 23:48:45 karl Exp $

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

#include "system.h"
#include "defun.h"
#include "xml.h"
#include "insertion.h"
#include "makeinfo.h"
#include "cmds.h"
#include "html.h"

#define DEFUN_SELF_DELIMITING(c) \
  ((c) == '(' || (c) == ')' || (c) == '[' || (c) == ']')

struct token_accumulator
{
  unsigned int length;
  unsigned int index;
  char **tokens;
};

static void
initialize_token_accumulator (struct token_accumulator *accumulator)
{
  accumulator->length = 0;
  accumulator->index = 0;
  accumulator->tokens = NULL;
}

static void
accumulate_token (struct token_accumulator *accumulator, char *token)
{
  if (accumulator->index >= accumulator->length)
    {
      accumulator->length += 10;
      accumulator->tokens = xrealloc (accumulator->tokens,
                                      (accumulator->length * sizeof (char *)));
    }
  accumulator->tokens[accumulator->index] = token;
  accumulator->index += 1;
}

/* Given STRING_POINTER pointing at an open brace, skip forward and return a
   pointer to just past the matching close brace. */
static int
scan_group_in_string (char **string_pointer)
{
  char *scan_string = (*string_pointer) + 1;
  unsigned int level = 1;
  int started_command = 0;

  for (;;)
    {
      int c;
      if (level == 0)
        {
          *string_pointer = scan_string;
          return 1;
        }
      c = *scan_string++;
      if (c == 0)
        {
          /* Tweak line_number to compensate for fact that
             we gobbled the whole line before coming here. */
          line_number--;
          line_error (_("Missing `}' in @def arg"));
          line_number++;
          *string_pointer = scan_string - 1;
          return 0;
        }

      if (c == '{' && !started_command)
        level++;
      if (c == '}' && !started_command)
        level--;

      /* remember if at @.  */
      started_command = (c == '@' && !started_command);
    }
}

/* Return a list of tokens from the contents of STRING.
   Commands and brace-delimited groups count as single tokens.
   Contiguous whitespace characters are converted to a token
   consisting of a single space. */
static char **
args_from_string (char *string)
{
  struct token_accumulator accumulator;
  char *token_start, *token_end;
  char *scan_string = string;

  initialize_token_accumulator (&accumulator);

  while (*scan_string)
    { /* Replace arbitrary whitespace by a single space. */
      if (whitespace (*scan_string))
        {
          scan_string += 1;
          while (whitespace (*scan_string))
            scan_string += 1;
          accumulate_token ((&accumulator), (xstrdup (" ")));
          continue;
        }

      /* Commands count as single tokens. */
      if (*scan_string == COMMAND_PREFIX)
        {
          token_start = scan_string;
          scan_string += 1;
          if (self_delimiting (*scan_string))
            scan_string += 1;
          else
            {
              int c;
              while (1)
                {
                  c = *scan_string++;

                  if ((c == 0) || (c == '{') || (whitespace (c)))
                    {
                      scan_string -= 1;
                      break;
                    }
                }

              if (*scan_string == '{')
                {
                  char *s = scan_string;
                  (void) scan_group_in_string (&s);
                  scan_string = s;
                }
            }
          token_end = scan_string;
        }

      /* Parentheses and brackets are self-delimiting. */
      else if (DEFUN_SELF_DELIMITING (*scan_string))
        {
          token_start = scan_string;
          scan_string += 1;
          token_end = scan_string;
        }

      /* Open brace introduces a group that is a single token. */
      else if (*scan_string == '{')
        {
          char *s = scan_string;
          int balanced = scan_group_in_string (&s);

          token_start = scan_string + 1;
          scan_string = s;
          token_end = balanced ? (scan_string - 1) : scan_string;
        }

      /* Make commas separate tokens so to differentiate them from
         parameter types in XML output. */
      else if (*scan_string == ',')
	{
          token_start = scan_string;
          scan_string += 1;
          token_end = scan_string;
	}

      /* Otherwise a token is delimited by whitespace, parentheses,
         brackets, or braces.  A token is also ended by a command. */
      else
        {
          token_start = scan_string;

          for (;;)
            {
              int c;

              c = *scan_string++;

              /* Do not back up if we're looking at a }; since the only
                 valid }'s are those matched with {'s, we want to give
                 an error.  If we back up, we go into an infinite loop.  */
              if (!c || whitespace (c) || DEFUN_SELF_DELIMITING (c)
                  || c == '{')
                {
                  scan_string--;
                  break;
                }

	      /* End token if we are looking at a comma, as commas are
		 delimiters too. */
	      if (c == ',')
		{
		  scan_string--;
		  break;
		}

              /* If we encounter a command embedded within a token,
                 then end the token. */
              if (c == COMMAND_PREFIX)
                {
                  scan_string--;
                  break;
                }
            }
          token_end = scan_string;
        }

      accumulate_token (&accumulator, substring (token_start, token_end));
    }
  accumulate_token (&accumulator, NULL);
  return accumulator.tokens;
}

static void
process_defun_args (char **defun_args, int auto_var_p)
{
  int pending_space = 0;

  if (xml)
    {
      xml_process_defun_args (defun_args, auto_var_p);
      return;
    }

  for (;;)
    {
      char *defun_arg = *defun_args++;

      if (defun_arg == NULL)
        break;

      if (defun_arg[0] == ' ')
        {
          pending_space = 1;
          continue;
        }

      if (pending_space)
        {
          add_char (' ');
          pending_space = 0;
        }

      if (DEFUN_SELF_DELIMITING (defun_arg[0]))
        {
          /* Within @deffn and friends, texinfo.tex makes parentheses
             sans serif and brackets bold.  We use roman instead.  */
          if (html)
            insert_html_tag (START, "");

          add_char (defun_arg[0]);

          if (html)
            insert_html_tag (END, "");
        }
      /* else if (defun_arg[0] == '&' || defun_arg[0] == COMMAND_PREFIX) */
        /* execute_string ("%s", defun_arg); */
      /* else if (auto_var_p) */
        /* execute_string ("%s", defun_arg); */
      else
        execute_string ("%s", defun_arg);
    }
}

static char *
next_nonwhite_defun_arg (char ***arg_pointer)
{
  char **scan = (*arg_pointer);
  char *arg = (*scan++);

  if ((arg != 0) && (*arg == ' '))
    arg = *scan++;

  if (arg == 0)
    scan -= 1;

  *arg_pointer = scan;

  return (arg == 0) ? "" : arg;
}

/* This is needed also in insertion.c.  */

enum insertion_type
get_base_type (enum insertion_type type)
{
  enum insertion_type base_type;
  switch (type)
    {
    case defivar:	base_type = defcv; break;
    case defmac:	base_type = deffn; break;
    case defmethod:	base_type = defop; break;
    case defopt:	base_type = defvr; break;
    case defspec:	base_type = deffn; break;
    case deftypecv:	base_type = deftypecv; break;
    case deftypefun:	base_type = deftypefn; break;
    case deftypeivar:	base_type = deftypeivar; break;
    case deftypemethod:	base_type = deftypemethod; break;
    case deftypeop:	base_type = deftypeop; break;
    case deftypevar:	base_type = deftypevr; break;
    case defun:		base_type = deffn; break;
    case defvar:	base_type = defvr; break;
    default:
      base_type = type;
      break;
    }

  return base_type;
}

/* Make the defun type insertion.
   TYPE says which insertion this is.
   X_P, if nonzero, says not to start a new insertion. */
static void
defun_internal (enum insertion_type type, int x_p)
{
  enum insertion_type base_type;
  char **defun_args, **scan_args;
  const char *category;
  char *defined_name;
  char *type_name = NULL;
  char *type_name2 = NULL;

  {
    char *line;

    /* The @def.. line is the only place in Texinfo where you are
       allowed to use unquoted braces that don't delimit arguments of
       a command or a macro; in any other place it will trigger an
       error message from the reader loop.  The special handling of
       this case inside `args_from_string' is an extra special hack
       which allows this.  The side effect is that if we try to expand
       the rest of the line below, the recursive reader loop will
       signal an error if there are brace-delimited arguments on that line.

       The best solution to this would be to change the syntax of
       @def.. commands so that it doesn't violate Texinfo's own rules.
       But it's probably too late for this now, as it will break a lot
       of existing manuals.

       Unfortunately, this means that you can't call macros, use @value, etc.
       inside @def.. commands, sigh.  */
    get_rest_of_line (0, &line);

    /* Basic line continuation.  If a line ends with \s*@\s* concatanate
       the next line. */
    {
      char *next_line, *new_line;
      int i;

      line_continuation:
        i = strlen (line) - 1;

        if (i > 0 && line[i] == '@' && line[i-1] != '@')
          {
            get_rest_of_line (0, &next_line);
            new_line = (char *) xmalloc (i + strlen (next_line) + 2);
            strncpy (new_line, line, i);
            new_line[i] = '\0';
            free (line);
            strcat (new_line, " ");
            strcat (new_line, next_line);
            line = xstrdup (new_line);
            free (next_line);
            free (new_line);

            goto line_continuation;
          }
    }

    defun_args = (args_from_string (line));
    free (line);
  }

  scan_args = defun_args;

  /* Get base type and category string.  */
  base_type = get_base_type (type);

  /* xx all these const strings should be determined upon
     documentlanguage argument and NOT via gettext  (kama).  */
  switch (type)
    {
    case defun:
    case deftypefun:
      category = gdt("Function");
      break;
    case defmac:
      category = gdt("Macro");
      break;
    case defspec:
      category = gdt("Special Form");
      break;
    case defvar:
    case deftypevar:
      category = gdt("Variable");
      break;
    case defopt:
      category = gdt("User Option");
      break;
    case defivar:
    case deftypeivar:
      category = gdt("Instance Variable");
      break;
    case defmethod:
    case deftypemethod:
      category = gdt("Method");
      break;
    default:
      category = next_nonwhite_defun_arg (&scan_args);
      break;
    }

  /* The class name.  */
  if ((base_type == deftypecv)
      || (base_type == deftypefn)
      || (base_type == deftypevr)
      || (base_type == defcv)
      || (base_type == defop)
      || (base_type == deftypeivar)
      || (base_type == deftypemethod)
      || (base_type == deftypeop)
     )
    type_name = next_nonwhite_defun_arg (&scan_args);

  /* The type name for typed languages.  */
  if ((base_type == deftypecv)
      || (base_type == deftypeivar)
      || (base_type == deftypemethod)
      || (base_type == deftypeop)
     )
    type_name2 = next_nonwhite_defun_arg (&scan_args);

  /* The function or whatever that's actually being defined.  */
  defined_name = next_nonwhite_defun_arg (&scan_args);

  /* This hack exists solely for the purposes of formatting the Texinfo
     manual.  I couldn't think of a better way.  The token might be a
     simple @@ followed immediately by more text.  If this is the case,
     then the next defun arg is part of this one, and we should
     concatenate them. */
  if (*scan_args && **scan_args && !whitespace (**scan_args)
       && STREQ (defined_name, "@@"))
    {
      char *tem = xmalloc (3 + strlen (scan_args[0]));

      sprintf (tem, "@@%s", scan_args[0]);

      free (scan_args[0]);
      scan_args[0] = tem;
      scan_args++;
      defined_name = tem;
    }

  /* It's easy to write @defun foo(arg1 arg2), but a following ( is
     misparsed by texinfo.tex and this is next to impossible to fix.
     Warn about it.  */
  if (*scan_args && **scan_args && **scan_args == '(')
    warning ("`%c' follows defined name `%s' instead of whitespace",
             **scan_args, defined_name);

  if (!x_p)
    begin_insertion (type);

  /* Write the definition header line.
     This should start at the normal indentation.  */
  current_indent -= default_indentation_increment;
  start_paragraph ();

  if (!html && !xml)
    switch (base_type)
      {
      case deffn:
      case defvr:
      case deftp:
        execute_string (" --- %s: %s", category, defined_name);
        break;
      case deftypefn:
      case deftypevr:
        execute_string (" --- %s: %s %s", category, type_name, defined_name);
        break;
      case defcv:
        execute_string (" --- %s %s %s: %s", category, gdt("of"), type_name,
                        defined_name);
        break;
      case deftypecv:
      case deftypeivar:
        execute_string (" --- %s %s %s: %s %s", category, gdt("of"), type_name,
                        type_name2, defined_name);
        break;
      case defop:
        execute_string (" --- %s %s %s: %s", category, gdt("on"), type_name,
                        defined_name);
        break;
      case deftypeop:
        execute_string (" --- %s %s %s: %s %s", category, gdt("on"), type_name,
                        type_name2, defined_name);
        break;
      case deftypemethod:
        execute_string (" --- %s %s %s: %s %s", category, gdt("on"), type_name,
                        type_name2, defined_name);
        break;
      }
  else if (html)
    {
      /* If this is not a @def...x version, it could only
         be a normal version @def.... So start the table here.  */
      if (!x_p)
        insert_string ("
\n");
      else
        rollback_empty_tag ("blockquote");

      /* xx The single words (on, off) used here, should depend on
         documentlanguage and NOT on gettext  --kama.  */
      switch (base_type)
        {
        case deffn:
        case defvr:
        case deftp:
        case deftypefn:
        case deftypevr:
          execute_string ("--- %s: ", category);
          break;

        case defcv:
        case deftypecv:
        case deftypeivar:
	  execute_string ("--- %s %s %s: ", category, gdt("of"), type_name);
	  break;

        case defop:
        case deftypemethod:
        case deftypeop:
	  execute_string ("--- %s %s %s: ", category, gdt("on"), type_name);
	  break;
	} /* switch (base_type)... */

      switch (base_type)
        {
        case deffn:
        case defvr:
        case deftp:
          /* <var> is for the following function arguments.  */
          insert_html_tag (START, "b");
          execute_string ("%s", defined_name);
          insert_html_tag (END, "b");
          insert_html_tag (START, "var");
          break;
        case deftypefn:
        case deftypevr:
          execute_string ("%s ", type_name);
          insert_html_tag (START, "b");
          execute_string ("%s", defined_name);
          insert_html_tag (END, "b");
          insert_html_tag (START, "var");
          break;
        case defcv:
        case defop:
          insert_html_tag (START, "b");
          execute_string ("%s", defined_name);
          insert_html_tag (END, "b");
          insert_html_tag (START, "var");
          break;
        case deftypecv:
        case deftypeivar:
        case deftypemethod:
        case deftypeop:
          execute_string ("%s ", type_name2);
          insert_html_tag (START, "b");
          execute_string ("%s", defined_name);
          insert_html_tag (END, "b");
          insert_html_tag (START, "var");
          break;
        }
    }
  else if (xml)
    xml_begin_def_term (base_type, category, defined_name, type_name,
	type_name2);

  current_indent += default_indentation_increment;

  /* Now process the function arguments, if any.  If these carry onto
     the next line, they should be indented by two increments to
     distinguish them from the body of the definition, which is indented
     by one increment.  */
  current_indent += default_indentation_increment;

  switch (base_type)
    {
    case deffn:
    case defop:
      process_defun_args (scan_args, 1);
      break;

      /* Through Makeinfo 1.67 we processed remaining args only for deftp,
         deftypefn, and deftypemethod.  But the libc manual, for example,
         needs to say:
            @deftypevar {char *} tzname[2]
         And simply allowing the extra text seems far simpler than trying
         to invent yet more defn commands.  In any case, we should either
         output it or give an error, not silently ignore it.  */
    default:
      process_defun_args (scan_args, 0);
      break;
    }

  current_indent -= default_indentation_increment;
  if (!html)
    close_single_paragraph ();

  /* Make an entry in the appropriate index.  (XML and
     Docbook already got their entries, so skip them.)  */
  if (!xml)
    switch (base_type)
      {
      case deffn:
      case deftypefn:
	execute_string ("@findex %s\n", defined_name);
	break;
      case defcv:
      case deftypecv:
      case deftypevr:
      case defvr:
	execute_string ("@vindex %s\n", defined_name);
	break;
      case deftypeivar:
	execute_string ("@vindex %s %s %s\n", defined_name, gdt("of"),
                        type_name);
	break;
      case defop:
      case deftypeop:
      case deftypemethod:
	execute_string ("@findex %s %s %s\n", defined_name, gdt("on"),
                        type_name);
	break;
      case deftp:
	execute_string ("@tindex %s\n", defined_name);
	break;
      }

  if (xml)
    xml_end_def_term ();
  else if (html)
    {
      inhibit_paragraph_indentation = 1;
      no_indent = 1;
      insert_html_tag (END, "var");
      insert_string ("
\n");
      /* Indent the definition a bit.  */
      add_html_block_elt ("
");
      no_indent = 0;
      inhibit_paragraph_indentation = 0;
      paragraph_is_open = 0;
    }

  /* Deallocate the token list. */
  scan_args = defun_args;
  while (1)
    {
      char * arg = (*scan_args++);
      if (arg == NULL)
        break;
      free (arg);
    }
  free (defun_args);
}

/* Add an entry for a function, macro, special form, variable, or option.
   If the name of the calling command ends in `x', then this is an extra
   entry included in the body of an insertion of the same type. */
void
cm_defun (void)
{
  enum insertion_type type;
  char *base_command = xstrdup (command);  /* command with any `x' removed */
  /* FIXME: is strlen(command) allways > 0? */
  int x_p = (command[strlen (command) - 1] == 'x');

  if (x_p)
    base_command[strlen (base_command) - 1] = 0;

  type = find_type_from_name (base_command);

  /* If we are adding to an already existing insertion, then make sure
     that we are already in an insertion of type TYPE. */
  if (x_p)
    {
      INSERTION_ELT *i = insertion_stack;
      /* Skip over ifclear and ifset conditionals.  */
      while (i && (i->insertion == ifset || i->insertion == ifclear))
        i = i->next;

      if (!i || i->insertion != type)
        {
          line_error (_("Must be in `@%s' environment to use `@%s'"),
                      base_command, command);
          discard_until ("\n");
          return;
        }
    }

  defun_internal (type, x_p);
  free (base_command);
}

repo_name: mthl/texinfo

license: mit


code:
/* defun.h -- declaration for defuns.
   $Id: defun.h,v 1.6 2007-07-01 21:20:32 karl Exp $

   Copyright (C) 1999, 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/>.

   Written by Karl Heinz Marbaise <kama@hippo.fido.de>.  */

#ifndef DEFUN_H
#define DEFUN_H

#include "insertion.h"

extern enum insertion_type get_base_type (enum insertion_type);
extern void cm_defun (void);

#endif /* !DEFUN_H */


code:
/* files.c -- file-related functions for makeinfo.
   $Id: files.c,v 1.8 2007-07-01 21:20:32 karl Exp $

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

#include "system.h"
#include "files.h"
#include "html.h"
#include "index.h"
#include "macro.h"
#include "makeinfo.h"
#include "node.h"

FSTACK *filestack = NULL;

static int node_filename_stack_index = 0;
static int node_filename_stack_size = 0;
static char **node_filename_stack = NULL;

/* Looking for include files.  */

/* Given a string containing units of information separated by colons,
   return the next one pointed to by INDEX, or NULL if there are no more.
   Advance INDEX to the character after the colon. */
static char *
extract_colon_unit (char *string, int *index)
{
  int start;
  int path_sep_char = PATH_SEP[0];
  int i = *index;

  if (!string || (i >= strlen (string)))
    return NULL;

  /* Each call to this routine leaves the index pointing at a colon if
     there is more to the path.  If i > 0, then increment past the
     `:'.  If i == 0, then the path has a leading colon.  Trailing colons
     are handled OK by the `else' part of the if statement; an empty
     string is returned in that case. */
  if (i && string[i] == path_sep_char)
    i++;

  start = i;
  while (string[i] && string[i] != path_sep_char) i++;
  *index = i;

  if (i == start)
    {
      if (string[i])
        (*index)++;

      /* Return "" in the case of a trailing `:'. */
      return xstrdup ("");
    }
  else
    {
      char *value;

      value = xmalloc (1 + (i - start));
      memcpy (value, &string[start], (i - start));
      value [i - start] = 0;

      return value;
    }
}

/* Return the full pathname for FILENAME by searching along PATH.
   When found, return the stat () info for FILENAME in FINFO.
   If PATH is NULL, only the current directory is searched.
   If the file could not be found, return a NULL pointer. */
char *
get_file_info_in_path (char *filename, char *path, struct stat *finfo)
{
  char *dir;
  int result, index = 0;

  if (path == NULL)
    path = ".";

  /* Handle absolute pathnames.  */
  if (IS_ABSOLUTE (filename)
      || (*filename == '.'
          && (IS_SLASH (filename[1])
              || (filename[1] == '.' && IS_SLASH (filename[2])))))
    {
      if (stat (filename, finfo) == 0)
        return xstrdup (filename);
      else
        return NULL;
    }

  while ((dir = extract_colon_unit (path, &index)))
    {
      char *fullpath;

      if (!*dir)
        {
          free (dir);
          dir = xstrdup (".");
        }

      fullpath = xmalloc (2 + strlen (dir) + strlen (filename));
      sprintf (fullpath, "%s/%s", dir, filename);
      free (dir);

      result = stat (fullpath, finfo);

      if (result == 0)
        return fullpath;
      else
        free (fullpath);
    }
  return NULL;
}

/* Prepend and append new paths to include_files_path.  */
void
prepend_to_include_path (char *path)
{
  if (!include_files_path)
    {
      include_files_path = xstrdup (path);
      include_files_path = xrealloc (include_files_path,
          strlen (include_files_path) + 3); /* 3 for ":.\0" */
      strcat (strcat (include_files_path, PATH_SEP), ".");
    }
  else
    {
      char *tmp = xstrdup (include_files_path);
      include_files_path = xrealloc (include_files_path,
          strlen (include_files_path) + strlen (path) + 2); /* 2 for ":\0" */
      strcpy (include_files_path, path);
      strcat (include_files_path, PATH_SEP);
      strcat (include_files_path, tmp);
      free (tmp);
    }
}

void
append_to_include_path (char *path)
{
  if (!include_files_path)
    include_files_path = xstrdup (".");

  include_files_path = (char *) xrealloc (include_files_path,
        2 + strlen (include_files_path) + strlen (path));
  strcat (include_files_path, PATH_SEP);
  strcat (include_files_path, path);
}

/* Remove the first path from the include_files_path.  */
void
pop_path_from_include_path (void)
{
  int i = 0;
  char *tmp;

  if (include_files_path)
    for (i = 0; i < strlen (include_files_path)
        && include_files_path[i] != ':'; i++);

  /* Advance include_files_path to the next char from ':'  */
  tmp = (char *) xmalloc (strlen (include_files_path) - i);
  strcpy (tmp, (char *) include_files_path + i + 1);

  free (include_files_path);
  include_files_path = tmp;
}

/* Find and load the file named FILENAME.  Return a pointer to
   the loaded file, or NULL if it can't be loaded.  If USE_PATH is zero,
   just look for the given file (this is used in handle_delayed_writes),
   else search along include_files_path.   */

char *
find_and_load (char *filename, int use_path)
{
  struct stat fileinfo;
  long file_size;
  int file = -1, count = 0;
  char *fullpath, *result;
  int n, bytes_to_read;

  result = fullpath = NULL;

  fullpath
    = get_file_info_in_path (filename, use_path ? include_files_path : NULL,
                             &fileinfo);

  if (!fullpath)
    goto error_exit;

  filename = fullpath;
  file_size = (long) fileinfo.st_size;

  file = open (filename, O_RDONLY);
  if (file < 0)
    goto error_exit;

  /* Load the file, with enough room for a newline and a null. */
  result = xmalloc (file_size + 2);

  /* VMS stat lies about the st_size value.  The actual number of
     readable bytes is always less than this value.  The arcane
     mysteries of VMS/RMS are too much to probe, so this hack
    suffices to make things work.  It's also needed on Cygwin.  And so
    we might as well use it everywhere.  */
  bytes_to_read = file_size;
  while ((n = read (file, result + count, bytes_to_read)) > 0)
    {
      count += n;
      bytes_to_read -= n;
    }
  if (0 < count && count < file_size)
    result = xrealloc (result, count + 2); /* why waste the slack? */
  else if (n == -1)
error_exit:
    {
      if (result)
        free (result);

      if (fullpath)
        free (fullpath);

      if (file != -1)
        close (file);

      return NULL;
    }
  close (file);

  /* Set the globals to the new file. */
  input_text = result;
  input_text_length = count;
  input_filename = fullpath;
  node_filename = xstrdup (fullpath);
  input_text_offset = 0;
  line_number = 1;
  /* Not strictly necessary.  This magic prevents read_token () from doing
     extra unnecessary work each time it is called (that is a lot of times).
     INPUT_TEXT_LENGTH is one past the actual end of the text. */
  input_text[input_text_length] = '\n';
  /* This, on the other hand, is always necessary.  */
  input_text[input_text_length+1] = 0;
  return result;
}

/* Pushing and popping files.  */
static void
push_node_filename (void)
{
  if (node_filename_stack_index + 1 > node_filename_stack_size)
    node_filename_stack = xrealloc
    (node_filename_stack, (node_filename_stack_size += 10) * sizeof (char *));

  node_filename_stack[node_filename_stack_index] = node_filename;
  node_filename_stack_index++;
}

static void
pop_node_filename (void)
{
  node_filename = node_filename_stack[--node_filename_stack_index];
}

/* Save the state of the current input file. */
void
pushfile (void)
{
  FSTACK *newstack = xmalloc (sizeof (FSTACK));
  newstack->filename = input_filename;
  newstack->text = input_text;
  newstack->size = input_text_length;
  newstack->offset = input_text_offset;
  newstack->line_number = line_number;
  newstack->next = filestack;

  filestack = newstack;
  push_node_filename ();
}

/* Make the current file globals be what is on top of the file stack. */
void
popfile (void)
{
  FSTACK *tos = filestack;

  if (!tos)
    abort ();                   /* My fault.  I wonder what I did? */

  if (macro_expansion_output_stream)
    {
      maybe_write_itext (input_text, input_text_offset);
      forget_itext (input_text);
    }

  /* Pop the stack. */
  filestack = filestack->next;

  /* Make sure that commands with braces have been satisfied. */
  if (!executing_string && !me_executing_string)
    discard_braces ();

  /* Get the top of the stack into the globals. */
  input_filename = tos->filename;
  input_text = tos->text;
  input_text_length = tos->size;
  input_text_offset = tos->offset;
  line_number = tos->line_number;
  free (tos);

  /* Go back to the (now) current node. */
  pop_node_filename ();
}

/* Flush all open files on the file stack. */
void
flush_file_stack (void)
{
  while (filestack)
    {
      char *fname = input_filename;
      char *text = input_text;
      popfile ();
      free (fname);
      free (text);
    }
}

/* Return the index of the first character in the filename
   which is past all the leading directory characters.  */
static int
skip_directory_part (char *filename)
{
  int i = strlen (filename) - 1;

  while (i && !IS_SLASH (filename[i]))
    i--;
  if (IS_SLASH (filename[i]))
    i++;
  else if (filename[i] && HAVE_DRIVE (filename))
    i = 2;

  return i;
}

static char *
filename_non_directory (char *name)
{
  return xstrdup (name + skip_directory_part (name));
}

/* Return just the simple part of the filename; i.e. the
   filename without the path information, or extensions.
   This conses up a new string. */
char *
filename_part (char *filename)
{
  char *basename = filename_non_directory (filename);

#ifdef REMOVE_OUTPUT_EXTENSIONS
  /* See if there is an extension to remove.  If so, remove it. */
  {
    char *temp = strrchr (basename, '.');
    if (temp)
      *temp = 0;
  }
#endif /* REMOVE_OUTPUT_EXTENSIONS */
  return basename;
}

/* Return the pathname part of filename.  This can be NULL. */
char *
pathname_part (char *filename)
{
  char *result = NULL;
  int i;

  filename = expand_filename (filename, "");

  i = skip_directory_part (filename);
  if (i)
    {
      result = xmalloc (1 + i);
      strncpy (result, filename, i);
      result[i] = 0;
    }
  free (filename);
  return result;
}

/* Return the full path to FILENAME. */
static char *
full_pathname (char *filename)
{
  int initial_character;
  char *result;

  /* No filename given? */
  if (!filename || !*filename)
    return xstrdup ("");

  /* Already absolute? */
  if (IS_ABSOLUTE (filename) ||
      (*filename == '.' &&
       (IS_SLASH (filename[1]) ||
        (filename[1] == '.' && IS_SLASH (filename[2])))))
    return xstrdup (filename);

  initial_character = *filename;
  if (initial_character != '~')
    {
      char *localdir = xmalloc (1025);
#ifdef HAVE_GETCWD
      if (!getcwd (localdir, 1024))
#else
      if (!getwd (localdir))
#endif
        {
          fprintf (stderr, _("%s: getwd: %s, %s\n"),
                   progname, filename, localdir);
          xexit (1);
        }

      strcat (localdir, "/");
      strcat (localdir, filename);
      result = xstrdup (localdir);
      free (localdir);
    }
  else
    { /* Does anybody know why WIN32 doesn't want to support $HOME?
         If the reason is they don't have getpwnam, they should
         only disable the else clause below.  */
#ifndef WIN32
      if (IS_SLASH (filename[1]))
        {
          /* Return the concatenation of the environment variable HOME
             and the rest of the string. */
          char *temp_home;

          temp_home = (char *) getenv ("HOME");
          result = xmalloc (strlen (&filename[1])
                                    + 1
                                    + temp_home ? strlen (temp_home)
                                    : 0);
          *result = 0;

          if (temp_home)
            strcpy (result, temp_home);

          strcat (result, &filename[1]);
        }
      else
        {
          struct passwd *user_entry;
          int i, c;
          char *username = xmalloc (257);

          for (i = 1; (c = filename[i]); i++)
            {
              if (IS_SLASH (c))
                break;
              else
                username[i - 1] = c;
            }
          if (c)
            username[i - 1] = 0;

          user_entry = getpwnam (username);

          if (!user_entry)
            return xstrdup (filename);

          result = xmalloc (1 + strlen (user_entry->pw_dir)
                                    + strlen (&filename[i]));
          strcpy (result, user_entry->pw_dir);
          strcat (result, &filename[i]);
        }
#endif /* not WIN32 */
    }
  return result;
}

/* Return the expansion of FILENAME. */
char *
expand_filename (char *filename, char *input_name)
{
  int i;

  if (filename)
    {
      filename = full_pathname (filename);
      if (IS_ABSOLUTE (filename)
	  || (*filename == '.' &&
	      (IS_SLASH (filename[1]) ||
	       (filename[1] == '.' && IS_SLASH (filename[2])))))
	return filename;
    }
  else
    {
      filename = filename_non_directory (input_name);

      if (!*filename)
        {
          free (filename);
          filename = xstrdup ("noname.texi");
        }

      for (i = strlen (filename) - 1; i; i--)
        if (filename[i] == '.')
          break;

      if (!i)
        i = strlen (filename);

      if (i + 6 > (strlen (filename)))
        filename = xrealloc (filename, i + 6);
      strcpy (filename + i, html ? ".html" : ".info");
      return filename;
    }

  if (IS_ABSOLUTE (input_name))
    {
      /* Make it so that relative names work. */
      char *result;

      i = strlen (input_name) - 1;

      result = xmalloc (1 + strlen (input_name) + strlen (filename));
      strcpy (result, input_name);

      while (!IS_SLASH (result[i]) && i)
        i--;
      if (IS_SLASH (result[i]))
        i++;

      strcpy (&result[i], filename);
      free (filename);
      return result;
    }
  return filename;
}

char *
output_name_from_input_name (char *name)
{
  return expand_filename (NULL, name);
}

/* Modify the file name FNAME so that it fits the limitations of the
   underlying filesystem.  In particular, truncate the file name as it
   would be truncated by the filesystem.  We assume the result can
   never be longer than the original, otherwise we couldn't be sure we
   have enough space in the original string to modify it in place.  */
char *
normalize_filename (char *fname)
{
  int maxlen;
  char orig[PATH_MAX + 1];
  int i;
  char *lastdot, *p;

#ifdef _PC_NAME_MAX
  maxlen = pathconf (fname, _PC_NAME_MAX);
  if (maxlen < 1)
#endif
    maxlen = PATH_MAX;

  i = skip_directory_part (fname);
  if (fname[i] == '\0')
    return fname;	/* only a directory name -- don't modify */
  strcpy (orig, fname + i);

  switch (maxlen)
    {
      case 12:	/* MS-DOS 8+3 filesystem */
	if (orig[0] == '.')	/* leading dots are not allowed */
	  orig[0] = '_';
	lastdot = strrchr (orig, '.');
	if (!lastdot)
	  lastdot = orig + strlen (orig);
	strncpy (fname + i, orig, lastdot - orig);
	for (p = fname + i;
	     p < fname + i + (lastdot - orig) && p < fname + i + 8;
	     p++)
	  if (*p == '.')
	    *p = '_';
	*p = '\0';
	if (*lastdot == '.')
	  strncat (fname + i, lastdot, 4);
	break;
      case 14:	/* old Unix systems with 14-char limitation */
	strcpy (fname + i, orig);
	if (strlen (fname + i) > 14)
	  fname[i + 14] = '\0';
	break;
      default:
	strcpy (fname + i, orig);
	if (strlen (fname) > maxlen - 1)
	  fname[maxlen - 1] = '\0';
	break;
    }

  return fname;
}

/* Delayed writing functions.  A few of the commands
   needs to be handled at the end, namely @contents,
   @shortcontents, @printindex and @listoffloats.
   These functions take care of that.  */
static DELAYED_WRITE *delayed_writes = NULL;
int handling_delayed_writes = 0;

void
register_delayed_write (char *delayed_command)
{
  DELAYED_WRITE *new;

  if (!current_output_filename || !*current_output_filename)
    {
      /* Cannot register if we don't know what the output file is.  */
      warning (_("`%s' omitted before output filename"), delayed_command);
      return;
    }

  if (STREQ (current_output_filename, "-"))
    {
      /* Do not register a new write if the output file is not seekable.
         Let the user know about it first, though.  */
      warning (_("`%s' omitted since writing to stdout"), delayed_command);
      return;
    }

  /* Don't complain if the user is writing /dev/null, since surely they
     don't care, but don't register the delayed write, either.  */
  if (FILENAME_CMP (current_output_filename, NULL_DEVICE) == 0
      || FILENAME_CMP (current_output_filename, ALSO_NULL_DEVICE) == 0)
    return;

  /* We need the HTML header in the output,
     to get a proper output_position.  */
  if (!executing_string && html)
    output_head ();
  /* Get output_position updated.  */
  flush_output ();

  new = xmalloc (sizeof (DELAYED_WRITE));
  new->command = xstrdup (delayed_command);
  new->filename = xstrdup (current_output_filename);
  new->input_filename = xstrdup (input_filename);
  new->position = output_position;
  new->calling_line = line_number;
  new->node = current_node ? xstrdup (current_node): "";

  new->node_order = node_order;
  new->index_order = index_counter;

  new->next = delayed_writes;
  delayed_writes = new;
}

void
handle_delayed_writes (void)
{
  DELAYED_WRITE *temp = (DELAYED_WRITE *) reverse_list
    ((GENERIC_LIST *) delayed_writes);
  int position_shift_amount, line_number_shift_amount;
  char *delayed_buf;

  handling_delayed_writes = 1;

  while (temp)
    {
      delayed_buf = find_and_load (temp->filename, 0);

      if (output_paragraph_offset > 0)
        {
          error (_("Output buffer not empty."));
          return;
        }

      if (!delayed_buf)
        {
          fs_error (temp->filename);
          return;
        }

      output_stream = fopen (temp->filename, "w");
      if (!output_stream)
        {
          fs_error (temp->filename);
          return;
        }

      if (fwrite (delayed_buf, 1, temp->position, output_stream) != temp->position)
        {
          fs_error (temp->filename);
          return;
        }

      {
        int output_position_at_start = output_position;
        int line_number_at_start = output_line_number;

        /* In order to make warnings and errors
           refer to the correct line number.  */
        input_filename = temp->input_filename;
        line_number = temp->calling_line;

        execute_string ("%s", temp->command);
        flush_output ();

        /* Since the output file is modified, following delayed writes
           need to be updated by this amount.  */
        position_shift_amount = output_position - output_position_at_start;
        line_number_shift_amount = output_line_number - line_number_at_start;
      }

      if (fwrite (delayed_buf + temp->position, 1,
            input_text_length - temp->position, output_stream)
          != input_text_length - temp->position
          || fclose (output_stream) != 0)
        fs_error (temp->filename);

      /* Done with the buffer.  */
      free (delayed_buf);

      /* Update positions in tag table for nodes that are defined after
         the line this delayed write is registered.  */
      if (!html && !xml)
        {
          TAG_ENTRY *node;
          for (node = tag_table; node; node = node->next_ent)
            if (node->order > temp->node_order)
              node->position += position_shift_amount;
        }

      /* Something similar for the line numbers in all of the defined
         indices.  */
      {
        int i;
        for (i = 0; i < defined_indices; i++)
          if (name_index_alist[i])
            {
              char *name = ((INDEX_ALIST *) name_index_alist[i])->name;
              INDEX_ELT *index;
              for (index = index_list (name); index; index = index->next)
                if ((no_headers || STREQ (index->node, temp->node))
                    && index->entry_number > temp->index_order)
                  index->output_line += line_number_shift_amount;
            }
      }

      /* Shift remaining delayed positions
         by the length of this write.  */
      {
        DELAYED_WRITE *future_write = temp->next;
        while (future_write)
          {
            if (STREQ (temp->filename, future_write->filename))
              future_write->position += position_shift_amount;
            future_write = future_write->next;
          }
      }

      temp = temp->next;
    }
}


code:
/* files.h -- declarations for files.c.
   $Id: files.h,v 1.7 2007-07-01 21:20:32 karl Exp $

   Copyright (C) 1998, 2002, 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 FILES_H
#define FILES_H

/* A stack of file information records.  If a new file is read in with
   "@input", we remember the old input file state on this stack. */
typedef struct fstack
{
  struct fstack *next;
  char *filename;
  char *text;
  int size;
  int offset;
  int line_number;
} FSTACK;
extern FSTACK *filestack;

extern void pushfile (void);
extern void popfile (void);
extern void flush_file_stack (void);
extern char *get_file_info_in_path (char *filename, char *path,
    struct stat *finfo);
extern char *find_and_load (char *filename, int use_path);
extern char *output_name_from_input_name (char *name);
extern char *expand_filename (char *filename, char *input_name);
extern char *filename_part (char *filename);
extern char *pathname_part (char *filename);
extern char *normalize_filename (char *fname);
extern void append_to_include_path (char *path);
extern void prepend_to_include_path (char *path);
extern void pop_path_from_include_path (void);
extern void register_delayed_write (char *delayed_command);
extern void handle_delayed_writes (void);

typedef struct delayed_write
{
  struct delayed_write *next;
  char *command;
  char *filename;
  char *input_filename;
  char *node;
  int position;
  int calling_line;

  int node_order;
  int index_order;
} DELAYED_WRITE;

extern int handling_delayed_writes;

#endif /* !FILES_H */


code:
/* float.c -- float environment functions.
   $Id: float.c,v 1.12 2007-07-01 21:20:32 karl Exp $

   Copyright (C) 2003, 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/>.

   Originally written by Alper Ersoy <dirt@gtk.org>.  */

#include "system.h"
#include "makeinfo.h"
#include "cmds.h"
#include "files.h"
#include "float.h"
#include "html.h"
#include "sectioning.h"
#include "xml.h"

static FLOAT_ELT *float_stack = NULL;

void
add_new_float (char *id, char *title, char *shorttitle,
    char *type, char *position)
{
  FLOAT_ELT *new = xmalloc (sizeof (FLOAT_ELT));
  unsigned long num_len;

  new->id = id;
  new->type = type;
  new->title = title;
  new->shorttitle = shorttitle;
  new->position = position;
  new->title_used = 0;
  new->defining_line = line_number - 1;

  new->number = current_chapter_number ();
  /* Append dot if not @unnumbered.  */
  num_len = strlen (new->number);
  if (num_len > 0)
    {
      new->number = xrealloc (new->number, num_len + 1 + 1);
      new->number[num_len] = '.';
      new->number[num_len+1] = '\0';
    }

  { /* Append the current float number.  */
    unsigned len = strlen (new->number) + 21;  /* that's 64 bits */
    char *s = xmalloc (len + 1);

    sprintf (s, "%s%d", new->number,
                count_floats_of_type_in_chapter (text_expansion (type),
                                                 new->number) + 1);
    free (new->number);
    new->number = xstrdup (s);
  }

  /* Plain text output needs sectioning number and its title,
     when listing floats.  */
  if (!html && !xml && no_headers)
    {
      new->section = current_sectioning_number ();
      if (strlen (new->section) == 0)
        new->section_name = current_sectioning_name ();
      else
        new->section_name = "";
    }

  new->next = float_stack;
  float_stack = new;
}

int
count_floats_of_type_in_chapter (char *type, char *chapter)
{
  int i = 0;
  int l = strlen (chapter);
  FLOAT_ELT *temp = float_stack;

  while (temp && strncmp (temp->number, chapter, l) == 0)
    {
      if (strlen (temp->id) > 0 && STREQ (text_expansion (temp->type), type))
        i++;
      temp = temp->next;
    }

  return i;
}

char *
current_float_title (void)
{
  return float_stack->title;
}

char *
current_float_shorttitle (void)
{
  return float_stack->shorttitle;
}

char *
current_float_type (void)
{
  return float_stack->type;
}

char *
current_float_position (void)
{
  return float_stack->position;
}

char *
current_float_number (void)
{
  return float_stack->number;
}

char *
current_float_id (void)
{
  return float_stack->id;
}

char *
get_float_ref (char *id)
{
  FLOAT_ELT *temp = float_stack;

  while (temp)
    {
      if (STREQ (id, temp->id))
        {
          char *s = xmalloc (strlen (temp->type) + strlen (temp->number) + 2);
          sprintf (s, "%s %s", temp->type, temp->number);
          return s;
        }
      temp = temp->next;
    }

  return NULL;
}

static int
float_type_exists (char *check_type)
{
  /* Check if the requested float_type exists in the floats stack.  */
  FLOAT_ELT *temp;

  for (temp = float_stack; temp; temp = temp->next)
    if (STREQ (temp->type, check_type) && temp->id && *temp->id)
      return 1;

  return 0;
}

void
cm_listoffloats (void)
{
  char *float_type;
  get_rest_of_line (1, &float_type);

  /* get_rest_of_line increments the line number by one,
     so to make warnings/errors point to the correct line,
     we decrement the line_number again.  */
  if (!handling_delayed_writes)
    line_number--;

  if (handling_delayed_writes && !float_type_exists (float_type))
    warning (_("Requested float type `%s' not previously used"), float_type);

  if (xml)
    {
      xml_insert_element_with_attribute (LISTOFFLOATS, START,
          "type=\"%s\"", text_expansion (float_type));
      xml_insert_element (LISTOFFLOATS, END);
    }
  else if (!handling_delayed_writes)
    {
      int command_len = sizeof ("@ ") + strlen (command) + strlen (float_type);
      char *list_command = xmalloc (command_len + 1);

      /* These are for the text following @listoffloats command.
         Handling them with delayed writes is too late.  */
      close_paragraph ();
      cm_noindent ();

      sprintf (list_command, "@%s %s", command, float_type);
      register_delayed_write (list_command);
      free (list_command);
    }
  else if (float_type_exists (float_type))
    {
      FLOAT_ELT *temp = (FLOAT_ELT *) reverse_list
        ((GENERIC_LIST *) float_stack);
      FLOAT_ELT *new_start = temp;

      if (html)
        insert_string ("
\n");
      else
        {
          if (!no_headers)
            insert_string ("* Menu:\n\n");
        }

      while (temp)
        {
          if (strlen (temp->id) > 0 && STREQ (float_type, temp->type))
            {
              char *caption;
              if (strlen (temp->shorttitle) > 0)
                caption = expansion (temp->shorttitle, 0);
              else if (strlen (temp->title) > 0)
                caption = expansion (temp->title, 0);
              else
                caption = "";

              if (html)
                {
                  /* A bit of space for HTML reabality.  */
                  insert_string ("  ");
                  add_html_block_elt ("
");

                  /* Simply relying on @ref command doesn't work here, because
                     commas in the caption may confuse the argument parsing.  */
                  add_word ("id, 1);
                  add_word ("\">");

                  if (strlen (float_type) > 0)
                    execute_string ("%s", float_type);

                  if (strlen (temp->id) > 0)
                    {
                      if (strlen (float_type) > 0)
                        add_char (' ');

                      add_word (temp->number);
                    }

                  if (caption)
                    {
                      if (strlen (float_type) > 0 || strlen (temp->id) > 0)
                        insert_string (": ");
                      execute_string ("%s", caption);
                    }

                  add_word ("");

                  add_html_block_elt ("
\n");
                }
              else
                {
                  char *entry;
                  char *raw_entry;

                  int len;
                  int aux_chars_len; /* these are asterisk, colon, etc.  */
                  int column_width; /* width of the first column in menus.  */
                  int number_len; /* length of Figure X.Y: etc.   */
                  int i = 0;

                  /* Chosen widths are to match what @printindex produces.  */
                  if (no_headers)
                    {
                      column_width = 43;
                      /* We have only one auxiliary character, NULL.  */
                      aux_chars_len = sizeof ("");
                    }
                  else
                    {
                      column_width = 37;
                      /* We'll be adding an asterisk, followed by a space
                         and then a colon after the title, to construct a
                         proper menu item.  */
                      aux_chars_len = sizeof ("* :");
                    }

                  /* Allocate enough space for possible expansion later.  */
                  raw_entry = (char *) xmalloc (strlen (float_type)
                      + strlen (temp->number) + strlen (caption)
                      + sizeof (":  "));

                  sprintf (raw_entry, "%s %s", float_type, temp->number);

                  if (strlen (caption) > 0)
                    strcat (raw_entry, ": ");

                  number_len = strlen (raw_entry);

                  len = strlen (caption) + strlen (raw_entry);

                  strcat (raw_entry, caption);
                  len = strlen (raw_entry);

                  if (len + aux_chars_len > column_width)
                    { /* Shorten long titles by looking for a space before
                         column_width - strlen (" ...").  */
                      /* -1 is for NULL, which is already in aux_chars_len.  */
                      aux_chars_len += sizeof ("...") - 1;
                      len = column_width - aux_chars_len;
                      while (raw_entry[len] != ' ' && len >= 0)
                        len--;

                      /* Advance to the whitespace.  */
                      len++;

                      /* If we are at the end of, say, Figure X.Y:, but
                         we have a title, then this means title does not
                         contain any whitespaces.  Or it may be that we
                         went as far as the beginning.  Just print as much
                         as possible of the title.  */
                      if (len == 0
                          || (len == number_len && strlen (caption) > 0))
                        len = column_width - sizeof ("...");

                      /* Break here.  */
                      raw_entry[len] = 0;

                      entry = xmalloc (len + aux_chars_len);

                      if (!no_headers)
                        strcpy (entry, "* ");
                      else
                        entry[0] = 0;

                      strcat (entry, raw_entry);
                      strcat (entry, "...");

                      if (!no_headers)
                        strcat (entry, ":");
                    }
                  else
                    {
                      entry = xmalloc (len + aux_chars_len);

                      if (!no_headers)
                        strcpy (entry, "* ");
                      else
                        entry[0] = 0;

                      strcat (entry, raw_entry);

                      if (!no_headers)
                        strcat (entry, ":");
                    }

                  insert_string (entry);

                  i = strlen (entry);
                  /* We insert space chars until ``column_width + four spaces''
                     is reached, to make the layout the same with what we produce
                     for @printindex.  This is of course not obligatory, though
                     easier on the eye.  -1 is for NULL.  */
                  while (i < column_width + sizeof ("    ") - 1)
                    {
                      insert (' ');
                      i++;
                    }

                  if (no_headers)
                    {
                      if (strlen (temp->section) > 0)
                        { /* We got your number.  */
                          insert_string ((char *) _("See "));
                          insert_string (temp->section);
                        }
                      else
                        { /* Sigh, @float in an @unnumbered. :-\  */
                          insert_string ("\n          ");
                          insert_string ((char *) _("See "));
                          insert_string ("``");
                          insert_string (expansion (temp->section_name, 0));
                          insert_string ("''");
                        }
                    }
                  else
                    insert_string (temp->id);

                  insert_string (".\n");

                  free (entry);
                }

              if (strlen (caption) > 0)
                free (caption);
            }
          temp = temp->next;
        }

      if (html)
        {
          inhibit_paragraph_indentation = 1;
          insert_string ("
\n\n");
        }
      else
        insert ('\n');

      /* Retain the original order of float stack.  */
      temp = new_start;
      float_stack = (FLOAT_ELT *) reverse_list ((GENERIC_LIST *) temp);
    }

  free (float_type);
  /* Re-increment the line number, because get_rest_of_line
     left us looking at the next line after the command.  */
  line_number++;
}

int
current_float_used_title (void)
{
	return float_stack->title_used;
}

void current_float_set_title_used (void)
{
	float_stack->title_used = 1;
}


code:
/* float.h -- declarations for the float environment.
   $Id: float.h,v 1.8 2007-07-01 21:20:32 karl Exp $

   Copyright (C) 2003, 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/>.

   Written by Alper Ersoy <dirt@gtk.org>.  */

#ifndef FLOAT_H
#define FLOAT_H

typedef struct float_elt
{
  struct float_elt *next;
  char *id;
  char *type;
  char *title;
  char *shorttitle;
  char *position;
  char *number;
  char *section;
  char *section_name;
  short title_used;
  int defining_line;
} FLOAT_ELT;

extern void add_new_float (char *id, char *title, char *shorttitle,
    char *type, char *position);
extern void current_float_set_title_used (void);

/* Information retrieval about the current float env.  */
extern char *current_float_id (void);
extern char *current_float_title (void);
extern char *current_float_shorttitle (void);
extern char *current_float_type (void);
extern char *current_float_position (void);
extern char *current_float_number (void);
extern char *get_float_ref (char *id);

extern int count_floats_of_type_in_chapter (char *type, char *chapter);
extern int current_float_used_title (void);

#endif /* not FLOAT_H */


code:
/* footnote.c -- footnotes for Texinfo.
   $Id: footnote.c,v 1.12 2007-12-03 01:38:43 karl Exp $

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

#include "system.h"
#include "footnote.h"
#include "macro.h"
#include "makeinfo.h"
#include "node.h"
#include "xml.h"
#include "xref.h"

/* Nonzero means that the footnote style for this document was set on
   the command line, which overrides any other settings. */
int footnote_style_preset = 0;

/* The current footnote number in this node.  Each time a new node is
   started this is reset to 1. */
int current_footnote_number = 1;

/* Nonzero means we automatically number footnotes with no specified marker. */
int number_footnotes = 1;

/* Nonzero means we are currently outputting footnotes. */
int already_outputting_pending_notes = 0;

/* Footnotes can be handled in one of two ways:

   separate_node:
        Make them look like followed references, with the reference
        destinations in a makeinfo manufactured node or,
   end_node:
        Make them appear at the bottom of the node that they originally
        appeared in. */

#define separate_node 0
#define end_node 1

int footnote_style = end_node;
int first_footnote_this_node = 1;
int footnote_count = 0;

/* Set the footnote style based on the style identifier in STRING. */
int
set_footnote_style (char *string)
{
  if (mbscasecmp (string, "separate") == 0)
    footnote_style = separate_node;
  else if (mbscasecmp (string, "end") == 0)
    footnote_style = end_node;
  else
    return -1;

 return 0;
}

void
cm_footnotestyle (void)
{
  char *arg;

  get_rest_of_line (1, &arg);

  /* If set on command line, do not change the footnote style.  */
  if (!footnote_style_preset && set_footnote_style (arg) != 0)
    line_error (_("Bad argument to %c%s"), COMMAND_PREFIX, command);

  free (arg);
}

typedef struct fn
{
  struct fn *next;
  char *marker;
  char *note;
  int number;
}  FN;

FN *pending_notes = NULL;

/* A method for remembering footnotes.  Note that this list gets output
   at the end of the current node. */
static void
remember_note (char *marker, char *note)
{
  FN *temp = xmalloc (sizeof (FN));

  temp->marker = xstrdup (marker);
  temp->note = xstrdup (note);
  temp->next = pending_notes;
  temp->number = current_footnote_number;
  pending_notes = temp;
  footnote_count++;
}

/* How to get rid of existing footnotes. */
static void
free_pending_notes (void)
{
  FN *temp;

  while ((temp = pending_notes))
    {
      free (temp->marker);
      free (temp->note);
      pending_notes = pending_notes->next;
      free (temp);
    }
  first_footnote_this_node = 1;
  footnote_count = 0;
  current_footnote_number = 1;	/* for html */
}

/* What to do when you see a @footnote construct. */

 /* Handle a "footnote".
    footnote *{this is a footnote}
    where "*" is the (optional) marker character for this note. */
void
cm_footnote (void)
{
  char *marker;
  char *note;

  get_until ("{", &marker);
  canon_white (marker);

  if (macro_expansion_output_stream && !executing_string)
    append_to_expansion_output (input_text_offset + 1); /* include the { */

  /* Read the argument in braces. */
  if (curchar () != '{')
    {
      line_error (_("`%c%s' needs an argument `{...}', not just `%s'"),
                  COMMAND_PREFIX, command, marker);
      free (marker);
      return;
    }
  else
    {
      int len;
      int braces = 1;
      int loc = ++input_text_offset;

      while (braces)
        {
          if (loc == input_text_length)
            {
              line_error (_("No closing brace for footnote `%s'"), marker);
              return;
            }

          if (input_text[loc] == '{')
            braces++;
          else if (input_text[loc] == '}')
            braces--;
          else if (input_text[loc] == '\n')
            line_number++;

          loc++;
        }

      len = (loc - input_text_offset) - 1;
      note = xmalloc (len + 1);
      memcpy (note, &input_text[input_text_offset], len);
      note[len] = 0;
      input_text_offset = loc;
    }

  /* Must write the macro-expanded argument to the macro expansion
     output stream.  This is like the case in index_add_arg.  */
  if (macro_expansion_output_stream && !executing_string)
    {
      /* Calling me_execute_string on a lone } provokes an error, since
         as far as the reader knows there is no matching {.  We wrote
         the { above in the call to append_to_expansion_output. */
      me_execute_string_keep_state (note, "}");
    }

  if (!current_node || !*current_node)
    {
      line_error (_("Footnote defined without parent node"));
      free (marker);
      free (note);
      return;
    }

  /* output_pending_notes is non-reentrant (it uses a global data
     structure pending_notes, which it frees before it returns), and
     TeX doesn't grok footnotes inside footnotes anyway.  Disallow
     that.  */
  if (already_outputting_pending_notes)
    {
      line_error (_("Footnotes inside footnotes are not allowed"));
      free (marker);
      free (note);
      return;
    }

  if (!*marker)
    {
      free (marker);

      if (number_footnotes)
        {
          marker = xmalloc (10);
          sprintf (marker, "%d", current_footnote_number);
        }
      else
        marker = xstrdup ("*");
    }

  if (xml)
    xml_insert_footnote (note);
  else
    {
  remember_note (marker, note);

  /* fixme: html: footnote processing needs work; we currently ignore
     the style requested; we could clash with a node name of the form
     `fn-<n>', though that's unlikely. */
  if (html)
    {
      /* Hyperlink also serves as an anchor (mnemonic: fnd is footnote
         definition.)  */
      add_html_elt ("%s",
		     current_footnote_number, current_footnote_number,
                     marker);
    }
  else
    /* Your method should at least insert MARKER. */
    switch (footnote_style)
      {
      case separate_node:
        add_word_args ("(%s)", marker);
        execute_string (" (*note %s-Footnote-%d::)",
                        current_node, current_footnote_number);
        if (first_footnote_this_node)
          {
            char *temp_string, *expanded_ref;

            temp_string = xmalloc (strlen (current_node)
                                   + strlen ("-Footnotes") + 1);

            strcpy (temp_string, current_node);
            strcat (temp_string, "-Footnotes");
            expanded_ref = expansion (temp_string, 0);
            remember_node_reference (expanded_ref, line_number,
                                     followed_reference);
            free (temp_string);
            free (expanded_ref);
            first_footnote_this_node = 0;
          }
        break;

      case end_node:
        add_word_args ("(%s)", marker);
        break;

      default:
        break;
      }
  current_footnote_number++;
    }
  free (marker);
  free (note);
}

/* Output the footnotes.  We are at the end of the current node. */
void
output_pending_notes (void)
{
  FN *footnote = pending_notes;

  if (!pending_notes)
    return;

  if (html)
    {
      add_html_block_elt ("
\n
\n");
      /* We add an anchor here so @printindex can refer to this point
         (as the node name) for entries defined in footnotes.  */
      if (!splitting)
        add_word ("");
      add_word_args ("
%s
", (char *) _("Footnotes"));
    }
  else
    switch (footnote_style)
      {
      case separate_node:
        {
          char *old_current_node = current_node;
          char *old_command = xstrdup (command);

          already_outputting_pending_notes++;
          execute_string ("%cnode %s-Footnotes,,,%s\n",
                          COMMAND_PREFIX, current_node, current_node);
          already_outputting_pending_notes--;
          current_node = old_current_node;
          free (command);
          command = old_command;
        }
      break;

      case end_node:
        close_paragraph ();
        in_fixed_width_font++;
        /* This string should be translated according to the
           @documentlanguage, not the current LANG.  We can't do that
           yet, so leave it in English.  */
        execute_string ("---------- Footnotes ----------\n\n");
        in_fixed_width_font--;
        break;
      }

  /* Handle the footnotes in reverse order. */
  {
    int save_in_fixed_width_font = in_fixed_width_font;
    FN **array = xmalloc ((footnote_count + 1) * sizeof (FN *));
    array[footnote_count] = NULL;

    while (--footnote_count > -1)
      {
        array[footnote_count] = footnote;
        footnote = footnote->next;
      }

    filling_enabled = 1;
    indented_fill = 1;
    in_fixed_width_font = 0;

    while ((footnote = array[++footnote_count]))
      {
        if (html)
          {
	    /* Make the text of every footnote begin a separate paragraph.  */
            add_html_block_elt ("
");
            /* Make footnote number a link to its definition.  */
            add_word_args ("[%d]",
			   footnote->number, footnote->number, footnote->number);
            add_word (" ");
            already_outputting_pending_notes++;
            execute_string ("%s", footnote->note);
            already_outputting_pending_notes--;
            add_word ("
\n");
          }
        else
          {
            char *old_current_node = current_node;
            char *old_command = xstrdup (command);

            already_outputting_pending_notes++;
            execute_string ("%canchor{%s-Footnote-%d}(%s) %s",
                            COMMAND_PREFIX, current_node, footnote->number,
                            footnote->marker, footnote->note);
            already_outputting_pending_notes--;
            current_node = old_current_node;
            free (command);
            command = old_command;
          }

        close_paragraph ();
      }

    if (html)
      add_html_block_elt ("

");
    close_paragraph ();
    free (array);

    in_fixed_width_font = save_in_fixed_width_font;
  }

  free_pending_notes ();
}


code:
/* footnote.h -- declarations for footnote.c.
   $Id: footnote.h,v 1.5 2007-07-01 21:20:32 karl Exp $

   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 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 FOOTNOTE_H
#define FOOTNOTE_H

extern int footnote_style_preset;
extern int current_footnote_number;
extern int number_footnotes;
extern int already_outputting_pending_notes;

/* The Texinfo @commands.  */
extern void cm_footnote (void);
extern void cm_footnotestyle (void);

extern int set_footnote_style (char *string);    /* called for -s option */

extern void output_pending_notes (void); /* called for output */

#endif /* !FOOTNOTE_H */


code:
/* html.c -- html-related utilities.
   $Id: html.c,v 1.43 2011-04-06 21:32:42 gray Exp $

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

#include "system.h"
#include "cmds.h"
#include "files.h"
#include "html.h"
#include "lang.h"
#include "makeinfo.h"
#include "node.h"
#include "sectioning.h"

/* Filename to which to write list of index entries */
char *internal_links_filename = NULL;
FILE *internal_links_stream = NULL;

/* Append CHAR to BUFFER, (re)allocating as necessary.  We don't handle
   null characters.  */

typedef struct
{
  unsigned size;    /* allocated */
  unsigned length;  /* used */
  char *buffer;
} buffer_type;

static buffer_type *
init_buffer (void)
{
  buffer_type *buf = xmalloc (sizeof (buffer_type));
  buf->length = 0;
  buf->size = 0;
  buf->buffer = NULL;

  return buf;
}

static void
append_char (buffer_type *buf, int c)
{
  buf->length++;
  if (buf->length >= buf->size)
    {
      buf->size += 100;
      buf->buffer = xrealloc (buf->buffer, buf->size);
    }
  buf->buffer[buf->length - 1] = c;
  buf->buffer[buf->length] = 0;
}

/* Read the cascading style-sheet file FILENAME.  Write out any @import
   commands, which must come first, by the definition of css.  If the
   file contains any actual css code following the @imports, return it;
   else return NULL.  */
static char *
process_css_file (char *filename)
{
  int c;
  int lastchar = 0;
  FILE *f;
  buffer_type *import_text = init_buffer ();
  buffer_type *inline_text = init_buffer ();
  unsigned lineno = 1;
  enum { null_state, comment_state, import_state, inline_state } state
    = null_state, prev_state;

  prev_state = null_state;

  /* read from stdin if `-' is the filename.  */
  f = STREQ (filename, "-") ? stdin : fopen (filename, "r");
  if (!f)
    {
      error (_("%s: could not open --css-file: %s"), progname, filename);
      return NULL;
    }

  /* Read the file.  The @import statements must come at the beginning,
     with only whitespace and comments allowed before any inline css code.  */
  while ((c = getc (f)) >= 0)
    {
      if (c == '\n')
        lineno++;

      switch (state)
        {
        case null_state: /* between things */
          if (c == '@')
            { /* Only @import and @charset should switch into
                 import_state, other @-commands, such as @media, should
                 put us into inline_state.  I don't think any other css
                 @-commands start with `i' or `c', although of course
                 this will break when such a command is defined.  */
              int nextchar = getc (f);
              if (nextchar == 'i' || nextchar == 'c')
                {
                  append_char (import_text, c);
                  append_char (import_text, nextchar);
                  state = import_state;
                }
              else
                {
                  ungetc (nextchar, f);  /* wasn't an @import */
                  state = inline_state;
                }
            }
          else if (c == '/')
            { /* possible start of a comment */
              int nextchar = getc (f);
              if (nextchar == '*')
                state = comment_state;
              else
                {
                  ungetc (nextchar, f); /* wasn't a comment */
                  state = inline_state;
                }
            }
          else if (isspace (c))
            ; /* skip whitespace; maybe should use c_isspace?  */

          else
            /* not an @import, not a comment, not whitespace: we must
               have started the inline text.  */
            state = inline_state;

          if (state == inline_state)
            append_char (inline_text, c);

          if (state != null_state)
            prev_state = null_state;
          break;

        case comment_state:
          if (c == '/' && lastchar == '*')
            state = prev_state;  /* end of comment */
          break;  /* else ignore this comment char */

        case import_state:
          append_char (import_text, c);  /* include this import char */
          if (c == ';')
            { /* done with @import */
              append_char (import_text, '\n');  /* make the output nice */
              state = null_state;
              prev_state = import_state;
            }
          break;

        case inline_state:
          /* No harm in writing out comments, so don't bother parsing
             them out, just append everything.  */
          append_char (inline_text, c);
          break;
        }

      lastchar = c;
    }

  fclose (f);  /* Even closing stdin should be ok, can't read it more
                  than once?  */

  /* Reached the end of the file.  We should not be still in a comment.  */
  if (state == comment_state)
    warning (_("%s:%d: --css-file ended in comment"), filename, lineno);

  /* Write the @import text, if any.  */
  if (import_text->buffer)
    {
      add_word (import_text->buffer);
      free (import_text->buffer);
      free (import_text);
    }

  /* We're wasting the buffer struct memory, but so what.  */
  return inline_text->buffer;
}

HSTACK *htmlstack = NULL;

/* See html.h.  */
int html_title_written = 0;

void
html_output_head (void)
{
  static const char *html_title = NULL;
  char *encoding = current_document_encoding ();

  /* The <title> should not have markup, so use text_expansion.  */
  if (!html_title)
    html_title = escape_string (title ?
        text_expansion (title) : (char *) gdt("Untitled"));

  /* Make sure this is the very first string of the output document.  */
  output_paragraph_offset = 0;

  add_html_block_elt_args ("\n\n",
      language_table[language_code].abbrev);

  /* When splitting, add current node's name to title if it's available
     and not Top.  */
  if (splitting && current_node && !STREQ (current_node, "Top"))
    add_word_args ("<title>%s - %s</title>\n",
        escape_string (xstrdup (current_node)), html_title);
  else
    add_word_args ("<title>%s</title>\n",  html_title);

  add_word ("<meta http-equiv=\"Content-Type\" content=\"text/html");
  if (encoding && *encoding)
    add_word_args ("; charset=%s", encoding);

  add_word ("\">\n");

  if (!document_description)
    document_description = html_title;

  add_word_args ("<meta name=\"description\" content=\"%s\">\n",
                 document_description);
  add_word_args ("<meta name=\"generator\" content=\"makeinfo %s\">\n",
                 VERSION);

  /* Navigation bar links.  */
  if (!splitting)
    add_word ("<link title=\"Top\" rel=\"top\" href=\"#Top\">\n");
  else if (tag_table)
    {
      /* Always put a top link.  */
      add_word ("<link title=\"Top\" rel=\"start\" href=\"index.html#Top\">\n");

      /* We already have a top link, avoid duplication.  */
      if (tag_table->up && !STREQ (tag_table->up, "Top"))
        add_link (tag_table->up, "rel=\"up\"");

      if (tag_table->prev)
        add_link (tag_table->prev, "rel=\"prev\"");

      if (tag_table->next)
        add_link (tag_table->next, "rel=\"next\"");

      /* fixxme: Look for a way to put links to various indices in the
         document.  Also possible candidates to be added here are First and
         Last links.  */
    }
  else
    {
      /* We are splitting, but we neither have a tag_table.  So this must be
         index.html.  So put a link to Top. */
      add_word ("<link title=\"Top\" rel=\"start\" href=\"#Top\">\n");
    }

  add_word ("<link href=\"http://www.gnu.org/software/texinfo/\" \
rel=\"generator-home\" title=\"Texinfo Homepage\">\n");

  if (copying_text)
    { /* It is not ideal that we include the html markup here within
         , so we use text_expansion.  */
      insert_string ("\n");
    }

  /* Put the style definitions in a comment for the sake of browsers
     that don't support \n");
  }
  if (css_ref)
    add_word_args ("<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">\n",
	            css_ref);

  add_word ("\n\n");

  if (title && !html_title_written && titlepage_cmd_present)
    {
      add_word_args ("
%s
\n", html_title);
      html_title_written = 1;
    }

  free (encoding);
}

/* Escape HTML special characters in the string if necessary,
   returning a pointer to a possibly newly-allocated one. */
char *
escape_string (char *string)
{
  char *newstring;
  int i = 0, newlen = 0;

  do
    {
      /* Find how much to allocate. */
      switch (string[i])
        {
        case '"':
          newlen += 6;          /* `"' */
          break;
        case '&':
          newlen += 5;          /* `&' */
          break;
        case '<':
        case '>':
          newlen += 4;          /* `<', `>' */
          break;
        default:
          newlen++;
        }
    }
  while (string[i++]);

  if (newlen == i) return string; /* Already OK. */

  newstring = xmalloc (newlen);
  i = 0;
  do
    {
      switch (string[i])
        {
        case '"':
          strcpy (newstring, """);
          newstring += 6;
          break;
        case '&':
          strcpy (newstring, "&");
          newstring += 5;
          break;
        case '<':
          strcpy (newstring, "<");
          newstring += 4;
          break;
        case '>':
          strcpy (newstring, ">");
          newstring += 4;
          break;
        default:
          newstring[0] = string[i];
          newstring++;
        }
    }
  while (string[i++]);

  return newstring - newlen;
}

/* Save current tag.  */
static void
push_tag (char *tag, char *attribs)
{
  HSTACK *newstack = xmalloc (sizeof (HSTACK));

  newstack->tag = tag;
  newstack->attribs = xstrdup (attribs);
  newstack->next = htmlstack;
  htmlstack = newstack;
}

/* Get last tag.  */
static void
pop_tag (void)
{
  HSTACK *tos = htmlstack;

  if (!tos)
    {
      line_error (_("[unexpected] no html tag to pop"));
      return;
    }

  free (htmlstack->attribs);

  htmlstack = htmlstack->next;
  free (tos);
}

/* Check if tag is an empty or a whitespace only element.
   If so, remove it, keeping whitespace intact.  */
int
rollback_empty_tag (char *tag)
{
  int check_position = output_paragraph_offset;
  int taglen = strlen (tag);
  int rollback_happened = 0;
  char *contents = "";			/* FIXME (ptr to constant, later
  					   assigned to malloc'd address).
					 */
  char *contents_canon_white = "";

  /* If output_paragraph is empty, we cannot rollback :-\  */
  if (output_paragraph_offset <= 0)
    return 0;

  /* Find the end of the previous tag.  */
  while (check_position > 0 && output_paragraph[check_position-1] != '>')
    check_position--;

  /* Save stuff between tag's end to output_paragraph's end.  */
  if (check_position != output_paragraph_offset)
    {
      contents = xmalloc (output_paragraph_offset - check_position + 1);
      memcpy (contents, output_paragraph + check_position,
          output_paragraph_offset - check_position);

      contents[output_paragraph_offset - check_position] = '\0';

      contents_canon_white = xstrdup (contents);
      canon_white (contents_canon_white);
    }

  /* Find the start of the previous tag.  */
  while (check_position > 0 && output_paragraph[check_position-1] != '<')
    check_position--;

  /* Check to see if this is the tag.  */
  if (strncmp ((char *) output_paragraph + check_position, tag, taglen) == 0
      && (whitespace (output_paragraph[check_position + taglen])
          || output_paragraph[check_position + taglen] == '>'))
    {
      if (!contents_canon_white || !*contents_canon_white)
        {
          /* Empty content after whitespace removal, so roll it back.  */
          output_paragraph_offset = check_position - 1;
          rollback_happened = 1;

          /* Original contents may not be empty (whitespace.)  */
          if (contents && *contents)
            {
              insert_string (contents);
              free (contents);
            }
        }
    }

  return rollback_happened;
}

/* Open or close TAG according to START_OR_END. */
void
insert_html_tag_with_attribute (int start_or_end, char *tag, char *format, ...)
{
  char *old_tag = NULL;
  char *old_attribs = NULL;
  char formatted_attribs[2000]; /* xx no fixed limits */
  int do_return = 0;
  extern int in_html_elt;

  if (start_or_end != START)
    pop_tag ();

  if (htmlstack)
    {
      old_tag = htmlstack->tag;
      old_attribs = htmlstack->attribs;
    }

  if (format)
    {
      va_list ap;

      va_start (ap, format);
      vsnprintf (formatted_attribs, sizeof (formatted_attribs), format, ap);
      va_end (ap);
    }
  else
    formatted_attribs[0] = '\0';

  /* Exception: can nest multiple spans.  */
  if (htmlstack
      && STREQ (htmlstack->tag, tag)
      && !(STREQ (tag, "span") && STREQ (old_attribs, formatted_attribs)))
    do_return = 1;

  if (start_or_end == START)
    push_tag (tag, formatted_attribs);

  if (do_return)
    return;

  in_html_elt++;

  /* texinfo.tex doesn't support more than one font attribute
     at the same time.  */
  if ((start_or_end == START) && old_tag && *old_tag
      && !STREQ (old_tag, "samp")
      && !rollback_empty_tag (old_tag))
    add_word_args ("</%s>", old_tag);

  if (*tag)
    {
      if (start_or_end == START)
        add_word_args (format ? "<%s %s>" : "<%s>", tag, formatted_attribs);
      else if (STREQ (tag, "samp") || !rollback_empty_tag (tag))
        /* Insert close tag only if we didn't rollback,
           in which case the opening tag is removed.  */
        add_word_args ("</%s>", tag);
    }

  if ((start_or_end != START) && old_tag && *old_tag && !STREQ (old_tag, "samp"))
    add_word_args (strlen (old_attribs) > 0 ? "<%s %s>" : "<%s>",
        old_tag, old_attribs);

  in_html_elt--;
}

void
insert_html_tag (int start_or_end, char *tag)
{
  insert_html_tag_with_attribute (start_or_end, tag, NULL);
}

/* Output an HTML <link> to the filename for NODE, including the
   other string as extra attributes. */

void
add_link (char *nodename, char *attributes)
{
  if (nodename)
    {
      char *escaped_nodename;
      add_html_elt ("<link ");
      add_word_args ("%s", attributes);
      add_word_args (" href=\"");
      add_anchor_name (nodename, 1);
      escaped_nodename = escape_string (nodename);
      add_word_args ("\" title=\"%s\">\n", escaped_nodename);
      if (escaped_nodename != nodename)
        free (escaped_nodename);
    }
}

/* Copy a name with characters escaped as appropriate for an anchor
   name, i.e., escape URL special characters with our _00hh convention.
   (See the manual for details on the new scheme.) */

char *
escaped_anchor_name (const char *name)
{
  /* The factor 5 in the next allocation allows all chars to be expanded.  */
  char *res = xmalloc (5 * strlen (name) + 1);
  char *d = res;

  for (; *name; name++)
    {
      if (cr_or_whitespace (*name))
        *d++ = '-';
      else if (! URL_SAFE_CHAR (*name))
        {
          sprintf (d, "_00%x", (unsigned char) *name);
          /* do this manually since sprintf returns char * on
             SunOS 4 and other old systems.  */
          while (*d)
            d++;
        }
      else
        *d++ = *name;
    }
  *d = 0;
  return res;
}

/* Output NAME with characters escaped as appropriate for an anchor
   name, i.e., escape URL special characters with our _00hh convention
   if OLD is zero.  (See the manual for details on the new scheme.)

   If OLD is nonzero, generate the node name with the 4.6-and-earlier
   convention of %hh (and more special characters output as-is, notably
   - and *).  This is only so that external references to old names can
   still work with HTML generated by the new makeinfo; the gcc folks
   needed this.  Our own HTML does not refer to these names.  */

void
add_escaped_anchor_name (char *name, int old)
{
  canon_white (name);

  if (!old && !strchr ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
                       *name))
    { /* XHTML does not allow anything but an ASCII letter to start an
         identifier.  Therefore kludge in this constant string if we
         have a nonletter.  */
      add_word ("g_t");
    }
  if (!old)
    {
      char *expanded = escaped_anchor_name (name);
      add_word (expanded);
      free (expanded);
    }
  else for (; *name; name++)
    {
      if (cr_or_whitespace (*name))
        add_char ('-');

      else if (!URL_SAFE_CHAR (*name) && !OLD_URL_SAFE_CHAR (*name))
        /* Cast so characters with the high bit set are treated as >128,
           for example o-umlaut should be 246, not -10.  */
        add_word_args ("%%%x", (unsigned char) *name);
      else
        add_char (*name);
    }
}

/* Insert the text for the name of a reference in an HTML anchor
   appropriate for NODENAME.

   If HREF is zero, generate text for name= in the new node name
     conversion convention.
   If HREF is negative, generate text for name= in the old convention.
   If HREF is positive, generate the name for an href= attribute, i.e.,
     including the `#' if it's an internal reference.   */
void
add_anchor_name (char *nodename, int href)
{
  if (href > 0)
    {
      if (splitting)
	add_url_name (nodename, href);
      add_char ('#');
    }
  /* Always add NODENAME, so that the reference would pinpoint the
     exact node on its file.  This is so several nodes could share the
     same file, in case of file-name clashes, but also for more
     accurate browser positioning.  */
  if (mbscasecmp (nodename, "(dir)") == 0)
    /* Strip the parens, but keep the original letter-case.  */
    add_word_args ("%.3s", nodename + 1);
  else if (mbscasecmp (nodename, "top") == 0)
    add_word ("Top");
  else
    add_escaped_anchor_name (nodename, href < 0);
}

/* Insert the text for the name of a reference in an HTML url, aprropriate
   for NODENAME */
void
add_url_name (char *nodename, int href)
{
    add_nodename_to_filename (nodename, href);
}

/* Convert non [A-Za-z0-9] characters depending on the command line options given.
   If --transliterate-file-names is specified, these are replaced with their ASCII
   phonetic transliteration. Otherwise, _00xx notation is used, where xx means the
   hexadecimal representation of the ASCII character.  Also convert spaces and
   newlines to dashes.  */
static void
fix_filename (char *filename)
{
  int i;
  int len = strlen (filename);
  char *oldname = xstrdup (filename);

  *filename = '\0';

  for (i = 0; i < len; i++)
    {
      const char *p = lang_transliterate_char (oldname[i]);

      if (p)
	strcat (filename, p);
      else if (cr_or_whitespace (oldname[i]))
        strcat (filename, "-");
      else if (URL_SAFE_CHAR (oldname[i]))
        strncat (filename, (char *) oldname + i, 1);
      else
        {
          char *hexchar = xmalloc (6 * sizeof (char));
          sprintf (hexchar, "_00%x", (unsigned char) oldname[i]);
          strcat (filename, hexchar);
          free (hexchar);
        }

      /* Check if we are nearing boundaries.  */
      if (strlen (filename) >= PATH_MAX - 20)
        break;
    }

  free (oldname);
}

/* As we can't look-up a (forward-referenced) nodes' html filename
   from the tentry, we take the easy way out.  We assume that
   nodenames are unique, and generate the html filename from the
   nodename, that's always known.  */
static char *
nodename_to_filename_1 (char *nodename, int href)
{
  char *p;
  char *filename;
  char dirname[PATH_MAX];

  if (mbscasecmp (nodename, "Top") == 0)
    {
      /* We want to convert references to the Top node into
	 "index.html#Top".  */
      if (href)
	filename = xstrdup ("index.html"); /* "#Top" is added by our callers */
      else
	filename = xstrdup ("Top");
    }
  else if (mbscasecmp (nodename, "(dir)") == 0)
    /* We want to convert references to the (dir) node into
       "../index.html".  */
    filename = xstrdup ("../index.html");
  else
    {
      filename = xmalloc (PATH_MAX);
      dirname[0] = '\0';
      *filename = '\0';

      /* Check for external reference: ``(info-document)node-name''
	 Assume this node lives at: ``../info-document/node-name.html''

	 We need to handle the special case (sigh): ``(info-document)'',
	 ie, an external top-node, which should translate to:
	 ``../info-document/info-document.html'' */

      p = nodename;
      if (*nodename == '(')
	{
	  int length;

	  p = strchr (nodename, ')');
	  if (p == NULL)
	    {
	      line_error (_("[unexpected] invalid node name: `%s'"), nodename);
	      xexit (1);
	    }

	  length = p - nodename - 1;
	  if (length > 5 &&
	      FILENAME_CMPN (p - 5, ".info", 5) == 0)
	    length -= 5;
	  /* This is for DOS, and also for Windows and GNU/Linux
	     systems that might have Info files copied from a DOS 8+3
	     filesystem.  */
	  if (length > 4 &&
	      FILENAME_CMPN (p - 4, ".inf", 4) == 0)
	    length -= 4;
	  strcpy (filename, "../");
	  strncpy (dirname, nodename + 1, length);
	  *(dirname + length) = '\0';
	  fix_filename (dirname);
	  strcat (filename, dirname);
	  strcat (filename, "/");
	  p++;
	}

      /* In the case of just (info-document), there will be nothing
	 remaining, and we will refer to ../info-document/, which will
	 work fine.  */
      strcat (filename, p);
      if (*p)
	{
	  /* Hmm */
	  fix_filename (filename + strlen (filename) - strlen (p));
	  strcat (filename, ".html");
	}
    }

  /* Produce a file name suitable for the underlying filesystem.  */
  normalize_filename (filename);

#if 0
  /* We add ``#Nodified-filename'' anchor to external references to be
     prepared for non-split HTML support.  Maybe drop this. */
  if (href && *dirname)
    {
      strcat (filename, "#");
      strcat (filename, p);
      /* Hmm, again */
      fix_filename (filename + strlen (filename) - strlen (p));
    }
#endif

  return filename;
}

/* If necessary, ie, if current filename != filename of node, output
   the node name.  */
void
add_nodename_to_filename (char *nodename, int href)
{
  /* for now, don't check: always output filename */
  char *filename = nodename_to_filename_1 (nodename, href);
  add_word (filename);
  free (filename);
}

char *
nodename_to_filename (char *nodename)
{
  /* The callers of nodename_to_filename use the result to produce
     <a href=, so call nodename_to_filename_1 with last arg non-zero.  */
  return nodename_to_filename_1 (nodename, 1);
}