Back to index

tetex-bin  3.0
string-utils.c
Go to the documentation of this file.
00001 /*
00002  * miscellaneous string utility functions
00003  * 
00004  * Copyright (c) 2001-2004 the xdvik development team
00005  * 
00006  * Permission is hereby granted, free of charge, to any person obtaining a copy
00007  * of this software and associated documentation files (the "Software"), to
00008  * deal in the Software without restriction, including without limitation the
00009  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
00010  * sell copies of the Software, and to permit persons to whom the Software is
00011  * furnished to do so, subject to the following conditions:
00012  * 
00013  * The above copyright notice and this permission notice shall be included in
00014  * all copies or substantial portions of the Software.
00015  * 
00016  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
00017  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
00018  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
00019  * IN NO EVENT SHALL PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE
00020  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
00021  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
00022  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00023 */
00024 
00025 /*  #define MYDEBUG 1 */
00026 
00027 #include <string.h>
00028 #include <stdio.h>
00029 #include <locale.h>
00030 #include <ctype.h>
00031 #include "xdvi-config.h"
00032 #include "xdvi.h"
00033 #include "string-utils.h"
00034 #include "util.h"
00035 
00036 /*------------------------------------------------------------
00037  *  str_is_prefix
00038  *
00039  *  Purpose:
00040  *     Return True if <str1> is a prefix of <str2>, else False.
00041  *
00042  *     If `case_sensitive' is set to False, it performs matching
00043  *     in a case-insensitive manner; in that case, str1 should
00044  *     be lowercase.
00045  *------------------------------------------------------------*/
00046 
00047 Boolean
00048 str_is_prefix(const char *str1, const char *str2, Boolean case_sensitive)
00049 {
00050     int i;
00051 
00052     for (i = 0; *str1 != '\0' && *str2 != '\0'; i++) {
00053        if ((case_sensitive && *str1 != *str2) ||
00054            (!case_sensitive && *str1 != tolower((int)*str2)))
00055            return False;
00056        str1++;
00057        str2++;
00058     }
00059     return *str1 == '\0';
00060 }
00061 
00062 
00063 
00064 /*------------------------------------------------------------
00065  *  str_is_suffix
00066  *
00067  *  Purpose:
00068  *     Return True if str1 is a suffix of str2, else False.
00069  *     E.g. returns true if str1 = ".tex", str2 = "test.tex".
00070  *
00071  *     If `case_sensitive' is set to False, it performs matching
00072  *     in a case-insensitive manner; in that case, str1 should
00073  *     be lowercase.
00074  *------------------------------------------------------------*/
00075 
00076 Boolean
00077 str_is_suffix(const char *str1, const char *str2, Boolean case_sensitive)
00078 {
00079     int len1 = strlen(str1);
00080     int len2 = strlen(str2);
00081 
00082     while (len2 > len1) {
00083        str2++;
00084        len2--;
00085     }
00086     if (case_sensitive)
00087        return strcmp(str1, str2) == 0;
00088     else
00089        /* also compare terminating 0; second string
00090           is assumed to be all-lowercase! */
00091        return memicmp(str2, str1, len1 + 1) == 0;
00092 }
00093 
00094 /*
00095  * Like strstr(), but does case-insensitive matching: Brute-force algorithm
00096  * to find the first occurrence of subsrtring `needle' (which should be all-lowercase)
00097  * in string `haystack', ignoring case in (i.e. lowercasing) haystack. The terminating
00098  * '\0' characters are not compared.
00099  * Returns a pointer to the beginning of the substring if found, NULL else.
00100  *
00101  * This code was derived from public domain code (Stristr.c on www.snippets.org).
00102  * Currently unused.
00103  */
00104 char *
00105 my_stristr(const char *haystack, const char *needle)
00106 {
00107   const char *curr;
00108 
00109   for (curr = haystack; *curr != '\0'; curr++) {
00110       const char *ptr1, *ptr2;
00111       /* search for first character */
00112       for (; ((*curr != '\0') && (tolower((int)*curr) != *needle)); curr++) { ; }
00113 
00114       if (*curr == '\0') /* not found */
00115        return NULL;
00116 
00117       /* now compare other characters */
00118       ptr1 = needle;
00119       ptr2 = curr;
00120       while (tolower((int)*ptr2) == *ptr1) {
00121          ptr2++;
00122          ptr1++;
00123          /* success if at end of needle */
00124          if (*ptr1 == '\0')
00125              return (char *)curr;
00126       }
00127   }
00128   return NULL;
00129 }
00130 
00131 /*
00132   expand filename to include full path name;
00133   returns result in a freshly allocated string.
00134 */
00135 char *
00136 expand_filename(const char *filename, expandPathTypeT type)
00137 {
00138     char *path_name = NULL;
00139 
00140     if (*filename == '/') /* already an absolute path */
00141        return xstrdup(filename);
00142     
00143     if (type == USE_CWD_PATH) {
00144        size_t path_name_len = 512;
00145        size_t len = strlen(filename) + 1;
00146 
00147        /* append to cwd if it's not already a full path */
00148        if (filename[0] != '/') {
00149            for (;;) {
00150               char *tmp;
00151               path_name = xrealloc(path_name, path_name_len);
00152               if ((tmp = getcwd(path_name, path_name_len)) == NULL && errno == ERANGE) {
00153                   path_name_len *= 2;
00154               }
00155               else {
00156                   path_name = tmp;
00157                   break;
00158               }
00159            }
00160            len += strlen(path_name) + 1;  /* 1 for '/' */
00161            path_name = xrealloc(path_name, len);
00162            strcat(path_name, "/");
00163            strcat(path_name, filename);
00164        }
00165        
00166        TRACE_HTEX((stderr,
00167                   "Expanding filename |%s| with CWD gives |%s|",
00168                   filename, path_name == NULL ? "<NULL>" : path_name));
00169        return path_name ? path_name : xstrdup(filename);
00170     }
00171     else {
00172        ASSERT(globals.dvi_file.dirname != NULL, "globals.dvi_file.dirname should have been initialized before");
00173        path_name = xstrdup(globals.dvi_file.dirname);
00174        path_name = xstrcat(path_name, filename);
00175        TRACE_HTEX((stderr,
00176                   "Expanding filename |%s| with globals.dvi_file.dirname |%s| gives |%s|",
00177                   filename, globals.dvi_file.dirname, path_name));
00178        return path_name;
00179     }
00180 }
00181 
00182 /* expand filename to include `.dvi' extension and full path name;
00183    returns malloc()ed string (caller is responsible for free()ing).
00184 */
00185 char *
00186 filename_append_dvi(const char *filename)
00187 {
00188     char *expanded_filename = NULL;
00189     const char *orig_filename = filename;
00190     size_t len;
00191     char *p;
00192     
00193     /* skip over `file:' prefix if present */
00194     if (str_is_prefix("file:", filename, True)) {
00195        filename += strlen("file:");
00196        if (str_is_prefix("//", filename + strlen("file:"), True)) { /* is there a hostname following? */
00197            char *tmp = strchr(filename+2, '/'); /* skip over host name */
00198            if (tmp == NULL) {
00199               XDVI_WARNING((stderr, "Malformed hostname part in filename `%s'; not expanding file name",
00200                            orig_filename));
00201            }
00202            else {
00203               /* deal with multiple `//' after "file://localhost";
00204                  while the RFC seems to suggest that file://localhost/foo/bar defines a path
00205                  `foo/bar', most browsers (and actually, also libwww) will treat this path as absolute:
00206                  `/foo/bar'. So we treat
00207                  file://localhost//foo/bar
00208                  and
00209                  file://localhost/foo/bar
00210                  as equivalent.
00211               */
00212               while (*(tmp+1) == '/') 
00213                   tmp++;
00214               filename = tmp;
00215            }
00216        }
00217     }
00218 
00219     len = strlen(filename) + 5; /* 5 in case we need to add `.dvi\0' */
00220 
00221     expanded_filename = xmalloc(len);
00222 
00223     strcpy(expanded_filename, filename);
00224 
00225     /* Append ".dvi" extension if no extension is present.
00226        Only look at the filename part when trying to find a `.'.
00227      */
00228     if ((p = strrchr(expanded_filename, '/')) == NULL) {
00229        p = expanded_filename;
00230     }
00231     if ((p = strrchr(p, '.')) == NULL) {
00232        TRACE_HTEX((stderr, "appending .dvi extension to filename |%s|", expanded_filename));
00233        strcat(expanded_filename, ".dvi");
00234     }
00235     return expanded_filename;
00236 }
00237 
00238 char *
00239 expand_filename_append_dvi(const char *filename, expandPathTypeT type, Boolean must_exist)
00240 {
00241     char canonical_path[MAXPATHLEN + 1];
00242     char *normalized_fname = filename_append_dvi(filename);
00243     char *expanded_fname = expand_filename(normalized_fname, type);
00244     if (must_exist) {
00245        char *canonical_name = REALPATH(expanded_fname, canonical_path);
00246        free(normalized_fname);
00247        free(expanded_fname);
00248        return xstrdup(canonical_name);
00249     }
00250     else {
00251        free(normalized_fname);
00252        return expanded_fname;
00253     }
00254 }
00255 
00256 char *
00257 format_arg(const char *fmt, const char *arg, int *match)
00258 {
00259     char *tmp = xmalloc(strlen(fmt) + strlen(arg) + 1);
00260     if (strchr(fmt, '%') != NULL) {
00261        sprintf(tmp, fmt, arg);
00262        *match = 1;
00263     }
00264     else {
00265        strcpy(tmp, fmt);
00266     }
00267     return tmp;
00268 }
00269 
00270 /* escape single `%' characters in arg and return newly allocated string */
00271 char *
00272 escape_format_arg(const char *arg)
00273 {
00274     char *ret, *ptr;
00275     ASSERT(arg != NULL, "");
00276     ret = xmalloc(strlen(arg) * 2 + 1); /* more than enuff */
00277     
00278     ptr = ret;
00279     while (*arg != '\0') {
00280        /* need to escape? */
00281        if (*arg == '%' && (ptr == ret
00282                          || (ptr > ret && *(arg - 1) != '%'))) {
00283            *ptr++ = '%';
00284        }
00285        *ptr++ = *arg++;
00286     }
00287     *ptr = '\0';
00288 
00289     /* trim return buffer */
00290     return xrealloc(ret, strlen(ret) + 1);
00291 }
00292 
00293 char *
00294 unquote_arg(const char *fmt, const char *arg, int *match, int *len)
00295 {
00296     char *ptr;
00297     char c;
00298     
00299     c = fmt[0];
00300     fmt++;
00301     if ((ptr = strchr(fmt, c)) != NULL) {
00302        *ptr++ = '\0';
00303        while (*ptr == ' ' || *ptr == '\t') {
00304            ptr++;
00305        }
00306        *len = ptr - fmt;
00307        return format_arg(fmt, arg, match);
00308     }
00309     else {
00310        *len = strlen(fmt);
00311        XDVI_WARNING((stderr, "Ignoring lonesome quote in string %s", fmt - 1));
00312        return format_arg(fmt, arg, match);
00313     }
00314 }
00315 
00316 /* Chop `source' into chunks separated by `sep', and save these
00317  * to newly allocated return list. The end of the list is indicated
00318  * by a NULL element (i.e. the returned list will always contain at
00319  * least 1 element). The caller is responsible for free()ing the returned
00320  * list.
00321  *
00322  * If `do_unquote' is True, separators inside the quotes will not be
00323  * treated as boundaries; instead, the quotes surrounding the chunk are removed.
00324  */
00325 char **
00326 get_separated_list(const char *source, const char *sep, Boolean do_unquote)
00327 {
00328     /* allocate at least list of size 1, for NULL termination below */
00329     char **chunks = xmalloc(sizeof *chunks);
00330     const char *b_ptr, *e_ptr;
00331     size_t i = 0;
00332     
00333     b_ptr = source;
00334 
00335     while (*b_ptr != '\0' && strchr(sep, *b_ptr) != NULL)
00336        b_ptr++;
00337     
00338     for (i = 0; *b_ptr != '\0'; i++) {
00339        char *quote;
00340        char quotechar = 0;
00341        size_t len, chunk_len, alloc_len = 0;
00342        
00343        if ((len = strcspn(b_ptr, sep)) == 0) /* at end */
00344            break;
00345 
00346        /* check for quoted chunk, in which case we don't want to treat
00347           spaces as chunk separators */
00348        if (do_unquote && (quote = strchr("'\"", *b_ptr)) != NULL) {
00349            const char *curr = b_ptr + 1;
00350            quotechar = *quote;
00351 
00352            for (;;) { /* find end of quote */
00353               char *maybe_end = strchr(curr, quotechar);
00354               if (maybe_end != NULL) {
00355                   if (maybe_end - curr > 0 &&
00356                      *(maybe_end - 1) == '\\') { /* escaped quote */
00357                      curr = ++maybe_end;
00358                   }
00359                   else { /* found */
00360                      b_ptr++;
00361                      len = maybe_end - b_ptr;
00362                      break;
00363                   }
00364               }
00365               else { /* not found end - warn, and forget about this quote */
00366                   XDVI_WARNING((stderr, "Unmatched quote character in string `%s'", b_ptr));
00367                   break;
00368               }
00369            }
00370        }
00371        
00372        e_ptr = b_ptr + len;
00373        chunk_len = e_ptr - b_ptr;
00374        while (i + 1 >= alloc_len) {
00375            alloc_len++;
00376        }
00377        chunks = xrealloc(chunks, alloc_len * sizeof *chunks);
00378        chunks[i] = xmalloc(chunk_len + 1);
00379        memcpy(chunks[i], b_ptr, chunk_len);
00380        chunks[i][chunk_len] = '\0';
00381 
00382        /* skip trailing quotechar and spaces */
00383        b_ptr = e_ptr;
00384        if (do_unquote && quotechar != 0 && *b_ptr == quotechar)
00385            b_ptr++;
00386        while (*b_ptr != '\0' && strchr(sep, *b_ptr) != NULL)
00387            b_ptr++;
00388     }
00389     /* terminate list with NULL element */
00390     chunks[i] = NULL;
00391     return chunks;
00392 }
00393 
00394 const char *
00395 find_format_str(const char *input, const char *fmt)
00396 {
00397     const char *ptr = input;
00398     while ((ptr = strstr(ptr, fmt)) != NULL) {
00399        if (ptr > input && *(ptr - 1) == '\\') {
00400            ptr++;
00401            continue;
00402        }
00403        else
00404            return ptr;
00405     }
00406     return NULL;
00407 }
00408 
00409 /* return directory component of `path', or NULL if path is a filename only */
00410 char *
00411 get_dir_component(const char *path)
00412 {
00413     char *ret = NULL;
00414     char *p;
00415     
00416     if ((p = strrchr(path, '/')) != NULL) {
00417        /* copy, chop off filename (but not the '/') */
00418        ret = xstrdup(path);
00419        *(ret + (p - path) + 1) = '\0';
00420        TRACE_CLIENT((stderr, "get_dir_component of |%s| is |%s|\n", path, ret));
00421     }
00422     return ret;
00423 }
00424 
00425 /*
00426   If `src' is an absolute path, compare it with `target', ignoring `.tex' extension in
00427   both strings. Else, compare the concatenation of `src' with `dvi_path' and `target',
00428   in the same way.
00429   Since efficiency is a concern here, we don't copy any strings; instead, we loop through
00430   `src' and `target' from the end of both strings (which makes expanding `../' easier, and
00431   will terminate earlier for non-equal files), replacing `src' with `dvi_path' when
00432   dropping off the beginning of `src'.
00433 */
00434 Boolean
00435 src_compare(const char *src, int src_len, const char *target, const char *dvi_path, size_t dvi_path_len)
00436 {
00437     int i, j;
00438     Boolean matched = True;
00439 
00440     /* This macro sets the `src' pointer to `dvi_path' after having
00441        dropped off the beginning of src, or returns False if dvi_path
00442        is NULL (which either means that `src' was an absolute path, or
00443        that the dvi_path has been used up already).
00444     */
00445 #define CHK_USE_DIR(i)                                                       \
00446        if (i == -1) {                                                        \
00447            if (dvi_path == NULL) /* already done */                          \
00448               return False;                                           \
00449            src = dvi_path;                                            \
00450            dvi_path = NULL;                                           \
00451            i = dvi_path_len - 1;                                      \
00452            MYTRACE((stderr, "swapped, now using: |%s| of len %d", src, i));  \
00453        }
00454 
00455     /* ignore path in both filenames if one of them does not contain a path */
00456     {
00457        char *src_p = strrchr(src, '/');
00458        char *target_p = strrchr(target, '/');
00459 
00460        if (src_p == NULL) {
00461            dvi_path = NULL;
00462            if (target_p != NULL)
00463               target = target_p + 1;
00464        }
00465 
00466        if (target_p == NULL) {
00467            dvi_path = NULL;
00468            if (src_p != NULL)
00469               src = src_p + 1;
00470        }
00471     }
00472 
00473     /* don't prepend dvi_path if src is absolute */
00474     if (*src == '/') { 
00475        dvi_path = NULL;
00476     }
00477     
00478     /* skip `.tex' suffix in strings if present */
00479     i = src_len;
00480     MYTRACE((stderr, "len of |%s| %d", src, i));
00481     if (i >= 4 && strcmp(src + (i - 4), ".tex") == 0) {
00482        MYTRACE((stderr, "src |%s| has .tex suffix!", src));
00483        i -= 4;
00484     }
00485 
00486     j = strlen(target);
00487     MYTRACE((stderr, "len of |%s| %d", target, j));
00488     if (j >= 4 && strcmp(target + (j - 4), ".tex") == 0) {
00489        MYTRACE((stderr, "target |%s| has .tex suffix!", target));
00490        j -= 4;
00491     }
00492 
00493     /* start with index of last char */
00494     i--;
00495     j--;
00496 
00497     while (i >= 0 && j >= 0) {
00498        int skip_dirs = 0;
00499 /*     fprintf(stderr, "check: %d[%c]\n", i, src[i]); */
00500        while (src[i] == '.' && src[i + 1] == '/') { /* normalize `../' and `./' */
00501            MYTRACE((stderr, "check2: %d[%c]", i, src[i]));
00502 
00503            if (i >= 2 && src[i - 1] == '.' && src[i - 2] == '/') {
00504               MYTRACE((stderr, "case /.."));
00505               i -= 3;
00506               skip_dirs++;
00507            }
00508            else if (i == 1) { /* `../' or `/./' at start of src */
00509               if (src[0] == '.') { /* `../' */
00510                   MYTRACE((stderr, "../ at start"));
00511                   i -= 2;
00512                   skip_dirs++;
00513               }
00514               else if (src[0] == '/') { /* `/./' */
00515                   MYTRACE((stderr, "/./ at start"));
00516                   i -= 1;
00517               }
00518               else /* something else */
00519                   break;
00520            }
00521            else if (i == 0 || (i > 1 && src[i - 1] == '/')) { /* ./ at start, or /./ somewhere */
00522               i -= 1;
00523            }
00524            else /* might be `abc./' or `abc../' (strange but legal) */
00525               break;
00526 
00527            CHK_USE_DIR(i);
00528            while (i >= 0 && src[i] == '/') i--;
00529            CHK_USE_DIR(i);
00530            
00531            while (src[i] != '.' && skip_dirs-- > 0) { /* unless there are subsequent `../' */
00532               /* skip directories backwards */
00533               while (i >= 0 && src[i] != '/') {
00534                   MYTRACE((stderr, "non-slash: %d,%c", i, src[i]));
00535                   i--;
00536               }
00537               CHK_USE_DIR(i);
00538               while (i >= 0 && src[i] == '/') {
00539                   MYTRACE((stderr, "slash: %d,%c", i, src[i]));
00540                   i--;
00541               }
00542               CHK_USE_DIR(i);
00543               MYTRACE((stderr, "skipped backwards: %d,%c", i, src[i]));
00544            }
00545        }
00546 
00547        /* skip multiple '/'. No need to fall off the beginning of src and use
00548           CHK_USE_DIR() here, since with (multiple) '/' at the beginning, src
00549           must be an absolute path. */
00550        while (i > 0 && src[i] == '/' && src[i - 1] == '/') {
00551            i--;
00552        }
00553        
00554        MYTRACE((stderr, "comparing: %d[%c] - %d[%c]", i, src[i], j, target[j]));
00555        if (src[i] != target[j]) {
00556            matched = False;
00557            break;
00558        }
00559        if (i == 0 && j == 0) /* at start of both strings */
00560            break;
00561        i--;
00562        j--;
00563        CHK_USE_DIR(i);
00564     }
00565 
00566     if (i != 0 || j != 0) /* not at start of both strings, no match */
00567        return False;
00568     else
00569        return matched;
00570 #undef CHK_USE_DIR
00571 }
00572 
00573 
00574 /*
00575   Contributed by ZLB: Return a canonicalized version of path (with `../'
00576   and `./' resolved and `//' reduced to '/') in a freshly malloc()ed
00577   buffer, which the caller is responsible for free()ing. Cannot fail.
00578 */
00579 char *
00580 canonicalize_path(const char *path)
00581 {
00582     char *p, *q, *start;
00583     char c;
00584     size_t len = strlen(path);
00585 
00586     assert(path != NULL);
00587     assert(*path == '/');
00588 
00589     start = q = p = xstrdup(path);
00590 
00591     /* len is the length of string */
00592     while (p < start + len) {
00593        if ((c = p[1]) == '/') {
00594            /* remove multiple '/' in pathname */
00595            memmove(p + 1, p + 2, len - (p + 2 - start) + 1);
00596            len--;
00597            continue;
00598        }
00599        else if (c == '.') {
00600            if ((c = p[2]) == '/') {
00601               /* p = '/.' in pathname */
00602               memmove(p + 1, p + 3, len - (p + 3 - start) + 1);
00603               len -= 2;
00604               continue;
00605            }
00606            else if (c == '.' && ((c = p[3]) == '/' || c == '\0')) {
00607               /* p == "/.." */
00608               memmove(q, p + 3, len - (p + 3 - start) + 1);
00609               len -= (p - q) + 3;
00610               p = q;
00611               /* check if the new dirname at p is "//" or './' or '../' */
00612               if ((c = p[1]) == '/')
00613                   continue;
00614               else if (c == '.') {
00615                   if ((c = p[2]) == '/')
00616                      continue;
00617                   else if (c == '.' && ((c = p[3]) == '/' || c == '\0')) {
00618                      while (--q >= start && *q != '/');
00619                      if (q < start)
00620                          q = start;
00621                      continue;
00622                   }
00623               }
00624            }
00625        }
00626 
00627        /* search next '/' */
00628        q = p;
00629        while (++p <= start + len && *p != '/');
00630     }
00631 
00632     return start;
00633 }
00634 
00635 /* Escape all of the following characters in str:
00636    ` \ ; ( )
00637    making it safe to pass str to a shell. Return result in a newly
00638    allocated string, which the caller is responsible to free() after use.
00639 */
00640 char *
00641 shell_escape_string(const char *str)
00642 {
00643     size_t len = strlen(str);
00644     char *new_str = xmalloc(len * 2 + 1); /* safe amount, since each char will be doubled at most */
00645     
00646     const char *src_ptr = str;
00647     char *target_ptr = new_str;
00648     while (*src_ptr != '\0') {
00649        if (*src_ptr == '\\'
00650            || *src_ptr == '`'
00651            || *src_ptr == '('
00652            || *src_ptr == ')'
00653            || *src_ptr == ';') {
00654 #if 0
00655            /* only if not yet escaped? */
00656            && (src_ptr == str || (src_ptr > str && *(src_ptr - 1) != '\\'))) {
00657 #endif
00658            *target_ptr++ = '\\';
00659        }
00660        *target_ptr++ = *src_ptr++;
00661     }
00662     *target_ptr = '\0'; /* terminate */
00663     return new_str;
00664 }
00665 
00666 void
00667 replace_extension(const char *fname, const char *extension,
00668                 char *buf, size_t buf_len)
00669 {
00670     char *sep;
00671     if ((sep = strrchr(fname, '.')) != NULL) {
00672        size_t len = strlen(extension);
00673        if (len + (sep - fname) > buf_len)
00674            return;
00675        strncpy(buf, fname, sep - fname);
00676        strcpy(buf + (sep - fname), extension);
00677     }
00678     return;
00679 }
00680 
00681 #if 0
00682 /*
00683  * Estimate the string length needed for %p conversion. Currently unused,
00684  * since we use the more general VSNPRINTF() approach.
00685  */
00686 #define PTR_CONVERSION_LEN_GUESS sizeof(void *) * CHAR_BIT
00687 #endif
00688 
00689    
00690 /*
00691  * Return a formatted string in newly allocated memory.
00692  */
00693 char *
00694 get_string_va(const char *fmt, ...)
00695 {
00696     char *buf = NULL;
00697     XDVI_GET_STRING_ARGP(buf, fmt);
00698     return buf;
00699 }
00700 
00701 /* Wrapper for atof() for strtod()-like error checking,
00702  * with an XDVI_WARNING if the conversion of <str> wasn't complete.
00703  */
00704 double
00705 my_atof(const char *str)
00706 {
00707     char *ptr;
00708     double f;
00709     
00710     f = strtod(str, (char **)&ptr);
00711     if (*ptr != '\0') {
00712        XDVI_WARNING((stderr, "strtod: incomplete conversion of %s to %f", str, f));
00713     }
00714     return f;
00715 }
00716 
00717 /* return length of a string representation of the integer n */
00718 int
00719 length_of_int(int n)
00720 {
00721     int ret = 1;
00722 
00723     if (n < 0) {
00724        ret++;
00725        n *= -1;
00726     }
00727     while (n >= 10) {
00728        n /= 10;
00729        ret++;
00730     }
00731 
00732     return ret;
00733 }
00734 
00735 Boolean
00736 is_spaces_only(const char *ptr)
00737 {
00738     for (; *ptr; ptr++) {
00739        if (!isspace((int)*ptr))
00740            return False;
00741     }
00742     return True;
00743 }