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. */ 32 int ISO_Latin_p = 1; 33 34 /* Variable which holds the most recent filename parsed as a result of 35 calling info_parse_xxx (). */ 36 char *info_parsed_filename = (char *)NULL; 37 38 /* Variable which holds the most recent nodename parsed as a result of 39 calling info_parse_xxx (). */ 40 char *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 (). */ 44 int info_parsed_line_number = 0; 45 46 /* Functions to remember a filename or nodename for later return. */ 47 static void save_filename (char *filename); 48 static void saven_filename (char *filename, int len); 49 static void save_nodename (char *nodename); 50 static void saven_nodename (char *nodename, int len); 51 52 /* How to get a reference (either menu or cross). */ 53 static 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). */ 61 void 62 info_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. */ 143 void 144 info_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. */ 173 REFERENCE ** 174 info_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. */ 203 REFERENCE ** 204 info_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. */ 224 REFERENCE ** 225 info_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. */ 233 REFERENCE ** 234 info_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. */ 241 static REFERENCE ** 242 info_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. */ 335 REFERENCE * 336 info_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. */ 352 REFERENCE ** 353 info_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. */ 392 REFERENCE * 393 info_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. */ 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 = (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 (). */ 482 static 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. */ 486 char * 487 printed_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 (). */ 546 static int parsed_filename_size = 0; 547 548 /* Amount of space allocated to INFO_PARSED_NODENAME via xmalloc (). */ 549 static int parsed_nodename_size = 0; 550 551 static void save_string (char *string, char **string_p, int *string_size_p); 552 static 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. */ 557 static void 558 save_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. */ 564 static void 565 saven_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. */ 573 static void 574 save_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. */ 580 static void 581 saven_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. */ 590 static void 591 save_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. */ 612 static void 613 saven_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. */ 634 char * 635 filename_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. */ 649 int 650 internal_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. */ 665 void 666 name_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. */ 679 WINDOW * 680 get_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. */ 693 WINDOW * 694 get_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