Home | History | Annotate | Line # | Download | only in makeinfo
      1 /*	$NetBSD: index.c,v 1.6 2025/12/31 22:18:50 oster Exp $	*/
      2 
      3 /* index.c -- indexing for Texinfo.
      4    Id: index.c,v 1.17 2004/11/30 02:03:23 karl Exp
      5 
      6    Copyright (C) 1998, 1999, 2002, 2003, 2004 Free Software Foundation,
      7    Inc.
      8 
      9    This program is free software; you can redistribute it and/or modify
     10    it under the terms of the GNU General Public License as published by
     11    the Free Software Foundation; either version 2, or (at your option)
     12    any later version.
     13 
     14    This program is distributed in the hope that it will be useful,
     15    but WITHOUT ANY WARRANTY; without even the implied warranty of
     16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17    GNU General Public License for more details.
     18 
     19    You should have received a copy of the GNU General Public License
     20    along with this program; if not, write to the Free Software Foundation,
     21    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
     22 
     23 #include "system.h"
     24 #include "files.h"
     25 #include "footnote.h"
     26 #include "html.h"
     27 #include "index.h"
     28 #include "lang.h"
     29 #include "macro.h"
     30 #include "sectioning.h"
     31 #include "toc.h"
     32 #include "xml.h"
     33 
     34 INDEX_ALIST **name_index_alist = NULL;
     35 
     36 /* An array of pointers.  Each one is for a different index.  The
     37    "synindex" command changes which array slot is pointed to by a
     38    given "index". */
     39 INDEX_ELT **the_indices = NULL;
     40 
     41 /* The number of defined indices. */
     42 int defined_indices = 0;
     43 
     44 /* This is the order of the index.  */
     45 int index_counter = 0;
     46 
     47 /* Stuff for defining commands on the fly. */
     48 COMMAND **user_command_array = NULL;
     49 int user_command_array_len = 0;
     50 
     51 /* How to compare index entries for sorting.  May be set to strcoll.  */
     52 int (*index_compare_fn) (const char *a, const char *b) = strcasecmp;
     53 
     54 /* Function to compare index entries for sorting.  (Calls
     55    `index_compare_fn' above.)  */
     56 int index_element_compare (const void *element1, const void *element2);
     57 
     58 /* Find which element in the known list of indices has this name.
     60    Returns -1 if NAME isn't found. */
     61 static int
     62 find_index_offset (char *name)
     63 {
     64   int i;
     65   for (i = 0; i < defined_indices; i++)
     66     if (name_index_alist[i] && STREQ (name, name_index_alist[i]->name))
     67       return i;
     68   return -1;
     69 }
     70 
     71 /* Return a pointer to the entry of (name . index) for this name.
     72    Return NULL if the index doesn't exist. */
     73 static INDEX_ALIST *
     74 find_index (char *name)
     75 {
     76   int offset = find_index_offset (name);
     77   if (offset > -1)
     78     return name_index_alist[offset];
     79   else
     80     return NULL;
     81 }
     82 
     83 /* User-defined commands, which happens only from user-defined indexes.
     85    Used to initialize the builtin indices, too.  */
     86 static void
     87 define_user_command (char *name, COMMAND_FUNCTION (*proc), int needs_braces_p)
     88 {
     89   int slot = user_command_array_len;
     90   user_command_array_len++;
     91 
     92   if (!user_command_array)
     93     user_command_array = xmalloc (1 * sizeof (COMMAND *));
     94 
     95   user_command_array = xrealloc (user_command_array,
     96                             (1 + user_command_array_len) * sizeof (COMMAND *));
     97 
     98   user_command_array[slot] = xmalloc (sizeof (COMMAND));
     99   user_command_array[slot]->name = xstrdup (name);
    100   user_command_array[slot]->proc = proc;
    101   user_command_array[slot]->argument_in_braces = needs_braces_p;
    102 }
    103 
    104 /* Please release me, let me go... */
    106 static void
    107 free_index (INDEX_ELT *index)
    108 {
    109   INDEX_ELT *temp;
    110 
    111   while ((temp = index))
    112     {
    113       free (temp->entry);
    114       free (temp->entry_text);
    115       /* Do not free the node, because we already freed the tag table,
    116          which freed all the node names.  */
    117       /* free (temp->node); */
    118       index = index->next;
    119       free (temp);
    120     }
    121 }
    122 
    123 /* Flush an index by name.  This will delete the list of entries that
    124    would be written by a @printindex command for this index. */
    125 static void
    126 undefindex (char *name)
    127 {
    128   int i;
    129   int which = find_index_offset (name);
    130 
    131   /* The index might have already been freed if this was the target of
    132      an @synindex.  */
    133   if (which < 0 || !name_index_alist[which])
    134     return;
    135 
    136   i = name_index_alist[which]->read_index;
    137 
    138   free_index (the_indices[i]);
    139   the_indices[i] = NULL;
    140 
    141   free (name_index_alist[which]->name);
    142   free (name_index_alist[which]);
    143   name_index_alist[which] = NULL;
    144 }
    145 
    146 /* Add the arguments to the current index command to the index NAME.  */
    148 static void
    149 index_add_arg (char *name)
    150 {
    151   int which;
    152   char *index_entry;
    153   INDEX_ALIST *tem;
    154 
    155   tem = find_index (name);
    156 
    157   which = tem ? tem->write_index : -1;
    158 
    159   if (macro_expansion_output_stream && !executing_string)
    160     append_to_expansion_output (input_text_offset + 1);
    161 
    162   get_rest_of_line (0, &index_entry);
    163   ignore_blank_line ();
    164 
    165   if (macro_expansion_output_stream && !executing_string)
    166     {
    167       char *index_line = xmalloc (strlen (index_entry) + 2);
    168       sprintf (index_line, "%s\n", index_entry);
    169       me_execute_string_keep_state (index_line, NULL);
    170       free (index_line);
    171     }
    172 
    173   if (which < 0)
    174     {
    175       line_error (_("Unknown index `%s'"), name);
    176       free (index_entry);
    177     }
    178   else
    179     {
    180       INDEX_ELT *new = xmalloc (sizeof (INDEX_ELT));
    181 
    182       index_counter++;
    183 
    184       /* Get output line number updated before doing anything.  */
    185       if (!html && !xml)
    186         flush_output ();
    187 
    188       new->next = the_indices[which];
    189       new->entry = NULL;
    190       new->entry_text = index_entry;
    191       /* Since footnotes are handled at the very end of the document,
    192          node name in the non-split HTML outputs always show the last
    193          node.  We artificially make it ``Footnotes''.  */
    194       if (html && !splitting && already_outputting_pending_notes)
    195         new->node = xstrdup (_("Footnotes"));
    196       else
    197         new->node = current_node ? current_node : xstrdup ("");
    198       if (!html && !xml && no_headers)
    199         {
    200           new->section = current_sectioning_number ();
    201           if (strlen (new->section) == 0)
    202             new->section_name = current_sectioning_name ();
    203           else
    204             new->section_name = "";
    205         }
    206       else
    207         {
    208           new->section = NULL;
    209           new->section_name = NULL;
    210         }
    211       new->code = tem->code;
    212       new->defining_line = line_number - 1;
    213       new->output_line = no_headers ? output_line_number : node_line_number;
    214       /* We need to make a copy since input_filename may point to
    215          something that goes away, for example, inside a macro.
    216          (see the findexerr test).  */
    217       new->defining_file = xstrdup (input_filename);
    218 
    219       if (html && splitting)
    220         {
    221           if (current_output_filename && *current_output_filename)
    222             new->output_file = filename_part (current_output_filename);
    223           else
    224             new->output_file = xstrdup ("");
    225         }
    226       else
    227         new->output_file = NULL;
    228 
    229       new->entry_number = index_counter;
    230       the_indices[which] = new;
    231 
    232 #if 0
    233       /* The index breaks if there are colons in the entry.
    234          -- This is true, but it's too painful to force changing index
    235          entries to use `colon', and too confusing for users.  The real
    236          fix is to change Info support to support arbitrary characters
    237          in node names, and we're not ready to do that.  --karl,
    238          19mar02.  */
    239       if (strchr (new->entry_text, ':'))
    240         warning (_("Info cannot handle `:' in index entry `%s'"),
    241                  new->entry_text);
    242 #endif
    243 
    244       if (html)
    245         {
    246           /* Anchor.  */
    247           int removed_empty_elt = 0;
    248 
    249           /* We must put the anchor outside the <dl> and <ul> blocks.  */
    250           if (rollback_empty_tag ("dl"))
    251             removed_empty_elt = 1;
    252           else if (rollback_empty_tag ("ul"))
    253             removed_empty_elt = 2;
    254 
    255           add_word ("<a name=\"index-");
    256           add_escaped_anchor_name (index_entry, 0);
    257           add_word_args ("-%d\"></a>", index_counter);
    258 
    259           if (removed_empty_elt == 1)
    260             add_html_block_elt_args ("\n<dl>");
    261           else if (removed_empty_elt == 2)
    262             add_html_block_elt_args ("\n<ul>");
    263         }
    264     }
    265 
    266   if (xml)
    267     xml_insert_indexterm (index_entry, name);
    268 }
    269 
    270 /* The function which user defined index commands call. */
    271 static void
    272 gen_index (int arg, int arg2, int arg3)
    273 {
    274   char *name = xstrdup (command);
    275   if (strlen (name) >= strlen ("index"))
    276     name[strlen (name) - strlen ("index")] = 0;
    277   index_add_arg (name);
    278   free (name);
    279 }
    280 
    281 /* Define an index known as NAME.  We assign the slot number.
    283    If CODE is nonzero, make this a code index. */
    284 static void
    285 defindex (char *name, int code)
    286 {
    287   int i, slot;
    288 
    289   /* If it already exists, flush it. */
    290   undefindex (name);
    291 
    292   /* Try to find an empty slot. */
    293   slot = -1;
    294   for (i = 0; i < defined_indices; i++)
    295     if (!name_index_alist[i])
    296       {
    297         slot = i;
    298         break;
    299       }
    300 
    301   if (slot < 0)
    302     { /* No such luck.  Make space for another index. */
    303       slot = defined_indices;
    304       defined_indices++;
    305 
    306       name_index_alist = (INDEX_ALIST **)
    307         xrealloc (name_index_alist, (1 + defined_indices)
    308                                     * sizeof (INDEX_ALIST *));
    309       the_indices = (INDEX_ELT **)
    310         xrealloc (the_indices, (1 + defined_indices) * sizeof (INDEX_ELT *));
    311     }
    312 
    313   /* We have a slot.  Start assigning. */
    314   name_index_alist[slot] = xmalloc (sizeof (INDEX_ALIST));
    315   name_index_alist[slot]->name = xstrdup (name);
    316   name_index_alist[slot]->read_index = slot;
    317   name_index_alist[slot]->write_index = slot;
    318   name_index_alist[slot]->code = code;
    319 
    320   the_indices[slot] = NULL;
    321 }
    322 
    323 /* Define an index NAME, implicitly @code if CODE is nonzero.  */
    324 static void
    325 top_defindex (char *name, int code)
    326 {
    327   char *temp;
    328 
    329   temp = xmalloc (1 + strlen (name) + strlen ("index"));
    330   sprintf (temp, "%sindex", name);
    331   define_user_command (temp, gen_index, 0);
    332   defindex (name, code);
    333   free (temp);
    334 }
    335 
    336 /* Set up predefined indices.  */
    338 void
    339 init_indices (void)
    340 {
    341   int i;
    342 
    343   /* Create the default data structures. */
    344 
    345   /* Initialize data space. */
    346   if (!the_indices)
    347     {
    348       the_indices = xmalloc ((1 + defined_indices) * sizeof (INDEX_ELT *));
    349       the_indices[defined_indices] = NULL;
    350 
    351       name_index_alist = xmalloc ((1 + defined_indices)
    352                                   * sizeof (INDEX_ALIST *));
    353       name_index_alist[defined_indices] = NULL;
    354     }
    355 
    356   /* If there were existing indices, get rid of them now. */
    357   for (i = 0; i < defined_indices; i++)
    358     {
    359       if (name_index_alist[i])
    360         { /* Suppose we're called with two input files, and the first
    361              does a @synindex pg cp.  Then, when we get here to start
    362              the second file, the "pg" element won't get freed by
    363              undefindex (because it's pointing to "cp").  So free it
    364              here; otherwise, when we try to define the pg index again
    365              just below, it will still point to cp.  */
    366           undefindex (name_index_alist[i]->name);
    367 
    368           /* undefindex sets all this to null in some cases.  */
    369           if (name_index_alist[i])
    370             {
    371               free (name_index_alist[i]->name);
    372               free (name_index_alist[i]);
    373               name_index_alist[i] = NULL;
    374             }
    375         }
    376     }
    377 
    378   /* Add the default indices. */
    379   top_defindex ("cp", 0);           /* cp is the only non-code index.  */
    380   top_defindex ("fn", 1);
    381   top_defindex ("ky", 1);
    382   top_defindex ("pg", 1);
    383   top_defindex ("tp", 1);
    384   top_defindex ("vr", 1);
    385 }
    386 
    387 /* Given an index name, return the offset in the_indices of this index,
    388    or -1 if there is no such index. */
    389 static int
    390 translate_index (char *name)
    391 {
    392   INDEX_ALIST *which = find_index (name);
    393 
    394   if (which)
    395     return which->read_index;
    396   else
    397     return -1;
    398 }
    399 
    400 /* Return the index list which belongs to NAME. */
    401 INDEX_ELT *
    402 index_list (char *name)
    403 {
    404   int which = translate_index (name);
    405   if (which < 0)
    406     return (INDEX_ELT *) -1;
    407   else
    408     return the_indices[which];
    409 }
    410 
    411 /* Define a new index command.  Arg is name of index. */
    412 static void
    413 gen_defindex (int code)
    414 {
    415   char *name;
    416   get_rest_of_line (0, &name);
    417 
    418   if (find_index (name))
    419     {
    420       line_error (_("Index `%s' already exists"), name);
    421     }
    422   else
    423     {
    424       char *temp = xmalloc (strlen (name) + sizeof ("index"));
    425       sprintf (temp, "%sindex", name);
    426       define_user_command (temp, gen_index, 0);
    427       defindex (name, code);
    428       free (temp);
    429     }
    430 
    431   free (name);
    432 }
    433 
    434 void
    435 cm_defindex (int arg, int arg2, int arg3)
    436 {
    437   gen_defindex (0);
    438 }
    439 
    440 void
    441 cm_defcodeindex (int arg, int arg2, int arg3)
    442 {
    443   gen_defindex (1);
    444 }
    445 
    446 /* Expects 2 args, on the same line.  Both are index abbreviations.
    447    Make the first one be a synonym for the second one, i.e. make the
    448    first one have the same index as the second one. */
    449 void
    450 cm_synindex (int arg, int arg2, int arg3)
    451 {
    452   int source, target;
    453   char *abbrev1, *abbrev2;
    454 
    455   skip_whitespace ();
    456   get_until_in_line (0, " ", &abbrev1);
    457   target = find_index_offset (abbrev1);
    458   skip_whitespace ();
    459   get_until_in_line (0, " ", &abbrev2);
    460   source = find_index_offset (abbrev2);
    461   if (source < 0 || target < 0)
    462     {
    463       line_error (_("Unknown index `%s' and/or `%s' in @synindex"),
    464                   abbrev1, abbrev2);
    465     }
    466   else
    467     {
    468       if (xml && !docbook)
    469         xml_synindex (abbrev1, abbrev2);
    470       else
    471         name_index_alist[target]->write_index
    472           = name_index_alist[source]->write_index;
    473     }
    474 
    475   free (abbrev1);
    476   free (abbrev2);
    477 }
    478 
    479 void
    480 cm_pindex (int arg, int arg2, int arg3)                    /* Pinhead index. */
    481 {
    482   index_add_arg ("pg");
    483 }
    484 
    485 void
    486 cm_vindex (int arg, int arg2, int arg3)                    /* Variable index. */
    487 {
    488   index_add_arg ("vr");
    489 }
    490 
    491 void
    492 cm_kindex (int arg, int arg2, int arg3)                    /* Key index. */
    493 {
    494   index_add_arg ("ky");
    495 }
    496 
    497 void
    498 cm_cindex (int arg, int arg2, int arg3)                    /* Concept index. */
    499 {
    500   index_add_arg ("cp");
    501 }
    502 
    503 void
    504 cm_findex (int arg, int arg2, int arg3)                    /* Function index. */
    505 {
    506   index_add_arg ("fn");
    507 }
    508 
    509 void
    510 cm_tindex (int arg, int arg2, int arg3)                    /* Data Type index. */
    511 {
    512   index_add_arg ("tp");
    513 }
    514 
    515 int
    516 index_element_compare (const void *element1, const void *element2)
    517 {
    518   INDEX_ELT **elt1 = (INDEX_ELT **) element1;
    519   INDEX_ELT **elt2 = (INDEX_ELT **) element2;
    520   int ret = 0;
    521 
    522   /* Find a stable sort order.  */
    523   if (ret == 0)
    524     ret = index_compare_fn ((*elt1)->entry, (*elt2)->entry);
    525   if (ret == 0)
    526     ret = strcmp ((*elt1)->defining_file, (*elt2)->defining_file);
    527   if (ret == 0)
    528     ret = strcmp ((*elt1)->node, (*elt2)->node);
    529   if (ret == 0) {
    530     if ((*elt1)->defining_line < (*elt2)->defining_line)
    531       ret = -1;
    532     else if ((*elt1)->defining_line > (*elt2)->defining_line)
    533       ret = 1;
    534   }
    535   if (ret == 0) {
    536     if ((*elt1)->entry_number < (*elt2)->entry_number)
    537       ret = -1;
    538     else if ((*elt1)->entry_number > (*elt2)->entry_number)
    539       ret = 1;
    540   }
    541   if (ret == 0) {
    542     abort ();
    543   }
    544 
    545   return ret;
    546 }
    547 
    548 /* Force all index entries to be unique. */
    549 static void
    550 make_index_entries_unique (INDEX_ELT **array, int count)
    551 {
    552   int i, j;
    553   INDEX_ELT **copy;
    554   int counter = 1;
    555 
    556   copy = xmalloc ((1 + count) * sizeof (INDEX_ELT *));
    557 
    558   for (i = 0, j = 0; i < count; i++)
    559     {
    560       if (i == (count - 1)
    561           || array[i]->node != array[i + 1]->node
    562           || !STREQ (array[i]->entry, array[i + 1]->entry))
    563         copy[j++] = array[i];
    564       else
    565         {
    566           free (array[i]->entry);
    567           free (array[i]->entry_text);
    568           free (array[i]);
    569         }
    570     }
    571   copy[j] = NULL;
    572 
    573   /* Now COPY contains only unique entries.  Duplicated entries in the
    574      original array have been freed.  Replace the current array with
    575      the copy, fixing the NEXT pointers. */
    576   for (i = 0; copy[i]; i++)
    577     {
    578       copy[i]->next = copy[i + 1];
    579 
    580       /* Fix entry names which are the same.  They point to different nodes,
    581          so we make the entry name unique. */
    582       if (copy[i+1]
    583           && STREQ (copy[i]->entry, copy[i + 1]->entry)
    584           && !html)
    585         {
    586           char *new_entry_name;
    587 
    588           new_entry_name = xmalloc (10 + strlen (copy[i]->entry));
    589           sprintf (new_entry_name, "%s <%d>", copy[i]->entry, counter);
    590           free (copy[i]->entry);
    591           copy[i]->entry = new_entry_name;
    592           counter++;
    593         }
    594       else
    595         counter = 1;
    596 
    597       array[i] = copy[i];
    598     }
    599   array[i] = NULL;
    600 
    601   /* Free the storage used only by COPY. */
    602   free (copy);
    603 }
    604 
    605 
    606 /* Sort the index passed in INDEX, returning an array of pointers to
    607    elements.  The array is terminated with a NULL pointer.  */
    608 
    609 static INDEX_ELT **
    610 sort_index (INDEX_ELT *index)
    611 {
    612   INDEX_ELT **array;
    613   INDEX_ELT *temp;
    614   int count = 0;
    615   int save_line_number = line_number;
    616   char *save_input_filename = input_filename;
    617   int save_html = html;
    618 
    619   /* Pretend we are in non-HTML mode, for the purpose of getting the
    620      expanded index entry that lacks any markup and other HTML escape
    621      characters which could produce a wrong sort order.  */
    622   /* fixme: html: this still causes some markup, such as non-ASCII
    623      characters @AE{} etc., to sort incorrectly.  */
    624   html = 0;
    625 
    626   for (temp = index, count = 0; temp; temp = temp->next, count++)
    627     ;
    628   /* We have the length, now we can allocate an array. */
    629   array = xmalloc ((count + 1) * sizeof (INDEX_ELT *));
    630 
    631   for (temp = index, count = 0; temp; temp = temp->next, count++)
    632     {
    633       /* Allocate new memory for the return array, since parts of the
    634          original INDEX get freed.  Otherwise, if the document calls
    635          @printindex twice on the same index, with duplicate entries,
    636          we'll have garbage the second time.  There are cleaner ways to
    637          deal, but this will suffice for now.  */
    638       array[count] = xmalloc (sizeof (INDEX_ELT));
    639       *(array[count]) = *(temp);  /* struct assignment, hope it's ok */
    640 
    641       /* Adjust next pointers to use the new memory.  */
    642       if (count > 0)
    643         array[count-1]->next = array[count];
    644 
    645       /* Set line number and input filename to the source line for this
    646          index entry, as this expansion finds any errors.  */
    647       line_number = array[count]->defining_line;
    648       input_filename = array[count]->defining_file;
    649 
    650       /* If this particular entry should be printed as a "code" index,
    651          then expand it as @code{entry}, i.e., as in fixed-width font.  */
    652       array[count]->entry = expansion (temp->entry_text, array[count]->code);
    653     }
    654   array[count] = NULL;    /* terminate the array. */
    655 
    656   line_number = save_line_number;
    657   input_filename = save_input_filename;
    658   html = save_html;
    659 
    660 #ifdef HAVE_STRCOLL
    661   /* This is not perfect.  We should set (then restore) the locale to the
    662      documentlanguage, so strcoll operates according to the document's
    663      locale, not the user's.  For now, I'm just going to assume that
    664      those few new documents which use @documentlanguage will be
    665      processed in the appropriate locale.  In any case, don't use
    666      strcoll in the C (aka POSIX) locale, that is the ASCII ordering.  */
    667   if (language_code != en)
    668     {
    669       char *lang_env = getenv ("LANG");
    670       if (lang_env && !STREQ (lang_env, "C") && !STREQ (lang_env, "POSIX"))
    671         index_compare_fn = strcoll;
    672     }
    673 #endif /* HAVE_STRCOLL */
    674 
    675   /* Sort the array. */
    676   qsort (array, count, sizeof (INDEX_ELT *), index_element_compare);
    677 
    678   /* Remove duplicate entries.  */
    679   make_index_entries_unique (array, count);
    680 
    681   /* Replace the original index with the sorted one, in case the
    682      document wants to print it again.  If the index wasn't empty.  */
    683   if (index)
    684     *index = **array;
    685 
    686   return array;
    687 }
    688 
    689 static void
    690 insert_index_output_line_no (int line_number, int output_line_number_len)
    691 {
    692   int last_column;
    693   int str_size = output_line_number_len + strlen (_("(line )"))
    694     + sizeof (NULL);
    695   char *out_line_no_str = (char *) xmalloc (str_size + 1);
    696 
    697   /* Do not translate ``(line NNN)'' below for !no_headers case (Info output),
    698      because it's something like the ``* Menu'' strings.  For plaintext output
    699      it should be translated though.  */
    700   sprintf (out_line_no_str,
    701       no_headers ? _("(line %*d)") : "(line %*d)",
    702       output_line_number_len, line_number);
    703 
    704   {
    705     int i = output_paragraph_offset;
    706     while (0 < i && output_paragraph[i-1] != '\n')
    707       i--;
    708     last_column = output_paragraph_offset - i;
    709   }
    710 
    711   if (last_column + strlen (out_line_no_str) > fill_column)
    712     {
    713       insert ('\n');
    714       last_column = 0;
    715     }
    716 
    717   while (last_column + strlen (out_line_no_str) < fill_column)
    718     {
    719       insert (' ');
    720       last_column++;
    721     }
    722 
    723   insert_string (out_line_no_str);
    724   insert ('\n');
    725 
    726   free (out_line_no_str);
    727 }
    728 
    729 /* Nonzero means that we are in the middle of printing an index. */
    730 int printing_index = 0;
    731 
    732 /* Takes one arg, a short name of an index to print.
    733    Outputs a menu of the sorted elements of the index. */
    734 void
    735 cm_printindex (int arg, int arg2, int arg3)
    736 {
    737   char *index_name;
    738   get_rest_of_line (0, &index_name);
    739 
    740   /* get_rest_of_line increments the line number by one,
    741      so to make warnings/errors point to the correct line,
    742      we decrement the line_number again.  */
    743   if (!handling_delayed_writes)
    744     line_number--;
    745 
    746   if (xml && !docbook)
    747     {
    748       xml_insert_element (PRINTINDEX, START);
    749       insert_string (index_name);
    750       xml_insert_element (PRINTINDEX, END);
    751     }
    752   else if (!handling_delayed_writes)
    753     {
    754       int command_len = sizeof ("@ ") + strlen (command) + strlen (index_name);
    755       char *index_command = xmalloc (command_len + 1);
    756 
    757       close_paragraph ();
    758       if (docbook)
    759         xml_begin_index ();
    760 
    761       sprintf (index_command, "@%s %s", command, index_name);
    762       register_delayed_write (index_command);
    763       free (index_command);
    764     }
    765   else
    766     {
    767       int item;
    768       INDEX_ELT *index;
    769       INDEX_ELT *last_index = 0;
    770       INDEX_ELT **array;
    771       unsigned line_length;
    772       char *line;
    773       int saved_inhibit_paragraph_indentation = inhibit_paragraph_indentation;
    774       int saved_filling_enabled = filling_enabled;
    775       int saved_line_number = line_number;
    776       char *saved_input_filename = input_filename;
    777       unsigned output_line_number_len;
    778 
    779       index = index_list (index_name);
    780       if (index == (INDEX_ELT *)-1)
    781         {
    782           line_error (_("Unknown index `%s' in @printindex"), index_name);
    783           free (index_name);
    784           return;
    785         }
    786 
    787       /* Do this before sorting, so execute_string is in the good environment */
    788       if (xml && docbook)
    789         xml_begin_index ();
    790 
    791       /* Do this before sorting, so execute_string in index_element_compare
    792          will give the same results as when we actually print.  */
    793       printing_index = 1;
    794       filling_enabled = 0;
    795       inhibit_paragraph_indentation = 1;
    796       xml_sort_index = 1;
    797       array = sort_index (index);
    798       xml_sort_index = 0;
    799       close_paragraph ();
    800       if (html)
    801         add_html_block_elt_args ("<ul class=\"index-%s\" compact>",
    802                                  index_name);
    803       else if (!no_headers && !docbook)
    804         { /* Info.  Add magic cookie for info readers (to treat this
    805              menu differently), and the usual start-of-menu.  */
    806           add_char ('\0');
    807           add_word ("\010[index");
    808           add_char ('\0');
    809           add_word ("\010]\n");
    810           add_word ("* Menu:\n\n");
    811         }
    812 
    813       me_inhibit_expansion++;
    814 
    815       /* This will probably be enough.  */
    816       line_length = 100;
    817       line = xmalloc (line_length);
    818 
    819       {
    820         char *max_output_line_number = (char *) xmalloc (25 * sizeof (char));
    821 
    822         if (no_headers)
    823           sprintf (max_output_line_number, "%d", output_line_number);
    824         else
    825           {
    826             INDEX_ELT *tmp_entry = index;
    827             unsigned tmp = 0;
    828             for (tmp_entry = index; tmp_entry; tmp_entry = tmp_entry->next)
    829               tmp = tmp_entry->output_line > tmp ? tmp_entry->output_line : tmp;
    830             sprintf (max_output_line_number, "%d", tmp);
    831           }
    832 
    833         output_line_number_len = strlen (max_output_line_number);
    834         free (max_output_line_number);
    835       }
    836 
    837       for (item = 0; (index = array[item]); item++)
    838         {
    839           /* A pathological document might have an index entry outside of any
    840              node.  Don't crash; try using the section name instead.  */
    841           char *index_node = index->node;
    842 
    843           line_number = index->defining_line;
    844           input_filename = index->defining_file;
    845 
    846           if ((!index_node || !*index_node) && html)
    847             index_node = toc_find_section_of_node (index_node);
    848 
    849           if (!index_node || !*index_node)
    850             {
    851               line_error (_("Entry for index `%s' outside of any node"),
    852                           index_name);
    853               if (html || !no_headers)
    854                 index_node = (char *) _("(outside of any node)");
    855             }
    856 
    857           if (html)
    858             {
    859               /* For HTML, we need to expand and HTML-escape the
    860                  original entry text, at the same time.  Consider
    861                  @cindex J@"urgen.  We want J&uuml;urgen.  We can't
    862                  expand and then escape since we'll end up with
    863                  J&amp;uuml;rgen.  We can't escape and then expand
    864                  because then `expansion' will see J@&quot;urgen, and
    865                  @&quot;urgen is not a command.  */
    866               char *html_entry =
    867                 maybe_escaped_expansion (index->entry_text, index->code, 1);
    868 
    869               add_html_block_elt_args ("\n<li><a href=\"%s#index-",
    870                   (splitting && index->output_file) ? index->output_file : "");
    871               add_escaped_anchor_name (index->entry_text, 0);
    872               add_word_args ("-%d\">%s</a>: ", index->entry_number,
    873                   html_entry);
    874               free (html_entry);
    875 
    876               add_word ("<a href=\"");
    877               if (index->node && *index->node)
    878                 {
    879                   /* Ensure any non-macros in the node name are expanded.  */
    880                   char *expanded_index;
    881 
    882                   in_fixed_width_font++;
    883                   expanded_index = expansion (index_node, 0);
    884                   in_fixed_width_font--;
    885                   add_anchor_name (expanded_index, 1);
    886 		  expanded_index = escape_string (expanded_index);
    887                   add_word_args ("\">%s</a>", expanded_index);
    888                   free (expanded_index);
    889                 }
    890               else if (STREQ (index_node, _("(outside of any node)")))
    891                 {
    892                   add_anchor_name (index_node, 1);
    893                   add_word_args ("\">%s</a>", index_node);
    894                 }
    895               else
    896                 /* If we use the section instead of the (missing) node, then
    897                    index_node already includes all we need except the #.  */
    898                 add_word_args ("#%s</a>", index_node);
    899 
    900               add_html_block_elt ("</li>");
    901             }
    902           else if (xml && docbook)
    903             {
    904               /* In the DocBook case, the expanded index entry is not
    905                  good for us, since it was expanded for non-DocBook mode
    906                  inside sort_index.  So we send the original entry text
    907                  to be used with execute_string.  */
    908               xml_insert_indexentry (index->entry_text, index_node);
    909             }
    910           else
    911             {
    912               unsigned new_length = strlen (index->entry);
    913 
    914               if (new_length < 50) /* minimum length used below */
    915                 new_length = 50;
    916               new_length += strlen (index_node) + 7; /* * : .\n\0 */
    917 
    918               if (new_length > line_length)
    919                 {
    920                   line_length = new_length;
    921                   line = xrealloc (line, line_length);
    922                 }
    923               /* Print the entry, nicely formatted.  We've already
    924                  expanded any commands in index->entry, including any
    925                  implicit @code.  Thus, can't call execute_string, since
    926                  @@ has turned into @. */
    927               if (!no_headers)
    928                 {
    929                   sprintf (line, "* %-37s  ", index->entry);
    930                   line[2 + strlen (index->entry)] = ':';
    931                   insert_string (line);
    932                   /* Make sure any non-macros in the node name are expanded.  */
    933                   in_fixed_width_font++;
    934                   execute_string ("%s. ", index_node);
    935                   insert_index_output_line_no (index->output_line,
    936                       output_line_number_len);
    937                   in_fixed_width_font--;
    938                 }
    939               else
    940                 {
    941                   /* With --no-headers, the @node lines are gone, so
    942                      there's little sense in referring to them in the
    943                      index.  Instead, output the number or name of the
    944                      section that corresponds to that node.  */
    945                   sprintf (line, "%-*s ", number_sections ? 46 : 1, index->entry);
    946                   line[strlen (index->entry)] = ':';
    947                   insert_string (line);
    948 
    949                   if (strlen (index->section) > 0)
    950                     { /* We got your number.  */
    951                       insert_string ((char *) _("See "));
    952                       insert_string (index->section);
    953                     }
    954                   else
    955                     { /* Sigh, index in an @unnumbered. :-\  */
    956                       insert_string ("\n          ");
    957                       insert_string ((char *) _("See "));
    958                       insert_string ("``");
    959                       insert_string (expansion (index->section_name, 0));
    960                       insert_string ("''");
    961                     }
    962 
    963                   insert_string (". ");
    964                   insert_index_output_line_no (index->output_line,
    965                       output_line_number_len);
    966                 }
    967             }
    968 
    969           /* Prevent `output_paragraph' from growing to the size of the
    970              whole index.  */
    971           flush_output ();
    972           last_index = index;
    973         }
    974 
    975       free (line);
    976 
    977       me_inhibit_expansion--;
    978       printing_index = 0;
    979 
    980       close_single_paragraph ();
    981       filling_enabled = saved_filling_enabled;
    982       inhibit_paragraph_indentation = saved_inhibit_paragraph_indentation;
    983       input_filename = saved_input_filename;
    984       line_number = saved_line_number;
    985 
    986       if (html)
    987         add_html_block_elt ("</ul>");
    988       else if (xml && docbook)
    989         xml_end_index ();
    990     }
    991 
    992   free (index_name);
    993   /* Re-increment the line number, because get_rest_of_line
    994      left us looking at the next line after the command.  */
    995   line_number++;
    996 }
    997