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