summaryrefslogtreecommitdiff
path: root/Build/source/texk/xdvik/string-utils.c
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2006-01-17 21:41:51 +0000
committerKarl Berry <karl@freefriends.org>2006-01-17 21:41:51 +0000
commit487ca4806cc046076293cf6cc5fbba0db282bac7 (patch)
tree847b412ab5158dd7bdd7ed7e5a4cc3fbca94be32 /Build/source/texk/xdvik/string-utils.c
parenta3d3111bfe26b8e5f5bc6049dfb2a4ca2edc7881 (diff)
texk 1
git-svn-id: svn://tug.org/texlive/trunk@1485 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/texk/xdvik/string-utils.c')
-rw-r--r--Build/source/texk/xdvik/string-utils.c743
1 files changed, 743 insertions, 0 deletions
diff --git a/Build/source/texk/xdvik/string-utils.c b/Build/source/texk/xdvik/string-utils.c
new file mode 100644
index 00000000000..8ade715ed5d
--- /dev/null
+++ b/Build/source/texk/xdvik/string-utils.c
@@ -0,0 +1,743 @@
+/*
+ * miscellaneous string utility functions
+ *
+ * Copyright (c) 2001-2004 the xdvik development team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/* #define MYDEBUG 1 */
+
+#include <string.h>
+#include <stdio.h>
+#include <locale.h>
+#include <ctype.h>
+#include "xdvi-config.h"
+#include "xdvi.h"
+#include "string-utils.h"
+#include "util.h"
+
+/*------------------------------------------------------------
+ * str_is_prefix
+ *
+ * Purpose:
+ * Return True if <str1> is a prefix of <str2>, else False.
+ *
+ * If `case_sensitive' is set to False, it performs matching
+ * in a case-insensitive manner; in that case, str1 should
+ * be lowercase.
+ *------------------------------------------------------------*/
+
+Boolean
+str_is_prefix(const char *str1, const char *str2, Boolean case_sensitive)
+{
+ int i;
+
+ for (i = 0; *str1 != '\0' && *str2 != '\0'; i++) {
+ if ((case_sensitive && *str1 != *str2) ||
+ (!case_sensitive && *str1 != tolower((int)*str2)))
+ return False;
+ str1++;
+ str2++;
+ }
+ return *str1 == '\0';
+}
+
+
+
+/*------------------------------------------------------------
+ * str_is_suffix
+ *
+ * Purpose:
+ * Return True if str1 is a suffix of str2, else False.
+ * E.g. returns true if str1 = ".tex", str2 = "test.tex".
+ *
+ * If `case_sensitive' is set to False, it performs matching
+ * in a case-insensitive manner; in that case, str1 should
+ * be lowercase.
+ *------------------------------------------------------------*/
+
+Boolean
+str_is_suffix(const char *str1, const char *str2, Boolean case_sensitive)
+{
+ int len1 = strlen(str1);
+ int len2 = strlen(str2);
+
+ while (len2 > len1) {
+ str2++;
+ len2--;
+ }
+ if (case_sensitive)
+ return strcmp(str1, str2) == 0;
+ else
+ /* also compare terminating 0; second string
+ is assumed to be all-lowercase! */
+ return memicmp(str2, str1, len1 + 1) == 0;
+}
+
+/*
+ * Like strstr(), but does case-insensitive matching: Brute-force algorithm
+ * to find the first occurrence of subsrtring `needle' (which should be all-lowercase)
+ * in string `haystack', ignoring case in (i.e. lowercasing) haystack. The terminating
+ * '\0' characters are not compared.
+ * Returns a pointer to the beginning of the substring if found, NULL else.
+ *
+ * This code was derived from public domain code (Stristr.c on www.snippets.org).
+ * Currently unused.
+ */
+char *
+my_stristr(const char *haystack, const char *needle)
+{
+ const char *curr;
+
+ for (curr = haystack; *curr != '\0'; curr++) {
+ const char *ptr1, *ptr2;
+ /* search for first character */
+ for (; ((*curr != '\0') && (tolower((int)*curr) != *needle)); curr++) { ; }
+
+ if (*curr == '\0') /* not found */
+ return NULL;
+
+ /* now compare other characters */
+ ptr1 = needle;
+ ptr2 = curr;
+ while (tolower((int)*ptr2) == *ptr1) {
+ ptr2++;
+ ptr1++;
+ /* success if at end of needle */
+ if (*ptr1 == '\0')
+ return (char *)curr;
+ }
+ }
+ return NULL;
+}
+
+/*
+ expand filename to include full path name;
+ returns result in a freshly allocated string.
+*/
+char *
+expand_filename(const char *filename, expandPathTypeT type)
+{
+ char *path_name = NULL;
+
+ if (*filename == '/') /* already an absolute path */
+ return xstrdup(filename);
+
+ if (type == USE_CWD_PATH) {
+ size_t path_name_len = 512;
+ size_t len = strlen(filename) + 1;
+
+ /* append to cwd if it's not already a full path */
+ if (filename[0] != '/') {
+ for (;;) {
+ char *tmp;
+ path_name = xrealloc(path_name, path_name_len);
+ if ((tmp = getcwd(path_name, path_name_len)) == NULL && errno == ERANGE) {
+ path_name_len *= 2;
+ }
+ else {
+ path_name = tmp;
+ break;
+ }
+ }
+ len += strlen(path_name) + 1; /* 1 for '/' */
+ path_name = xrealloc(path_name, len);
+ strcat(path_name, "/");
+ strcat(path_name, filename);
+ }
+
+ TRACE_HTEX((stderr,
+ "Expanding filename |%s| with CWD gives |%s|",
+ filename, path_name == NULL ? "<NULL>" : path_name));
+ return path_name ? path_name : xstrdup(filename);
+ }
+ else {
+ ASSERT(globals.dvi_file.dirname != NULL, "globals.dvi_file.dirname should have been initialized before");
+ path_name = xstrdup(globals.dvi_file.dirname);
+ path_name = xstrcat(path_name, filename);
+ TRACE_HTEX((stderr,
+ "Expanding filename |%s| with globals.dvi_file.dirname |%s| gives |%s|",
+ filename, globals.dvi_file.dirname, path_name));
+ return path_name;
+ }
+}
+
+/* expand filename to include `.dvi' extension and full path name;
+ returns malloc()ed string (caller is responsible for free()ing).
+*/
+char *
+filename_append_dvi(const char *filename)
+{
+ char *expanded_filename = NULL;
+ const char *orig_filename = filename;
+ size_t len;
+ char *p;
+
+ /* skip over `file:' prefix if present */
+ if (str_is_prefix("file:", filename, True)) {
+ filename += strlen("file:");
+ if (str_is_prefix("//", filename + strlen("file:"), True)) { /* is there a hostname following? */
+ char *tmp = strchr(filename+2, '/'); /* skip over host name */
+ if (tmp == NULL) {
+ XDVI_WARNING((stderr, "Malformed hostname part in filename `%s'; not expanding file name",
+ orig_filename));
+ }
+ else {
+ /* deal with multiple `//' after "file://localhost";
+ while the RFC seems to suggest that file://localhost/foo/bar defines a path
+ `foo/bar', most browsers (and actually, also libwww) will treat this path as absolute:
+ `/foo/bar'. So we treat
+ file://localhost//foo/bar
+ and
+ file://localhost/foo/bar
+ as equivalent.
+ */
+ while (*(tmp+1) == '/')
+ tmp++;
+ filename = tmp;
+ }
+ }
+ }
+
+ len = strlen(filename) + 5; /* 5 in case we need to add `.dvi\0' */
+
+ expanded_filename = xmalloc(len);
+
+ strcpy(expanded_filename, filename);
+
+ /* Append ".dvi" extension if no extension is present.
+ Only look at the filename part when trying to find a `.'.
+ */
+ if ((p = strrchr(expanded_filename, '/')) == NULL) {
+ p = expanded_filename;
+ }
+ if ((p = strrchr(p, '.')) == NULL) {
+ TRACE_HTEX((stderr, "appending .dvi extension to filename |%s|", expanded_filename));
+ strcat(expanded_filename, ".dvi");
+ }
+ return expanded_filename;
+}
+
+char *
+expand_filename_append_dvi(const char *filename, expandPathTypeT type, Boolean must_exist)
+{
+ char canonical_path[MAXPATHLEN + 1];
+ char *normalized_fname = filename_append_dvi(filename);
+ char *expanded_fname = expand_filename(normalized_fname, type);
+ if (must_exist) {
+ char *canonical_name = REALPATH(expanded_fname, canonical_path);
+ free(normalized_fname);
+ free(expanded_fname);
+ return xstrdup(canonical_name);
+ }
+ else {
+ free(normalized_fname);
+ return expanded_fname;
+ }
+}
+
+char *
+format_arg(const char *fmt, const char *arg, int *match)
+{
+ char *tmp = xmalloc(strlen(fmt) + strlen(arg) + 1);
+ if (strchr(fmt, '%') != NULL) {
+ sprintf(tmp, fmt, arg);
+ *match = 1;
+ }
+ else {
+ strcpy(tmp, fmt);
+ }
+ return tmp;
+}
+
+/* escape single `%' characters in arg and return newly allocated string */
+char *
+escape_format_arg(const char *arg)
+{
+ char *ret, *ptr;
+ ASSERT(arg != NULL, "");
+ ret = xmalloc(strlen(arg) * 2 + 1); /* more than enuff */
+
+ ptr = ret;
+ while (*arg != '\0') {
+ /* need to escape? */
+ if (*arg == '%' && (ptr == ret
+ || (ptr > ret && *(arg - 1) != '%'))) {
+ *ptr++ = '%';
+ }
+ *ptr++ = *arg++;
+ }
+ *ptr = '\0';
+
+ /* trim return buffer */
+ return xrealloc(ret, strlen(ret) + 1);
+}
+
+char *
+unquote_arg(const char *fmt, const char *arg, int *match, int *len)
+{
+ char *ptr;
+ char c;
+
+ c = fmt[0];
+ fmt++;
+ if ((ptr = strchr(fmt, c)) != NULL) {
+ *ptr++ = '\0';
+ while (*ptr == ' ' || *ptr == '\t') {
+ ptr++;
+ }
+ *len = ptr - fmt;
+ return format_arg(fmt, arg, match);
+ }
+ else {
+ *len = strlen(fmt);
+ XDVI_WARNING((stderr, "Ignoring lonesome quote in string %s", fmt - 1));
+ return format_arg(fmt, arg, match);
+ }
+}
+
+/* Chop `source' into chunks separated by `sep', and save these
+ * to newly allocated return list. The end of the list is indicated
+ * by a NULL element (i.e. the returned list will always contain at
+ * least 1 element). The caller is responsible for free()ing the returned
+ * list.
+ *
+ * If `do_unquote' is True, separators inside the quotes will not be
+ * treated as boundaries; instead, the quotes surrounding the chunk are removed.
+ */
+char **
+get_separated_list(const char *source, const char *sep, Boolean do_unquote)
+{
+ /* allocate at least list of size 1, for NULL termination below */
+ char **chunks = xmalloc(sizeof *chunks);
+ const char *b_ptr, *e_ptr;
+ size_t i = 0;
+
+ b_ptr = source;
+
+ while (*b_ptr != '\0' && strchr(sep, *b_ptr) != NULL)
+ b_ptr++;
+
+ for (i = 0; *b_ptr != '\0'; i++) {
+ char *quote;
+ char quotechar = 0;
+ size_t len, chunk_len, alloc_len = 0;
+
+ if ((len = strcspn(b_ptr, sep)) == 0) /* at end */
+ break;
+
+ /* check for quoted chunk, in which case we don't want to treat
+ spaces as chunk separators */
+ if (do_unquote && (quote = strchr("'\"", *b_ptr)) != NULL) {
+ const char *curr = b_ptr + 1;
+ quotechar = *quote;
+
+ for (;;) { /* find end of quote */
+ char *maybe_end = strchr(curr, quotechar);
+ if (maybe_end != NULL) {
+ if (maybe_end - curr > 0 &&
+ *(maybe_end - 1) == '\\') { /* escaped quote */
+ curr = ++maybe_end;
+ }
+ else { /* found */
+ b_ptr++;
+ len = maybe_end - b_ptr;
+ break;
+ }
+ }
+ else { /* not found end - warn, and forget about this quote */
+ XDVI_WARNING((stderr, "Unmatched quote character in string `%s'", b_ptr));
+ break;
+ }
+ }
+ }
+
+ e_ptr = b_ptr + len;
+ chunk_len = e_ptr - b_ptr;
+ while (i + 1 >= alloc_len) {
+ alloc_len++;
+ }
+ chunks = xrealloc(chunks, alloc_len * sizeof *chunks);
+ chunks[i] = xmalloc(chunk_len + 1);
+ memcpy(chunks[i], b_ptr, chunk_len);
+ chunks[i][chunk_len] = '\0';
+
+ /* skip trailing quotechar and spaces */
+ b_ptr = e_ptr;
+ if (do_unquote && quotechar != 0 && *b_ptr == quotechar)
+ b_ptr++;
+ while (*b_ptr != '\0' && strchr(sep, *b_ptr) != NULL)
+ b_ptr++;
+ }
+ /* terminate list with NULL element */
+ chunks[i] = NULL;
+ return chunks;
+}
+
+const char *
+find_format_str(const char *input, const char *fmt)
+{
+ const char *ptr = input;
+ while ((ptr = strstr(ptr, fmt)) != NULL) {
+ if (ptr > input && *(ptr - 1) == '\\') {
+ ptr++;
+ continue;
+ }
+ else
+ return ptr;
+ }
+ return NULL;
+}
+
+/* return directory component of `path', or NULL if path is a filename only */
+char *
+get_dir_component(const char *path)
+{
+ char *ret = NULL;
+ char *p;
+
+ if ((p = strrchr(path, '/')) != NULL) {
+ /* copy, chop off filename (but not the '/') */
+ ret = xstrdup(path);
+ *(ret + (p - path) + 1) = '\0';
+ TRACE_CLIENT((stderr, "get_dir_component of |%s| is |%s|\n", path, ret));
+ }
+ return ret;
+}
+
+/*
+ If `src' is an absolute path, compare it with `target', ignoring `.tex' extension in
+ both strings. Else, compare the concatenation of `src' with `dvi_path' and `target',
+ in the same way.
+ Since efficiency is a concern here, we don't copy any strings; instead, we loop through
+ `src' and `target' from the end of both strings (which makes expanding `../' easier, and
+ will terminate earlier for non-equal files), replacing `src' with `dvi_path' when
+ dropping off the beginning of `src'.
+*/
+Boolean
+src_compare(const char *src, int src_len, const char *target, const char *dvi_path, size_t dvi_path_len)
+{
+ int i, j;
+ Boolean matched = True;
+
+ /* This macro sets the `src' pointer to `dvi_path' after having
+ dropped off the beginning of src, or returns False if dvi_path
+ is NULL (which either means that `src' was an absolute path, or
+ that the dvi_path has been used up already).
+ */
+#define CHK_USE_DIR(i) \
+ if (i == -1) { \
+ if (dvi_path == NULL) /* already done */ \
+ return False; \
+ src = dvi_path; \
+ dvi_path = NULL; \
+ i = dvi_path_len - 1; \
+ MYTRACE((stderr, "swapped, now using: |%s| of len %d", src, i)); \
+ }
+
+ /* ignore path in both filenames if one of them does not contain a path */
+ {
+ char *src_p = strrchr(src, '/');
+ char *target_p = strrchr(target, '/');
+
+ if (src_p == NULL) {
+ dvi_path = NULL;
+ if (target_p != NULL)
+ target = target_p + 1;
+ }
+
+ if (target_p == NULL) {
+ dvi_path = NULL;
+ if (src_p != NULL)
+ src = src_p + 1;
+ }
+ }
+
+ /* don't prepend dvi_path if src is absolute */
+ if (*src == '/') {
+ dvi_path = NULL;
+ }
+
+ /* skip `.tex' suffix in strings if present */
+ i = src_len;
+ MYTRACE((stderr, "len of |%s| %d", src, i));
+ if (i >= 4 && strcmp(src + (i - 4), ".tex") == 0) {
+ MYTRACE((stderr, "src |%s| has .tex suffix!", src));
+ i -= 4;
+ }
+
+ j = strlen(target);
+ MYTRACE((stderr, "len of |%s| %d", target, j));
+ if (j >= 4 && strcmp(target + (j - 4), ".tex") == 0) {
+ MYTRACE((stderr, "target |%s| has .tex suffix!", target));
+ j -= 4;
+ }
+
+ /* start with index of last char */
+ i--;
+ j--;
+
+ while (i >= 0 && j >= 0) {
+ int skip_dirs = 0;
+/* fprintf(stderr, "check: %d[%c]\n", i, src[i]); */
+ while (src[i] == '.' && src[i + 1] == '/') { /* normalize `../' and `./' */
+ MYTRACE((stderr, "check2: %d[%c]", i, src[i]));
+
+ if (i >= 2 && src[i - 1] == '.' && src[i - 2] == '/') {
+ MYTRACE((stderr, "case /.."));
+ i -= 3;
+ skip_dirs++;
+ }
+ else if (i == 1) { /* `../' or `/./' at start of src */
+ if (src[0] == '.') { /* `../' */
+ MYTRACE((stderr, "../ at start"));
+ i -= 2;
+ skip_dirs++;
+ }
+ else if (src[0] == '/') { /* `/./' */
+ MYTRACE((stderr, "/./ at start"));
+ i -= 1;
+ }
+ else /* something else */
+ break;
+ }
+ else if (i == 0 || (i > 1 && src[i - 1] == '/')) { /* ./ at start, or /./ somewhere */
+ i -= 1;
+ }
+ else /* might be `abc./' or `abc../' (strange but legal) */
+ break;
+
+ CHK_USE_DIR(i);
+ while (i >= 0 && src[i] == '/') i--;
+ CHK_USE_DIR(i);
+
+ while (src[i] != '.' && skip_dirs-- > 0) { /* unless there are subsequent `../' */
+ /* skip directories backwards */
+ while (i >= 0 && src[i] != '/') {
+ MYTRACE((stderr, "non-slash: %d,%c", i, src[i]));
+ i--;
+ }
+ CHK_USE_DIR(i);
+ while (i >= 0 && src[i] == '/') {
+ MYTRACE((stderr, "slash: %d,%c", i, src[i]));
+ i--;
+ }
+ CHK_USE_DIR(i);
+ MYTRACE((stderr, "skipped backwards: %d,%c", i, src[i]));
+ }
+ }
+
+ /* skip multiple '/'. No need to fall off the beginning of src and use
+ CHK_USE_DIR() here, since with (multiple) '/' at the beginning, src
+ must be an absolute path. */
+ while (i > 0 && src[i] == '/' && src[i - 1] == '/') {
+ i--;
+ }
+
+ MYTRACE((stderr, "comparing: %d[%c] - %d[%c]", i, src[i], j, target[j]));
+ if (src[i] != target[j]) {
+ matched = False;
+ break;
+ }
+ if (i == 0 && j == 0) /* at start of both strings */
+ break;
+ i--;
+ j--;
+ CHK_USE_DIR(i);
+ }
+
+ if (i != 0 || j != 0) /* not at start of both strings, no match */
+ return False;
+ else
+ return matched;
+#undef CHK_USE_DIR
+}
+
+
+/*
+ Contributed by ZLB: Return a canonicalized version of path (with `../'
+ and `./' resolved and `//' reduced to '/') in a freshly malloc()ed
+ buffer, which the caller is responsible for free()ing. Cannot fail.
+*/
+char *
+canonicalize_path(const char *path)
+{
+ char *p, *q, *start;
+ char c;
+ size_t len = strlen(path);
+
+ assert(path != NULL);
+ assert(*path == '/');
+
+ start = q = p = xstrdup(path);
+
+ /* len is the length of string */
+ while (p < start + len) {
+ if ((c = p[1]) == '/') {
+ /* remove multiple '/' in pathname */
+ memmove(p + 1, p + 2, len - (p + 2 - start) + 1);
+ len--;
+ continue;
+ }
+ else if (c == '.') {
+ if ((c = p[2]) == '/') {
+ /* p = '/.' in pathname */
+ memmove(p + 1, p + 3, len - (p + 3 - start) + 1);
+ len -= 2;
+ continue;
+ }
+ else if (c == '.' && ((c = p[3]) == '/' || c == '\0')) {
+ /* p == "/.." */
+ memmove(q, p + 3, len - (p + 3 - start) + 1);
+ len -= (p - q) + 3;
+ p = q;
+ /* check if the new dirname at p is "//" or './' or '../' */
+ if ((c = p[1]) == '/')
+ continue;
+ else if (c == '.') {
+ if ((c = p[2]) == '/')
+ continue;
+ else if (c == '.' && ((c = p[3]) == '/' || c == '\0')) {
+ while (--q >= start && *q != '/');
+ if (q < start)
+ q = start;
+ continue;
+ }
+ }
+ }
+ }
+
+ /* search next '/' */
+ q = p;
+ while (++p <= start + len && *p != '/');
+ }
+
+ return start;
+}
+
+/* Escape all of the following characters in str:
+ ` \ ; ( )
+ making it safe to pass str to a shell. Return result in a newly
+ allocated string, which the caller is responsible to free() after use.
+*/
+char *
+shell_escape_string(const char *str)
+{
+ size_t len = strlen(str);
+ char *new_str = xmalloc(len * 2 + 1); /* safe amount, since each char will be doubled at most */
+
+ const char *src_ptr = str;
+ char *target_ptr = new_str;
+ while (*src_ptr != '\0') {
+ if (*src_ptr == '\\'
+ || *src_ptr == '`'
+ || *src_ptr == '('
+ || *src_ptr == ')'
+ || *src_ptr == ';') {
+#if 0
+ /* only if not yet escaped? */
+ && (src_ptr == str || (src_ptr > str && *(src_ptr - 1) != '\\'))) {
+#endif
+ *target_ptr++ = '\\';
+ }
+ *target_ptr++ = *src_ptr++;
+ }
+ *target_ptr = '\0'; /* terminate */
+ return new_str;
+}
+
+void
+replace_extension(const char *fname, const char *extension,
+ char *buf, size_t buf_len)
+{
+ char *sep;
+ if ((sep = strrchr(fname, '.')) != NULL) {
+ size_t len = strlen(extension);
+ if (len + (sep - fname) > buf_len)
+ return;
+ strncpy(buf, fname, sep - fname);
+ strcpy(buf + (sep - fname), extension);
+ }
+ return;
+}
+
+#if 0
+/*
+ * Estimate the string length needed for %p conversion. Currently unused,
+ * since we use the more general VSNPRINTF() approach.
+ */
+#define PTR_CONVERSION_LEN_GUESS sizeof(void *) * CHAR_BIT
+#endif
+
+
+/*
+ * Return a formatted string in newly allocated memory.
+ */
+char *
+get_string_va(const char *fmt, ...)
+{
+ char *buf = NULL;
+ XDVI_GET_STRING_ARGP(buf, fmt);
+ return buf;
+}
+
+/* Wrapper for atof() for strtod()-like error checking,
+ * with an XDVI_WARNING if the conversion of <str> wasn't complete.
+ */
+double
+my_atof(const char *str)
+{
+ char *ptr;
+ double f;
+
+ f = strtod(str, (char **)&ptr);
+ if (*ptr != '\0') {
+ XDVI_WARNING((stderr, "strtod: incomplete conversion of %s to %f", str, f));
+ }
+ return f;
+}
+
+/* return length of a string representation of the integer n */
+int
+length_of_int(int n)
+{
+ int ret = 1;
+
+ if (n < 0) {
+ ret++;
+ n *= -1;
+ }
+ while (n >= 10) {
+ n /= 10;
+ ret++;
+ }
+
+ return ret;
+}
+
+Boolean
+is_spaces_only(const char *ptr)
+{
+ for (; *ptr; ptr++) {
+ if (!isspace((int)*ptr))
+ return False;
+ }
+ return True;
+}