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