Don't use MB_CUR_MAX as array size, as it can be non-constant in some compilers.
[mpdm.git] / mpdm_s.c
blob53c88845817018dbbb1105fe8a9b066c2e32ad32
1 /*
3 MPDM - Minimum Profit Data Manager
4 Copyright (C) 2003/2007 Angel Ortega <angel@triptico.com>
6 mpdm_s.c - String management
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
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.
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.
22 http://www.triptico.com
26 #include "config.h"
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <wchar.h>
32 #include <locale.h>
33 #include <wctype.h>
35 #ifdef CONFOPT_GETTEXT
36 #include <libintl.h>
37 #endif
39 #include "mpdm.h"
42 /*******************
43 Data
44 ********************/
46 /*******************
47 Code
48 ********************/
50 void * mpdm_poke(void * dst, int * dsize, void * org, int osize, int esize)
51 /* pokes (adds) org into dst, which is a dynamic string, making it grow */
53 if(org != NULL && osize)
55 /* makes room for the new string */
56 if((dst = realloc(dst, (*dsize + osize) * esize)) != NULL)
58 /* copies it */
59 memcpy(dst + (*dsize * esize), org, osize * esize);
61 /* adds to final size */
62 *dsize += osize;
66 return(dst);
70 wchar_t * mpdm_pokev(wchar_t * dst, int * dsize, mpdm_t v)
71 /* adds the string in v to dst using mpdm_poke() */
73 if(v != NULL)
75 wchar_t * ptr = mpdm_string(v);
77 dst = mpdm_poke(dst, dsize, ptr, wcslen(ptr), sizeof(wchar_t));
80 return(dst);
84 wchar_t * mpdm_mbstowcs(char * str, int * s, int l)
85 /* converts an mbs to a wcs, but filling invalid chars
86 with question marks instead of just failing */
88 wchar_t * ptr = NULL;
89 char tmp[64]; /* really MB_CUR_MAX + 1 */
90 wchar_t wc;
91 int n, i, c, t = 0;
92 char * cstr;
94 /* allow NULL values for s */
95 if(s == NULL) s = &t;
97 /* if there is a limit, duplicate and break the string */
98 if(l >= 0)
100 cstr = strdup(str);
101 cstr[l] = '\0';
103 else
104 cstr = str;
106 /* try first a direct conversion with mbstowcs */
107 if((*s = mbstowcs(NULL, cstr, 0)) != -1)
109 /* direct conversion is possible; do it */
110 if((ptr = malloc((*s + 1) * sizeof(wchar_t))) != NULL)
112 mbstowcs(ptr, cstr, *s);
113 ptr[*s] = L'\0';
116 else
118 /* zero everything */
119 *s = n = i = 0;
121 for(;;)
123 /* no more characters to process? */
124 if((c = cstr[n + i]) == '\0' && i == 0)
125 break;
127 tmp[i++] = c; tmp[i] = '\0';
129 /* try to convert */
130 if(mbstowcs(&wc, tmp, 1) == -1)
132 /* can still be an incomplete multibyte char? */
133 if(c != '\0' && i <= MB_CUR_MAX)
134 continue;
135 else
137 /* too many failing bytes; skip 1 byte */
138 wc = L'?';
139 i = 1;
143 /* skip used bytes and back again */
144 n += i;
145 i = 0;
147 /* store new char */
148 if((ptr = mpdm_poke(ptr, s, &wc, 1, sizeof(wchar_t))) == NULL)
149 break;
152 /* null terminate and count one less */
153 if(ptr != NULL)
155 ptr = mpdm_poke(ptr, s, L"", 1, sizeof(wchar_t));
156 (*s)--;
160 /* free the duplicate */
161 if(cstr != str) free(cstr);
163 return(ptr);
167 char * mpdm_wcstombs(wchar_t * str, int * s)
168 /* converts a wcs to an mbs, but filling invalid chars
169 with question marks instead of just failing */
171 char * ptr = NULL;
172 char tmp[64]; /* really MB_CUR_MAX + 1 */
173 int l, t = 0;
175 /* allow NULL values for s */
176 if(s == NULL) s = &t;
178 /* try first a direct conversion with wcstombs */
179 if((*s = wcstombs(NULL, str, 0)) != -1)
181 /* direct conversion is possible; do it and return */
182 if((ptr = malloc(*s + 1)) != NULL)
184 wcstombs(ptr, str, *s);
185 ptr[*s] = '\0';
188 return(ptr);
191 /* invalid encoding? convert characters one by one */
192 *s = 0;
194 while(*str)
196 if((l = wctomb(tmp, *str)) <= 0)
198 /* if char couldn't be converted,
199 write a question mark instead */
200 l = wctomb(tmp, L'?');
203 tmp[l] = '\0';
204 if((ptr = mpdm_poke(ptr, s, tmp, l, 1)) == NULL)
205 break;
207 str++;
210 /* null terminate and count one less */
211 if(ptr != NULL)
213 ptr = mpdm_poke(ptr, s, "", 1, 1);
214 (*s)--;
217 return(ptr);
221 mpdm_t mpdm_new_wcs(int flags, wchar_t * str, int size, int cpy)
222 /* creates a new string value from a wcs */
224 wchar_t * ptr;
226 /* a size of -1 means 'calculate it' */
227 if(size == -1 && str != NULL)
228 size = wcslen(str);
230 /* create a copy? */
231 if(cpy)
233 /* free() on destruction */
234 flags |= MPDM_FREE;
236 /* allocs */
237 if((ptr = malloc((size + 1) * sizeof(wchar_t))) == NULL)
238 return(NULL);
240 /* if no source, reset to zeroes; otherwise, copy */
241 if(str == NULL)
242 memset(ptr, '\0', size * sizeof(wchar_t));
243 else
245 wcsncpy(ptr, str, size);
246 ptr[size] = L'\0';
249 str = ptr;
252 /* it's a string */
253 flags |= MPDM_STRING;
255 return(mpdm_new(flags, str, size));
259 mpdm_t mpdm_new_mbstowcs(int flags, char * str, int l)
260 /* creates a new string value from an mbs */
262 wchar_t * ptr;
263 int size;
265 if((ptr = mpdm_mbstowcs(str, &size, l)) == NULL)
266 return(NULL);
268 /* it's a string */
269 flags |= (MPDM_STRING|MPDM_FREE);
271 return(mpdm_new(flags, ptr, size));
275 mpdm_t mpdm_new_wcstombs(int flags, wchar_t * str)
276 /* creates a new mbs value from a wbs */
278 char * ptr;
279 int size;
281 ptr = mpdm_wcstombs(str, &size);
283 flags |= MPDM_FREE;
285 /* unset the string flag; mbs,s are not 'strings' */
286 flags &= ~ MPDM_STRING;
288 return(mpdm_new(flags, ptr, size));
292 mpdm_t mpdm_new_i(int ival)
293 /* creates a new string value from an integer */
295 mpdm_t v;
296 char tmp[32];
298 /* creates the visual representation */
299 snprintf(tmp, sizeof(tmp), "%d", ival);
301 v = MPDM_MBS(tmp);
302 v->flags |= MPDM_IVAL;
303 v->ival = ival;
305 return(v);
309 mpdm_t mpdm_new_r(double rval)
310 /* creates a new string value from a real number */
312 mpdm_t v;
313 char tmp[128];
315 /* creates the visual representation */
316 snprintf(tmp, sizeof(tmp), "%lf", rval);
318 /* manually strip useless zeroes */
319 if(strchr(tmp, '.') != NULL)
321 char * ptr;
323 for(ptr = tmp + strlen(tmp) - 1;*ptr == '0';ptr--);
325 /* if it's over the ., strip it also */
326 if(*ptr != '.') ptr++;
328 *ptr = '\0';
331 v = MPDM_MBS(tmp);
332 v->flags |= MPDM_RVAL;
333 v->rval = rval;
335 return(v);
339 /* interface */
342 * mpdm_string - Returns a printable representation of a value.
343 * @v: the value
345 * Returns a printable representation of a value. For strings, it's
346 * the value data itself; for any other type, a conversion to string
347 * is returned instead. This value should be used immediately, as it
348 * can be a pointer to a static buffer.
349 * [Strings]
351 wchar_t * mpdm_string(mpdm_t v)
353 static wchar_t wtmp[32];
354 char tmp[32];
356 /* if it's NULL, return a constant */
357 if(v == NULL)
358 return(L"[NULL]");
360 /* if it's a string, return it */
361 if(v->flags & MPDM_STRING)
362 return((wchar_t *)v->data);
364 /* otherwise, return a visual representation */
365 snprintf(tmp, sizeof(tmp), "%p", v);
366 mbstowcs(wtmp, tmp, sizeof(wtmp));
367 wtmp[sizeof(wtmp) - 1] = L'\0';
369 return(wtmp);
374 * mpdm_cmp - Compares two values.
375 * @v1: the first value
376 * @v2: the second value
378 * Compares two values. If both has the MPDM_STRING flag set,
379 * a comparison using wcscmp() is returned; if both are arrays,
380 * the size is compared first and, if they have the same number
381 * elements, each one is compared; otherwise, a simple pointer
382 * comparison is done.
383 * [Strings]
385 int mpdm_cmp(mpdm_t v1, mpdm_t v2)
387 int r;
389 /* special treatment to NULL values */
390 if(v1 == NULL)
391 return(-1);
392 if(v2 == NULL)
393 return(1);
395 if(MPDM_IS_STRING(v1) && MPDM_IS_STRING(v2))
396 r = wcscoll((wchar_t *)v1->data, (wchar_t *)v2->data);
397 else
398 if(MPDM_IS_ARRAY(v1) && MPDM_IS_ARRAY(v2))
400 /* compare first the sizes */
401 if((r = mpdm_size(v1) - mpdm_size(v2)) == 0)
403 int n;
405 /* they have the same size;
406 compare each pair of elements */
407 for(n = 0;n < mpdm_size(v1);n++)
409 if((r = mpdm_cmp(mpdm_aget(v1, n),
410 mpdm_aget(v2, n))) != 0)
411 break;
415 else
416 /* in any other case, compare just pointers */
417 r = (int)(v1->data - v2->data);
419 return(r);
424 * mpdm_splice - Creates a new string value from another.
425 * @v: the original value
426 * @i: the value to be inserted
427 * @offset: offset where the substring is to be inserted
428 * @del: number of characters to delete
430 * Creates a new string value from @v, deleting @del chars at @offset
431 * and substituting them by @i. If @del is 0, no deletion is done.
432 * both @offset and @del can be negative; if this is the case, it's
433 * assumed as counting from the end of @v. If @v is NULL, @i will become
434 * the new string, and both @offset and @del will be ignored. If @v is
435 * not NULL and @i is, no insertion process is done (only deletion, if
436 * applicable).
438 * Returns a two element array, with the new string in the first
439 * element and the deleted string in the second (with a NULL value
440 * if @del is 0).
441 * [Strings]
443 mpdm_t mpdm_splice(mpdm_t v, mpdm_t i, int offset, int del)
445 mpdm_t w;
446 mpdm_t n = NULL;
447 mpdm_t d = NULL;
448 int os, ns, r;
449 int ins = 0;
450 wchar_t * ptr;
452 if(v != NULL)
454 os = mpdm_size(v);
456 /* negative offsets start from the end */
457 if(offset < 0) offset = os + 1 - offset;
459 /* never add further the end */
460 if(offset > os) offset = os;
462 /* negative del counts as 'characters left' */
463 if(del < 0) del = os + 1 - offset + del;
465 /* something to delete? */
466 if(del > 0)
468 /* never delete further the end */
469 if(offset + del > os) del = os - offset;
471 /* deleted string */
472 d = MPDM_NS(((wchar_t *) v->data) + offset, del);
474 else
475 del = 0;
477 /* something to insert? */
478 ins = mpdm_size(i);
480 /* new size and remainder */
481 ns = os + ins - del;
482 r = offset + del;
484 if((n = MPDM_NS(NULL, ns)) == NULL)
485 return(NULL);
487 ptr = n->data;
489 /* copy the beginning */
490 if(offset > 0)
492 wcsncpy(ptr, v->data, offset);
493 ptr += offset;
496 /* copy the text to be inserted */
497 if(ins > 0)
499 wcsncpy(ptr, i->data, ins);
500 ptr += ins;
503 /* copy the remaining */
504 os -= r;
505 if(os > 0)
507 wcsncpy(ptr, ((wchar_t *) v->data) + r, os);
508 ptr += os;
511 /* null terminate */
512 *ptr = L'\0';
514 else
515 n = i;
517 /* creates the output array */
518 w = MPDM_A(2);
520 mpdm_aset(w, n, 0);
521 mpdm_aset(w, d, 1);
523 return(w);
528 * mpdm_strcat - Concatenates two strings.
529 * @s1: the first string
530 * @s2: the second string
532 * Returns a new string formed by the concatenation of @s1 and @s2.
533 * [Strings]
535 mpdm_t mpdm_strcat(mpdm_t s1, mpdm_t s2)
537 wchar_t * ptr = NULL;
538 int s = 0;
540 if(s1 == NULL && s2 == NULL)
541 return(NULL);
543 ptr = mpdm_pokev(ptr, &s, s1);
544 ptr = mpdm_pokev(ptr, &s, s2);
546 /* if no characters were added, returns an empty string */
547 if(ptr == NULL)
548 return(MPDM_LS(L""));
550 ptr = mpdm_poke(ptr, &s, L"", 1, sizeof(wchar_t));
551 return(MPDM_ENS(ptr, s - 1));
556 * mpdm_ival - Returns a value's data as an integer.
557 * @v: the value
559 * Returns a value's data as an integer. If the value is a string,
560 * it's converted via sscanf and returned; non-string values have all
561 * an ival of 0. The converted integer is cached, so costly string
562 * conversions are only done once. Values created with the MPDM_IVAL
563 * flag set have its ival cached from the beginning.
564 * [Strings]
565 * [Value Management]
567 int mpdm_ival(mpdm_t v)
569 if(v == NULL)
570 return(0);
572 /* if there is no cached integer, calculate it */
573 if(!(v->flags & MPDM_IVAL))
575 int i = 0;
577 /* if it's a string, calculate it; other
578 values will have an ival of 0 */
579 if(v->flags & MPDM_STRING)
581 char tmp[32];
582 char * fmt = "%i";
584 wcstombs(tmp, (wchar_t *)v->data, sizeof(tmp));
585 tmp[sizeof(tmp) - 1]='\0';
587 /* workaround for mingw32: as it doesn't
588 correctly parse octal and hexadecimal
589 numbers, they are tried as special cases */
590 if(tmp[0] == '0')
592 if(tmp[1] == 'x' || tmp[1] == 'X')
593 fmt = "%x";
594 else
595 fmt = "%o";
598 sscanf(tmp, fmt, &i);
601 v->ival = i;
602 v->flags |= MPDM_IVAL;
605 return(v->ival);
610 * mpdm_rval - Returns a value's data as a real number (double).
611 * @v: the value
613 * Returns a value's data as a real number (double float). If the value
614 * is a string, it's converted via sscanf and returned; non-string values
615 * have all an rval of 0. The converted double is cached, so costly string
616 * conversions are only done once. Values created with the MPDM_RVAL
617 * flag set have its rval cached from the beginning.
618 * [Strings]
619 * [Value Management]
621 double mpdm_rval(mpdm_t v)
623 if(v == NULL)
624 return(0);
626 /* if there is no cached double, calculate it */
627 if(!(v->flags & MPDM_RVAL))
629 double r = 0.0;
631 /* if it's a string, calculate it; other
632 values will have an rval of 0.0 */
633 if(v->flags & MPDM_STRING)
635 char tmp[128];
636 char * prev_locale;
638 wcstombs(tmp, (wchar_t *)v->data, sizeof(tmp));
639 tmp[sizeof(tmp) - 1] = '\0';
641 /* if the number starts with 0, it's
642 an octal or hexadecimal number; just
643 take the integer value and cast it */
644 if(tmp[0] == '0' && tmp[1] != '.')
645 r = (double) mpdm_ival(v);
646 else
648 /* set locale to C for non locale-dependent
649 floating point conversion */
650 prev_locale = setlocale(LC_NUMERIC, "C");
652 /* read */
653 sscanf(tmp, "%lf", &r);
655 /* set previous locale */
656 setlocale(LC_NUMERIC, prev_locale);
660 v->rval = r;
661 v->flags |= MPDM_RVAL;
664 return(v->rval);
669 * mpdm_gettext - Translates a string to the current language.
670 * @str: the string
672 * Translates the @str string to the current language.
674 * This function can still be used even if there is no real gettext
675 * support() by manually filling the __I18N__ hash.
677 * If the string is found in the current table, the translation is
678 * returned; otherwise, the same @str value is returned.
679 * [Strings]
680 * [Localization]
682 mpdm_t mpdm_gettext(mpdm_t str)
684 mpdm_t v;
685 mpdm_t i18n = NULL;
687 /* gets the cache, if any */
688 if((i18n = mpdm_hget_s(mpdm_root(), L"__I18N__")) == NULL)
689 return(str);
691 /* try first the cache */
692 if((v = mpdm_hget(i18n, str)) == NULL)
694 #ifdef CONFOPT_GETTEXT
695 char * s;
697 /* convert to mbs */
698 v = MPDM_2MBS(str->data);
700 /* ask gettext for it */
701 s = gettext((char *)v->data);
703 /* create new value only if it's different */
704 if(s != v->data)
706 v = MPDM_MBS(s);
708 /* store in the cache */
709 mpdm_hset(i18n, str, v);
711 else
713 #endif /* CONFOPT_GETTEXT */
715 v = str;
718 return(v);
723 * mpdm_gettext_domain - Sets domain and data directory for translations.
724 * @dom: the domain (application name)
725 * @data: directory contaning the .mo files
727 * Sets the domain (application name) and translation data for translating
728 * strings that will be returned by mpdm_gettext().@data must point to a
729 * directory containing the .mo (compiled .po) files.
731 * If there is no gettext support, returns 0, or 1 otherwise.
732 * [Strings]
733 * [Localization]
735 int mpdm_gettext_domain(mpdm_t dom, mpdm_t data)
737 int ret = 0;
739 #ifdef CONFOPT_GETTEXT
741 /* convert both to mbs,s */
742 dom = MPDM_2MBS(dom->data);
743 data = MPDM_2MBS(data->data);
745 /* bind and set domain */
746 bindtextdomain((char *)dom->data, (char *)data->data);
747 textdomain((char *)dom->data);
749 mpdm_hset_s(mpdm_root(), L"__I18N__", MPDM_H(0));
751 ret = 1;
753 #endif /* CONFOPT_GETTEXT */
755 return(ret);
759 #ifdef CONFOPT_WCWIDTH
761 int wcwidth(wchar_t);
763 int mpdm_wcwidth(wchar_t c) { return(wcwidth(c)); }
765 #else /* CONFOPT_WCWIDTH */
767 #include "wcwidth.c"
769 int mpdm_wcwidth(wchar_t c) { return(mk_wcwidth(c)); }
771 #endif /* CONFOPT_WCWIDTH */
775 * mpdm_sprintf - Formats a sprintf()-like string
776 * @fmt: the string format
777 * @args: an array of values
779 * Formats a string using the sprintf() format taking the values from @args.
780 * [Strings]
782 mpdm_t mpdm_sprintf(mpdm_t fmt, mpdm_t args)
784 wchar_t * i = fmt->data;
785 wchar_t * o = NULL;
786 int l = 0, n = 0;
787 wchar_t c;
789 /* loop all characters */
790 while((c = *i++) != L'\0')
792 int m = 0;
793 wchar_t * tptr = NULL;
794 wchar_t * wptr = NULL;
796 if(c == L'%')
798 /* format directive */
799 char t_fmt[128];
800 char tmp[1024];
801 mpdm_t v;
802 char * ptr = NULL;
804 /* transfer the % */
805 t_fmt[m++] = '%';
807 /* transform the format to mbs */
808 while(*i != L'\0' && m < sizeof(t_fmt) - MB_CUR_MAX - 1 &&
809 wcschr(L"-.0123456789", *i) != NULL)
810 m += wctomb(&t_fmt[m], *i++);
812 /* transfer the directive */
813 m += wctomb(&t_fmt[m], *i++);
815 t_fmt[m] = '\0';
817 /* by default, copies the format */
818 strcpy(tmp, t_fmt);
820 /* any values left? */
821 if(n < mpdm_size(args) && (v = mpdm_aget(args, n++)) != NULL)
823 switch(t_fmt[m - 1])
825 case 'd':
826 case 'i':
827 case 'x':
828 case 'X':
829 case 'o':
831 /* integer value */
832 snprintf(tmp, sizeof(tmp) - 1, t_fmt, mpdm_ival(v));
833 break;
835 case 'f':
837 /* float (real) value */
838 snprintf(tmp, sizeof(tmp) - 1, t_fmt, mpdm_rval(v));
839 break;
841 case 's':
843 /* string value */
844 ptr = mpdm_wcstombs(mpdm_string(v), NULL);
845 snprintf(tmp, sizeof(tmp) - 1, t_fmt, ptr);
846 free(ptr);
848 break;
850 case 'c':
852 /* char */
853 m = 1;
854 wptr = &c;
855 c = mpdm_ival(v);
856 break;
860 /* transfer */
861 if(wptr == NULL)
862 wptr = tptr = mpdm_mbstowcs(tmp, &m, -1);
864 else
866 /* raw character */
867 m = 1;
868 wptr = &c;
871 /* transfer */
872 o = mpdm_poke(o, &l, wptr, m, sizeof(wchar_t));
874 /* free the temporary buffer, if any */
875 if(tptr != NULL) free(tptr);
878 if(o == NULL)
879 return(NULL);
881 /* null-terminate */
882 o = mpdm_poke(o, &l, L"", 1, sizeof(wchar_t));
884 return(MPDM_ENS(o, l - 1));
889 * mpdm_ulc - Converts a string to uppercase or lowecase
890 * @s: the string
891 * @u: convert to uppercase (1) or to lowercase (0).
893 * Converts @s to uppercase (for @u == 1) or to lowercase (@u == 0).
894 * [Strings]
896 mpdm_t mpdm_ulc(mpdm_t s, int u)
898 mpdm_t r = NULL;
899 wchar_t * optr;
900 int i = mpdm_size(s);
902 if((optr = malloc((i + 1) * sizeof(wchar_t))) != NULL)
904 wchar_t * iptr = mpdm_string(s);
905 int n;
907 for(n = 0;n < i;n++)
908 optr[n] = u ? towupper(iptr[n]) : towlower(iptr[n]);
910 optr[n] = L'\0';
911 r = MPDM_ENS(optr, i);
914 return(r);