1 /* info-utils.c -- miscellanous.
2 $Id: info-utils.c,v 1.1.1.5 2006/07/17 16:03:43 espie 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. */
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 /* 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
info_parse_node(char * string,int newlines_okay)60 info_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. */
141 void
info_parse_label(char * label,NODE * node)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 ((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. */
171 REFERENCE **
info_menu_of_node(NODE * node)172 info_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. */
201 REFERENCE **
info_xrefs_of_node(NODE * node)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 **
info_menu_items(SEARCH_BINDING * binding)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 **
info_xrefs(SEARCH_BINDING * binding)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 **
info_references_internal(char * label,SEARCH_BINDING * binding)240 info_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. */
333 REFERENCE *
info_get_labeled_reference(char * label,REFERENCE ** references)334 info_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. */
350 REFERENCE **
info_concatenate_references(REFERENCE ** ref1,REFERENCE ** ref2)351 info_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. */
390 REFERENCE *
info_copy_reference(REFERENCE * src)391 info_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. */
406 void
info_free_references(REFERENCE ** references)407 info_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. */
430 void
canonicalize_whitespace(char * string)431 canonicalize_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 (). */
480 static 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. */
484 char *
printed_representation(unsigned char character,int hpos)485 printed_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 (). */
544 static int parsed_filename_size = 0;
545
546 /* Amount of space allocated to INFO_PARSED_NODENAME via xmalloc (). */
547 static int parsed_nodename_size = 0;
548
549 static void save_string (char *string, char **string_p, int *string_size_p);
550 static 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. */
555 static void
save_filename(char * filename)556 save_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. */
562 static void
saven_filename(char * filename,int len)563 saven_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. */
571 static void
save_nodename(char * nodename)572 save_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. */
578 static void
saven_nodename(char * nodename,int len)579 saven_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. */
588 static void
save_string(char * string,char ** string_p,int * string_size_p)589 save_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. */
610 static void
saven_string(char * string,int len,char ** string_p,int * string_size_p)611 saven_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. */
632 char *
filename_non_directory(char * pathname)633 filename_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. */
647 int
internal_info_node_p(NODE * node)648 internal_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. */
663 void
name_internal_node(NODE * node,char * name)664 name_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. */
677 WINDOW *
get_internal_info_window(char * name)678 get_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. */
691 WINDOW *
get_window_of_node(NODE * node)692 get_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