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. */
30int ISO_Latin_p = 1;
31
32/* Variable which holds the most recent filename parsed as a result of
33   calling info_parse_xxx (). */
34char *info_parsed_filename = (char *)NULL;
35
36/* Variable which holds the most recent nodename parsed as a result of
37   calling info_parse_xxx (). */
38char *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 (). */
42int info_parsed_line_number = 0;
43
44/* Functions to remember a filename or nodename for later return. */
45static void save_filename (char *filename);
46static void saven_filename (char *filename, int len);
47static void save_nodename (char *nodename);
48static void saven_nodename (char *nodename, int len);
49
50/* How to get a reference (either menu or cross). */
51static 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). */
59void
60info_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. */
141void
142info_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
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. */
171REFERENCE **
172info_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. */
201REFERENCE **
202info_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. */
222REFERENCE **
223info_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. */
231REFERENCE **
232info_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. */
239static REFERENCE **
240info_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. */
333REFERENCE *
334info_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. */
350REFERENCE **
351info_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
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.  */
390REFERENCE *
391info_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
405/* Free the data associated with REFERENCES. */
406void
407info_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. */
430void
431canonicalize_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 (). */
480static 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. */
484char *
485printed_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
537/* **************************************************************** */
538/*                                                                  */
539/*                  Functions Static To This File                   */
540/*                                                                  */
541/* **************************************************************** */
542
543/* Amount of space allocated to INFO_PARSED_FILENAME via xmalloc (). */
544static int parsed_filename_size = 0;
545
546/* Amount of space allocated to INFO_PARSED_NODENAME via xmalloc (). */
547static int parsed_nodename_size = 0;
548
549static void save_string (char *string, char **string_p, int *string_size_p);
550static 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. */
555static void
556save_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. */
562static void
563saven_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. */
571static void
572save_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. */
578static void
579saven_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. */
588static void
589save_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. */
610static void
611saven_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. */
632char *
633filename_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. */
647int
648internal_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. */
663void
664name_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. */
677WINDOW *
678get_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. */
691WINDOW *
692get_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}
702