Vuo 2.4.4
Loading...
Searching...
No Matches
VuoText.c
Go to the documentation of this file.
1
11#include <Carbon/Carbon.h>
12#include <fnmatch.h>
13#include <regex.h>
14#include <string.h>
15
16#include "VuoText.h"
17#include "VuoReal.h"
18
20#ifdef VUO_COMPILER
22 "title" : "Text",
23 "description" : "A Unicode (UTF-8) text string.",
24 "keywords" : [ "char *", "character" ],
25 "version" : "1.0.0",
26 "dependencies" : [
27 "Carbon.framework",
28 "VuoInteger",
29 "VuoReal",
30 "VuoTextCase",
31 "VuoList_VuoInteger",
32 "VuoList_VuoText",
33 ]
34 });
35#endif
37
38dispatch_queue_t VuoText_utf8LocaleQueue = NULL;
39locale_t VuoText_utf8Locale = NULL;
40
41dispatch_queue_t VuoText_systemLocaleQueue = NULL;
42locale_t VuoText_systemLocale = NULL;
43
49void __attribute__((constructor)) VuoText_init(void)
50{
51 VuoText_utf8LocaleQueue = dispatch_queue_create("org.vuo.VuoText.locale.utf8", NULL);
52 VuoText_utf8Locale = newlocale(LC_ALL_MASK, "en_US.UTF-8", NULL);
53
54 VuoText_systemLocaleQueue = dispatch_queue_create("org.vuo.VuoText.locale.system", NULL);
55
56 CFLocaleRef localeCF = CFLocaleCopyCurrent();
57 VuoText localeIdentifier = VuoText_makeFromCFString(CFLocaleGetIdentifier(localeCF));
58 VuoLocal(localeIdentifier);
59 CFRelease(localeCF);
60
61 VuoText_systemLocale = newlocale(LC_ALL_MASK, localeIdentifier, NULL);
62}
63
69void VuoText_performWithUTF8Locale(void (^function)(locale_t utf8Locale))
70{
71 dispatch_sync(VuoText_utf8LocaleQueue, ^{
72 locale_t oldLocale = uselocale(VuoText_utf8Locale);
73 if (oldLocale != LC_GLOBAL_LOCALE)
74 freelocale(oldLocale);
75
76 function(VuoText_utf8Locale);
77
78 uselocale(LC_GLOBAL_LOCALE);
79 });
80}
81
88void VuoText_performWithSystemLocale(void (^function)(locale_t systemLocale))
89{
90 dispatch_sync(VuoText_systemLocaleQueue, ^{
91 locale_t oldLocale = uselocale(VuoText_systemLocale);
92 if (oldLocale != LC_GLOBAL_LOCALE)
93 freelocale(oldLocale);
94
95 function(VuoText_systemLocale);
96
97 uselocale(LC_GLOBAL_LOCALE);
98 });
99}
100
106{
107 const char *textString = NULL;
108 if (json_object_get_type(js) == json_type_string)
109 textString = VuoText_make(json_object_get_string(js));
110 return textString;
111}
112
117json_object * VuoText_getJson(const VuoText value)
118{
119 if (!value)
120 return NULL;
121
122 return json_object_new_string(value);
123}
124
131{
132 if (!subject)
133 return VuoText_make("");
134
135 size_t length = VuoText_length(subject);
136 if (length <= maxLength)
137 return subject;
138 else
139 {
140 VuoText abbreviation = VuoText_substring(subject, (where == VuoTextTruncation_End ? 1 : 1 + length - maxLength), maxLength);
141 VuoText ellipsis = VuoText_make("…");
142 VuoText summaryParts[2] = { abbreviation, ellipsis };
143 if (where == VuoTextTruncation_Beginning)
144 {
145 summaryParts[0] = ellipsis;
146 summaryParts[1] = abbreviation;
147 }
148 VuoText summaryWhole = VuoText_append(summaryParts, 2);
149
150 VuoRetain(abbreviation);
151 VuoRelease(abbreviation);
152 VuoRetain(ellipsis);
153 VuoRelease(ellipsis);
154 return summaryWhole;
155 }
156}
157
165char * VuoText_getSummary(const VuoText value)
166{
167 if (VuoText_isEmpty(value))
168 return strdup("<code>&#0;</code>");
169
170 VuoText truncatedText = VuoText_truncateWithEllipsis(value, 1024, VuoTextTruncation_End);
171
172 // VuoText_truncateWithEllipsis() may return the same string passed in.
173 // Only dealloc it if new text was actually created.
174 if (truncatedText != value)
175 VuoRetain(truncatedText);
176
177 VuoText escapedText = VuoText_replace(truncatedText, "&", "&amp;");
178 if (truncatedText != value)
179 VuoRelease(truncatedText);
180
181 VuoLocal(escapedText);
182 VuoText escapedText2 = VuoText_replace(escapedText, "<", "&lt;");
183 VuoLocal(escapedText2);
184 char *summary = VuoText_format("<code>%s</code>", escapedText2);
185 return summary;
186}
187
196{
197 VuoRegister(string, free);
198 return string;
199}
200
210VuoText VuoText_make(const char *string)
211{
212 if (string)
213 return VuoText_makeWithoutCopying(strdup(string));
214 else
215 return VuoText_makeWithoutCopying(strdup(""));
216}
217
233VuoText VuoText_makeWithMaxLength(const void *data, const size_t maxLength)
234{
235 char *text;
236 if (data && ((char *)data)[maxLength-1] == 0)
237 {
238 // Faster than scanning through the array byte-by-byte.
239 text = (char *)calloc(1, maxLength);
240 memcpy(text, data, maxLength);
241 }
242 else if (data)
243 {
244 text = (char *)calloc(1, maxLength+1);
245 for (unsigned int i = 0; i < maxLength; ++i)
246 {
247 text[i] = ((char *)data)[i];
248 if (((char *)data)[i] == 0)
249 break;
250 }
251 }
252 else
253 text = strdup("");
254 VuoRegister(text, free);
255 return text;
256}
257
262{
263 if (!cfs)
264 return NULL;
265
266 CFStringRef cfString = (CFStringRef)cfs;
267
268 // https://stackoverflow.com/questions/1609565/whats-the-cfstring-equiv-of-nsstrings-utf8string
269 const char *utf8StringPtr = CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8);
270 if (utf8StringPtr)
271 return VuoText_make(utf8StringPtr);
272 else
273 {
274 CFIndex maxBytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfString), kCFStringEncodingUTF8) + 1;
275 char *t = calloc(1, maxBytes);
276 CFStringGetCString(cfString, t, maxBytes, kCFStringEncodingUTF8);
277 VuoRegister(t, free);
278 return t;
279 }
280}
281
295#define UTF8_ACCEPT 0
296#define UTF8_REJECT 1
297
301static const uint8_t utf8d[] = {
302 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
303 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
304 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
305 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
306 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
307 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
308 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
309 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
310 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
311 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
312 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
313 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
314 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
315 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
316};
317
321static bool VuoText_isValidUtf8(const unsigned char *data, unsigned long size)
322{
323 // Faster than CFStringCreateFromExternalRepresentation.
324 uint32_t codepoint;
325 for (uint32_t pos = 0, state = 0; *data && pos++ < size; ++data)
326 {
327 uint32_t byte = *data;
328 uint32_t type = utf8d[byte];
329
330 codepoint = (state != UTF8_ACCEPT) ?
331 (byte & 0x3fu) | (codepoint << 6) :
332 (0xff >> type) & (byte);
333
334 state = utf8d[256 + state*16 + type];
335
336 if (state == UTF8_REJECT)
337 return false;
338 }
339
340 return true;
341}
342
351VuoText VuoText_makeFromData(const unsigned char *data, const unsigned long size)
352{
353 if (!size || !data)
354 return NULL;
355
356 if (!VuoText_isValidUtf8(data, size))
357 return NULL;
358
359 return VuoText_makeWithMaxLength(data, size);
360}
361
369VuoText VuoText_makeFromUtf32(const uint32_t* data, size_t size)
370{
371 CFStringRef cf_str = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*) data, size * sizeof(uint32_t), kCFStringEncodingUTF32LE, false);
372
373 if(cf_str != NULL)
374 {
375 CFIndex str_len = CFStringGetLength(cf_str);
376 CFRange range = CFRangeMake(0, str_len);
377 CFIndex usedBufLen;
378 CFIndex length = CFStringGetBytes(cf_str, range, kCFStringEncodingUTF8, '?', false, NULL, str_len, &usedBufLen );
379
380 if(length > 0)
381 {
382 char* buffer = (char*) malloc(sizeof(char) * usedBufLen + 1);
383 CFIndex used;
384 CFStringGetBytes(cf_str, range, kCFStringEncodingUTF8, '?', false, (uint8_t*) buffer, usedBufLen + 1, &used);
385 buffer[used] = '\0';
386 VuoText text = VuoText_makeWithoutCopying(buffer);
387 CFRelease(cf_str);
388 return text;
389 }
390
391 CFRelease(cf_str);
392 }
393
394 return VuoText_make("");
395}
396
401{
402 if (!string)
403 return NULL;
404
405 size_t len = strlen(string);
406 CFStringRef cf = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)string, len, kCFStringEncodingMacRoman, false);
407 if (!cf)
408 return NULL;
409
411
412 CFRelease(cf);
413 return t;
414}
415
420size_t VuoText_length(const VuoText text)
421{
422 if (! text)
423 return 0;
424
425 CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8);
426 if (!s)
427 return 0;
428
429 size_t length = CFStringGetLength(s);
430 CFRelease(s);
431 return length;
432}
433
437size_t VuoText_byteCount(const VuoText text)
438{
439 if (! text)
440 return 0;
441
442 return strlen(text);
443}
444
448bool VuoText_isEmpty(const VuoText text)
449{
450 return !text || text[0] == 0;
451}
452
457{
458 return !VuoText_isEmpty(text);
459}
460
467bool VuoText_areEqual(const VuoText text1, const VuoText text2)
468{
469 if (text1 == text2)
470 return true;
471
472 if (! text1 || ! text2)
473 return (! text1 && ! text2);
474
475 CFStringRef s1 = CFStringCreateWithCString(kCFAllocatorDefault, text1, kCFStringEncodingUTF8);
476 if (!s1)
477 return false;
478
479 CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, text2, kCFStringEncodingUTF8);
480 if (!s2)
481 {
482 CFRelease(s1);
483 return false;
484 }
485
486 CFComparisonResult result = CFStringCompare(s1, s2, kCFCompareNonliteral | kCFCompareWidthInsensitive);
487
488 CFRelease(s1);
489 CFRelease(s2);
490 return (result == kCFCompareEqualTo);
491}
492
496static bool isLessThan(const VuoText text1, const VuoText text2, CFStringCompareFlags flags)
497{
498 // Treat null text as greater than non-null text,
499 // so the more useful non-null text sorts to the beginning of the list.
500 if (! text1 || ! text2)
501 return text1 && !text2;
502
503 CFStringRef s1 = CFStringCreateWithCString(kCFAllocatorDefault, text1, kCFStringEncodingUTF8);
504 if (!s1)
505 return false;
506
507 CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, text2, kCFStringEncodingUTF8);
508 if (!s2)
509 {
510 CFRelease(s1);
511 return false;
512 }
513
514 CFComparisonResult result = CFStringCompare(s1, s2, kCFCompareNonliteral | kCFCompareWidthInsensitive | flags);
515
516 CFRelease(s1);
517 CFRelease(s2);
518 return (result == kCFCompareLessThan);
519}
520
526bool VuoText_isLessThan(const VuoText text1, const VuoText text2)
527{
528 return isLessThan(text1, text2, 0);
529}
530
537{
538 return isLessThan(text1, text2, kCFCompareCaseInsensitive);
539}
540
545bool VuoText_isLessThanNumeric(const VuoText text1, const VuoText text2)
546{
548}
549
557bool VuoText_compare(VuoText text1, VuoTextComparison comparison, VuoText text2)
558{
559 if (! comparison.isCaseSensitive)
560 {
563 }
564
565 __block bool match = false;
566 if (comparison.type == VuoTextComparison_Equals)
567 {
568 match = VuoText_areEqual(text1, text2);
569 }
570 else if (comparison.type == VuoTextComparison_Contains)
571 {
572 match = (VuoText_findFirstOccurrence(text1, text2, 1) > 0);
573 }
574 else if (comparison.type == VuoTextComparison_BeginsWith || comparison.type == VuoTextComparison_EndsWith)
575 {
576 size_t aLength = VuoText_length(text1);
577 size_t bLength = VuoText_length(text2);
578
579 if (bLength == 0)
580 {
581 match = true;
582 }
583 else
584 {
585 int startIndex = (comparison.type == VuoTextComparison_BeginsWith ? 1 : aLength - bLength + 1);
586 VuoText aSub = VuoText_substring(text1, startIndex, bLength);
587 match = VuoText_areEqual(aSub, text2);
588
589 VuoRetain(aSub);
590 VuoRelease(aSub);
591 }
592 }
593
594 else if (comparison.type == VuoTextComparison_MatchesWildcard)
595 {
596 bool t1empty = VuoText_isEmpty(text1);
597 bool t2empty = VuoText_isEmpty(text2);
598 if (t2empty)
599 return t1empty == t2empty;
600
601 VuoText_performWithUTF8Locale(^(locale_t locale){
602 match = fnmatch(text2, text1, 0) != FNM_NOMATCH;
603 });
604 }
605
606 else if (comparison.type == VuoTextComparison_MatchesRegEx)
607 {
608 bool t1empty = VuoText_isEmpty(text1);
609 bool t2empty = VuoText_isEmpty(text2);
610 if (t2empty)
611 return t1empty == t2empty;
612
613 VuoText_performWithUTF8Locale(^(locale_t locale){
614 regex_t re;
615 int ret = regcomp_l(&re, text2, REG_EXTENDED, locale);
616 if (ret)
617 {
618 char errstr[256];
619 regerror(ret, &re, errstr, sizeof(errstr));
620 VUserLog("Error compiling regular expression: %s", errstr);
621 return;
622 }
623
624 ret = regexec(&re, text1, 0, NULL, 0);
625 if (ret == 0)
626 match = true;
627 else if (ret == REG_NOMATCH)
628 match = false;
629 else
630 {
631 char errstr[256];
632 regerror(ret, &re, errstr, sizeof(errstr));
633 VUserLog("Error executing regular expression: %s", errstr);
634 }
635 regfree(&re);
636 });
637 }
638
639 if (! comparison.isCaseSensitive)
640 {
641 VuoRetain(text1);
642 VuoRelease(text1);
643 VuoRetain(text2);
644 VuoRelease(text2);
645 }
646
647 return match;
648}
649
658size_t VuoText_findFirstOccurrence(const VuoText string, const VuoText substring, const size_t startIndex)
659{
660 if (VuoText_isEmpty(substring))
661 return 1;
662
663 if (! string)
664 return 0;
665
666 size_t stringLength = VuoText_length(string);
667 size_t substringLength = VuoText_length(substring);
668 if (stringLength < substringLength)
669 return 0;
670
671 for (size_t i = startIndex; i <= stringLength - substringLength + 1; ++i)
672 {
673 VuoText currSubstring = VuoText_substring(string, i, substringLength);
674 bool found = VuoText_areEqual(substring, currSubstring);
675 VuoRetain(currSubstring);
676 VuoRelease(currSubstring);
677 if (found)
678 return i;
679 }
680
681 return 0;
682}
683
692size_t VuoText_findLastOccurrence(const VuoText string, const VuoText substring)
693{
694 if (VuoText_isEmpty(substring))
695 {
696 if (string)
697 return VuoText_length(string) + 1;
698 else
699 return 1;
700 }
701
702 if (! string)
703 return 0;
704
705 size_t foundIndex = 0;
706
707 size_t stringLength = VuoText_length(string);
708 size_t substringLength = VuoText_length(substring);
709 if (stringLength < substringLength)
710 return 0;
711
712 for (size_t i = 1; i <= stringLength - substringLength + 1; ++i)
713 {
714 VuoText currSubstring = VuoText_substring(string, i, substringLength);
715 if (VuoText_areEqual(substring, currSubstring))
716 foundIndex = i;
717 VuoRetain(currSubstring);
718 VuoRelease(currSubstring);
719 }
720
721 return foundIndex;
722}
723
731{
732 if (VuoText_isEmpty(string) || VuoText_isEmpty(substring))
733 return NULL;
734
735 size_t stringLength = VuoText_length(string);
736 size_t substringLength = VuoText_length(substring);
737 if (stringLength < substringLength)
738 return 0;
739
741 for (size_t i = 1; i <= stringLength - substringLength + 1; ++i)
742 {
743 VuoText currSubstring = VuoText_substring(string, i, substringLength);
744 VuoLocal(currSubstring);
745 if (VuoText_areEqual(substring, currSubstring))
747 }
748
749 return found;
750}
751
766VuoText VuoText_substring(const VuoText string, int startIndex, int length)
767{
768 if (! string)
769 return VuoText_make("");
770
771 int originalLength = VuoText_length(string);
772 if (startIndex > originalLength)
773 return VuoText_make("");
774
775 if (startIndex < 1)
776 {
777 length -= 1 - startIndex;
778 startIndex = 1;
779 }
780
781 if (length < 0)
782 return VuoText_make("");
783
784 if (startIndex + length - 1 > originalLength)
785 length = originalLength - startIndex + 1;
786
787 size_t startIndexFromZero = startIndex - 1;
788
789 CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, string, kCFStringEncodingUTF8);
790 if (!s)
791 return VuoText_make("");
792
793 CFStringRef ss = CFStringCreateWithSubstring(kCFAllocatorDefault, s, CFRangeMake(startIndexFromZero, length));
794 if (!ss)
795 return VuoText_make("");
796
797 VuoText substring = VuoText_makeFromCFString(ss);
798
799 CFRelease(s);
800 CFRelease(ss);
801 return substring;
802}
803
808VuoText VuoText_append(VuoText *texts, size_t textsCount)
809{
810 if (!textsCount)
811 return NULL;
812
813 CFMutableArrayRef a = CFArrayCreateMutable(kCFAllocatorDefault, textsCount, &kCFTypeArrayCallBacks);
814 for (size_t i = 0; i < textsCount; ++i)
815 {
816 if (texts[i])
817 {
818 CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, texts[i], kCFStringEncodingUTF8);
819 if (!s)
820 continue;
821 CFArrayAppendValue(a, s);
822 CFRelease(s);
823 }
824 }
825
826 CFStringRef s = CFStringCreateByCombiningStrings(kCFAllocatorDefault, a, CFSTR(""));
827 VuoText compositeString = VuoText_makeFromCFString(s);
828
829 CFRelease(s);
830 CFRelease(a);
831 return compositeString;
832}
833
838VuoText VuoText_appendWithSeparator(VuoList_VuoText texts, VuoText separator, bool includeEmptyParts)
839{
840 unsigned long textsCount = VuoListGetCount_VuoText(texts);
841 if (!textsCount)
842 return NULL;
843
844 VuoText *textsArray = (VuoText *)malloc((textsCount * 2 - 1) * sizeof(VuoText));
845 unsigned long outputIndex = 0;
846 bool previousText = false;
847 for (unsigned long inputIndex = 1; inputIndex <= textsCount; ++inputIndex)
848 {
849 VuoText t = VuoListGetValue_VuoText(texts, inputIndex);
850 if (includeEmptyParts)
851 {
852 textsArray[outputIndex++] = t;
853 if (inputIndex < textsCount)
854 textsArray[outputIndex++] = separator;
855 }
856 else if (VuoText_isPopulated(t))
857 {
858 if (previousText && inputIndex <= textsCount)
859 textsArray[outputIndex++] = separator;
860 textsArray[outputIndex++] = t;
861 previousText = true;
862 }
863 }
864
865 VuoText compositeText = VuoText_append(textsArray, outputIndex);
866
867 free(textsArray);
868
869 return compositeText;
870}
871
876VuoText * VuoText_split(VuoText text, VuoText separator, bool includeEmptyParts, size_t *partsCount)
877{
878 if (!text || !separator)
879 return NULL;
880
881 CFMutableArrayRef splitTexts = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
882
883 CFStringRef textCF = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8);
884 if (!textCF)
885 return NULL;
886 VuoDefer(^{ CFRelease(textCF); });
887
888 size_t textLength = CFStringGetLength(textCF);
889
890 CFStringRef separatorCF = CFStringCreateWithCString(kCFAllocatorDefault, separator, kCFStringEncodingUTF8);
891 if (!separatorCF)
892 return NULL;
893 VuoDefer(^{ CFRelease(separatorCF); });
894
895 size_t separatorLength = CFStringGetLength(separatorCF);
896
897 if (separatorLength > 0)
898 {
899 size_t startIndex = 1;
900 size_t separatorIndex = 0;
901
902 while (startIndex <= textLength)
903 {
904 CFRange rangeToSearch = CFRangeMake(startIndex - 1, textLength - (startIndex - 1));
905 CFRange foundRange;
906 Boolean found = CFStringFindWithOptions(textCF, separatorCF, rangeToSearch, 0, &foundRange);
907 separatorIndex = foundRange.location + 1;
908 if (!found)
909 separatorIndex = textLength + 1;
910
911 if (separatorIndex > startIndex || includeEmptyParts)
912 {
913 CFStringRef partStr = CFStringCreateWithSubstring(kCFAllocatorDefault, textCF, CFRangeMake(startIndex - 1, separatorIndex - startIndex));
914 if (partStr)
915 {
916 CFArrayAppendValue(splitTexts, partStr);
917 CFRelease(partStr);
918 }
919 }
920
921 startIndex = separatorIndex + separatorLength;
922 }
923
924 if (includeEmptyParts && textLength > 0 && separatorIndex + separatorLength - 1 == textLength)
925 {
926 CFStringRef emptyPartStr = CFStringCreateWithCString(kCFAllocatorDefault, "", kCFStringEncodingUTF8);
927 if (emptyPartStr)
928 {
929 CFArrayAppendValue(splitTexts, emptyPartStr);
930 CFRelease(emptyPartStr);
931 }
932 }
933 }
934 else
935 {
936 for (size_t i = 1; i <= textLength; ++i)
937 {
938 CFStringRef partStr = CFStringCreateWithSubstring(kCFAllocatorDefault, textCF, CFRangeMake(i - 1, 1));
939 if (partStr)
940 {
941 CFArrayAppendValue(splitTexts, partStr);
942 CFRelease(partStr);
943 }
944 }
945 }
946
947 *partsCount = CFArrayGetCount(splitTexts);
948 VuoText *splitTextsArr = (VuoText *)malloc(*partsCount * sizeof(VuoText));
949 for (size_t i = 0; i < *partsCount; ++i)
950 {
951 CFStringRef part = CFArrayGetValueAtIndex(splitTexts, i);
952 splitTextsArr[i] = VuoText_makeFromCFString(part);
953 }
954 CFRelease(splitTexts);
955
956 return splitTextsArr;
957}
958
964VuoText VuoText_replace(VuoText subject, VuoText stringToFind, VuoText replacement)
965{
966 if (!subject)
967 return NULL;
968 if (!stringToFind)
969 return subject;
970
971 CFMutableStringRef subjectCF = CFStringCreateMutable(NULL, 0);
972 CFStringAppendCString(subjectCF, subject, kCFStringEncodingUTF8);
973
974 CFStringRef stringToFindCF = CFStringCreateWithCString(NULL, stringToFind, kCFStringEncodingUTF8);
975 if (!stringToFindCF)
976 {
977 CFRelease(subjectCF);
978 return subject;
979 }
980
981 CFStringRef replacementCF = nil;
982 if (replacement)
983 {
984 replacementCF = CFStringCreateWithCString(NULL, replacement, kCFStringEncodingUTF8);
985 if (!replacementCF)
986 {
987 CFRelease(stringToFindCF);
988 CFRelease(subjectCF);
989 return subject;
990 }
991 }
992
993 CFStringFindAndReplace(subjectCF, stringToFindCF, replacementCF, CFRangeMake(0,CFStringGetLength(subjectCF)), kCFCompareNonliteral);
994
995 VuoText replacedSubject = VuoText_makeFromCFString(subjectCF);
996 if (replacementCF)
997 CFRelease(replacementCF);
998 CFRelease(stringToFindCF);
999 CFRelease(subjectCF);
1000
1001 return replacedSubject;
1002}
1003
1010VuoText VuoText_insert(const VuoText string, int startIndex, const VuoText newText)
1011{
1012 if (!newText)
1013 return string;
1014 if (!string)
1015 return newText;
1016
1017 int len = VuoText_length(string);
1018
1019 if(startIndex > len) {
1020 const char *append[2] = { string, newText };
1021 return VuoText_append(append, 2);
1022 } else if(startIndex <= 1) {
1023 const char *append[2] = { newText, string };
1024 return VuoText_append(append, 2);
1025 }
1026
1027 VuoText left = VuoText_substring(string, 1, startIndex - 1);
1028 VuoText right = VuoText_substring(string, startIndex, (len + 1) - startIndex);
1029
1030 VuoLocal(left);
1031 VuoLocal(right);
1032
1033 const char *append[3] = { left, newText, right };
1034 return VuoText_append(append, 3);
1035}
1036
1042VuoText VuoText_removeAt(const VuoText string, int startIndex, int length)
1043{
1044 int len = VuoText_length(string);
1045
1046 if(startIndex < 1)
1047 {
1048 length -= (1 - startIndex);
1049 startIndex = 1;
1050 }
1051
1052 // if start is greater than original length or start + len is the whole array
1053 if(startIndex > len || length < 1)
1054 return VuoText_make(string);
1055
1056 VuoText left = VuoText_substring(string, 1, startIndex - 1);
1057 VuoText right = VuoText_substring(string, startIndex + length, (len + 1) - startIndex);
1058
1059 VuoLocal(left);
1060 VuoLocal(right);
1061
1062 return VuoText_makeWithoutCopying(VuoText_format("%s%s", left, right));
1063}
1064
1073char *VuoText_format(const char *format, ...)
1074{
1075 va_list args;
1076
1077 va_start(args, format);
1078 int size = vsnprintf(NULL, 0, format, args);
1079 va_end(args);
1080
1081 char *formattedString = (char *)malloc(size+1);
1082 va_start(args, format);
1083 vsnprintf(formattedString, size+1, format, args);
1084 va_end(args);
1085
1086 return formattedString;
1087}
1088
1097{
1098 if (!text)
1099 return NULL;
1100
1101 size_t len = strlen(text);
1102 size_t firstNonSpace;
1103 for (firstNonSpace = 0; firstNonSpace < len && isspace(text[firstNonSpace]); ++firstNonSpace);
1104
1105 if (firstNonSpace == len)
1106 return VuoText_make("");
1107
1108 size_t lastNonSpace;
1109 for (lastNonSpace = len-1; lastNonSpace > firstNonSpace && isspace(text[lastNonSpace]); --lastNonSpace);
1110
1111 return VuoText_makeWithMaxLength(text + firstNonSpace, lastNonSpace - firstNonSpace + 1);
1112}
1113
1117static bool VuoText_isASCII7(VuoText text)
1118{
1119 size_t len = strlen(text);
1120 for (size_t i = 0; i < len; ++i)
1121 if (((unsigned char *)text)[i] > 127)
1122 return false;
1123
1124 return true;
1125}
1126
1131{
1132 if (!text)
1133 return NULL;
1134
1135 // Optimized conversion for plain ASCII7 text.
1136 if (VuoText_isASCII7(text))
1137 {
1138 if (textCase == VuoTextCase_LowercaseAll)
1139 {
1140 size_t len = strlen(text);
1141 char *processedString = malloc(len + 1);
1142 for (size_t i = 0; i < len; ++i)
1143 processedString[i] = tolower(text[i]);
1144 processedString[len] = 0;
1145 VuoRegister(processedString, free);
1146 return processedString;
1147 }
1148 }
1149
1150 CFMutableStringRef mutable_str = CFStringCreateMutable(NULL, 0);
1151 CFStringAppendCString(mutable_str, text, kCFStringEncodingUTF8);
1152 CFLocaleRef locale = CFLocaleCopyCurrent();
1153
1154 switch( textCase )
1155 {
1157 CFStringLowercase(mutable_str, locale);
1158 break;
1159
1161 CFStringUppercase(mutable_str, locale);
1162 break;
1163
1165 CFStringCapitalize(mutable_str, locale);
1166 break;
1167
1169 {
1170 // The rest of the string functions lower-case everything by default
1171 CFStringLowercase(mutable_str, locale);
1172
1173 // the sentence tokenizer does a better job when all characters are capitalized
1174 CFStringRef tmp = CFStringCreateWithSubstring(kCFAllocatorDefault, mutable_str, CFRangeMake(0, CFStringGetLength(mutable_str)));
1175 CFMutableStringRef all_upper = CFStringCreateMutableCopy(NULL, 0, tmp);
1176 CFRelease(tmp);
1177 CFStringUppercase(all_upper, locale);
1178
1179 CFStringTokenizerRef tokenizer = CFStringTokenizerCreate( kCFAllocatorDefault,
1180 all_upper,
1181 CFRangeMake(0, CFStringGetLength(all_upper)),
1182 kCFStringTokenizerUnitSentence,
1183 locale);
1184
1185 CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone;
1186
1187 // https://stackoverflow.com/questions/15515128/capitalize-first-letter-of-every-sentence-in-nsstring
1188 while(kCFStringTokenizerTokenNone != (tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)))
1189 {
1190 CFRange tokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer);
1191
1192 if (tokenRange.location != kCFNotFound && tokenRange.length > 0)
1193 {
1194 CFRange firstCharRange = CFRangeMake(tokenRange.location, 1);
1195 CFStringRef firstLetter = CFStringCreateWithSubstring(kCFAllocatorDefault, mutable_str, firstCharRange);
1196 CFMutableStringRef upperFirst = CFStringCreateMutableCopy(NULL, 0, firstLetter);
1197 CFRelease(firstLetter);
1198 CFStringCapitalize(upperFirst, locale);
1199 CFStringReplace(mutable_str, firstCharRange, upperFirst);
1200 CFRelease(upperFirst);
1201 }
1202 }
1203
1204 CFRelease(all_upper);
1205 CFRelease(tokenizer);
1206 }
1207 break;
1208 }
1209
1210 VuoText processedString = VuoText_makeFromCFString(mutable_str);
1211
1212 CFRelease(locale);
1213 CFRelease(mutable_str);
1214
1215 return processedString;
1216}
1217
1227uint32_t* VuoText_getUtf32Values(const VuoText text, size_t* length)
1228{
1229 CFMutableStringRef cf_str = CFStringCreateMutable(NULL, 0);
1230 CFStringAppendCString(cf_str, text, kCFStringEncodingUTF8);
1231
1232 size_t str_len = CFStringGetLength(cf_str);
1233
1234 CFRange range = CFRangeMake(0, str_len);
1235 CFIndex usedBufLen;
1236 *length = (size_t) CFStringGetBytes(cf_str, range, kCFStringEncodingUTF32, '?', false, NULL, str_len, &usedBufLen );
1237
1238 uint32_t* decimal = (uint32_t*) NULL;
1239
1240 if(*length > 0)
1241 {
1242 decimal = (uint32_t*) malloc( sizeof(uint32_t) * usedBufLen );
1243 CFStringGetBytes(cf_str, range, kCFStringEncodingUTF32, '?', false, (uint8_t*) decimal, usedBufLen, NULL);
1244 }
1245
1246 CFRelease(cf_str);
1247
1248 return decimal;
1249}