xref: /openbsd-src/gnu/usr.bin/texinfo/info/info-utils.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /* info-utils.c -- miscellanous.
2    $Id: info-utils.c,v 1.1.1.3 2000/02/09 01:24:44 espie 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 
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   /* Show CTRL-x as ^X.  */
448   if (iscntrl (character) && character < 127)
449     {
450       switch (character)
451         {
452         case '\r':
453         case '\n':
454           the_rep[i++] = character;
455           break;
456 
457         case '\t':
458           {
459             int tw;
460 
461             tw = ((hpos + 8) & 0xf8) - hpos;
462             while (i < tw)
463               the_rep[i++] = ' ';
464           }
465           break;
466 
467         default:
468           the_rep[i++] = '^';
469           the_rep[i++] = (character | 0x40);
470         }
471     }
472   /* Show META-x as 0370.  */
473   else if (character > printable_limit)
474     {
475       sprintf (the_rep + i, "\\%0o", character);
476       i = strlen (the_rep);
477     }
478   else if (character == DEL)
479     {
480       the_rep[i++] = '^';
481       the_rep[i++] = '?';
482     }
483   else
484     the_rep[i++] = character;
485 
486   the_rep[i] = 0;
487 
488   return the_rep;
489 }
490 
491 
492 /* **************************************************************** */
493 /*                                                                  */
494 /*                  Functions Static To This File                   */
495 /*                                                                  */
496 /* **************************************************************** */
497 
498 /* Amount of space allocated to INFO_PARSED_FILENAME via xmalloc (). */
499 static int parsed_filename_size = 0;
500 
501 /* Amount of space allocated to INFO_PARSED_NODENAME via xmalloc (). */
502 static int parsed_nodename_size = 0;
503 
504 static void save_string (), saven_string ();
505 
506 /* Remember FILENAME in PARSED_FILENAME.  An empty FILENAME is translated
507    to a NULL pointer in PARSED_FILENAME. */
508 static void
509 save_filename (filename)
510      char *filename;
511 {
512   save_string (filename, &info_parsed_filename, &parsed_filename_size);
513 }
514 
515 /* Just like save_filename (), but you pass the length of the string. */
516 static void
517 saven_filename (filename, len)
518      char *filename;
519      int len;
520 {
521   saven_string (filename, len,
522                 &info_parsed_filename, &parsed_filename_size);
523 }
524 
525 /* Remember NODENAME in PARSED_NODENAME.  An empty NODENAME is translated
526    to a NULL pointer in PARSED_NODENAME. */
527 static void
528 save_nodename (nodename)
529      char *nodename;
530 {
531   save_string (nodename, &info_parsed_nodename, &parsed_nodename_size);
532 }
533 
534 /* Just like save_nodename (), but you pass the length of the string. */
535 static void
536 saven_nodename (nodename, len)
537      char *nodename;
538      int len;
539 {
540   saven_string (nodename, len,
541                 &info_parsed_nodename, &parsed_nodename_size);
542 }
543 
544 /* Remember STRING in STRING_P.  STRING_P should currently have STRING_SIZE_P
545    bytes allocated to it.  An empty STRING is translated to a NULL pointer
546    in STRING_P. */
547 static void
548 save_string (string, string_p, string_size_p)
549      char *string;
550      char **string_p;
551      int *string_size_p;
552 {
553   if (!string || !*string)
554     {
555       if (*string_p)
556         free (*string_p);
557 
558       *string_p = (char *)NULL;
559       *string_size_p = 0;
560     }
561   else
562     {
563       if (strlen (string) >= *string_size_p)
564         *string_p = (char *)xrealloc
565           (*string_p, (*string_size_p = 1 + strlen (string)));
566 
567       strcpy (*string_p, string);
568     }
569 }
570 
571 /* Just like save_string (), but you also pass the length of STRING. */
572 static void
573 saven_string (string, len, string_p, string_size_p)
574      char *string;
575      int len;
576      char **string_p;
577      int *string_size_p;
578 {
579   if (!string)
580     {
581       if (*string_p)
582         free (*string_p);
583 
584       *string_p = (char *)NULL;
585       *string_size_p = 0;
586     }
587   else
588     {
589       if (len >= *string_size_p)
590         *string_p = (char *)xrealloc (*string_p, (*string_size_p = 1 + len));
591 
592       strncpy (*string_p, string, len);
593       (*string_p)[len] = '\0';
594     }
595 }
596 
597 /* Return a pointer to the part of PATHNAME that simply defines the file. */
598 char *
599 filename_non_directory (pathname)
600      char *pathname;
601 {
602   register char *filename = pathname + strlen (pathname);
603 
604   if (HAVE_DRIVE (pathname))
605     pathname += 2;
606 
607   while (filename > pathname && !IS_SLASH (filename[-1]))
608     filename--;
609 
610   return (filename);
611 }
612 
613 /* Return non-zero if NODE is one especially created by Info. */
614 int
615 internal_info_node_p (node)
616      NODE *node;
617 {
618 #if defined (NEVER)
619   if (node &&
620       (node->filename && !*node->filename) &&
621       !node->parent && node->nodename)
622     return (1);
623   else
624     return (0);
625 #else
626   return ((node != (NODE *)NULL) && ((node->flags & N_IsInternal) != 0));
627 #endif /* !NEVER */
628 }
629 
630 /* Make NODE appear to be one especially created by Info. */
631 void
632 name_internal_node (node, name)
633      NODE *node;
634      char *name;
635 {
636   if (!node)
637     return;
638 
639   node->filename = "";
640   node->parent = (char *)NULL;
641   node->nodename = name;
642   node->flags |= N_IsInternal;
643 }
644 
645 /* Return the window displaying NAME, the name of an internally created
646    Info window. */
647 WINDOW *
648 get_internal_info_window (name)
649      char *name;
650 {
651   WINDOW *win;
652 
653   for (win = windows; win; win = win->next)
654     if (internal_info_node_p (win->node) &&
655         (strcmp (win->node->nodename, name) == 0))
656       break;
657 
658   return (win);
659 }
660 
661 /* Return a window displaying the node NODE. */
662 WINDOW *
663 get_window_of_node (node)
664      NODE *node;
665 {
666   WINDOW *win = (WINDOW *)NULL;
667 
668   for (win = windows; win; win = win->next)
669     if (win->node == node)
670       break;
671 
672   return (win);
673 }
674