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