summaryrefslogtreecommitdiff
path: root/info/info-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'info/info-utils.c')
-rw-r--r--info/info-utils.c729
1 files changed, 729 insertions, 0 deletions
diff --git a/info/info-utils.c b/info/info-utils.c
new file mode 100644
index 0000000..7236452
--- /dev/null
+++ b/info/info-utils.c
@@ -0,0 +1,729 @@
+/* info-utils.c -- miscellanous.
+ $Id: info-utils.c,v 1.12 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1993, 1998, 2003, 2004, 2007, 2008
+ Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "info-utils.h"
+#if defined (HANDLE_MAN_PAGES)
+# include "man.h"
+#endif /* HANDLE_MAN_PAGES */
+
+/* When non-zero, various display and input functions handle ISO Latin
+ character sets correctly. */
+int ISO_Latin_p = 1;
+
+/* Variable which holds the most recent filename parsed as a result of
+ calling info_parse_xxx (). */
+char *info_parsed_filename = NULL;
+
+/* Variable which holds the most recent nodename parsed as a result of
+ calling info_parse_xxx (). */
+char *info_parsed_nodename = NULL;
+
+/* Variable which holds the most recent line number parsed as a result of
+ calling info_parse_xxx (). */
+int info_parsed_line_number = 0;
+
+/* Functions to remember a filename or nodename for later return. */
+static void save_filename (char *filename);
+static void saven_filename (char *filename, int len);
+static void save_nodename (char *nodename);
+static void saven_nodename (char *nodename, int len);
+
+/* How to get a reference (either menu or cross). */
+static REFERENCE **info_references_internal (char *label,
+ SEARCH_BINDING *binding);
+
+/* Parse the filename and nodename out of STRING. If STRING doesn't
+ contain a filename (i.e., it is NOT (FILENAME)NODENAME) then set
+ INFO_PARSED_FILENAME to NULL. If second argument NEWLINES_OKAY is
+ non-zero, it says to allow the nodename specification to cross a
+ newline boundary (i.e., only `,', `.', or `TAB' can end the spec). */
+void
+info_parse_node (char *string, int newlines_okay)
+{
+ register int i = 0;
+
+ /* Default the answer. */
+ save_filename (NULL);
+ save_nodename (NULL);
+
+ /* Special case of nothing passed. Return nothing. */
+ if (!string || !*string)
+ return;
+
+ string += skip_whitespace (string);
+
+ /* Check for (FILENAME)NODENAME. */
+ if (*string == '(')
+ {
+ i = 0;
+ /* Advance past the opening paren. */
+ string++;
+
+ /* Find the closing paren. */
+ while (string[i] && string[i] != ')')
+ i++;
+
+ /* Remember parsed filename. */
+ saven_filename (string, i);
+
+ /* Point directly at the nodename. */
+ string += i;
+
+ if (*string)
+ string++;
+ }
+
+ /* Parse out nodename. */
+ i = skip_node_characters (string, newlines_okay);
+ saven_nodename (string, i);
+ canonicalize_whitespace (info_parsed_nodename);
+ if (info_parsed_nodename && !*info_parsed_nodename)
+ {
+ free (info_parsed_nodename);
+ info_parsed_nodename = NULL;
+ }
+
+ /* Parse ``(line ...)'' part of menus, if any. */
+ {
+ char *rest = string + i;
+
+ /* Advance only if it's not already at end of string. */
+ if (*rest)
+ rest++;
+
+ /* Skip any whitespace first, and then a newline in case the item
+ was so long to contain the ``(line ...)'' string in the same
+ physical line. */
+ while (whitespace(*rest))
+ rest++;
+ if (*rest == '\n')
+ {
+ rest++;
+ while (whitespace(*rest))
+ rest++;
+ }
+
+ /* Are we looking at an opening parenthesis? That can only mean
+ we have a winner. :) */
+ if (strncmp (rest, "(line ", strlen ("(line ")) == 0)
+ {
+ rest += strlen ("(line ");
+ info_parsed_line_number = strtol (rest, NULL, 0);
+ }
+ else
+ info_parsed_line_number = 0;
+ }
+}
+
+/* Return the node addressed by LABEL in NODE (usually one of "Prev:",
+ "Next:", "Up:", "File:", or "Node:". After a call to this function,
+ the global INFO_PARSED_NODENAME and INFO_PARSED_FILENAME contain
+ the information. */
+void
+info_parse_label (char *label, NODE *node)
+{
+ register int i;
+ char *nodeline;
+
+ /* Default answer to failure. */
+ save_nodename (NULL);
+ save_filename (NULL);
+
+ /* Find the label in the first line of this node. */
+ nodeline = node->contents;
+ i = string_in_line (label, nodeline);
+
+ if (i == -1)
+ return;
+
+ nodeline += i;
+ nodeline += skip_whitespace (nodeline);
+ info_parse_node (nodeline, DONT_SKIP_NEWLINES);
+}
+
+/* **************************************************************** */
+/* */
+/* Finding and Building Menus */
+/* */
+/* **************************************************************** */
+
+/* Return a NULL terminated array of REFERENCE * which represents the menu
+ found in NODE. If there is no menu in NODE, just return a NULL pointer. */
+REFERENCE **
+info_menu_of_node (NODE *node)
+{
+ long position;
+ SEARCH_BINDING tmp_search;
+ REFERENCE **menu = NULL;
+
+ tmp_search.buffer = node->contents;
+ tmp_search.start = 0;
+ tmp_search.end = node->nodelen;
+ tmp_search.flags = S_FoldCase;
+
+ /* Find the start of the menu. */
+ position = search_forward (INFO_MENU_LABEL, &tmp_search);
+
+ if (position == -1)
+ return NULL;
+
+ /* We have the start of the menu now. Glean menu items from the rest
+ of the node. */
+ tmp_search.start = position + strlen (INFO_MENU_LABEL);
+ tmp_search.start += skip_line (tmp_search.buffer + tmp_search.start);
+ tmp_search.start--;
+ menu = info_menu_items (&tmp_search);
+ return menu;
+}
+
+/* Return a NULL terminated array of REFERENCE * which represents the cross
+ refrences found in NODE. If there are no cross references in NODE, just
+ return a NULL pointer. */
+REFERENCE **
+info_xrefs_of_node (NODE *node)
+{
+ SEARCH_BINDING tmp_search;
+
+#if defined (HANDLE_MAN_PAGES)
+ if (node->flags & N_IsManPage)
+ return xrefs_of_manpage (node);
+#endif
+
+ tmp_search.buffer = node->contents;
+ tmp_search.start = 0;
+ tmp_search.end = node->nodelen;
+ tmp_search.flags = S_FoldCase;
+
+ return info_xrefs (&tmp_search);
+}
+
+/* Glean menu entries from BINDING->buffer + BINDING->start until we
+ have looked at the entire contents of BINDING. Return an array
+ of REFERENCE * that represents each menu item in this range. */
+REFERENCE **
+info_menu_items (SEARCH_BINDING *binding)
+{
+ return info_references_internal (INFO_MENU_ENTRY_LABEL, binding);
+}
+
+/* Glean cross references from BINDING->buffer + BINDING->start until
+ BINDING->end. Return an array of REFERENCE * that represents each
+ cross reference in this range. */
+REFERENCE **
+info_xrefs (SEARCH_BINDING *binding)
+{
+ return info_references_internal (INFO_XREF_LABEL, binding);
+}
+
+/* Glean cross references or menu items from BINDING. Return an array
+ of REFERENCE * that represents the items found. */
+static REFERENCE **
+info_references_internal (char *label, SEARCH_BINDING *binding)
+{
+ SEARCH_BINDING tmp_search;
+ REFERENCE **refs = NULL;
+ int refs_index = 0, refs_slots = 0;
+ int searching_for_menu_items = 0;
+ long position;
+
+ tmp_search.buffer = binding->buffer;
+ tmp_search.start = binding->start;
+ tmp_search.end = binding->end;
+ tmp_search.flags = S_FoldCase | S_SkipDest;
+
+ searching_for_menu_items = (mbscasecmp (label, INFO_MENU_ENTRY_LABEL) == 0);
+
+ while ((position = search_forward (label, &tmp_search)) != -1)
+ {
+ int offset, start;
+ char *refdef;
+ REFERENCE *entry;
+
+ tmp_search.start = position;
+ tmp_search.start += skip_whitespace (tmp_search.buffer + tmp_search.start);
+ start = tmp_search.start - binding->start;
+ refdef = tmp_search.buffer + tmp_search.start;
+ offset = string_in_line (":", refdef);
+
+ /* When searching for menu items, if no colon, there is no
+ menu item on this line. */
+ if (offset == -1)
+ {
+ if (searching_for_menu_items)
+ continue;
+ else
+ {
+ int temp;
+
+ temp = skip_line (refdef);
+ offset = string_in_line (":", refdef + temp);
+ if (offset == -1)
+ continue; /* Give up? */
+ else
+ offset += temp;
+ }
+ }
+
+ entry = xmalloc (sizeof (REFERENCE));
+ entry->filename = NULL;
+ entry->nodename = NULL;
+ entry->label = xmalloc (offset);
+ strncpy (entry->label, refdef, offset - 1);
+ entry->label[offset - 1] = '\0';
+ canonicalize_whitespace (entry->label);
+ entry->line_number = 0;
+
+ refdef += offset;
+ entry->start = start;
+ entry->end = refdef - binding->buffer;
+
+ /* If this reference entry continues with another ':' then the
+ nodename is the same as the label. */
+ if (*refdef == ':')
+ {
+ entry->nodename = xstrdup (entry->label);
+ }
+ else
+ {
+ /* This entry continues with a specific nodename. Parse the
+ nodename from the specification. */
+
+ refdef += skip_whitespace_and_newlines (refdef);
+
+ if (searching_for_menu_items)
+ info_parse_node (refdef, DONT_SKIP_NEWLINES);
+ else
+ info_parse_node (refdef, SKIP_NEWLINES);
+
+ if (info_parsed_filename)
+ entry->filename = xstrdup (info_parsed_filename);
+
+ if (info_parsed_nodename)
+ entry->nodename = xstrdup (info_parsed_nodename);
+
+ entry->line_number = info_parsed_line_number;
+ }
+
+ add_pointer_to_array
+ (entry, refs_index, refs, refs_slots, 50, REFERENCE *);
+ }
+ return refs;
+}
+
+/* Get the entry associated with LABEL in REFERENCES. Return a pointer
+ to the ENTRY if found, or NULL. */
+REFERENCE *
+info_get_labeled_reference (char *label, REFERENCE **references)
+{
+ register int i;
+ REFERENCE *entry;
+
+ for (i = 0; references && (entry = references[i]); i++)
+ {
+ if (strcmp (label, entry->label) == 0)
+ return entry;
+ }
+ return NULL;
+}
+
+/* A utility function for concatenating REFERENCE **. Returns a new
+ REFERENCE ** which is the concatenation of REF1 and REF2. The REF1
+ and REF2 arrays are freed, but their contents are not. */
+REFERENCE **
+info_concatenate_references (REFERENCE **ref1, REFERENCE **ref2)
+{
+ register int i, j;
+ REFERENCE **result;
+ int size;
+
+ /* With one argument passed as NULL, simply return the other arg. */
+ if (!ref1)
+ return ref2;
+ else if (!ref2)
+ return ref1;
+
+ /* Get the total size of the slots that we will need. */
+ for (i = 0; ref1[i]; i++);
+ size = i;
+ for (i = 0; ref2[i]; i++);
+ size += i;
+
+ result = xmalloc ((1 + size) * sizeof (REFERENCE *));
+
+ /* Copy the contents over. */
+ for (i = 0; ref1[i]; i++)
+ result[i] = ref1[i];
+
+ j = i;
+ for (i = 0; ref2[i]; i++)
+ result[j++] = ref2[i];
+
+ result[j] = NULL;
+ free (ref1);
+ free (ref2);
+ return result;
+}
+
+
+
+/* Copy a reference structure. Since we tend to free everything at
+ every opportunity, we don't share any points, but copy everything into
+ new memory. */
+REFERENCE *
+info_copy_reference (REFERENCE *src)
+{
+ REFERENCE *dest = xmalloc (sizeof (REFERENCE));
+ dest->label = src->label ? xstrdup (src->label) : NULL;
+ dest->filename = src->filename ? xstrdup (src->filename) : NULL;
+ dest->nodename = src->nodename ? xstrdup (src->nodename) : NULL;
+ dest->start = src->start;
+ dest->end = src->end;
+ dest->line_number = 0;
+
+ return dest;
+}
+
+
+
+/* Free the data associated with REFERENCES. */
+void
+info_free_references (REFERENCE **references)
+{
+ register int i;
+ REFERENCE *entry;
+
+ if (references)
+ {
+ for (i = 0; references && (entry = references[i]); i++)
+ {
+ maybe_free (entry->label);
+ maybe_free (entry->filename);
+ maybe_free (entry->nodename);
+
+ free (entry);
+ }
+
+ free (references);
+ }
+}
+
+/* Search for sequences of whitespace or newlines in STRING, replacing
+ all such sequences with just a single space. Remove whitespace from
+ start and end of string. */
+void
+canonicalize_whitespace (char *string)
+{
+ register int i, j;
+ int len, whitespace_found, whitespace_loc = 0;
+ char *temp;
+
+ if (!string)
+ return;
+
+ len = strlen (string);
+ temp = xmalloc (1 + len);
+
+ /* Search for sequences of whitespace or newlines. Replace all such
+ sequences in the string with just a single space. */
+
+ whitespace_found = 0;
+ for (i = 0, j = 0; string[i]; i++)
+ {
+ if (whitespace_or_newline (string[i]))
+ {
+ whitespace_found++;
+ whitespace_loc = i;
+ continue;
+ }
+ else
+ {
+ if (whitespace_found && whitespace_loc)
+ {
+ whitespace_found = 0;
+
+ /* Suppress whitespace at start of string. */
+ if (j)
+ temp[j++] = ' ';
+ }
+
+ temp[j++] = string[i];
+ }
+ }
+
+ /* Kill trailing whitespace. */
+ if (j && whitespace (temp[j - 1]))
+ j--;
+
+ temp[j] = '\0';
+ strcpy (string, temp);
+ free (temp);
+}
+
+/* String representation of a char returned by printed_representation (). */
+static char *the_rep;
+static size_t the_rep_size;
+
+/* Return a pointer to a string which is the printed representation
+ of CHARACTER if it were printed at HPOS. */
+char *
+printed_representation (const unsigned char *cp, size_t len, size_t hpos,
+ /* Return: */
+ size_t *plen)
+{
+ register int i = 0;
+ int printable_limit = ISO_Latin_p ? 255 : 127;
+#define REPSPACE(s) \
+ do \
+ { \
+ while (the_rep_size < s) \
+ { \
+ if (the_rep_size == 0) \
+ the_rep_size = 8; /* Initial allocation */ \
+ the_rep = x2realloc (the_rep, &the_rep_size); \
+ } \
+ } \
+ while (0)
+
+#define SC(c) \
+ do \
+ { \
+ REPSPACE(i + 1); \
+ the_rep[i++] = c; \
+ } \
+ while (0)
+
+ for (; len > 0; cp++, len--)
+ {
+ if (raw_escapes_p && *cp == '\033')
+ SC(*cp);
+ /* Show CTRL-x as ^X. */
+ else if (iscntrl (*cp) && *cp < 127)
+ {
+ switch (*cp)
+ {
+ case '\r':
+ case '\n':
+ SC(*cp);
+ break;
+
+ case '\t':
+ {
+ int tw;
+
+ tw = ((hpos + 8) & 0xf8) - hpos;
+ while (i < tw)
+ SC(' ');
+ break;
+ }
+
+ default:
+ SC('^');
+ SC(*cp | 0x40);
+ }
+ }
+ /* Show META-x as 0370. */
+ else if (*cp > printable_limit)
+ {
+ REPSPACE (i + 5);
+ sprintf (the_rep + i, "\\%0o", *cp);
+ i = strlen (the_rep);
+ }
+ else if (*cp == DEL)
+ {
+ SC('^');
+ SC('?');
+ }
+ else
+ SC(*cp);
+ }
+
+ SC(0);
+ *plen = i - 1;
+ return the_rep;
+}
+
+
+/* **************************************************************** */
+/* */
+/* Functions Static To This File */
+/* */
+/* **************************************************************** */
+
+/* Amount of space allocated to INFO_PARSED_FILENAME via xmalloc (). */
+static int parsed_filename_size = 0;
+
+/* Amount of space allocated to INFO_PARSED_NODENAME via xmalloc (). */
+static int parsed_nodename_size = 0;
+
+static void save_string (char *string, char **string_p, int *string_size_p);
+static void saven_string (char *string, int len, char **string_p,
+ int *string_size_p);
+
+/* Remember FILENAME in PARSED_FILENAME. An empty FILENAME is translated
+ to a NULL pointer in PARSED_FILENAME. */
+static void
+save_filename (char *filename)
+{
+ save_string (filename, &info_parsed_filename, &parsed_filename_size);
+}
+
+/* Just like save_filename (), but you pass the length of the string. */
+static void
+saven_filename (char *filename, int len)
+{
+ saven_string (filename, len,
+ &info_parsed_filename, &parsed_filename_size);
+}
+
+/* Remember NODENAME in PARSED_NODENAME. An empty NODENAME is translated
+ to a NULL pointer in PARSED_NODENAME. */
+static void
+save_nodename (char *nodename)
+{
+ save_string (nodename, &info_parsed_nodename, &parsed_nodename_size);
+}
+
+/* Just like save_nodename (), but you pass the length of the string. */
+static void
+saven_nodename (char *nodename, int len)
+{
+ saven_string (nodename, len,
+ &info_parsed_nodename, &parsed_nodename_size);
+}
+
+/* Remember STRING in STRING_P. STRING_P should currently have STRING_SIZE_P
+ bytes allocated to it. An empty STRING is translated to a NULL pointer
+ in STRING_P. */
+static void
+save_string (char *string, char **string_p, int *string_size_p)
+{
+ if (!string || !*string)
+ {
+ if (*string_p)
+ free (*string_p);
+
+ *string_p = NULL;
+ *string_size_p = 0;
+ }
+ else
+ {
+ if (strlen (string) >= (unsigned int) *string_size_p)
+ *string_p = xrealloc
+ (*string_p, (*string_size_p = 1 + strlen (string)));
+
+ strcpy (*string_p, string);
+ }
+}
+
+/* Just like save_string (), but you also pass the length of STRING. */
+static void
+saven_string (char *string, int len, char **string_p, int *string_size_p)
+{
+ if (!string)
+ {
+ if (*string_p)
+ free (*string_p);
+
+ *string_p = NULL;
+ *string_size_p = 0;
+ }
+ else
+ {
+ if (len >= *string_size_p)
+ *string_p = xrealloc (*string_p, (*string_size_p = 1 + len));
+
+ strncpy (*string_p, string, len);
+ (*string_p)[len] = '\0';
+ }
+}
+
+/* Return a pointer to the part of PATHNAME that simply defines the file. */
+char *
+filename_non_directory (char *pathname)
+{
+ register char *filename = pathname + strlen (pathname);
+
+ if (HAVE_DRIVE (pathname))
+ pathname += 2;
+
+ while (filename > pathname && !IS_SLASH (filename[-1]))
+ filename--;
+
+ return filename;
+}
+
+/* Return non-zero if NODE is one especially created by Info. */
+int
+internal_info_node_p (NODE *node)
+{
+#if defined (NEVER)
+ if (node &&
+ (node->filename && !*node->filename) &&
+ !node->parent && node->nodename)
+ return 1;
+ else
+ return 0;
+#else
+ return (node != NULL) && ((node->flags & N_IsInternal) != 0);
+#endif /* !NEVER */
+}
+
+/* Make NODE appear to be one especially created by Info. */
+void
+name_internal_node (NODE *node, char *name)
+{
+ if (!node)
+ return;
+
+ node->filename = "";
+ node->parent = NULL;
+ node->nodename = name;
+ node->flags |= N_IsInternal;
+}
+
+/* Return the window displaying NAME, the name of an internally created
+ Info window. */
+WINDOW *
+get_internal_info_window (char *name)
+{
+ WINDOW *win;
+
+ for (win = windows; win; win = win->next)
+ if (internal_info_node_p (win->node) &&
+ (strcmp (win->node->nodename, name) == 0))
+ break;
+
+ return win;
+}
+
+/* Return a window displaying the node NODE. */
+WINDOW *
+get_window_of_node (NODE *node)
+{
+ WINDOW *win = NULL;
+
+ for (win = windows; win; win = win->next)
+ if (win->node == node)
+ break;
+
+ return win;
+}