Upgrade Texinfo from 4.8 to 4.13 on the vendor branch
[dragonfly.git] / contrib / texinfo / info / info-utils.c
1 /* info-utils.c -- miscellanous.
2    $Id: info-utils.c,v 1.12 2008/06/11 09:55:42 gray Exp $
3
4    Copyright (C) 1993, 1998, 2003, 2004, 2007, 2008
5    Free Software Foundation, Inc.
6
7    This program is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation, either version 3 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.
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 = 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 = 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 (NULL);
66   save_nodename (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 = 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 (NULL);
149   save_filename (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 = 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 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 = 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 = (mbscasecmp (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 = xmalloc (sizeof (REFERENCE));
287       entry->filename = NULL;
288       entry->nodename = NULL;
289       entry->label = xmalloc (offset);
290       strncpy (entry->label, refdef, offset - 1);
291       entry->label[offset - 1] = '\0';
292       canonicalize_whitespace (entry->label);
293       entry->line_number = 0;
294       
295       refdef += offset;
296       entry->start = start;
297       entry->end = refdef - binding->buffer;
298
299       /* If this reference entry continues with another ':' then the
300          nodename is the same as the label. */
301       if (*refdef == ':')
302         {
303           entry->nodename = xstrdup (entry->label);
304         }
305       else
306         {
307           /* This entry continues with a specific nodename.  Parse the
308              nodename from the specification. */
309
310           refdef += skip_whitespace_and_newlines (refdef);
311
312           if (searching_for_menu_items)
313             info_parse_node (refdef, DONT_SKIP_NEWLINES);
314           else
315             info_parse_node (refdef, SKIP_NEWLINES);
316
317           if (info_parsed_filename)
318             entry->filename = xstrdup (info_parsed_filename);
319
320           if (info_parsed_nodename)
321             entry->nodename = xstrdup (info_parsed_nodename);
322
323           entry->line_number = info_parsed_line_number;
324         }
325
326       add_pointer_to_array
327         (entry, refs_index, refs, refs_slots, 50, REFERENCE *);
328     }
329   return refs;
330 }
331
332 /* Get the entry associated with LABEL in REFERENCES.  Return a pointer
333    to the ENTRY if found, or NULL. */
334 REFERENCE *
335 info_get_labeled_reference (char *label, REFERENCE **references)
336 {
337   register int i;
338   REFERENCE *entry;
339
340   for (i = 0; references && (entry = references[i]); i++)
341     {
342       if (strcmp (label, entry->label) == 0)
343         return entry;
344     }
345   return NULL;
346 }
347
348 /* A utility function for concatenating REFERENCE **.  Returns a new
349    REFERENCE ** which is the concatenation of REF1 and REF2.  The REF1
350    and REF2 arrays are freed, but their contents are not. */
351 REFERENCE **
352 info_concatenate_references (REFERENCE **ref1, REFERENCE **ref2)
353 {
354   register int i, j;
355   REFERENCE **result;
356   int size;
357
358   /* With one argument passed as NULL, simply return the other arg. */
359   if (!ref1)
360     return ref2;
361   else if (!ref2)
362     return ref1;
363
364   /* Get the total size of the slots that we will need. */
365   for (i = 0; ref1[i]; i++);
366   size = i;
367   for (i = 0; ref2[i]; i++);
368   size += i;
369
370   result = xmalloc ((1 + size) * sizeof (REFERENCE *));
371
372   /* Copy the contents over. */
373   for (i = 0; ref1[i]; i++)
374     result[i] = ref1[i];
375
376   j = i;
377   for (i = 0; ref2[i]; i++)
378     result[j++] = ref2[i];
379
380   result[j] = NULL;
381   free (ref1);
382   free (ref2);
383   return result;
384 }
385
386
387 \f
388 /* Copy a reference structure.  Since we tend to free everything at
389    every opportunity, we don't share any points, but copy everything into
390    new memory.  */
391 REFERENCE *
392 info_copy_reference (REFERENCE *src)
393 {
394   REFERENCE *dest = xmalloc (sizeof (REFERENCE));
395   dest->label = src->label ? xstrdup (src->label) : NULL;
396   dest->filename = src->filename ? xstrdup (src->filename) : NULL;
397   dest->nodename = src->nodename ? xstrdup (src->nodename) : NULL;
398   dest->start = src->start;
399   dest->end = src->end;
400   dest->line_number = 0;
401   
402   return dest;
403 }
404
405
406 \f
407 /* Free the data associated with REFERENCES. */
408 void
409 info_free_references (REFERENCE **references)
410 {
411   register int i;
412   REFERENCE *entry;
413
414   if (references)
415     {
416       for (i = 0; references && (entry = references[i]); i++)
417         {
418           maybe_free (entry->label);
419           maybe_free (entry->filename);
420           maybe_free (entry->nodename);
421
422           free (entry);
423         }
424
425       free (references);
426     }
427 }
428
429 /* Search for sequences of whitespace or newlines in STRING, replacing
430    all such sequences with just a single space.  Remove whitespace from
431    start and end of string. */
432 void
433 canonicalize_whitespace (char *string)
434 {
435   register int i, j;
436   int len, whitespace_found, whitespace_loc = 0;
437   char *temp;
438
439   if (!string)
440     return;
441
442   len = strlen (string);
443   temp = xmalloc (1 + len);
444
445   /* Search for sequences of whitespace or newlines.  Replace all such
446      sequences in the string with just a single space. */
447
448   whitespace_found = 0;
449   for (i = 0, j = 0; string[i]; i++)
450     {
451       if (whitespace_or_newline (string[i]))
452         {
453           whitespace_found++;
454           whitespace_loc = i;
455           continue;
456         }
457       else
458         {
459           if (whitespace_found && whitespace_loc)
460             {
461               whitespace_found = 0;
462
463               /* Suppress whitespace at start of string. */
464               if (j)
465                 temp[j++] = ' ';
466             }
467
468           temp[j++] = string[i];
469         }
470     }
471
472   /* Kill trailing whitespace. */
473   if (j && whitespace (temp[j - 1]))
474     j--;
475
476   temp[j] = '\0';
477   strcpy (string, temp);
478   free (temp);
479 }
480
481 /* String representation of a char returned by printed_representation (). */
482 static char *the_rep;
483 static size_t the_rep_size;
484
485 /* Return a pointer to a string which is the printed representation
486    of CHARACTER if it were printed at HPOS. */
487 char *
488 printed_representation (const unsigned char *cp, size_t len, size_t hpos,
489                         /* Return: */
490                         size_t *plen)
491 {
492   register int i = 0;
493   int printable_limit = ISO_Latin_p ? 255 : 127;
494 #define REPSPACE(s)                                            \
495   do                                                           \
496     {                                                          \
497       while (the_rep_size < s)                                 \
498         {                                                      \
499           if (the_rep_size == 0)                               \
500             the_rep_size = 8; /* Initial allocation */         \
501           the_rep = x2realloc (the_rep, &the_rep_size);        \
502         }                                                      \
503     }                                                          \
504   while (0)
505     
506 #define SC(c)                                                  \
507   do                                                           \
508     {                                                          \
509       REPSPACE(i + 1);                                         \
510       the_rep[i++] = c;                                        \
511     }                                                          \
512   while (0)
513   
514   for (; len > 0; cp++, len--)
515     {
516       if (raw_escapes_p && *cp == '\033')
517         SC(*cp);
518       /* Show CTRL-x as ^X.  */
519       else if (iscntrl (*cp) && *cp < 127)
520         {
521           switch (*cp)
522             {
523             case '\r':
524             case '\n':
525               SC(*cp);
526               break;
527
528             case '\t':
529               {
530                 int tw;
531
532                 tw = ((hpos + 8) & 0xf8) - hpos;
533                 while (i < tw)
534                   SC(' ');
535                 break;
536               }
537               
538             default:
539               SC('^');
540               SC(*cp | 0x40);
541             }
542         }
543       /* Show META-x as 0370.  */
544       else if (*cp > printable_limit)
545         {
546           REPSPACE (i + 5);
547           sprintf (the_rep + i, "\\%0o", *cp);
548           i = strlen (the_rep);
549         }
550       else if (*cp == DEL)
551         {
552           SC('^');
553           SC('?');
554         }
555       else
556         SC(*cp);
557     }
558   
559   SC(0);
560   *plen = i - 1;
561   return the_rep;
562 }
563
564 \f
565 /* **************************************************************** */
566 /*                                                                  */
567 /*                  Functions Static To This File                   */
568 /*                                                                  */
569 /* **************************************************************** */
570
571 /* Amount of space allocated to INFO_PARSED_FILENAME via xmalloc (). */
572 static int parsed_filename_size = 0;
573
574 /* Amount of space allocated to INFO_PARSED_NODENAME via xmalloc (). */
575 static int parsed_nodename_size = 0;
576
577 static void save_string (char *string, char **string_p, int *string_size_p);
578 static void saven_string (char *string, int len, char **string_p,
579     int *string_size_p);
580
581 /* Remember FILENAME in PARSED_FILENAME.  An empty FILENAME is translated
582    to a NULL pointer in PARSED_FILENAME. */
583 static void
584 save_filename (char *filename)
585 {
586   save_string (filename, &info_parsed_filename, &parsed_filename_size);
587 }
588
589 /* Just like save_filename (), but you pass the length of the string. */
590 static void
591 saven_filename (char *filename, int len)
592 {
593   saven_string (filename, len,
594                 &info_parsed_filename, &parsed_filename_size);
595 }
596
597 /* Remember NODENAME in PARSED_NODENAME.  An empty NODENAME is translated
598    to a NULL pointer in PARSED_NODENAME. */
599 static void
600 save_nodename (char *nodename)
601 {
602   save_string (nodename, &info_parsed_nodename, &parsed_nodename_size);
603 }
604
605 /* Just like save_nodename (), but you pass the length of the string. */
606 static void
607 saven_nodename (char *nodename, int len)
608 {
609   saven_string (nodename, len,
610                 &info_parsed_nodename, &parsed_nodename_size);
611 }
612
613 /* Remember STRING in STRING_P.  STRING_P should currently have STRING_SIZE_P
614    bytes allocated to it.  An empty STRING is translated to a NULL pointer
615    in STRING_P. */
616 static void
617 save_string (char *string, char **string_p, int *string_size_p)
618 {
619   if (!string || !*string)
620     {
621       if (*string_p)
622         free (*string_p);
623
624       *string_p = NULL;
625       *string_size_p = 0;
626     }
627   else
628     {
629       if (strlen (string) >= (unsigned int) *string_size_p)
630         *string_p = xrealloc
631           (*string_p, (*string_size_p = 1 + strlen (string)));
632
633       strcpy (*string_p, string);
634     }
635 }
636
637 /* Just like save_string (), but you also pass the length of STRING. */
638 static void
639 saven_string (char *string, int len, char **string_p, int *string_size_p)
640 {
641   if (!string)
642     {
643       if (*string_p)
644         free (*string_p);
645
646       *string_p = NULL;
647       *string_size_p = 0;
648     }
649   else
650     {
651       if (len >= *string_size_p)
652         *string_p = xrealloc (*string_p, (*string_size_p = 1 + len));
653
654       strncpy (*string_p, string, len);
655       (*string_p)[len] = '\0';
656     }
657 }
658
659 /* Return a pointer to the part of PATHNAME that simply defines the file. */
660 char *
661 filename_non_directory (char *pathname)
662 {
663   register char *filename = pathname + strlen (pathname);
664
665   if (HAVE_DRIVE (pathname))
666     pathname += 2;
667
668   while (filename > pathname && !IS_SLASH (filename[-1]))
669     filename--;
670
671   return filename;
672 }
673
674 /* Return non-zero if NODE is one especially created by Info. */
675 int
676 internal_info_node_p (NODE *node)
677 {
678 #if defined (NEVER)
679   if (node &&
680       (node->filename && !*node->filename) &&
681       !node->parent && node->nodename)
682     return 1;
683   else
684     return 0;
685 #else
686   return (node != NULL) && ((node->flags & N_IsInternal) != 0);
687 #endif /* !NEVER */
688 }
689
690 /* Make NODE appear to be one especially created by Info. */
691 void
692 name_internal_node (NODE *node, char *name)
693 {
694   if (!node)
695     return;
696
697   node->filename = "";
698   node->parent = NULL;
699   node->nodename = name;
700   node->flags |= N_IsInternal;
701 }
702
703 /* Return the window displaying NAME, the name of an internally created
704    Info window. */
705 WINDOW *
706 get_internal_info_window (char *name)
707 {
708   WINDOW *win;
709
710   for (win = windows; win; win = win->next)
711     if (internal_info_node_p (win->node) &&
712         (strcmp (win->node->nodename, name) == 0))
713       break;
714
715   return win;
716 }
717
718 /* Return a window displaying the node NODE. */
719 WINDOW *
720 get_window_of_node (NODE *node)
721 {
722   WINDOW *win = NULL;
723
724   for (win = windows; win; win = win->next)
725     if (win->node == node)
726       break;
727
728   return win;
729 }