Rename texinfo-4 directory to "texinfo" in vendor branch
[dragonfly.git] / contrib / texinfo / info / info-utils.c
1 /* info-utils.c -- miscellanous.
2    $Id: info-utils.c,v 1.4 2004/04/11 17:56:45 karl Exp $
3
4    Copyright (C) 1993, 1998, 2003, 2004 Free Software Foundation, Inc.
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2, or (at your option)
9    any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20    Originally written by Brian Fox (bfox@ai.mit.edu). */
21
22 #include "info.h"
23 #include "info-utils.h"
24 #if defined (HANDLE_MAN_PAGES)
25 #  include "man.h"
26 #endif /* HANDLE_MAN_PAGES */
27
28 /* When non-zero, various display and input functions handle ISO Latin
29    character sets correctly. */
30 int ISO_Latin_p = 1;
31
32 /* Variable which holds the most recent filename parsed as a result of
33    calling info_parse_xxx (). */
34 char *info_parsed_filename = (char *)NULL;
35
36 /* Variable which holds the most recent nodename parsed as a result of
37    calling info_parse_xxx (). */
38 char *info_parsed_nodename = (char *)NULL;
39
40 /* Variable which holds the most recent line number parsed as a result of
41    calling info_parse_xxx (). */
42 int info_parsed_line_number = 0;
43
44 /* Functions to remember a filename or nodename for later return. */
45 static void save_filename (char *filename);
46 static void saven_filename (char *filename, int len);
47 static void save_nodename (char *nodename);
48 static void saven_nodename (char *nodename, int len);
49
50 /* How to get a reference (either menu or cross). */
51 static REFERENCE **info_references_internal (char *label,
52     SEARCH_BINDING *binding);
53
54 /* Parse the filename and nodename out of STRING.  If STRING doesn't
55    contain a filename (i.e., it is NOT (FILENAME)NODENAME) then set
56    INFO_PARSED_FILENAME to NULL.  If second argument NEWLINES_OKAY is
57    non-zero, it says to allow the nodename specification to cross a
58    newline boundary (i.e., only `,', `.', or `TAB' can end the spec). */
59 void
60 info_parse_node (char *string, int newlines_okay)
61 {
62   register int i = 0;
63
64   /* Default the answer. */
65   save_filename ((char *)NULL);
66   save_nodename ((char *)NULL);
67
68   /* Special case of nothing passed.  Return nothing. */
69   if (!string || !*string)
70     return;
71
72   string += skip_whitespace (string);
73
74   /* Check for (FILENAME)NODENAME. */
75   if (*string == '(')
76     {
77       i = 0;
78       /* Advance past the opening paren. */
79       string++;
80
81       /* Find the closing paren. */
82       while (string[i] && string[i] != ')')
83         i++;
84
85       /* Remember parsed filename. */
86       saven_filename (string, i);
87
88       /* Point directly at the nodename. */
89       string += i;
90
91       if (*string)
92         string++;
93     }
94
95   /* Parse out nodename. */
96   i = skip_node_characters (string, newlines_okay);
97   saven_nodename (string, i);
98   canonicalize_whitespace (info_parsed_nodename);
99   if (info_parsed_nodename && !*info_parsed_nodename)
100     {
101       free (info_parsed_nodename);
102       info_parsed_nodename = (char *)NULL;
103     }
104
105   /* Parse ``(line ...)'' part of menus, if any.  */
106   {
107     char *rest = string + i;
108
109     /* Advance only if it's not already at end of string.  */
110     if (*rest)
111       rest++;
112
113     /* Skip any whitespace first, and then a newline in case the item
114        was so long to contain the ``(line ...)'' string in the same
115        physical line.  */
116     while (whitespace(*rest))
117       rest++;
118     if (*rest == '\n')
119       {
120         rest++;
121         while (whitespace(*rest))
122           rest++;
123       }
124
125     /* Are we looking at an opening parenthesis?  That can only mean
126        we have a winner. :)  */
127     if (strncmp (rest, "(line ", strlen ("(line ")) == 0)
128       {
129         rest += strlen ("(line ");
130         info_parsed_line_number = strtol (rest, NULL, 0);
131       }
132     else
133       info_parsed_line_number = 0;
134   }
135 }
136
137 /* Return the node addressed by LABEL in NODE (usually one of "Prev:",
138    "Next:", "Up:", "File:", or "Node:".  After a call to this function,
139    the global INFO_PARSED_NODENAME and INFO_PARSED_FILENAME contain
140    the information. */
141 void
142 info_parse_label (char *label, NODE *node)
143 {
144   register int i;
145   char *nodeline;
146
147   /* Default answer to failure. */
148   save_nodename ((char *)NULL);
149   save_filename ((char *)NULL);
150
151   /* Find the label in the first line of this node. */
152   nodeline = node->contents;
153   i = string_in_line (label, nodeline);
154
155   if (i == -1)
156     return;
157
158   nodeline += i;
159   nodeline += skip_whitespace (nodeline);
160   info_parse_node (nodeline, DONT_SKIP_NEWLINES);
161 }
162 \f
163 /* **************************************************************** */
164 /*                                                                  */
165 /*                  Finding and Building Menus                      */
166 /*                                                                  */
167 /* **************************************************************** */
168
169 /* Return a NULL terminated array of REFERENCE * which represents the menu
170    found in NODE.  If there is no menu in NODE, just return a NULL pointer. */
171 REFERENCE **
172 info_menu_of_node (NODE *node)
173 {
174   long position;
175   SEARCH_BINDING tmp_search;
176   REFERENCE **menu = (REFERENCE **)NULL;
177
178   tmp_search.buffer = node->contents;
179   tmp_search.start = 0;
180   tmp_search.end = node->nodelen;
181   tmp_search.flags = S_FoldCase;
182
183   /* Find the start of the menu. */
184   position = search_forward (INFO_MENU_LABEL, &tmp_search);
185
186   if (position == -1)
187     return ((REFERENCE **) NULL);
188
189   /* We have the start of the menu now.  Glean menu items from the rest
190      of the node. */
191   tmp_search.start = position + strlen (INFO_MENU_LABEL);
192   tmp_search.start += skip_line (tmp_search.buffer + tmp_search.start);
193   tmp_search.start--;
194   menu = info_menu_items (&tmp_search);
195   return (menu);
196 }
197
198 /* Return a NULL terminated array of REFERENCE * which represents the cross
199    refrences found in NODE.  If there are no cross references in NODE, just
200    return a NULL pointer. */
201 REFERENCE **
202 info_xrefs_of_node (NODE *node)
203 {
204   SEARCH_BINDING tmp_search;
205
206 #if defined (HANDLE_MAN_PAGES)
207   if (node->flags & N_IsManPage)
208     return (xrefs_of_manpage (node));
209 #endif
210
211   tmp_search.buffer = node->contents;
212   tmp_search.start = 0;
213   tmp_search.end = node->nodelen;
214   tmp_search.flags = S_FoldCase;
215
216   return (info_xrefs (&tmp_search));
217 }
218
219 /* Glean menu entries from BINDING->buffer + BINDING->start until we
220    have looked at the entire contents of BINDING.  Return an array
221    of REFERENCE * that represents each menu item in this range. */
222 REFERENCE **
223 info_menu_items (SEARCH_BINDING *binding)
224 {
225   return (info_references_internal (INFO_MENU_ENTRY_LABEL, binding));
226 }
227
228 /* Glean cross references from BINDING->buffer + BINDING->start until
229    BINDING->end.  Return an array of REFERENCE * that represents each
230    cross reference in this range. */
231 REFERENCE **
232 info_xrefs (SEARCH_BINDING *binding)
233 {
234   return (info_references_internal (INFO_XREF_LABEL, binding));
235 }
236
237 /* Glean cross references or menu items from BINDING.  Return an array
238    of REFERENCE * that represents the items found. */
239 static REFERENCE **
240 info_references_internal (char *label, SEARCH_BINDING *binding)
241 {
242   SEARCH_BINDING tmp_search;
243   REFERENCE **refs = (REFERENCE **)NULL;
244   int refs_index = 0, refs_slots = 0;
245   int searching_for_menu_items = 0;
246   long position;
247
248   tmp_search.buffer = binding->buffer;
249   tmp_search.start = binding->start;
250   tmp_search.end = binding->end;
251   tmp_search.flags = S_FoldCase | S_SkipDest;
252
253   searching_for_menu_items = (strcasecmp (label, INFO_MENU_ENTRY_LABEL) == 0);
254
255   while ((position = search_forward (label, &tmp_search)) != -1)
256     {
257       int offset, start;
258       char *refdef;
259       REFERENCE *entry;
260
261       tmp_search.start = position;
262       tmp_search.start += skip_whitespace (tmp_search.buffer + tmp_search.start);
263       start = tmp_search.start - binding->start;
264       refdef = tmp_search.buffer + tmp_search.start;
265       offset = string_in_line (":", refdef);
266
267       /* When searching for menu items, if no colon, there is no
268          menu item on this line. */
269       if (offset == -1)
270         {
271           if (searching_for_menu_items)
272             continue;
273           else
274             {
275               int temp;
276
277               temp = skip_line (refdef);
278               offset = string_in_line (":", refdef + temp);
279               if (offset == -1)
280                 continue;       /* Give up? */
281               else
282                 offset += temp;
283             }
284         }
285
286       entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
287       entry->filename = (char *)NULL;
288       entry->nodename = (char *)NULL;
289       entry->label = (char *)xmalloc (offset);
290       strncpy (entry->label, refdef, offset - 1);
291       entry->label[offset - 1] = '\0';
292       canonicalize_whitespace (entry->label);
293
294       refdef += offset;
295       entry->start = start;
296       entry->end = refdef - binding->buffer;
297
298       /* If this reference entry continues with another ':' then the
299          nodename is the same as the label. */
300       if (*refdef == ':')
301         {
302           entry->nodename = xstrdup (entry->label);
303         }
304       else
305         {
306           /* This entry continues with a specific nodename.  Parse the
307              nodename from the specification. */
308
309           refdef += skip_whitespace_and_newlines (refdef);
310
311           if (searching_for_menu_items)
312             info_parse_node (refdef, DONT_SKIP_NEWLINES);
313           else
314             info_parse_node (refdef, SKIP_NEWLINES);
315
316           if (info_parsed_filename)
317             entry->filename = xstrdup (info_parsed_filename);
318
319           if (info_parsed_nodename)
320             entry->nodename = xstrdup (info_parsed_nodename);
321
322           entry->line_number = info_parsed_line_number;
323         }
324
325       add_pointer_to_array
326         (entry, refs_index, refs, refs_slots, 50, REFERENCE *);
327     }
328   return (refs);
329 }
330
331 /* Get the entry associated with LABEL in REFERENCES.  Return a pointer
332    to the ENTRY if found, or NULL. */
333 REFERENCE *
334 info_get_labeled_reference (char *label, REFERENCE **references)
335 {
336   register int i;
337   REFERENCE *entry;
338
339   for (i = 0; references && (entry = references[i]); i++)
340     {
341       if (strcmp (label, entry->label) == 0)
342         return (entry);
343     }
344   return ((REFERENCE *)NULL);
345 }
346
347 /* A utility function for concatenating REFERENCE **.  Returns a new
348    REFERENCE ** which is the concatenation of REF1 and REF2.  The REF1
349    and REF2 arrays are freed, but their contents are not. */
350 REFERENCE **
351 info_concatenate_references (REFERENCE **ref1, REFERENCE **ref2)
352 {
353   register int i, j;
354   REFERENCE **result;
355   int size;
356
357   /* With one argument passed as NULL, simply return the other arg. */
358   if (!ref1)
359     return (ref2);
360   else if (!ref2)
361     return (ref1);
362
363   /* Get the total size of the slots that we will need. */
364   for (i = 0; ref1[i]; i++);
365   size = i;
366   for (i = 0; ref2[i]; i++);
367   size += i;
368
369   result = (REFERENCE **)xmalloc ((1 + size) * sizeof (REFERENCE *));
370
371   /* Copy the contents over. */
372   for (i = 0; ref1[i]; i++)
373     result[i] = ref1[i];
374
375   j = i;
376   for (i = 0; ref2[i]; i++)
377     result[j++] = ref2[i];
378
379   result[j] = (REFERENCE *)NULL;
380   free (ref1);
381   free (ref2);
382   return (result);
383 }
384
385
386 \f
387 /* Copy a reference structure.  Since we tend to free everything at
388    every opportunity, we don't share any points, but copy everything into
389    new memory.  */
390 REFERENCE *
391 info_copy_reference (REFERENCE *src)
392 {
393   REFERENCE *dest = xmalloc (sizeof (REFERENCE));
394   dest->label = src->label ? xstrdup (src->label) : NULL;
395   dest->filename = src->filename ? xstrdup (src->filename) : NULL;
396   dest->nodename = src->nodename ? xstrdup (src->nodename) : NULL;
397   dest->start = src->start;
398   dest->end = src->end;
399   
400   return dest;
401 }
402
403
404 \f
405 /* Free the data associated with REFERENCES. */
406 void
407 info_free_references (REFERENCE **references)
408 {
409   register int i;
410   REFERENCE *entry;
411
412   if (references)
413     {
414       for (i = 0; references && (entry = references[i]); i++)
415         {
416           maybe_free (entry->label);
417           maybe_free (entry->filename);
418           maybe_free (entry->nodename);
419
420           free (entry);
421         }
422
423       free (references);
424     }
425 }
426
427 /* Search for sequences of whitespace or newlines in STRING, replacing
428    all such sequences with just a single space.  Remove whitespace from
429    start and end of string. */
430 void
431 canonicalize_whitespace (char *string)
432 {
433   register int i, j;
434   int len, whitespace_found, whitespace_loc = 0;
435   char *temp;
436
437   if (!string)
438     return;
439
440   len = strlen (string);
441   temp = (char *)xmalloc (1 + len);
442
443   /* Search for sequences of whitespace or newlines.  Replace all such
444      sequences in the string with just a single space. */
445
446   whitespace_found = 0;
447   for (i = 0, j = 0; string[i]; i++)
448     {
449       if (whitespace_or_newline (string[i]))
450         {
451           whitespace_found++;
452           whitespace_loc = i;
453           continue;
454         }
455       else
456         {
457           if (whitespace_found && whitespace_loc)
458             {
459               whitespace_found = 0;
460
461               /* Suppress whitespace at start of string. */
462               if (j)
463                 temp[j++] = ' ';
464             }
465
466           temp[j++] = string[i];
467         }
468     }
469
470   /* Kill trailing whitespace. */
471   if (j && whitespace (temp[j - 1]))
472     j--;
473
474   temp[j] = '\0';
475   strcpy (string, temp);
476   free (temp);
477 }
478
479 /* String representation of a char returned by printed_representation (). */
480 static char the_rep[10];
481
482 /* Return a pointer to a string which is the printed representation
483    of CHARACTER if it were printed at HPOS. */
484 char *
485 printed_representation (unsigned char character, int hpos)
486 {
487   register int i = 0;
488   int printable_limit = ISO_Latin_p ? 255 : 127;
489
490   if (raw_escapes_p && character == '\033')
491     the_rep[i++] = character;
492   /* Show CTRL-x as ^X.  */
493   else if (iscntrl (character) && character < 127)
494     {
495       switch (character)
496         {
497         case '\r':
498         case '\n':
499           the_rep[i++] = character;
500           break;
501
502         case '\t':
503           {
504             int tw;
505
506             tw = ((hpos + 8) & 0xf8) - hpos;
507             while (i < tw)
508               the_rep[i++] = ' ';
509           }
510           break;
511
512         default:
513           the_rep[i++] = '^';
514           the_rep[i++] = (character | 0x40);
515         }
516     }
517   /* Show META-x as 0370.  */
518   else if (character > printable_limit)
519     {
520       sprintf (the_rep + i, "\\%0o", character);
521       i = strlen (the_rep);
522     }
523   else if (character == DEL)
524     {
525       the_rep[i++] = '^';
526       the_rep[i++] = '?';
527     }
528   else
529     the_rep[i++] = character;
530
531   the_rep[i] = 0;
532
533   return the_rep;
534 }
535
536 \f
537 /* **************************************************************** */
538 /*                                                                  */
539 /*                  Functions Static To This File                   */
540 /*                                                                  */
541 /* **************************************************************** */
542
543 /* Amount of space allocated to INFO_PARSED_FILENAME via xmalloc (). */
544 static int parsed_filename_size = 0;
545
546 /* Amount of space allocated to INFO_PARSED_NODENAME via xmalloc (). */
547 static int parsed_nodename_size = 0;
548
549 static void save_string (char *string, char **string_p, int *string_size_p);
550 static void saven_string (char *string, int len, char **string_p,
551     int *string_size_p);
552
553 /* Remember FILENAME in PARSED_FILENAME.  An empty FILENAME is translated
554    to a NULL pointer in PARSED_FILENAME. */
555 static void
556 save_filename (char *filename)
557 {
558   save_string (filename, &info_parsed_filename, &parsed_filename_size);
559 }
560
561 /* Just like save_filename (), but you pass the length of the string. */
562 static void
563 saven_filename (char *filename, int len)
564 {
565   saven_string (filename, len,
566                 &info_parsed_filename, &parsed_filename_size);
567 }
568
569 /* Remember NODENAME in PARSED_NODENAME.  An empty NODENAME is translated
570    to a NULL pointer in PARSED_NODENAME. */
571 static void
572 save_nodename (char *nodename)
573 {
574   save_string (nodename, &info_parsed_nodename, &parsed_nodename_size);
575 }
576
577 /* Just like save_nodename (), but you pass the length of the string. */
578 static void
579 saven_nodename (char *nodename, int len)
580 {
581   saven_string (nodename, len,
582                 &info_parsed_nodename, &parsed_nodename_size);
583 }
584
585 /* Remember STRING in STRING_P.  STRING_P should currently have STRING_SIZE_P
586    bytes allocated to it.  An empty STRING is translated to a NULL pointer
587    in STRING_P. */
588 static void
589 save_string (char *string, char **string_p, int *string_size_p)
590 {
591   if (!string || !*string)
592     {
593       if (*string_p)
594         free (*string_p);
595
596       *string_p = (char *)NULL;
597       *string_size_p = 0;
598     }
599   else
600     {
601       if (strlen (string) >= (unsigned int) *string_size_p)
602         *string_p = (char *)xrealloc
603           (*string_p, (*string_size_p = 1 + strlen (string)));
604
605       strcpy (*string_p, string);
606     }
607 }
608
609 /* Just like save_string (), but you also pass the length of STRING. */
610 static void
611 saven_string (char *string, int len, char **string_p, int *string_size_p)
612 {
613   if (!string)
614     {
615       if (*string_p)
616         free (*string_p);
617
618       *string_p = (char *)NULL;
619       *string_size_p = 0;
620     }
621   else
622     {
623       if (len >= *string_size_p)
624         *string_p = (char *)xrealloc (*string_p, (*string_size_p = 1 + len));
625
626       strncpy (*string_p, string, len);
627       (*string_p)[len] = '\0';
628     }
629 }
630
631 /* Return a pointer to the part of PATHNAME that simply defines the file. */
632 char *
633 filename_non_directory (char *pathname)
634 {
635   register char *filename = pathname + strlen (pathname);
636
637   if (HAVE_DRIVE (pathname))
638     pathname += 2;
639
640   while (filename > pathname && !IS_SLASH (filename[-1]))
641     filename--;
642
643   return (filename);
644 }
645
646 /* Return non-zero if NODE is one especially created by Info. */
647 int
648 internal_info_node_p (NODE *node)
649 {
650 #if defined (NEVER)
651   if (node &&
652       (node->filename && !*node->filename) &&
653       !node->parent && node->nodename)
654     return (1);
655   else
656     return (0);
657 #else
658   return ((node != (NODE *)NULL) && ((node->flags & N_IsInternal) != 0));
659 #endif /* !NEVER */
660 }
661
662 /* Make NODE appear to be one especially created by Info. */
663 void
664 name_internal_node (NODE *node, char *name)
665 {
666   if (!node)
667     return;
668
669   node->filename = "";
670   node->parent = (char *)NULL;
671   node->nodename = name;
672   node->flags |= N_IsInternal;
673 }
674
675 /* Return the window displaying NAME, the name of an internally created
676    Info window. */
677 WINDOW *
678 get_internal_info_window (char *name)
679 {
680   WINDOW *win;
681
682   for (win = windows; win; win = win->next)
683     if (internal_info_node_p (win->node) &&
684         (strcmp (win->node->nodename, name) == 0))
685       break;
686
687   return (win);
688 }
689
690 /* Return a window displaying the node NODE. */
691 WINDOW *
692 get_window_of_node (NODE *node)
693 {
694   WINDOW *win = (WINDOW *)NULL;
695
696   for (win = windows; win; win = win->next)
697     if (win->node == node)
698       break;
699
700   return (win);
701 }