xref: /netbsd-src/external/gpl2/texinfo/dist/makeinfo/index.c (revision 633172aec93945790be19dfdab67cb51790b095a)
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 
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.
59    Returns -1 if NAME isn't found. */
60 static int
find_index_offset(char * name)61 find_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. */
72 static INDEX_ALIST *
find_index(char * name)73 find_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.  */
84 static void
define_user_command(char * name,COMMAND_FUNCTION (* proc),int needs_braces_p)85 define_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... */
103 static void
free_index(INDEX_ELT * index)104 free_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. */
122 static void
undefindex(char * name)123 undefindex (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.  */
144 static void
index_add_arg(char * name)145 index_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. */
267 static void
gen_index(void)268 gen_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. */
279 static void
defindex(char * name,int code)280 defindex (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.  */
319 static void
top_defindex(char * name,int code)320 top_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.  */
332 void
init_indices(void)333 init_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. */
383 static int
translate_index(char * name)384 translate_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. */
395 INDEX_ELT *
index_list(char * name)396 index_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. */
406 static void
gen_defindex(int code)407 gen_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 
428 void
cm_defindex(void)429 cm_defindex (void)
430 {
431   gen_defindex (0);
432 }
433 
434 void
cm_defcodeindex(void)435 cm_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. */
443 void
cm_synindex(void)444 cm_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 
473 void
cm_pindex(void)474 cm_pindex (void)                    /* Pinhead index. */
475 {
476   index_add_arg ("pg");
477 }
478 
479 void
cm_vindex(void)480 cm_vindex (void)                    /* Variable index. */
481 {
482   index_add_arg ("vr");
483 }
484 
485 void
cm_kindex(void)486 cm_kindex (void)                    /* Key index. */
487 {
488   index_add_arg ("ky");
489 }
490 
491 void
cm_cindex(void)492 cm_cindex (void)                    /* Concept index. */
493 {
494   index_add_arg ("cp");
495 }
496 
497 void
cm_findex(void)498 cm_findex (void)                    /* Function index. */
499 {
500   index_add_arg ("fn");
501 }
502 
503 void
cm_tindex(void)504 cm_tindex (void)                    /* Data Type index. */
505 {
506   index_add_arg ("tp");
507 }
508 
509 int
index_element_compare(const void * element1,const void * element2)510 index_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. */
543 static void
make_index_entries_unique(INDEX_ELT ** array,int count)544 make_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 
603 static INDEX_ELT **
sort_index(INDEX_ELT * index)604 sort_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 
683 static void
insert_index_output_line_no(int line_number,int output_line_number_len)684 insert_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. */
724 int 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. */
728 void
cm_printindex(void)729 cm_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