SmartPDF - lightweight pdf viewer app for rosapps
[reactos.git] / rosapps / smartpdf / baseutils / str_util.c
1 /* Written by Krzysztof Kowalczyk (http://blog.kowalczyk.info)
2 The author disclaims copyright to this source code. */
3
4 /* The most basic things, including string handling functions */
5 #include "base_util.h"
6 #include "str_util.h"
7 #include "str_strsafe.h"
8
9 /* TODO: should probably be based on MSVC version */
10 #if defined(__GNUC__) || !defined(_WIN32) || (_MSC_VER < 1400)
11 void strcpy_s(char *dst, size_t dstLen, const char *src)
12 {
13 size_t toCopy;
14
15 assert(dst);
16 assert(src);
17 assert(dstLen > 0);
18
19 if (!dst || !src || dstLen <= 0)
20 return;
21
22 toCopy = strlen(src);
23 if (toCopy > (dstLen-1))
24 toCopy = dstLen - 1;
25
26 strncpy(dst, src, toCopy);
27 dst[toCopy] = 0;
28 }
29 #endif
30
31 void no_op(void)
32 {
33 /* This really is a no-op, just to silence the compiler */
34 }
35
36 int char_is_ws_or_zero(char c)
37 {
38 switch (c) {
39 case ' ':
40 case '\t':
41 case '\r':
42 case '\n':
43 case 0:
44 return TRUE;
45 }
46 return FALSE;
47 }
48
49 int char_is_ws(char c)
50 {
51 switch (c) {
52 case ' ':
53 case '\t':
54 case '\r':
55 case '\n':
56 return TRUE;
57 }
58 return FALSE;
59 }
60
61 int char_is_digit(char c)
62 {
63 if ((c >= '0') && (c <= '9'))
64 return TRUE;
65 return FALSE;
66 }
67
68 /* Concatenate 4 strings. Any string can be NULL.
69 Caller needs to free() memory. */
70 char *str_cat4(const char *str1, const char *str2, const char *str3, const char *str4)
71 {
72 char *str;
73 char *tmp;
74 size_t str1_len = 0;
75 size_t str2_len = 0;
76 size_t str3_len = 0;
77 size_t str4_len = 0;
78
79 if (str1)
80 str1_len = strlen(str1);
81 if (str2)
82 str2_len = strlen(str2);
83 if (str3)
84 str3_len = strlen(str3);
85 if (str4)
86 str4_len = strlen(str4);
87
88 str = (char*)zmalloc(str1_len + str2_len + str3_len + str4_len + 1);
89 if (!str)
90 return NULL;
91
92 tmp = str;
93 if (str1) {
94 memcpy(tmp, str1, str1_len);
95 tmp += str1_len;
96 }
97 if (str2) {
98 memcpy(tmp, str2, str2_len);
99 tmp += str2_len;
100 }
101 if (str3) {
102 memcpy(tmp, str3, str3_len);
103 tmp += str3_len;
104 }
105 if (str4) {
106 memcpy(tmp, str4, str1_len);
107 }
108 return str;
109 }
110
111 /* Concatenate 3 strings. Any string can be NULL.
112 Caller needs to free() memory. */
113 char *str_cat3(const char *str1, const char *str2, const char *str3)
114 {
115 return str_cat4(str1, str2, str3, NULL);
116 }
117
118 /* Concatenate 2 strings. Any string can be NULL.
119 Caller needs to free() memory. */
120 char *str_cat(const char *str1, const char *str2)
121 {
122 return str_cat4(str1, str2, NULL, NULL);
123 }
124
125 char *str_dup(const char *str)
126 {
127 return str_cat4(str, NULL, NULL, NULL);
128 }
129
130 char *str_dupn(const char *str, size_t str_len_cch)
131 {
132 char *copy;
133
134 if (!str)
135 return NULL;
136 copy = (char*)malloc(str_len_cch+1);
137 if (!copy)
138 return NULL;
139 memcpy(copy, str, str_len_cch);
140 copy[str_len_cch] = 0;
141 return copy;
142 }
143
144 int str_copyn(char *dst, size_t dst_cch_size, const char *src, size_t src_cch_size)
145 {
146 char *end = dst + dst_cch_size - 1;
147 if (0 == dst_cch_size) {
148 if (0 == src_cch_size)
149 return TRUE;
150 else
151 return FALSE;
152 }
153
154 while ((dst < end) && (src_cch_size > 0)) {
155 *dst++ = *src++;
156 --src_cch_size;
157 }
158 *dst = 0;
159 if (0 == src_cch_size)
160 return TRUE;
161 else
162 return FALSE;
163 }
164
165 int str_copy(char *dst, size_t dst_cch_size, const char *src)
166 {
167 char *end = dst + dst_cch_size - 1;
168 if (0 == dst_cch_size)
169 return FALSE;
170
171 while ((dst < end) && *src) {
172 *dst++ = *src++;
173 }
174 *dst = 0;
175 if (0 == *src)
176 return TRUE;
177 else
178 return FALSE;
179 }
180
181 int str_eq(const char *str1, const char *str2)
182 {
183 if (!str1 && !str2)
184 return TRUE;
185 if (!str1 || !str2)
186 return FALSE;
187 if (0 == strcmp(str1, str2))
188 return TRUE;
189 return FALSE;
190 }
191
192 int str_ieq(const char *str1, const char *str2)
193 {
194 if (!str1 && !str2)
195 return TRUE;
196 if (!str1 || !str2)
197 return FALSE;
198 if (0 == _stricmp(str1, str2))
199 return TRUE;
200 return FALSE;
201 }
202
203 int str_eqn(const char *str1, const char *str2, int len)
204 {
205 if (!str1 && !str2)
206 return TRUE;
207 if (!str1 || !str2)
208 return FALSE;
209 if (0 == strncmp(str1, str2, len))
210 return TRUE;
211 return FALSE;
212 }
213
214 /* return true if 'str' starts with 'txt', case-sensitive */
215 int str_startswith(const char *str, const char *txt)
216 {
217 if (!str && !txt)
218 return TRUE;
219 if (!str || !txt)
220 return FALSE;
221
222 if (0 == strncmp(str, txt, strlen(txt)))
223 return TRUE;
224 return FALSE;
225 }
226
227 /* return true if 'str' starts with 'txt', NOT case-sensitive */
228 int str_startswithi(const char *str, const char *txt)
229 {
230 if (!str && !txt)
231 return TRUE;
232 if (!str || !txt)
233 return FALSE;
234
235 if (0 == _strnicmp(str, txt, strlen(txt)))
236 return TRUE;
237 return FALSE;
238 }
239
240 int str_endswith(const char *txt, const char *end)
241 {
242 size_t end_len;
243 size_t txt_len;
244
245 if (!txt || !end)
246 return FALSE;
247
248 txt_len = strlen(txt);
249 end_len = strlen(end);
250 if (end_len > txt_len)
251 return FALSE;
252 if (str_eq(txt+txt_len-end_len, end))
253 return TRUE;
254 return FALSE;
255 }
256
257 int str_endswithi(const char *txt, const char *end)
258 {
259 size_t end_len;
260 size_t txt_len;
261
262 if (!txt || !end)
263 return FALSE;
264
265 txt_len = strlen(txt);
266 end_len = strlen(end);
267 if (end_len > txt_len)
268 return FALSE;
269 if (str_ieq(txt+txt_len-end_len, end))
270 return TRUE;
271 return FALSE;
272 }
273
274 int str_endswith_char(const char *str, char c)
275 {
276 char end[2];
277 end[0] = c;
278 end[1] = 0;
279 return str_endswith(str, end);
280 }
281
282 int str_empty(const char *str)
283 {
284 if (!str)
285 return TRUE;
286 if (0 == *str)
287 return TRUE;
288 return FALSE;
289 }
290
291 /* Find character 'c' in string 'txt'.
292 Return pointer to this character or NULL if not found */
293 const char *str_find_char(const char *txt, char c)
294 {
295 while (*txt != c) {
296 if (0 == *txt)
297 return NULL;
298 ++txt;
299 }
300 return txt;
301 }
302
303 /* split a string '*txt' at the border character 'c'. Something like python's
304 string.split() except called iteratively.
305 Returns a copy of the string (must be free()d by the caller).
306 Returns NULL to indicate there's no more items. */
307 char *str_split_iter(char **txt, char c)
308 {
309 const char *tmp;
310 const char *pos;
311 char *result;
312
313 tmp = (const char*)*txt;
314 if (!tmp)
315 return NULL;
316
317 pos = str_find_char(tmp, c);
318 if (pos) {
319 result = str_dupn(tmp, (int)(pos-tmp));
320 *txt = (char*)pos+1;
321 } else {
322 result = str_dup(tmp);
323 *txt = NULL; /* next iteration will return NULL */
324 }
325 return result;
326 }
327
328 /* Replace all posible versions (Unix, Windows, Mac) of newline character
329 with 'replace'. Returns newly allocated string with normalized newlines
330 or NULL if error.
331 Caller needs to free() the result */
332 char *str_normalize_newline(const char *txt, const char *replace)
333 {
334 size_t replace_len;
335 char c;
336 char * result;
337 const char * tmp;
338 char * tmp_out;
339 size_t result_len = 0;
340
341 replace_len = strlen(replace);
342 tmp = txt;
343 for (;;) {
344 c = *tmp++;
345 if (!c)
346 break;
347 if (0xa == c) {
348 /* a single 0xa => Unix */
349 result_len += replace_len;
350 } else if (0xd == c) {
351 if (0xa == *tmp) {
352 /* 0xd 0xa => dos */
353 result_len += replace_len;
354 ++tmp;
355 }
356 else {
357 /* just 0xd => Mac */
358 result_len += replace_len;
359 }
360 } else
361 ++result_len;
362 }
363
364 if (0 == result_len)
365 return NULL;
366
367 result = (char*)malloc(result_len+1);
368 if (!result)
369 return NULL;
370 tmp_out = result;
371 for (;;) {
372 c = *txt++;
373 if (!c)
374 break;
375 if (0xa == c) {
376 /* a single 0xa => Unix */
377 memcpy(tmp_out, replace, replace_len);
378 tmp_out += replace_len;
379 } else if (0xd == c) {
380 if (0xa == *txt) {
381 /* 0xd 0xa => dos */
382 memcpy(tmp_out, replace, replace_len);
383 tmp_out += replace_len;
384 ++txt;
385 }
386 else {
387 /* just 0xd => Mac */
388 memcpy(tmp_out, replace, replace_len);
389 tmp_out += replace_len;
390 }
391 } else
392 *tmp_out++ = c;
393 }
394
395 *tmp_out = 0;
396 return result;
397 }
398
399 #define WHITE_SPACE_CHARS " \n\t\r"
400
401 /* Strip all 'to_strip' characters from the beginning of the string.
402 Does stripping in-place */
403 void str_strip_left(char *txt, const char *to_strip)
404 {
405 char *new_start = txt;
406 char c;
407 if (!txt || !to_strip)
408 return;
409 for (;;) {
410 c = *new_start;
411 if (0 == c)
412 break;
413 if (!str_contains(to_strip, c))
414 break;
415 ++new_start;
416 }
417
418 if (new_start != txt) {
419 memmove(txt, new_start, strlen(new_start)+1);
420 }
421 }
422
423 /* Strip white-space characters from the beginning of the string.
424 Does stripping in-place */
425 void str_strip_ws_left(char *txt)
426 {
427 str_strip_left(txt, WHITE_SPACE_CHARS);
428 }
429
430 void str_strip_right(char *txt, const char *to_strip)
431 {
432 char * new_end;
433 char c;
434 if (!txt || !to_strip)
435 return;
436 if (0 == *txt)
437 return;
438 /* point at the last character in the string */
439 new_end = txt + strlen(txt) - 1;
440 for (;;) {
441 c = *new_end;
442 if (!str_contains(to_strip, c))
443 break;
444 if (txt == new_end)
445 break;
446 --new_end;
447 }
448 if (str_contains(to_strip, *new_end))
449 new_end[0] = 0;
450 else
451 new_end[1] = 0;
452 }
453
454 void str_strip_ws_right(char *txt)
455 {
456 str_strip_right(txt, WHITE_SPACE_CHARS);
457 }
458
459 void str_strip_both(char *txt, const char *to_strip)
460 {
461 str_strip_left(txt, to_strip);
462 str_strip_right(txt, to_strip);
463 }
464
465 void str_strip_ws_both(char *txt)
466 {
467 str_strip_ws_left(txt);
468 str_strip_ws_right(txt);
469 }
470
471 #if 0
472 int utf8_eq(const utf8* str1, const utf8* str2)
473 {
474 return str_eq(str1, str2);
475 }
476
477 int utf8_eqn(const utf8* str1, const utf8* str2, int len)
478 {
479 return str_eqn(str1, str2, len);
480 }
481
482 int utf8_copy(utf8 *dst, int dst_size_bytes, utf8* src)
483 {
484 return str_copy(dst, dst_size_bytes, src);
485 }
486
487 utf8 *utf8_dup(const utf8 *str)
488 {
489 return str_dup(str);
490 }
491
492 utf8 *utf8_cat4(const utf8 *str1, const utf8 *str2, const utf8 *str3, const utf8 *str4)
493 {
494 return str_cat4(str1, str2, str3, str4);
495 }
496
497 utf8 *utf8_cat3(const utf8 *str1, const utf8 *str2, const utf8 *str3)
498 {
499 return str_cat4(str1, str2, str3, NULL);
500 }
501
502 utf8 *utf8_cat(const utf8 *str1, const utf8 *str2)
503 {
504 return str_cat4(str1, str2, NULL, NULL);
505 }
506
507 int utf8_endswith(const utf8 *str, const utf8 *end)
508 {
509 return str_endswith(str, end);
510 }
511 #endif
512
513 #define HEX_NUMBERS "0123456789ABCDEF"
514 static void char_to_hex(unsigned char c, char* buffer)
515 {
516 buffer[0] = HEX_NUMBERS[c / 16];
517 buffer[1] = HEX_NUMBERS[c % 16];
518 }
519
520 int str_contains(const char *str, char c)
521 {
522 const char *pos = str_find_char(str, c);
523 if (!pos)
524 return FALSE;
525 return TRUE;
526 }
527
528 #define CHAR_URL_DONT_ENCODE "-_.!~*'()"
529
530 int char_needs_url_encode(char c)
531 {
532 if ((c >= 'a') && (c <= 'z'))
533 return FALSE;
534 if ((c >= 'A') && (c <= 'Z'))
535 return FALSE;
536 if ((c >= '0') && (c <= '9'))
537 return FALSE;
538 if (str_contains(CHAR_URL_DONT_ENCODE, c))
539 return FALSE;
540 return TRUE;
541 }
542
543 /* url-encode 'str'. Returns NULL in case of error. Caller needs to free()
544 the result */
545 char *str_url_encode(const char *str)
546 {
547 char * encoded;
548 char * result;
549 int res_len = 0;
550 const char * tmp = str;
551
552 /* calc the size of the string after url encoding */
553 while (*tmp) {
554 if (char_needs_url_encode(*tmp))
555 res_len += 3;
556 else
557 ++res_len;
558 tmp++;
559 }
560 if (0 == res_len)
561 return NULL;
562
563 encoded = (char*)malloc(res_len+1);
564 if (!encoded)
565 return NULL;
566
567 result = encoded;
568 tmp = str;
569 while (*tmp) {
570 if (char_needs_url_encode(*tmp)) {
571 *encoded++ = '%';
572 char_to_hex(*tmp, encoded);
573 encoded += 2;
574 } else {
575 if (' ' == *tmp)
576 *encoded++ = '+';
577 else
578 *encoded++ = *tmp;
579 }
580 tmp++;
581 }
582 *encoded = 0;
583 return result;
584 }
585
586 char *str_escape(const char *txt)
587 {
588 /* TODO: */
589 return str_dup(txt);
590 }
591
592 char *str_printf(const char *format, ...)
593 {
594 char *result;
595 va_list args;
596 va_start(args, format);
597 result = str_printf_args(format, args);
598 va_end(args);
599 return result;
600 }
601
602 char *str_printf_args(const char *format, va_list args)
603 {
604 #ifdef _WIN32
605 HRESULT hr;
606 char message[256];
607 char * buf;
608 size_t bufCchSize;
609 char * result = NULL;
610
611 buf = &(message[0]);
612 bufCchSize = sizeof(message);
613
614 for (;;)
615 {
616 /* TODO: this only works on windows with recent C library */
617 hr = StringCchVPrintfA(buf, bufCchSize, format, args);
618 if (S_OK == hr)
619 break;
620 if (STRSAFE_E_INSUFFICIENT_BUFFER != hr)
621 {
622 /* any error other than buffer not big enough:
623 a) should not happen
624 b) means we give up */
625 assert(FALSE);
626 goto Error;
627 }
628 /* we have to make the buffer bigger. The algorithm used to calculate
629 the new size is arbitrary (aka. educated guess) */
630 if (buf != &(message[0]))
631 free(buf);
632 if (bufCchSize < 4*1024)
633 bufCchSize += bufCchSize;
634 else
635 bufCchSize += 1024;
636 buf = (char *)malloc(bufCchSize*sizeof(char));
637 if (NULL == buf)
638 goto Error;
639 }
640
641 /* free the buffer if it was dynamically allocated */
642 if (buf == &(message[0]))
643 return str_dup(buf);
644
645 return buf;
646 Error:
647 if (buf != &(message[0]))
648 free((void*)buf);
649
650 return NULL;
651 #else
652 char* buf;
653 int len = vasprintf(&buf, format, args);
654 return buf;
655 #endif
656 }
657
658 #ifdef _WIN32
659 void win32_dbg_out(const char *format, ...)
660 {
661 char buf[4096];
662 char * p = buf;
663 int written;
664 va_list args;
665
666 va_start(args, format);
667 written = _vsnprintf(p,sizeof(buf), format, args);
668 /* printf(buf);
669 fflush(stdout); */
670 OutputDebugStringA(buf);
671 va_end(args);
672 }
673
674 void win32_dbg_out_hex(const char *dsc, const unsigned char *data, int dataLen)
675 {
676 unsigned char buf[64+1];
677 unsigned char * curPos;
678 int bufCharsLeft;
679
680 if (dsc) win32_dbg_out(dsc); /* a bit dangerous if contains formatting codes */
681 if (!data) return;
682
683 bufCharsLeft = sizeof(buf)-1;
684 curPos = buf;
685 while (dataLen > 0) {
686 if (bufCharsLeft <= 1) {
687 *curPos = 0;
688 win32_dbg_out((char*)buf);
689 bufCharsLeft = sizeof(buf)-1;
690 curPos = buf;
691 }
692 char_to_hex(*data, curPos);
693 curPos += 2;
694 bufCharsLeft -= 2;
695 --dataLen;
696 ++data;
697 }
698
699 if (curPos != buf) {
700 *curPos = 0;
701 win32_dbg_out(buf);
702 }
703 win32_dbg_out("\n");
704 }
705 #endif
706
707 /* Given a pointer to a string in '*txt', skip past whitespace in the string
708 and put the result in '*txt' */
709 void str_skip_ws(char **txtInOut)
710 {
711 char *cur;
712 if (!txtInOut)
713 return;
714 cur = *txtInOut;
715 if (!cur)
716 return;
717 while (char_is_ws(*cur)) {
718 ++cur;
719 }
720 *txtInOut = cur;
721 }
722
723 char *str_parse_quoted(char **txt)
724 {
725 char * strStart;
726 char * strCopy;
727 char * cur;
728 char * dst;
729 char c;
730 size_t len;
731
732 assert(txt);
733 if (!txt) return NULL;
734 strStart = *txt;
735 assert(strStart);
736 if (!strStart) return NULL;
737
738 assert('"' == *strStart);
739 /* TODO: rewrite as 2-phase logic so that counting and copying are always in sync */
740 ++strStart;
741 cur = strStart;
742 len = 0;
743 for (;;) {
744 c = *cur;
745 if ((0 == c) || ('"' == c))
746 break;
747 if ('\\' == c) {
748 /* TODO: should I un-escape more than '"' ?
749 I used to un-escape '\' as well, but it wasn't right and
750 files with UNC path like "\\foo\file.pdf" failed to load */
751 if ('"' == cur[1]) {
752 ++cur;
753 c = *cur;
754 }
755 }
756 ++cur;
757 ++len;
758 }
759
760 strCopy = (char*)malloc(len+1);
761 if (!strCopy)
762 return NULL;
763
764 cur = strStart;
765 dst = strCopy;
766 for (;;) {
767 c = *cur;
768 if (0 == c)
769 break;
770 if ('"' == c) {
771 ++cur;
772 break;
773 }
774 if ('\\' == c) {
775 /* TODO: should I un-escape more than '"' ?
776 I used to un-escape '\' as well, but it wasn't right and
777 files with UNC path like "\\foo\file.pdf" failed to load */
778 if ('"' == cur[1]) {
779 ++cur;
780 c = *cur;
781 }
782 }
783 *dst++ = c;
784 ++cur;
785 }
786 *dst = 0;
787 *txt = cur;
788 return strCopy;
789 }
790
791 char *str_parse_non_quoted(char **txt)
792 {
793 char * cur;
794 char * strStart;
795 char * strCopy;
796 char c;
797 size_t strLen;
798
799 strStart = *txt;
800 assert(strStart);
801 if (!strStart) return NULL;
802 assert('"' != *strStart);
803 cur = strStart;
804 for (;;) {
805 c = *cur;
806 if (char_is_ws_or_zero(c))
807 break;
808 ++cur;
809 }
810
811 strLen = cur - strStart;
812 assert(strLen > 0);
813 strCopy = str_dupn(strStart, strLen);
814 *txt = cur;
815 return strCopy;
816 }
817
818 /* 'txt' is path that can be:
819 - escaped, in which case it starts with '"', ends with '"' and each '"' that is part of the name is escaped
820 with '\'
821 - unescaped, in which case it start with != '"' and ends with ' ' or eol (0)
822 This function extracts escaped or unescaped path from 'txt'. Returns NULL in case of error.
823 Caller needs to free() the result. */
824 char *str_parse_possibly_quoted(char **txt)
825 {
826 char * cur;
827 char * str_copy;
828
829 if (!txt)
830 return NULL;
831 cur = *txt;
832 if (!cur)
833 return NULL;
834
835 str_skip_ws(&cur);
836 if (0 == *cur)
837 return NULL;
838 if ('"' == *cur)
839 str_copy = str_parse_quoted(&cur);
840 else
841 str_copy = str_parse_non_quoted(&cur);
842 *txt = cur;
843 return str_copy;
844 }
845
846 void str_array_init(str_array *str_arr)
847 {
848 assert(str_arr);
849 if (!str_arr) return;
850 memzero(str_arr, sizeof(str_array));
851 }
852
853 void str_array_free(str_array *str_arr)
854 {
855 int i;
856
857 assert(str_arr);
858 if (!str_arr) return;
859
860 for (i = 0; i < str_arr->items_count; i++)
861 free(str_arr->items[i]);
862 free(str_arr->items);
863 str_array_init(str_arr);
864 }
865
866 void str_array_delete(str_array *str_arr)
867 {
868 assert(str_arr);
869 if (!str_arr) return;
870 str_array_free(str_arr);
871 free((void*)str_arr);
872 }
873
874 str_item *str_array_get(str_array *str_arr, int index)
875 {
876 assert(str_arr);
877 if (!str_arr) return NULL;
878 assert(index >= 0);
879 assert(index < str_arr->items_count);
880 if ((index < 0) || (index >= str_arr->items_count))
881 return NULL;
882 return str_arr->items[index];
883 }
884
885 int str_array_get_count(str_array *str_arr)
886 {
887 assert(str_arr);
888 if (!str_arr) return 0;
889 return str_arr->items_count;
890 }
891
892 /* Set one string at position 'index' in 'str_arr'. Space for the item
893 must already be allocated. */
894 str_item *str_array_set(str_array *str_arr, int index, const char *str)
895 {
896 str_item * new_item;
897 size_t str_len_cch;
898
899 assert(str_arr);
900 if (!str_arr) return NULL;
901
902 if (index >= str_arr->items_count)
903 return NULL;
904
905 str_len_cch = str_len(str);
906 new_item = (str_item*)malloc(sizeof(str_item) + str_len_cch*sizeof(char));
907 if (!new_item)
908 return NULL;
909 str_copy(new_item->str, str_len_cch+1, str);
910 if (str_arr->items[index])
911 free(str_arr->items[index]);
912 str_arr->items[index] = new_item;
913 return new_item;
914 }
915
916 #define STR_ARR_GROW_VALUE 32
917
918 /* make a generic array alloc */
919 str_item *str_array_add(str_array *str_arr, const char *str)
920 {
921 str_item ** tmp;
922 str_item * new_item;
923 void * data;
924 int n;
925
926 if (str_arr->items_count >= str_arr->items_allocated) {
927 /* increase memory for items if necessary */
928 n = str_arr->items_allocated + STR_ARR_GROW_VALUE;
929 tmp = (str_item**)realloc(str_arr->items, n * sizeof(str_item *));
930 if (!tmp)
931 return NULL;
932 str_arr->items = tmp;
933 data = &(str_arr->items[str_arr->items_count]);
934 memzero(data, STR_ARR_GROW_VALUE * sizeof(str_item *));
935 str_arr->items_allocated = n;
936 }
937 str_arr->items_count++;
938 new_item = str_array_set(str_arr, str_arr->items_count - 1, str);
939 if (!new_item)
940 --str_arr->items_count;
941 return new_item;
942 }
943
944 int str_array_exists_no_case(str_array *str_arr, const char *str)
945 {
946 int count, i;
947 str_item * item;
948 char * item_str;
949
950 if (!str_arr || !str)
951 return FALSE;
952
953 count = str_arr->items_count;
954 for (i = 0; i < count; i++)
955 {
956 item = str_arr->items[i];
957 item_str = item->str;
958 if (str_ieq(str, item_str))
959 return TRUE;
960 }
961 return FALSE;
962 }
963
964 str_item *str_array_add_no_dups(str_array *str_arr, const char *str)
965 {
966 if (str_array_exists_no_case(str_arr, str))
967 return NULL;
968
969 return str_array_add(str_arr, str);
970 }