1 /*
2 * miscellaneous string utility functions
3 *
4 * Copyright (c) 2001-2004 the xdvik development team
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 * IN NO EVENT SHALL PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25 /* #define MYDEBUG 1 */
26
27 #include <string.h>
28 #include <stdio.h>
29 #include <locale.h>
30 #include <ctype.h>
31 #include "xdvi-config.h"
32 #include "xdvi.h"
33 #include "string-utils.h"
34 #include "util.h"
35
36 /*------------------------------------------------------------
37 * str_is_prefix
38 *
39 * Purpose:
40 * Return True if <str1> is a prefix of <str2>, else False.
41 *
42 * If `case_sensitive' is set to False, it performs matching
43 * in a case-insensitive manner; in that case, str1 should
44 * be lowercase.
45 *------------------------------------------------------------*/
46
47 Boolean
str_is_prefix(const char * str1,const char * str2,Boolean case_sensitive)48 str_is_prefix(const char *str1, const char *str2, Boolean case_sensitive)
49 {
50 int i;
51
52 for (i = 0; *str1 != '\0' && *str2 != '\0'; i++) {
53 if ((case_sensitive && *str1 != *str2) ||
54 (!case_sensitive && *str1 != tolower((int)*str2)))
55 return False;
56 str1++;
57 str2++;
58 }
59 return *str1 == '\0';
60 }
61
62
63
64 /*------------------------------------------------------------
65 * str_is_suffix
66 *
67 * Purpose:
68 * Return True if str1 is a suffix of str2, else False.
69 * E.g. returns true if str1 = ".tex", str2 = "test.tex".
70 *
71 * If `case_sensitive' is set to False, it performs matching
72 * in a case-insensitive manner; in that case, str1 should
73 * be lowercase.
74 *------------------------------------------------------------*/
75
76 Boolean
str_is_suffix(const char * str1,const char * str2,Boolean case_sensitive)77 str_is_suffix(const char *str1, const char *str2, Boolean case_sensitive)
78 {
79 int len1 = strlen(str1);
80 int len2 = strlen(str2);
81
82 while (len2 > len1) {
83 str2++;
84 len2--;
85 }
86 if (case_sensitive)
87 return strcmp(str1, str2) == 0;
88 else
89 /* also compare terminating 0; second string
90 is assumed to be all-lowercase! */
91 return memicmp(str2, str1, len1 + 1) == 0;
92 }
93
94 /*
95 * Like strstr(), but does case-insensitive matching: Brute-force algorithm
96 * to find the first occurrence of subsrtring `needle' (which should be all-lowercase)
97 * in string `haystack', ignoring case in (i.e. lowercasing) haystack. The terminating
98 * '\0' characters are not compared.
99 * Returns a pointer to the beginning of the substring if found, NULL else.
100 *
101 * This code was derived from public domain code (Stristr.c on www.snippets.org).
102 * Currently unused.
103 */
104 char *
my_stristr(const char * haystack,const char * needle)105 my_stristr(const char *haystack, const char *needle)
106 {
107 const char *curr;
108
109 for (curr = haystack; *curr != '\0'; curr++) {
110 const char *ptr1, *ptr2;
111 /* search for first character */
112 for (; ((*curr != '\0') && (tolower((int)*curr) != *needle)); curr++) { ; }
113
114 if (*curr == '\0') /* not found */
115 return NULL;
116
117 /* now compare other characters */
118 ptr1 = needle;
119 ptr2 = curr;
120 while (tolower((int)*ptr2) == *ptr1) {
121 ptr2++;
122 ptr1++;
123 /* success if at end of needle */
124 if (*ptr1 == '\0')
125 return (char *)curr;
126 }
127 }
128 return NULL;
129 }
130
131 /*
132 expand filename to include full path name;
133 returns result in a freshly allocated string.
134 */
135 char *
expand_filename(const char * filename,expandPathTypeT type)136 expand_filename(const char *filename, expandPathTypeT type)
137 {
138 char *path_name = NULL;
139
140 if (*filename == '/') /* already an absolute path */
141 return xstrdup(filename);
142
143 if (type == USE_CWD_PATH) {
144 size_t path_name_len = 512;
145 size_t len = strlen(filename) + 1;
146
147 /* append to cwd if it's not already a full path */
148 if (filename[0] != '/') {
149 for (;;) {
150 char *tmp;
151 path_name = xrealloc(path_name, path_name_len);
152 if ((tmp = getcwd(path_name, path_name_len)) == NULL && errno == ERANGE) {
153 path_name_len *= 2;
154 }
155 else {
156 path_name = tmp;
157 break;
158 }
159 }
160 len += strlen(path_name) + 1; /* 1 for '/' */
161 path_name = xrealloc(path_name, len);
162 strcat(path_name, "/");
163 strcat(path_name, filename);
164 }
165
166 TRACE_HTEX((stderr,
167 "Expanding filename |%s| with CWD gives |%s|",
168 filename, path_name == NULL ? "<NULL>" : path_name));
169 return path_name ? path_name : xstrdup(filename);
170 }
171 else {
172 ASSERT(globals.dvi_file.dirname != NULL, "globals.dvi_file.dirname should have been initialized before");
173 path_name = xstrdup(globals.dvi_file.dirname);
174 path_name = xstrcat(path_name, filename);
175 TRACE_HTEX((stderr,
176 "Expanding filename |%s| with globals.dvi_file.dirname |%s| gives |%s|",
177 filename, globals.dvi_file.dirname, path_name));
178 return path_name;
179 }
180 }
181
182 /* expand filename to include `.dvi' extension and full path name;
183 returns malloc()ed string (caller is responsible for free()ing).
184 */
185 char *
filename_append_dvi(const char * filename)186 filename_append_dvi(const char *filename)
187 {
188 char *expanded_filename = NULL;
189 const char *orig_filename = filename;
190 size_t len;
191 char *p;
192
193 /* skip over `file:' prefix if present */
194 if (str_is_prefix("file:", filename, True)) {
195 filename += strlen("file:");
196 if (str_is_prefix("//", filename, True)) { /* is there a hostname following? */
197 char *tmp = strchr(filename+2, '/'); /* skip over host name */
198 if (tmp == NULL) {
199 XDVI_WARNING((stderr, "Malformed hostname part in filename `%s'; not expanding file name",
200 orig_filename));
201 }
202 else {
203 /* deal with multiple `//' after "file://localhost";
204 while the RFC seems to suggest that file://localhost/foo/bar defines a path
205 `foo/bar', most browsers (and actually, also libwww) will treat this path as absolute:
206 `/foo/bar'. So we treat
207 file://localhost//foo/bar
208 and
209 file://localhost/foo/bar
210 as equivalent.
211 */
212 while (*(tmp+1) == '/')
213 tmp++;
214 filename = tmp;
215 }
216 }
217 }
218
219 len = strlen(filename) + 5; /* 5 in case we need to add `.dvi\0' */
220
221 expanded_filename = xmalloc(len);
222
223 strcpy(expanded_filename, filename);
224
225 /* Append ".dvi" extension if no extension is present.
226 Only look at the filename part when trying to find a `.'.
227 */
228 if ((p = strrchr(expanded_filename, '/')) == NULL) {
229 p = expanded_filename;
230 }
231 if ((p = strrchr(p, '.')) == NULL) {
232 TRACE_HTEX((stderr, "appending .dvi extension to filename |%s|", expanded_filename));
233 strcat(expanded_filename, ".dvi");
234 }
235 return expanded_filename;
236 }
237
238 char *
expand_filename_append_dvi(const char * filename,expandPathTypeT type,Boolean must_exist)239 expand_filename_append_dvi(const char *filename, expandPathTypeT type, Boolean must_exist)
240 {
241 char canonical_path[MAXPATHLEN + 1];
242 char *normalized_fname = filename_append_dvi(filename);
243 char *expanded_fname = expand_filename(normalized_fname, type);
244 if (must_exist) {
245 char *canonical_name = REALPATH(expanded_fname, canonical_path);
246 free(normalized_fname);
247 free(expanded_fname);
248 return xstrdup(canonical_name);
249 }
250 else {
251 free(normalized_fname);
252 return expanded_fname;
253 }
254 }
255
256 char *
format_arg(const char * fmt,const char * arg,int * match)257 format_arg(const char *fmt, const char *arg, int *match)
258 {
259 char *tmp = xmalloc(strlen(fmt) + strlen(arg) + 1);
260 if (strchr(fmt, '%') != NULL) {
261 sprintf(tmp, fmt, arg);
262 *match = 1;
263 }
264 else {
265 strcpy(tmp, fmt);
266 /* NOTE: don't reset *match to 0, leave that to caller */
267 }
268 return tmp;
269 }
270
271 /* escape single `%' characters in arg and return newly allocated string */
272 char *
escape_format_arg(const char * arg)273 escape_format_arg(const char *arg)
274 {
275 char *ret, *ptr;
276 ASSERT(arg != NULL, "");
277 ret = xmalloc(strlen(arg) * 2 + 1); /* more than enuff */
278
279 ptr = ret;
280 while (*arg != '\0') {
281 /* need to escape? */
282 if (*arg == '%') { /* && (ptr == ret
283 || (ptr > ret && *(arg - 1) != '%'))) { */
284 *ptr++ = '%';
285 }
286 *ptr++ = *arg++;
287 }
288 *ptr = '\0';
289
290 /* trim return buffer */
291 return xrealloc(ret, strlen(ret) + 1);
292 }
293
294 char *
unquote_arg(const char * fmt,const char * arg,int * match,int * len)295 unquote_arg(const char *fmt, const char *arg, int *match, int *len)
296 {
297 char *ptr;
298 char c;
299
300 c = fmt[0];
301 fmt++;
302 if ((ptr = strchr(fmt, c)) != NULL) {
303 *ptr++ = '\0';
304 while (*ptr == ' ' || *ptr == '\t') {
305 ptr++;
306 }
307 *len = ptr - fmt;
308 return format_arg(fmt, arg, match);
309 }
310 else {
311 *len = strlen(fmt);
312 XDVI_WARNING((stderr, "Ignoring lonesome quote in string %s", fmt - 1));
313 return format_arg(fmt, arg, match);
314 }
315 }
316
317 /* Chop `source' into chunks separated by `sep', and save these
318 * to newly allocated return list. The end of the list is indicated
319 * by a NULL element (i.e. the returned list will always contain at
320 * least 1 element). The caller is responsible for free()ing the returned
321 * list.
322 *
323 * If `do_unquote' is True, separators inside single or double quotation marks will not be
324 * treated as boundaries. The quotation marks surrounding the chunk will be removed
325 * as well in that case.
326 */
327 char **
get_separated_list(const char * source,const char * sep,Boolean do_unquote)328 get_separated_list(const char *source, const char *sep, Boolean do_unquote)
329 {
330 /* allocate at least list of size 1, for NULL termination below */
331 char **chunks = xmalloc(sizeof *chunks);
332 const char *b_ptr, *e_ptr;
333 size_t i = 0;
334
335 b_ptr = source;
336
337 while (*b_ptr != '\0' && strchr(sep, *b_ptr) != NULL)
338 b_ptr++;
339
340 for (i = 0; *b_ptr != '\0'; i++) {
341 char *quote;
342 char quotechar = 0;
343 size_t len, chunk_len, alloc_len = 0;
344
345 if ((len = strcspn(b_ptr, sep)) == 0) /* at end */
346 break;
347
348 /* check for quoted chunk, in which case we don't want to treat
349 spaces as chunk separators */
350 if (do_unquote && (quote = strchr("'\"", *b_ptr)) != NULL) {
351 const char *curr = b_ptr + 1;
352 quotechar = *quote;
353
354 for (;;) { /* find end of quote */
355 char *maybe_end = strchr(curr, quotechar);
356 if (maybe_end != NULL) {
357 if (maybe_end - curr > 0 &&
358 *(maybe_end - 1) == '\\') { /* escaped quote */
359 curr = ++maybe_end;
360 }
361 else { /* found */
362 b_ptr++;
363 len = maybe_end - b_ptr;
364 break;
365 }
366 }
367 else { /* not found end - warn, and forget about this quote */
368 XDVI_WARNING((stderr, "Unmatched quote character in string `%s'", b_ptr));
369 break;
370 }
371 }
372 }
373
374 e_ptr = b_ptr + len;
375 chunk_len = e_ptr - b_ptr;
376 while (i + 1 >= alloc_len) {
377 alloc_len++;
378 }
379 chunks = xrealloc(chunks, alloc_len * sizeof *chunks);
380 chunks[i] = xmalloc(chunk_len + 1);
381 memcpy(chunks[i], b_ptr, chunk_len);
382 chunks[i][chunk_len] = '\0';
383
384 /* skip trailing quotechar and spaces */
385 b_ptr = e_ptr;
386 if (do_unquote && quotechar != 0 && *b_ptr == quotechar)
387 b_ptr++;
388 while (*b_ptr != '\0' && strchr(sep, *b_ptr) != NULL)
389 b_ptr++;
390 }
391 /* terminate list with NULL element */
392 chunks[i] = NULL;
393 return chunks;
394 }
395
396 const char *
find_format_str(const char * input,const char * fmt)397 find_format_str(const char *input, const char *fmt)
398 {
399 const char *ptr = input;
400 while ((ptr = strstr(ptr, fmt)) != NULL) {
401 if (ptr > input && *(ptr - 1) == '\\') {
402 ptr++;
403 continue;
404 }
405 else
406 return ptr;
407 }
408 return NULL;
409 }
410
411 /* return directory component of `path', or NULL if path is a filename only */
412 char *
get_dir_component(const char * path)413 get_dir_component(const char *path)
414 {
415 char *ret = NULL;
416 char *p;
417
418 if ((p = strrchr(path, '/')) != NULL) {
419 /* copy, chop off filename (but not the '/') */
420 ret = xstrdup(path);
421 *(ret + (p - path) + 1) = '\0';
422 TRACE_CLIENT((stderr, "get_dir_component of |%s| is |%s|\n", path, ret));
423 }
424 return ret;
425 }
426
427 /*
428 If `src' is an absolute path, compare it with `target', ignoring `.tex' extension in
429 both strings. Else, compare the concatenation of `src' with `dvi_path' and `target',
430 in the same way.
431 Since efficiency is a concern here, we don't copy any strings; instead, we loop through
432 `src' and `target' from the end of both strings (which makes expanding `../' easier, and
433 will terminate earlier for non-equal files), replacing `src' with `dvi_path' when
434 dropping off the beginning of `src'.
435 */
436 Boolean
src_compare(const char * src,int src_len,const char * target,const char * dvi_path,size_t dvi_path_len)437 src_compare(const char *src, int src_len, const char *target, const char *dvi_path, size_t dvi_path_len)
438 {
439 int i, j;
440 Boolean matched = True;
441
442 /* This macro sets the `src' pointer to `dvi_path' after having
443 dropped off the beginning of src, or returns False if dvi_path
444 is NULL (which either means that `src' was an absolute path, or
445 that the dvi_path has been used up already).
446 */
447 #define CHK_USE_DIR(i) \
448 if (i == -1) { \
449 if (dvi_path == NULL) /* already done */ \
450 return False; \
451 src = dvi_path; \
452 dvi_path = NULL; \
453 i = dvi_path_len - 1; \
454 MYTRACE((stderr, "swapped, now using: |%s| of len %d", src, i)); \
455 }
456
457 /* ignore path in both filenames if one of them does not contain a path */
458 {
459 char *src_p = strrchr(src, '/');
460 char *target_p = strrchr(target, '/');
461
462 if (src_p == NULL) {
463 dvi_path = NULL;
464 if (target_p != NULL)
465 target = target_p + 1;
466 }
467
468 if (target_p == NULL) {
469 dvi_path = NULL;
470 if (src_p != NULL)
471 src = src_p + 1;
472 }
473 }
474
475 /* don't prepend dvi_path if src is absolute */
476 if (*src == '/') {
477 dvi_path = NULL;
478 }
479
480 /* skip `.tex' suffix in strings if present */
481 i = src_len;
482 MYTRACE((stderr, "len of |%s| %d", src, i));
483 if (i >= 4 && strcmp(src + (i - 4), ".tex") == 0) {
484 MYTRACE((stderr, "src |%s| has .tex suffix!", src));
485 i -= 4;
486 }
487
488 j = strlen(target);
489 MYTRACE((stderr, "len of |%s| %d", target, j));
490 if (j >= 4 && strcmp(target + (j - 4), ".tex") == 0) {
491 MYTRACE((stderr, "target |%s| has .tex suffix!", target));
492 j -= 4;
493 }
494
495 /* start with index of last char */
496 i--;
497 j--;
498
499 while (i >= 0 && j >= 0) {
500 int skip_dirs = 0;
501 /* fprintf(stderr, "check: %d[%c]\n", i, src[i]); */
502 while (src[i] == '.' && src[i + 1] == '/') { /* normalize `../' and `./' */
503 MYTRACE((stderr, "check2: %d[%c]", i, src[i]));
504
505 if (i >= 2 && src[i - 1] == '.' && src[i - 2] == '/') {
506 MYTRACE((stderr, "case /.."));
507 i -= 3;
508 skip_dirs++;
509 }
510 else if (i == 1) { /* `../' or `/./' at start of src */
511 if (src[0] == '.') { /* `../' */
512 MYTRACE((stderr, "../ at start"));
513 i -= 2;
514 skip_dirs++;
515 }
516 else if (src[0] == '/') { /* `/./' */
517 MYTRACE((stderr, "/./ at start"));
518 i -= 1;
519 }
520 else /* something else */
521 break;
522 }
523 else if (i == 0 || (i > 1 && src[i - 1] == '/')) { /* ./ at start, or /./ somewhere */
524 i -= 1;
525 }
526 else /* might be `abc./' or `abc../' (strange but legal) */
527 break;
528
529 CHK_USE_DIR(i);
530 while (i >= 0 && src[i] == '/') i--;
531 CHK_USE_DIR(i);
532
533 while (src[i] != '.' && skip_dirs-- > 0) { /* unless there are subsequent `../' */
534 /* skip directories backwards */
535 while (i >= 0 && src[i] != '/') {
536 MYTRACE((stderr, "non-slash: %d,%c", i, src[i]));
537 i--;
538 }
539 CHK_USE_DIR(i);
540 while (i >= 0 && src[i] == '/') {
541 MYTRACE((stderr, "slash: %d,%c", i, src[i]));
542 i--;
543 }
544 CHK_USE_DIR(i);
545 MYTRACE((stderr, "skipped backwards: %d,%c", i, src[i]));
546 }
547 }
548
549 /* skip multiple '/'. No need to fall off the beginning of src and use
550 CHK_USE_DIR() here, since with (multiple) '/' at the beginning, src
551 must be an absolute path. */
552 while (i > 0 && src[i] == '/' && src[i - 1] == '/') {
553 i--;
554 }
555
556 MYTRACE((stderr, "comparing: %d[%c] - %d[%c]", i, src[i], j, target[j]));
557 if (src[i] != target[j]) {
558 matched = False;
559 break;
560 }
561 if (i == 0 && j == 0) /* at start of both strings */
562 break;
563 i--;
564 j--;
565 CHK_USE_DIR(i);
566 }
567
568 if (i != 0 || j != 0) /* not at start of both strings, no match */
569 return False;
570 else
571 return matched;
572 #undef CHK_USE_DIR
573 }
574
575
576 /*
577 Contributed by ZLB: Return a canonicalized version of path (with `../'
578 and `./' resolved and `//' reduced to '/') in a freshly malloc()ed
579 buffer, which the caller is responsible for free()ing. Cannot fail.
580 */
581 char *
canonicalize_path(const char * path)582 canonicalize_path(const char *path)
583 {
584 char *p, *q, *start;
585 char c;
586 size_t len = strlen(path);
587
588 assert(path != NULL);
589 assert(*path == '/');
590
591 start = q = p = xstrdup(path);
592
593 /* len is the length of string */
594 while (p < start + len) {
595 if ((c = p[1]) == '/') {
596 /* remove multiple '/' in pathname */
597 memmove(p + 1, p + 2, len - (p + 2 - start) + 1);
598 len--;
599 continue;
600 }
601 else if (c == '.') {
602 if ((c = p[2]) == '/') {
603 /* p = '/.' in pathname */
604 memmove(p + 1, p + 3, len - (p + 3 - start) + 1);
605 len -= 2;
606 continue;
607 }
608 else if (c == '.' && ((c = p[3]) == '/' || c == '\0')) {
609 /* p == "/.." */
610 memmove(q, p + 3, len - (p + 3 - start) + 1);
611 len -= (p - q) + 3;
612 p = q;
613 /* check if the new dirname at p is "//" or './' or '../' */
614 if ((c = p[1]) == '/')
615 continue;
616 else if (c == '.') {
617 if ((c = p[2]) == '/')
618 continue;
619 else if (c == '.' && ((c = p[3]) == '/' || c == '\0')) {
620 while (--q >= start && *q != '/');
621 if (q < start)
622 q = start;
623 continue;
624 }
625 }
626 }
627 }
628
629 /* search next '/' */
630 q = p;
631 while (++p <= start + len && *p != '/');
632 }
633
634 return start;
635 }
636
637 /* Escape all of the following characters in str:
638 ` \ ; ( )
639 making it safe to pass str to a shell. Return result in a newly
640 allocated string, which the caller is responsible to free() after use.
641 */
642 char *
shell_escape_string(const char * str)643 shell_escape_string(const char *str)
644 {
645 size_t len = strlen(str);
646 char *new_str = xmalloc(len * 2 + 1); /* safe amount, since each char will be doubled at most */
647
648 const char *src_ptr = str;
649 char *target_ptr = new_str;
650 while (*src_ptr != '\0') {
651 if (*src_ptr == '\\'
652 || *src_ptr == '`'
653 || *src_ptr == '('
654 || *src_ptr == ')'
655 || *src_ptr == ';') {
656 #if 0
657 /* only if not yet escaped? */
658 && (src_ptr == str || (src_ptr > str && *(src_ptr - 1) != '\\'))) {
659 #endif
660 *target_ptr++ = '\\';
661 }
662 *target_ptr++ = *src_ptr++;
663 }
664 *target_ptr = '\0'; /* terminate */
665 return new_str;
666 }
667
668 /* Get a pointer to the extension of the filename 'fname'
669 (ie. into the existing string),
670 or NULL if fname doens't have an extension.
671 */
672 const char *
673 get_extension(const char *fname)
674 {
675 char *sep, *tmp;
676 /* does filename have a directory component?
677 If so, be careful with dots within this component.
678 */
679 if ((sep = strrchr(fname, '/')) != NULL) {
680 tmp = sep;
681 if ((sep = strrchr(tmp, '.')) != NULL) {
682 return sep;
683 }
684 else {
685 return NULL;
686 }
687 }
688 else if ((sep = strrchr(fname, '.')) != NULL) {
689 return sep;
690 }
691 else {
692 return NULL;
693 }
694 }
695
696 void
697 replace_extension(const char *fname, const char *extension,
698 char *buf, size_t buf_len)
699 {
700 char *sep;
701 if ((sep = strrchr(fname, '.')) != NULL) {
702 size_t len = strlen(extension);
703 if (len + (sep - fname) > buf_len)
704 return;
705 strncpy(buf, fname, sep - fname);
706 strcpy(buf + (sep - fname), extension);
707 }
708 return;
709 }
710
711 #if 0
712 /*
713 * Estimate the string length needed for %p conversion. Currently unused,
714 * since we use the more general VSNPRINTF() approach.
715 */
716 #define PTR_CONVERSION_LEN_GUESS sizeof(void *) * CHAR_BIT
717 #endif
718
719
720 /*
721 * Return a formatted string in newly allocated memory.
722 */
723 char *
724 get_string_va(const char *fmt, ...)
725 {
726 char *buf = NULL;
727 XDVI_GET_STRING_ARGP(buf, fmt);
728 return buf;
729 }
730
731 /* Wrapper for atof() for strtod()-like error checking,
732 * with an XDVI_WARNING if the conversion of <str> wasn't complete.
733 */
734 double
735 my_atof(const char *str)
736 {
737 char *ptr;
738 double f;
739
740 f = strtod(str, (char **)&ptr);
741 if (*ptr != '\0') {
742 XDVI_WARNING((stderr, "strtod: incomplete conversion of %s to %f", str, f));
743 }
744 return f;
745 }
746
747 /* return length of a string representation of the integer n */
748 int
749 length_of_int(int n)
750 {
751 int ret = 1;
752
753 if (n < 0) {
754 ret++;
755 n *= -1;
756 }
757 while (n >= 10) {
758 n /= 10;
759 ret++;
760 }
761
762 return ret;
763 }
764
765 Boolean
766 is_spaces_only(const char *ptr)
767 {
768 for (; *ptr; ptr++) {
769 if (!isspace((int)*ptr))
770 return False;
771 }
772 return True;
773 }
774