Added AUTHORS and COPYING to the doc installation.
[mpdm.git] / mpdm_s.c
blob17458ea1e9cd926b94376ba451167dc859d95f5f
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) {
54 /* makes room for the new string */
55 if ((dst = realloc(dst, (*dsize + osize) * esize)) != NULL) {
56 /* copies it */
57 memcpy(dst + (*dsize * esize), org, osize * esize);
59 /* adds to final size */
60 *dsize += osize;
64 return dst;
68 wchar_t *mpdm_pokev(wchar_t * dst, int *dsize, mpdm_t v)
69 /* adds the string in v to dst using mpdm_poke() */
71 if (v != NULL) {
72 wchar_t *ptr = mpdm_string(v);
74 dst = mpdm_poke(dst, dsize, ptr, wcslen(ptr), sizeof(wchar_t));
77 return dst;
81 wchar_t *mpdm_mbstowcs(char *str, int *s, int l)
82 /* converts an mbs to a wcs, but filling invalid chars
83 with question marks instead of just failing */
85 wchar_t *ptr = NULL;
86 char tmp[64]; /* really MB_CUR_MAX + 1 */
87 wchar_t wc;
88 int n, i, c, t = 0;
89 char *cstr;
91 /* allow NULL values for s */
92 if (s == NULL)
93 s = &t;
95 /* if there is a limit, duplicate and break the string */
96 if (l >= 0) {
97 cstr = strdup(str);
98 cstr[l] = '\0';
100 else
101 cstr = str;
103 /* try first a direct conversion with mbstowcs */
104 if ((*s = mbstowcs(NULL, cstr, 0)) != -1) {
105 /* direct conversion is possible; do it */
106 if ((ptr = malloc((*s + 1) * sizeof(wchar_t))) != NULL) {
107 mbstowcs(ptr, cstr, *s);
108 ptr[*s] = L'\0';
111 else {
112 /* zero everything */
113 *s = n = i = 0;
115 for (;;) {
116 /* no more characters to process? */
117 if ((c = cstr[n + i]) == '\0' && i == 0)
118 break;
120 tmp[i++] = c;
121 tmp[i] = '\0';
123 /* try to convert */
124 if (mbstowcs(&wc, tmp, 1) == -1) {
125 /* can still be an incomplete multibyte char? */
126 if (c != '\0' && i <= MB_CUR_MAX)
127 continue;
128 else {
129 /* too many failing bytes; skip 1 byte */
130 wc = L'?';
131 i = 1;
135 /* skip used bytes and back again */
136 n += i;
137 i = 0;
139 /* store new char */
140 if ((ptr = mpdm_poke(ptr, s, &wc, 1, sizeof(wchar_t))) == NULL)
141 break;
144 /* null terminate and count one less */
145 if (ptr != NULL) {
146 ptr = mpdm_poke(ptr, s, L"", 1, sizeof(wchar_t));
147 (*s)--;
151 /* free the duplicate */
152 if (cstr != str)
153 free(cstr);
155 return ptr;
159 char *mpdm_wcstombs(wchar_t * str, int *s)
160 /* converts a wcs to an mbs, but filling invalid chars
161 with question marks instead of just failing */
163 char *ptr = NULL;
164 char tmp[64]; /* really MB_CUR_MAX + 1 */
165 int l, t = 0;
167 /* allow NULL values for s */
168 if (s == NULL)
169 s = &t;
171 /* try first a direct conversion with wcstombs */
172 if ((*s = wcstombs(NULL, str, 0)) != -1) {
173 /* direct conversion is possible; do it and return */
174 if ((ptr = malloc(*s + 1)) != NULL) {
175 wcstombs(ptr, str, *s);
176 ptr[*s] = '\0';
179 return ptr;
182 /* invalid encoding? convert characters one by one */
183 *s = 0;
185 while (*str) {
186 if ((l = wctomb(tmp, *str)) <= 0) {
187 /* if char couldn't be converted,
188 write a question mark instead */
189 l = wctomb(tmp, L'?');
192 tmp[l] = '\0';
193 if ((ptr = mpdm_poke(ptr, s, tmp, l, 1)) == NULL)
194 break;
196 str++;
199 /* null terminate and count one less */
200 if (ptr != NULL) {
201 ptr = mpdm_poke(ptr, s, "", 1, 1);
202 (*s)--;
205 return ptr;
209 mpdm_t mpdm_new_wcs(int flags, wchar_t * str, int size, int cpy)
210 /* creates a new string value from a wcs */
212 wchar_t *ptr;
214 /* a size of -1 means 'calculate it' */
215 if (size == -1 && str != NULL)
216 size = wcslen(str);
218 /* create a copy? */
219 if (cpy) {
220 /* free() on destruction */
221 flags |= MPDM_FREE;
223 /* allocs */
224 if ((ptr = malloc((size + 1) * sizeof(wchar_t))) == NULL)
225 return NULL;
227 /* if no source, reset to zeroes; otherwise, copy */
228 if (str == NULL)
229 memset(ptr, '\0', size * sizeof(wchar_t));
230 else {
231 wcsncpy(ptr, str, size);
232 ptr[size] = L'\0';
235 str = ptr;
238 /* it's a string */
239 flags |= MPDM_STRING;
241 return mpdm_new(flags, str, size);
245 mpdm_t mpdm_new_mbstowcs(int flags, char *str, int l)
246 /* creates a new string value from an mbs */
248 wchar_t *ptr;
249 int size;
251 if ((ptr = mpdm_mbstowcs(str, &size, l)) == NULL)
252 return NULL;
254 /* it's a string */
255 flags |= (MPDM_STRING | MPDM_FREE);
257 return mpdm_new(flags, ptr, size);
261 mpdm_t mpdm_new_wcstombs(int flags, wchar_t * str)
262 /* creates a new mbs value from a wbs */
264 char *ptr;
265 int size;
267 ptr = mpdm_wcstombs(str, &size);
269 flags |= MPDM_FREE;
271 /* unset the string flag; mbs,s are not 'strings' */
272 flags &= ~MPDM_STRING;
274 return mpdm_new(flags, ptr, size);
278 mpdm_t mpdm_new_i(int ival)
279 /* creates a new string value from an integer */
281 mpdm_t v;
282 char tmp[32];
284 /* creates the visual representation */
285 snprintf(tmp, sizeof(tmp), "%d", ival);
287 v = MPDM_MBS(tmp);
289 return mpdm_set_ival(v, ival);
293 mpdm_t mpdm_new_r(double rval)
294 /* creates a new string value from a real number */
296 mpdm_t v;
297 char tmp[128];
299 /* creates the visual representation */
300 snprintf(tmp, sizeof(tmp), "%lf", rval);
302 /* manually strip useless zeroes */
303 if (strchr(tmp, '.') != NULL) {
304 char *ptr;
306 for (ptr = tmp + strlen(tmp) - 1; *ptr == '0'; ptr--);
308 /* if it's over the ., strip it also */
309 if (*ptr != '.')
310 ptr++;
312 *ptr = '\0';
315 v = MPDM_MBS(tmp);
317 return mpdm_set_rval(v, rval);
321 /* interface */
324 * mpdm_string - Returns a printable representation of a value.
325 * @v: the value
327 * Returns a printable representation of a value. For strings, it's
328 * the value data itself; for any other type, a conversion to string
329 * is returned instead. This value should be used immediately, as it
330 * can be a pointer to a static buffer.
331 * [Strings]
333 wchar_t *mpdm_string(mpdm_t v)
335 static wchar_t wtmp[32];
336 char tmp[32];
338 /* if it's NULL, return a constant */
339 if (v == NULL)
340 return L"[NULL]";
342 /* if it's a string, return it */
343 if (v->flags & MPDM_STRING)
344 return (wchar_t *) v->data;
346 /* otherwise, return a visual representation */
347 snprintf(tmp, sizeof(tmp), "%p", v);
348 mbstowcs(wtmp, tmp, sizeof(wtmp));
349 wtmp[sizeof(wtmp) - 1] = L'\0';
351 return wtmp;
356 * mpdm_cmp - Compares two values.
357 * @v1: the first value
358 * @v2: the second value
360 * Compares two values. If both has the MPDM_STRING flag set,
361 * a comparison using wcscmp() is returned; if both are arrays,
362 * the size is compared first and, if they have the same number
363 * elements, each one is compared; otherwise, a simple pointer
364 * comparison is done.
365 * [Strings]
367 int mpdm_cmp(mpdm_t v1, mpdm_t v2)
369 int r;
371 /* same values? */
372 if (v1 == v2)
373 return 0;
375 /* is any value NULL? */
376 if (v1 == NULL)
377 return -1;
378 if (v2 == NULL)
379 return 1;
381 /* different values, but same content? (unlikely) */
382 if (v1->data == v2->data)
383 return 0;
385 if (MPDM_IS_STRING(v1) && MPDM_IS_STRING(v2))
386 r = wcscoll((wchar_t *) v1->data, (wchar_t *) v2->data);
387 else
388 if (MPDM_IS_ARRAY(v1) && MPDM_IS_ARRAY(v2)) {
389 /* compare first the sizes */
390 if ((r = mpdm_size(v1) - mpdm_size(v2)) == 0) {
391 int n;
393 /* they have the same size;
394 compare each pair of elements */
395 for (n = 0; n < mpdm_size(v1); n++) {
396 if ((r = mpdm_cmp(mpdm_aget(v1, n),
397 mpdm_aget(v2, n))) != 0)
398 break;
402 else
403 /* in any other case, compare just pointers */
404 r = (int) (v1->data - v2->data);
406 return r;
411 * mpdm_splice - Creates a new string value from another.
412 * @v: the original value
413 * @i: the value to be inserted
414 * @offset: offset where the substring is to be inserted
415 * @del: number of characters to delete
417 * Creates a new string value from @v, deleting @del chars at @offset
418 * and substituting them by @i. If @del is 0, no deletion is done.
419 * both @offset and @del can be negative; if this is the case, it's
420 * assumed as counting from the end of @v. If @v is NULL, @i will become
421 * the new string, and both @offset and @del will be ignored. If @v is
422 * not NULL and @i is, no insertion process is done (only deletion, if
423 * applicable).
425 * Returns a two element array, with the new string in the first
426 * element and the deleted string in the second (with a NULL value
427 * if @del is 0).
428 * [Strings]
430 mpdm_t mpdm_splice(mpdm_t v, mpdm_t i, int offset, int del)
432 mpdm_t w;
433 mpdm_t n = NULL;
434 mpdm_t d = NULL;
435 int os, ns, r;
436 int ins = 0;
437 wchar_t *ptr;
439 if (v != NULL) {
440 os = mpdm_size(v);
442 /* negative offsets start from the end */
443 if (offset < 0)
444 offset = os + 1 - offset;
446 /* never add further the end */
447 if (offset > os)
448 offset = os;
450 /* negative del counts as 'characters left' */
451 if (del < 0)
452 del = os + 1 - offset + del;
454 /* something to delete? */
455 if (del > 0) {
456 /* never delete further the end */
457 if (offset + del > os)
458 del = os - offset;
460 /* deleted string */
461 d = MPDM_NS(((wchar_t *) v->data) + offset, del);
463 else
464 del = 0;
466 /* something to insert? */
467 ins = mpdm_size(i);
469 /* new size and remainder */
470 ns = os + ins - del;
471 r = offset + del;
473 if ((n = MPDM_NS(NULL, ns)) == NULL)
474 return NULL;
476 ptr = n->data;
478 /* copy the beginning */
479 if (offset > 0) {
480 wcsncpy(ptr, v->data, offset);
481 ptr += offset;
484 /* copy the text to be inserted */
485 if (ins > 0) {
486 wcsncpy(ptr, i->data, ins);
487 ptr += ins;
490 /* copy the remaining */
491 os -= r;
492 if (os > 0) {
493 wcsncpy(ptr, ((wchar_t *) v->data) + r, os);
494 ptr += os;
497 /* null terminate */
498 *ptr = L'\0';
500 else
501 n = i;
503 /* creates the output array */
504 w = MPDM_A(2);
506 mpdm_aset(w, n, 0);
507 mpdm_aset(w, d, 1);
509 return w;
514 * mpdm_strcat - Concatenates two strings.
515 * @s1: the first string
516 * @s2: the second string
518 * Returns a new string formed by the concatenation of @s1 and @s2.
519 * [Strings]
521 mpdm_t mpdm_strcat(mpdm_t s1, mpdm_t s2)
523 wchar_t *ptr = NULL;
524 int s = 0;
526 if (s1 == NULL && s2 == NULL)
527 return NULL;
529 ptr = mpdm_pokev(ptr, &s, s1);
530 ptr = mpdm_pokev(ptr, &s, s2);
532 /* if no characters were added, returns an empty string */
533 if (ptr == NULL)
534 return MPDM_LS(L"");
536 ptr = mpdm_poke(ptr, &s, L"", 1, sizeof(wchar_t));
537 return MPDM_ENS(ptr, s - 1);
542 * mpdm_ival - Returns a value's data as an integer.
543 * @v: the value
545 * Returns a value's data as an integer. If the value is a string,
546 * it's converted via sscanf and returned; non-string values have all
547 * an ival of 0. The converted integer is cached, so costly string
548 * conversions are only done once. Values created with the MPDM_IVAL
549 * flag set have its ival cached from the beginning.
550 * [Strings]
551 * [Value Management]
553 int mpdm_ival(mpdm_t v)
555 if (v == NULL)
556 return 0;
558 /* if there is no cached integer, calculate it */
559 if (!(v->flags & MPDM_IVAL)) {
560 int i = 0;
562 /* if it's a string, calculate it; other
563 values will have an ival of 0 */
564 if (v->flags & MPDM_STRING) {
565 char tmp[32];
566 char *fmt = "%i";
568 wcstombs(tmp, (wchar_t *) v->data, sizeof(tmp));
569 tmp[sizeof(tmp) - 1] = '\0';
571 /* workaround for mingw32: as it doesn't
572 correctly parse octal and hexadecimal
573 numbers, they are tried as special cases */
574 if (tmp[0] == '0') {
575 if (tmp[1] == 'x' || tmp[1] == 'X')
576 fmt = "%x";
577 else
578 fmt = "%o";
581 sscanf(tmp, fmt, &i);
584 mpdm_set_ival(v, i);
587 return v->ival;
592 * mpdm_rval - Returns a value's data as a real number (double).
593 * @v: the value
595 * Returns a value's data as a real number (double float). If the value
596 * is a string, it's converted via sscanf and returned; non-string values
597 * have all an rval of 0. The converted double is cached, so costly string
598 * conversions are only done once. Values created with the MPDM_RVAL
599 * flag set have its rval cached from the beginning.
600 * [Strings]
601 * [Value Management]
603 double mpdm_rval(mpdm_t v)
605 if (v == NULL)
606 return 0;
608 /* if there is no cached double, calculate it */
609 if (!(v->flags & MPDM_RVAL)) {
610 double r = 0.0;
612 /* if it's a string, calculate it; other
613 values will have an rval of 0.0 */
614 if (v->flags & MPDM_STRING) {
615 char tmp[128];
616 char *prev_locale;
618 wcstombs(tmp, (wchar_t *) v->data, sizeof(tmp));
619 tmp[sizeof(tmp) - 1] = '\0';
621 /* if the number starts with 0, it's
622 an octal or hexadecimal number; just
623 take the integer value and cast it */
624 if (tmp[0] == '0' && tmp[1] != '.')
625 r = (double) mpdm_ival(v);
626 else {
627 /* set locale to C for non locale-dependent
628 floating point conversion */
629 prev_locale = setlocale(LC_NUMERIC, "C");
631 /* read */
632 sscanf(tmp, "%lf", &r);
634 /* set previous locale */
635 setlocale(LC_NUMERIC, prev_locale);
639 mpdm_set_rval(v, r);
642 return v->rval;
647 * mpdm_gettext - Translates a string to the current language.
648 * @str: the string
650 * Translates the @str string to the current language.
652 * This function can still be used even if there is no real gettext
653 * support() by manually filling the __I18N__ hash.
655 * If the string is found in the current table, the translation is
656 * returned; otherwise, the same @str value is returned.
657 * [Strings]
658 * [Localization]
660 mpdm_t mpdm_gettext(mpdm_t str)
662 mpdm_t v;
663 mpdm_t i18n = NULL;
665 /* gets the cache, if any */
666 if ((i18n = mpdm_hget_s(mpdm_root(), L"__I18N__")) == NULL)
667 return str;
669 /* try first the cache */
670 if ((v = mpdm_hget(i18n, str)) == NULL) {
671 #ifdef CONFOPT_GETTEXT
672 char *s;
674 /* convert to mbs */
675 v = MPDM_2MBS(str->data);
677 /* ask gettext for it */
678 s = gettext((char *) v->data);
680 /* create new value only if it's different */
681 if (s != v->data) {
682 v = MPDM_MBS(s);
684 /* store in the cache */
685 mpdm_hset(i18n, str, v);
687 else
688 #endif /* CONFOPT_GETTEXT */
690 v = str;
693 return v;
698 * mpdm_gettext_domain - Sets domain and data directory for translations.
699 * @dom: the domain (application name)
700 * @data: directory contaning the .mo files
702 * Sets the domain (application name) and translation data for translating
703 * strings that will be returned by mpdm_gettext().@data must point to a
704 * directory containing the .mo (compiled .po) files.
706 * If there is no gettext support, returns 0, or 1 otherwise.
707 * [Strings]
708 * [Localization]
710 int mpdm_gettext_domain(mpdm_t dom, mpdm_t data)
712 int ret = 0;
714 #ifdef CONFOPT_GETTEXT
716 /* convert both to mbs,s */
717 dom = MPDM_2MBS(dom->data);
718 data = MPDM_2MBS(data->data);
720 /* bind and set domain */
721 bindtextdomain((char *) dom->data, (char *) data->data);
722 textdomain((char *) dom->data);
724 mpdm_hset_s(mpdm_root(), L"__I18N__", MPDM_H(0));
726 ret = 1;
728 #endif /* CONFOPT_GETTEXT */
730 return ret;
734 #ifdef CONFOPT_WCWIDTH
736 int wcwidth(wchar_t);
738 int mpdm_wcwidth(wchar_t c)
740 return wcwidth(c);
743 #else /* CONFOPT_WCWIDTH */
745 #include "wcwidth.c"
747 int mpdm_wcwidth(wchar_t c)
749 return mk_wcwidth(c);
752 #endif /* CONFOPT_WCWIDTH */
756 * mpdm_sprintf - Formats a sprintf()-like string
757 * @fmt: the string format
758 * @args: an array of values
760 * Formats a string using the sprintf() format taking the values from @args.
761 * [Strings]
763 mpdm_t mpdm_sprintf(mpdm_t fmt, mpdm_t args)
765 wchar_t *i = fmt->data;
766 wchar_t *o = NULL;
767 int l = 0, n = 0;
768 wchar_t c;
770 /* loop all characters */
771 while ((c = *i++) != L'\0') {
772 int m = 0;
773 wchar_t *tptr = NULL;
774 wchar_t *wptr = NULL;
776 if (c == L'%') {
777 /* format directive */
778 char t_fmt[128];
779 char tmp[1024];
780 mpdm_t v;
781 char *ptr = NULL;
783 /* transfer the % */
784 t_fmt[m++] = '%';
786 /* transform the format to mbs */
787 while (*i != L'\0' && m < sizeof(t_fmt) - MB_CUR_MAX - 1 &&
788 wcschr(L"-.0123456789", *i) != NULL)
789 m += wctomb(&t_fmt[m], *i++);
791 /* transfer the directive */
792 m += wctomb(&t_fmt[m], *i++);
794 t_fmt[m] = '\0';
796 /* by default, copies the format */
797 strcpy(tmp, t_fmt);
799 /* any values left? */
800 if (n < mpdm_size(args) && (v = mpdm_aget(args, n++)) != NULL) {
801 switch (t_fmt[m - 1]) {
802 case 'd':
803 case 'i':
804 case 'x':
805 case 'X':
806 case 'o':
808 /* integer value */
809 snprintf(tmp, sizeof(tmp) - 1,
810 t_fmt, mpdm_ival(v));
811 break;
813 case 'f':
815 /* float (real) value */
816 snprintf(tmp, sizeof(tmp) - 1,
817 t_fmt, mpdm_rval(v));
818 break;
820 case 's':
822 /* string value */
823 ptr = mpdm_wcstombs(mpdm_string(v), NULL);
824 snprintf(tmp, sizeof(tmp) - 1, t_fmt, ptr);
825 free(ptr);
827 break;
829 case 'c':
831 /* char */
832 m = 1;
833 wptr = &c;
834 c = mpdm_ival(v);
835 break;
839 /* transfer */
840 if (wptr == NULL)
841 wptr = tptr = mpdm_mbstowcs(tmp, &m, -1);
843 else {
844 /* raw character */
845 m = 1;
846 wptr = &c;
849 /* transfer */
850 o = mpdm_poke(o, &l, wptr, m, sizeof(wchar_t));
852 /* free the temporary buffer, if any */
853 if (tptr != NULL)
854 free(tptr);
857 if (o == NULL)
858 return NULL;
860 /* null-terminate */
861 o = mpdm_poke(o, &l, L"", 1, sizeof(wchar_t));
863 return MPDM_ENS(o, l - 1);
868 * mpdm_ulc - Converts a string to uppercase or lowecase
869 * @s: the string
870 * @u: convert to uppercase (1) or to lowercase (0).
872 * Converts @s to uppercase (for @u == 1) or to lowercase (@u == 0).
873 * [Strings]
875 mpdm_t mpdm_ulc(mpdm_t s, int u)
877 mpdm_t r = NULL;
878 wchar_t *optr;
879 int i = mpdm_size(s);
881 if ((optr = malloc((i + 1) * sizeof(wchar_t))) != NULL) {
882 wchar_t *iptr = mpdm_string(s);
883 int n;
885 for (n = 0; n < i; n++)
886 optr[n] = u ? towupper(iptr[n]) : towlower(iptr[n]);
888 optr[n] = L'\0';
889 r = MPDM_ENS(optr, i);
892 return r;