Vuo  2.4.0
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#include <xlocale.h>
16
17#include "type.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
43{
44 const char *textString = NULL;
45 if (json_object_get_type(js) == json_type_string)
46 textString = VuoText_make(json_object_get_string(js));
47 return textString;
48}
49
55{
56 if (!value)
57 return NULL;
58
59 return json_object_new_string(value);
60}
61
68{
69 if (!subject)
70 return VuoText_make("");
71
72 size_t length = VuoText_length(subject);
73 if (length <= maxLength)
74 return subject;
75 else
76 {
77 VuoText abbreviation = VuoText_substring(subject, (where == VuoTextTruncation_End ? 1 : 1 + length - maxLength), maxLength);
78 VuoText ellipsis = VuoText_make("…");
79 VuoText summaryParts[2] = { abbreviation, ellipsis };
80 if (where == VuoTextTruncation_Beginning)
81 {
82 summaryParts[0] = ellipsis;
83 summaryParts[1] = abbreviation;
84 }
85 VuoText summaryWhole = VuoText_append(summaryParts, 2);
86
87 VuoRetain(abbreviation);
88 VuoRelease(abbreviation);
89 VuoRetain(ellipsis);
90 VuoRelease(ellipsis);
91 return summaryWhole;
92 }
93}
94
102char * VuoText_getSummary(const VuoText value)
103{
104 if (VuoText_isEmpty(value))
105 return strdup("<code>&#0;</code>");
106
107 VuoText truncatedText = VuoText_truncateWithEllipsis(value, 1024, VuoTextTruncation_End);
108
109 // VuoText_truncateWithEllipsis() may return the same string passed in.
110 // Only dealloc it if new text was actually created.
111 if (truncatedText != value)
112 VuoRetain(truncatedText);
113
114 VuoText escapedText = VuoText_replace(truncatedText, "&", "&amp;");
115 if (truncatedText != value)
116 VuoRelease(truncatedText);
117
118 VuoLocal(escapedText);
119 VuoText escapedText2 = VuoText_replace(escapedText, "<", "&lt;");
120 VuoLocal(escapedText2);
121 char *summary = VuoText_format("<code>%s</code>", escapedText2);
122 return summary;
123}
124
129VuoText VuoText_make(const char * unquotedString)
130{
131 VuoText text;
132 if (unquotedString)
133 text = strdup(unquotedString);
134 else
135 text = strdup("");
136 VuoRegister(text, free);
137 return text;
138}
139
154VuoText VuoText_makeWithMaxLength(const void *data, const size_t maxLength)
155{
156 char *text;
157 if (data && ((char *)data)[maxLength-1] == 0)
158 {
159 // Faster than scanning through the array byte-by-byte.
160 text = (char *)calloc(1, maxLength);
161 memcpy(text, data, maxLength);
162 }
163 else if (data)
164 {
165 text = (char *)calloc(1, maxLength+1);
166 for (unsigned int i = 0; i < maxLength; ++i)
167 {
168 text[i] = ((char *)data)[i];
169 if (((char *)data)[i] == 0)
170 break;
171 }
172 }
173 else
174 text = strdup("");
175 VuoRegister(text, free);
176 return text;
177}
178
183{
184 if (!cfs)
185 return NULL;
186
187 CFStringRef cfString = (CFStringRef)cfs;
188
189 // https://stackoverflow.com/questions/1609565/whats-the-cfstring-equiv-of-nsstrings-utf8string
190 const char *utf8StringPtr = CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8);
191 if (utf8StringPtr)
192 return VuoText_make(utf8StringPtr);
193 else
194 {
195 CFIndex maxBytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfString), kCFStringEncodingUTF8) + 1;
196 char *t = calloc(1, maxBytes);
197 CFStringGetCString(cfString, t, maxBytes, kCFStringEncodingUTF8);
198 VuoRegister(t, free);
199 return t;
200 }
201}
202
216#define UTF8_ACCEPT 0
217#define UTF8_REJECT 1
218
222static const uint8_t utf8d[] = {
223 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
224 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
225 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
226 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
227 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
228 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
229 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
230 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
231 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
232 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
233 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
234 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
235 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
236 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
237};
238
242static bool VuoText_isValidUtf8(const unsigned char *data, unsigned long size)
243{
244 // Faster than CFStringCreateFromExternalRepresentation.
245 uint32_t codepoint;
246 for (uint32_t pos = 0, state = 0; *data && pos++ < size; ++data)
247 {
248 uint32_t byte = *data;
249 uint32_t type = utf8d[byte];
250
251 codepoint = (state != UTF8_ACCEPT) ?
252 (byte & 0x3fu) | (codepoint << 6) :
253 (0xff >> type) & (byte);
254
255 state = utf8d[256 + state*16 + type];
256
257 if (state == UTF8_REJECT)
258 return false;
259 }
260
261 return true;
262}
263
271VuoText VuoText_makeFromData(const unsigned char *data, const unsigned long size)
272{
273 if (!size || !data)
274 return NULL;
275
276 if (!VuoText_isValidUtf8(data, size))
277 return NULL;
278
279 return VuoText_makeWithMaxLength(data, size);
280}
281
289VuoText VuoText_makeFromUtf32(const uint32_t* data, size_t size)
290{
291 CFStringRef cf_str = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*) data, size * sizeof(uint32_t), kCFStringEncodingUTF32LE, false);
292
293 if(cf_str != NULL)
294 {
295 CFIndex str_len = CFStringGetLength(cf_str);
296 CFRange range = CFRangeMake(0, str_len);
297 CFIndex usedBufLen;
298 CFIndex length = CFStringGetBytes(cf_str, range, kCFStringEncodingUTF8, '?', false, NULL, str_len, &usedBufLen );
299
300 if(length > 0)
301 {
302 char* buffer = (char*) malloc(sizeof(char) * usedBufLen + 1);
303 CFIndex used;
304 CFStringGetBytes(cf_str, range, kCFStringEncodingUTF8, '?', false, (uint8_t*) buffer, usedBufLen + 1, &used);
305 buffer[used] = '\0';
306 VuoText text = VuoText_make(buffer);
307 free(buffer);
308 CFRelease(cf_str);
309 return text;
310 }
311
312 CFRelease(cf_str);
313 }
314
315 return VuoText_make("");
316}
317
322{
323 if (!string)
324 return NULL;
325
326 size_t len = strlen(string);
327 CFStringRef cf = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)string, len, kCFStringEncodingMacRoman, false);
328 if (!cf)
329 return NULL;
330
332
333 CFRelease(cf);
334 return t;
335}
336
341size_t VuoText_length(const VuoText text)
342{
343 if (! text)
344 return 0;
345
346 CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8);
347 if (!s)
348 return 0;
349
350 size_t length = CFStringGetLength(s);
351 CFRelease(s);
352 return length;
353}
354
358size_t VuoText_byteCount(const VuoText text)
359{
360 if (! text)
361 return 0;
362
363 return strlen(text);
364}
365
369bool VuoText_isEmpty(const VuoText text)
370{
371 return !text || text[0] == 0;
372}
373
378{
379 return !VuoText_isEmpty(text);
380}
381
388bool VuoText_areEqual(const VuoText text1, const VuoText text2)
389{
390 if (text1 == text2)
391 return true;
392
393 if (! text1 || ! text2)
394 return (! text1 && ! text2);
395
396 CFStringRef s1 = CFStringCreateWithCString(kCFAllocatorDefault, text1, kCFStringEncodingUTF8);
397 if (!s1)
398 return false;
399
400 CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, text2, kCFStringEncodingUTF8);
401 if (!s2)
402 {
403 CFRelease(s1);
404 return false;
405 }
406
407 CFComparisonResult result = CFStringCompare(s1, s2, kCFCompareNonliteral | kCFCompareWidthInsensitive);
408
409 CFRelease(s1);
410 CFRelease(s2);
411 return (result == kCFCompareEqualTo);
412}
413
417static bool isLessThan(const VuoText text1, const VuoText text2, CFStringCompareFlags flags)
418{
419 // Treat null text as greater than non-null text,
420 // so the more useful non-null text sorts to the beginning of the list.
421 if (! text1 || ! text2)
422 return text1 && !text2;
423
424 CFStringRef s1 = CFStringCreateWithCString(kCFAllocatorDefault, text1, kCFStringEncodingUTF8);
425 if (!s1)
426 return false;
427
428 CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, text2, kCFStringEncodingUTF8);
429 if (!s2)
430 {
431 CFRelease(s1);
432 return false;
433 }
434
435 CFComparisonResult result = CFStringCompare(s1, s2, kCFCompareNonliteral | kCFCompareWidthInsensitive | flags);
436
437 CFRelease(s1);
438 CFRelease(s2);
439 return (result == kCFCompareLessThan);
440}
441
447bool VuoText_isLessThan(const VuoText text1, const VuoText text2)
448{
449 return isLessThan(text1, text2, 0);
450}
451
458{
459 return isLessThan(text1, text2, kCFCompareCaseInsensitive);
460}
461
466bool VuoText_isLessThanNumeric(const VuoText text1, const VuoText text2)
467{
468 VuoReal real1 = (text1 ? VuoReal_makeFromString(text1) : 0);
469 VuoReal real2 = (text2 ? VuoReal_makeFromString(text2) : 0);
470 return real1 < real2;
471}
472
480bool VuoText_compare(VuoText text1, VuoTextComparison comparison, VuoText text2)
481{
482 if (! comparison.isCaseSensitive)
483 {
486 }
487
488 bool match = false;
489 if (comparison.type == VuoTextComparison_Equals)
490 {
491 match = VuoText_areEqual(text1, text2);
492 }
493 else if (comparison.type == VuoTextComparison_Contains)
494 {
495 match = (VuoText_findFirstOccurrence(text1, text2, 1) > 0);
496 }
497 else if (comparison.type == VuoTextComparison_BeginsWith || comparison.type == VuoTextComparison_EndsWith)
498 {
499 size_t aLength = VuoText_length(text1);
500 size_t bLength = VuoText_length(text2);
501
502 if (bLength == 0)
503 {
504 match = true;
505 }
506 else
507 {
508 int startIndex = (comparison.type == VuoTextComparison_BeginsWith ? 1 : aLength - bLength + 1);
509 VuoText aSub = VuoText_substring(text1, startIndex, bLength);
510 match = VuoText_areEqual(aSub, text2);
511
512 VuoRetain(aSub);
513 VuoRelease(aSub);
514 }
515 }
516
517 else if (comparison.type == VuoTextComparison_MatchesWildcard)
518 {
519 bool t1empty = VuoText_isEmpty(text1);
520 bool t2empty = VuoText_isEmpty(text2);
521 if (t2empty)
522 return t1empty == t2empty;
523
524 locale_t locale = newlocale(LC_ALL_MASK, "en_US.UTF-8", NULL);
525 locale_t oldLocale = uselocale(locale);
526 if (oldLocale != LC_GLOBAL_LOCALE)
527 freelocale(oldLocale);
528
529 match = fnmatch(text2, text1, 0) != FNM_NOMATCH;
530 }
531
532 else if (comparison.type == VuoTextComparison_MatchesRegEx)
533 {
534 bool t1empty = VuoText_isEmpty(text1);
535 bool t2empty = VuoText_isEmpty(text2);
536 if (t2empty)
537 return t1empty == t2empty;
538
539 regex_t re;
540 int ret = regcomp(&re, text2, REG_EXTENDED);
541 if (ret)
542 {
543 char errstr[256];
544 regerror(ret, &re, errstr, sizeof(errstr));
545 VUserLog("Error compiling regular expression: %s", errstr);
546 return false;
547 }
548
549 ret = regexec(&re, text1, 0, NULL, 0);
550 if (ret == 0)
551 match = true;
552 else if (ret == REG_NOMATCH)
553 match = false;
554 else
555 {
556 char errstr[256];
557 regerror(ret, &re, errstr, sizeof(errstr));
558 VUserLog("Error executing regular expression: %s", errstr);
559 }
560 regfree(&re);
561 }
562
563 if (! comparison.isCaseSensitive)
564 {
565 VuoRetain(text1);
566 VuoRelease(text1);
567 VuoRetain(text2);
568 VuoRelease(text2);
569 }
570
571 return match;
572}
573
582size_t VuoText_findFirstOccurrence(const VuoText string, const VuoText substring, const size_t startIndex)
583{
584 if (VuoText_isEmpty(substring))
585 return 1;
586
587 if (! string)
588 return 0;
589
590 size_t stringLength = VuoText_length(string);
591 size_t substringLength = VuoText_length(substring);
592 if (stringLength < substringLength)
593 return 0;
594
595 for (size_t i = startIndex; i <= stringLength - substringLength + 1; ++i)
596 {
597 VuoText currSubstring = VuoText_substring(string, i, substringLength);
598 bool found = VuoText_areEqual(substring, currSubstring);
599 VuoRetain(currSubstring);
600 VuoRelease(currSubstring);
601 if (found)
602 return i;
603 }
604
605 return 0;
606}
607
616size_t VuoText_findLastOccurrence(const VuoText string, const VuoText substring)
617{
618 if (VuoText_isEmpty(substring))
619 {
620 if (string)
621 return VuoText_length(string) + 1;
622 else
623 return 1;
624 }
625
626 if (! string)
627 return 0;
628
629 size_t foundIndex = 0;
630
631 size_t stringLength = VuoText_length(string);
632 size_t substringLength = VuoText_length(substring);
633 if (stringLength < substringLength)
634 return 0;
635
636 for (size_t i = 1; i <= stringLength - substringLength + 1; ++i)
637 {
638 VuoText currSubstring = VuoText_substring(string, i, substringLength);
639 if (VuoText_areEqual(substring, currSubstring))
640 foundIndex = i;
641 VuoRetain(currSubstring);
642 VuoRelease(currSubstring);
643 }
644
645 return foundIndex;
646}
647
655{
656 if (VuoText_isEmpty(string) || VuoText_isEmpty(substring))
657 return NULL;
658
659 size_t stringLength = VuoText_length(string);
660 size_t substringLength = VuoText_length(substring);
661 if (stringLength < substringLength)
662 return 0;
663
665 for (size_t i = 1; i <= stringLength - substringLength + 1; ++i)
666 {
667 VuoText currSubstring = VuoText_substring(string, i, substringLength);
668 VuoLocal(currSubstring);
669 if (VuoText_areEqual(substring, currSubstring))
671 }
672
673 return found;
674}
675
690VuoText VuoText_substring(const VuoText string, int startIndex, int length)
691{
692 if (! string)
693 return VuoText_make("");
694
695 int originalLength = VuoText_length(string);
696 if (startIndex > originalLength)
697 return VuoText_make("");
698
699 if (startIndex < 1)
700 {
701 length -= 1 - startIndex;
702 startIndex = 1;
703 }
704
705 if (length < 0)
706 return VuoText_make("");
707
708 if (startIndex + length - 1 > originalLength)
709 length = originalLength - startIndex + 1;
710
711 size_t startIndexFromZero = startIndex - 1;
712
713 CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, string, kCFStringEncodingUTF8);
714 if (!s)
715 return VuoText_make("");
716
717 CFStringRef ss = CFStringCreateWithSubstring(kCFAllocatorDefault, s, CFRangeMake(startIndexFromZero, length));
718 if (!ss)
719 return VuoText_make("");
720
721 VuoText substring = VuoText_makeFromCFString(ss);
722
723 CFRelease(s);
724 CFRelease(ss);
725 return substring;
726}
727
732VuoText VuoText_append(VuoText *texts, size_t textsCount)
733{
734 if (!textsCount)
735 return NULL;
736
737 CFMutableArrayRef a = CFArrayCreateMutable(kCFAllocatorDefault, textsCount, &kCFTypeArrayCallBacks);
738 for (size_t i = 0; i < textsCount; ++i)
739 {
740 if (texts[i])
741 {
742 CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, texts[i], kCFStringEncodingUTF8);
743 if (!s)
744 continue;
745 CFArrayAppendValue(a, s);
746 CFRelease(s);
747 }
748 }
749
750 CFStringRef s = CFStringCreateByCombiningStrings(kCFAllocatorDefault, a, CFSTR(""));
751 VuoText compositeString = VuoText_makeFromCFString(s);
752
753 CFRelease(s);
754 CFRelease(a);
755 return compositeString;
756}
757
762VuoText VuoText_appendWithSeparator(VuoList_VuoText texts, VuoText separator, bool includeEmptyParts)
763{
764 unsigned long textsCount = VuoListGetCount_VuoText(texts);
765 if (!textsCount)
766 return NULL;
767
768 VuoText *textsArray = (VuoText *)malloc((textsCount * 2 - 1) * sizeof(VuoText));
769 unsigned long outputIndex = 0;
770 bool previousText = false;
771 for (unsigned long inputIndex = 1; inputIndex <= textsCount; ++inputIndex)
772 {
773 VuoText t = VuoListGetValue_VuoText(texts, inputIndex);
774 if (includeEmptyParts)
775 {
776 textsArray[outputIndex++] = t;
777 if (inputIndex < textsCount)
778 textsArray[outputIndex++] = separator;
779 }
780 else if (VuoText_isPopulated(t))
781 {
782 if (previousText && inputIndex <= textsCount)
783 textsArray[outputIndex++] = separator;
784 textsArray[outputIndex++] = t;
785 previousText = true;
786 }
787 }
788
789 VuoText compositeText = VuoText_append(textsArray, outputIndex);
790
791 free(textsArray);
792
793 return compositeText;
794}
795
800VuoText * VuoText_split(VuoText text, VuoText separator, bool includeEmptyParts, size_t *partsCount)
801{
802 if (!text || !separator)
803 return NULL;
804
805 CFMutableArrayRef splitTexts = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
806
807 CFStringRef textCF = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8);
808 if (!textCF)
809 return NULL;
810 VuoDefer(^{ CFRelease(textCF); });
811
812 size_t textLength = CFStringGetLength(textCF);
813
814 CFStringRef separatorCF = CFStringCreateWithCString(kCFAllocatorDefault, separator, kCFStringEncodingUTF8);
815 if (!separatorCF)
816 return NULL;
817 VuoDefer(^{ CFRelease(separatorCF); });
818
819 size_t separatorLength = CFStringGetLength(separatorCF);
820
821 if (separatorLength > 0)
822 {
823 size_t startIndex = 1;
824 size_t separatorIndex = 0;
825
826 while (startIndex <= textLength)
827 {
828 CFRange rangeToSearch = CFRangeMake(startIndex - 1, textLength - (startIndex - 1));
829 CFRange foundRange;
830 Boolean found = CFStringFindWithOptions(textCF, separatorCF, rangeToSearch, 0, &foundRange);
831 separatorIndex = foundRange.location + 1;
832 if (!found)
833 separatorIndex = textLength + 1;
834
835 if (separatorIndex > startIndex || includeEmptyParts)
836 {
837 CFStringRef partStr = CFStringCreateWithSubstring(kCFAllocatorDefault, textCF, CFRangeMake(startIndex - 1, separatorIndex - startIndex));
838 if (partStr)
839 {
840 CFArrayAppendValue(splitTexts, partStr);
841 CFRelease(partStr);
842 }
843 }
844
845 startIndex = separatorIndex + separatorLength;
846 }
847
848 if (includeEmptyParts && textLength > 0 && separatorIndex + separatorLength - 1 == textLength)
849 {
850 CFStringRef emptyPartStr = CFStringCreateWithCString(kCFAllocatorDefault, "", kCFStringEncodingUTF8);
851 if (emptyPartStr)
852 {
853 CFArrayAppendValue(splitTexts, emptyPartStr);
854 CFRelease(emptyPartStr);
855 }
856 }
857 }
858 else
859 {
860 for (size_t i = 1; i <= textLength; ++i)
861 {
862 CFStringRef partStr = CFStringCreateWithSubstring(kCFAllocatorDefault, textCF, CFRangeMake(i - 1, 1));
863 if (partStr)
864 {
865 CFArrayAppendValue(splitTexts, partStr);
866 CFRelease(partStr);
867 }
868 }
869 }
870
871 *partsCount = CFArrayGetCount(splitTexts);
872 VuoText *splitTextsArr = (VuoText *)malloc(*partsCount * sizeof(VuoText));
873 for (size_t i = 0; i < *partsCount; ++i)
874 {
875 CFStringRef part = CFArrayGetValueAtIndex(splitTexts, i);
876 splitTextsArr[i] = VuoText_makeFromCFString(part);
877 }
878 CFRelease(splitTexts);
879
880 return splitTextsArr;
881}
882
888VuoText VuoText_replace(VuoText subject, VuoText stringToFind, VuoText replacement)
889{
890 if (!subject)
891 return NULL;
892 if (!stringToFind)
893 return subject;
894
895 CFMutableStringRef subjectCF = CFStringCreateMutable(NULL, 0);
896 CFStringAppendCString(subjectCF, subject, kCFStringEncodingUTF8);
897
898 CFStringRef stringToFindCF = CFStringCreateWithCString(NULL, stringToFind, kCFStringEncodingUTF8);
899 if (!stringToFindCF)
900 {
901 CFRelease(subjectCF);
902 return subject;
903 }
904
905 CFStringRef replacementCF = nil;
906 if (replacement)
907 {
908 replacementCF = CFStringCreateWithCString(NULL, replacement, kCFStringEncodingUTF8);
909 if (!replacementCF)
910 {
911 CFRelease(stringToFindCF);
912 CFRelease(subjectCF);
913 return subject;
914 }
915 }
916
917 CFStringFindAndReplace(subjectCF, stringToFindCF, replacementCF, CFRangeMake(0,CFStringGetLength(subjectCF)), kCFCompareNonliteral);
918
919 VuoText replacedSubject = VuoText_makeFromCFString(subjectCF);
920 if (replacementCF)
921 CFRelease(replacementCF);
922 CFRelease(stringToFindCF);
923 CFRelease(subjectCF);
924
925 return replacedSubject;
926}
927
934VuoText VuoText_insert(const VuoText string, int startIndex, const VuoText newText)
935{
936 if (!newText)
937 return string;
938 if (!string)
939 return newText;
940
941 int len = VuoText_length(string);
942
943 if(startIndex > len) {
944 const char *append[2] = { string, newText };
945 return VuoText_append(append, 2);
946 } else if(startIndex <= 1) {
947 const char *append[2] = { newText, string };
948 return VuoText_append(append, 2);
949 }
950
951 VuoText left = VuoText_substring(string, 1, startIndex - 1);
952 VuoText right = VuoText_substring(string, startIndex, (len + 1) - startIndex);
953
954 VuoLocal(left);
955 VuoLocal(right);
956
957 const char *append[3] = { left, newText, right };
958 return VuoText_append(append, 3);
959}
960
966VuoText VuoText_removeAt(const VuoText string, int startIndex, int length)
967{
968 int len = VuoText_length(string);
969
970 if(startIndex < 1)
971 {
972 length -= (1 - startIndex);
973 startIndex = 1;
974 }
975
976 // if start is greater than original length or start + len is the whole array
977 if(startIndex > len || length < 1)
978 return VuoText_make(string);
979
980 VuoText left = VuoText_substring(string, 1, startIndex - 1);
981 VuoText right = VuoText_substring(string, startIndex + length, (len + 1) - startIndex);
982
983 VuoLocal(left);
984 VuoLocal(right);
985
986 return VuoText_make(VuoText_format("%s%s", left, right));
987}
988
994char *VuoText_format(const char *format, ...)
995{
996 va_list args;
997
998 va_start(args, format);
999 int size = vsnprintf(NULL, 0, format, args);
1000 va_end(args);
1001
1002 char *formattedString = (char *)malloc(size+1);
1003 va_start(args, format);
1004 vsnprintf(formattedString, size+1, format, args);
1005 va_end(args);
1006
1007 return formattedString;
1008}
1009
1018{
1019 if (!text)
1020 return NULL;
1021
1022 size_t len = strlen(text);
1023 size_t firstNonSpace;
1024 for (firstNonSpace = 0; firstNonSpace < len && isspace(text[firstNonSpace]); ++firstNonSpace);
1025
1026 if (firstNonSpace == len)
1027 return VuoText_make("");
1028
1029 size_t lastNonSpace;
1030 for (lastNonSpace = len-1; lastNonSpace > firstNonSpace && isspace(text[lastNonSpace]); --lastNonSpace);
1031
1032 return VuoText_makeWithMaxLength(text + firstNonSpace, lastNonSpace - firstNonSpace + 1);
1033}
1034
1038static bool VuoText_isASCII7(VuoText text)
1039{
1040 size_t len = strlen(text);
1041 for (size_t i = 0; i < len; ++i)
1042 if (((unsigned char *)text)[i] > 127)
1043 return false;
1044
1045 return true;
1046}
1047
1052{
1053 if (!text)
1054 return NULL;
1055
1056 // Optimized conversion for plain ASCII7 text.
1057 if (VuoText_isASCII7(text))
1058 {
1059 if (textCase == VuoTextCase_LowercaseAll)
1060 {
1061 size_t len = strlen(text);
1062 char *processedString = malloc(len + 1);
1063 for (size_t i = 0; i < len; ++i)
1064 processedString[i] = tolower(text[i]);
1065 processedString[len] = 0;
1066 VuoRegister(processedString, free);
1067 return processedString;
1068 }
1069 }
1070
1071 CFMutableStringRef mutable_str = CFStringCreateMutable(NULL, 0);
1072 CFStringAppendCString(mutable_str, text, kCFStringEncodingUTF8);
1073 CFLocaleRef locale = CFLocaleCopyCurrent();
1074
1075 switch( textCase )
1076 {
1078 CFStringLowercase(mutable_str, locale);
1079 break;
1080
1082 CFStringUppercase(mutable_str, locale);
1083 break;
1084
1086 CFStringCapitalize(mutable_str, locale);
1087 break;
1088
1090 {
1091 // The rest of the string functions lower-case everything by default
1092 CFStringLowercase(mutable_str, locale);
1093
1094 // the sentence tokenizer does a better job when all characters are capitalized
1095 CFStringRef tmp = CFStringCreateWithSubstring(kCFAllocatorDefault, mutable_str, CFRangeMake(0, CFStringGetLength(mutable_str)));
1096 CFMutableStringRef all_upper = CFStringCreateMutableCopy(NULL, 0, tmp);
1097 CFRelease(tmp);
1098 CFStringUppercase(all_upper, locale);
1099
1100 CFStringTokenizerRef tokenizer = CFStringTokenizerCreate( kCFAllocatorDefault,
1101 all_upper,
1102 CFRangeMake(0, CFStringGetLength(all_upper)),
1103 kCFStringTokenizerUnitSentence,
1104 locale);
1105
1106 CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone;
1107
1108 // https://stackoverflow.com/questions/15515128/capitalize-first-letter-of-every-sentence-in-nsstring
1109 while(kCFStringTokenizerTokenNone != (tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)))
1110 {
1111 CFRange tokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer);
1112
1113 if (tokenRange.location != kCFNotFound && tokenRange.length > 0)
1114 {
1115 CFRange firstCharRange = CFRangeMake(tokenRange.location, 1);
1116 CFStringRef firstLetter = CFStringCreateWithSubstring(kCFAllocatorDefault, mutable_str, firstCharRange);
1117 CFMutableStringRef upperFirst = CFStringCreateMutableCopy(NULL, 0, firstLetter);
1118 CFRelease(firstLetter);
1119 CFStringCapitalize(upperFirst, locale);
1120 CFStringReplace(mutable_str, firstCharRange, upperFirst);
1121 CFRelease(upperFirst);
1122 }
1123 }
1124
1125 CFRelease(all_upper);
1126 CFRelease(tokenizer);
1127 }
1128 break;
1129 }
1130
1131 VuoText processedString = VuoText_makeFromCFString(mutable_str);
1132
1133 CFRelease(locale);
1134 CFRelease(mutable_str);
1135
1136 return processedString;
1137}
1138
1148uint32_t* VuoText_getUtf32Values(const VuoText text, size_t* length)
1149{
1150 CFMutableStringRef cf_str = CFStringCreateMutable(NULL, 0);
1151 CFStringAppendCString(cf_str, text, kCFStringEncodingUTF8);
1152
1153 size_t str_len = CFStringGetLength(cf_str);
1154
1155 CFRange range = CFRangeMake(0, str_len);
1156 CFIndex usedBufLen;
1157 *length = (size_t) CFStringGetBytes(cf_str, range, kCFStringEncodingUTF32, '?', false, NULL, str_len, &usedBufLen );
1158
1159 uint32_t* decimal = (uint32_t*) NULL;
1160
1161 if(*length > 0)
1162 {
1163 decimal = (uint32_t*) malloc( sizeof(uint32_t) * usedBufLen );
1164 CFStringGetBytes(cf_str, range, kCFStringEncodingUTF32, '?', false, (uint8_t*) decimal, usedBufLen, NULL);
1165 }
1166
1167 CFRelease(cf_str);
1168
1169 return decimal;
1170}