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 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 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 ** 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 ** 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 = (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 * 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 ** 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 * 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 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 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 * 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 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 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 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 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 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 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 * 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 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 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 * 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 * 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