Vuo 2.4.2
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#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
133{
134 VuoRegister(string, free);
135 return string;
136}
137
147VuoText VuoText_make(const char *string)
148{
149 if (string)
150 return VuoText_makeWithoutCopying(strdup(string));
151 else
152 return VuoText_makeWithoutCopying(strdup(""));
153}
154
170VuoText VuoText_makeWithMaxLength(const void *data, const size_t maxLength)
171{
172 char *text;
173 if (data && ((char *)data)[maxLength-1] == 0)
174 {
175 // Faster than scanning through the array byte-by-byte.
176 text = (char *)calloc(1, maxLength);
177 memcpy(text, data, maxLength);
178 }
179 else if (data)
180 {
181 text = (char *)calloc(1, maxLength+1);
182 for (unsigned int i = 0; i < maxLength; ++i)
183 {
184 text[i] = ((char *)data)[i];
185 if (((char *)data)[i] == 0)
186 break;
187 }
188 }
189 else
190 text = strdup("");
191 VuoRegister(text, free);
192 return text;
193}
194
199{
200 if (!cfs)
201 return NULL;
202
203 CFStringRef cfString = (CFStringRef)cfs;
204
205 // https://stackoverflow.com/questions/1609565/whats-the-cfstring-equiv-of-nsstrings-utf8string
206 const char *utf8StringPtr = CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8);
207 if (utf8StringPtr)
208 return VuoText_make(utf8StringPtr);
209 else
210 {
211 CFIndex maxBytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfString), kCFStringEncodingUTF8) + 1;
212 char *t = calloc(1, maxBytes);
213 CFStringGetCString(cfString, t, maxBytes, kCFStringEncodingUTF8);
214 VuoRegister(t, free);
215 return t;
216 }
217}
218
232#define UTF8_ACCEPT 0
233#define UTF8_REJECT 1
234
238static const uint8_t utf8d[] = {
239 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
240 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
241 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
242 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
243 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
244 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
245 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
246 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
247 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
248 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
249 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
250 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
251 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
252 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
253};
254
258static bool VuoText_isValidUtf8(const unsigned char *data, unsigned long size)
259{
260 // Faster than CFStringCreateFromExternalRepresentation.
261 uint32_t codepoint;
262 for (uint32_t pos = 0, state = 0; *data && pos++ < size; ++data)
263 {
264 uint32_t byte = *data;
265 uint32_t type = utf8d[byte];
266
267 codepoint = (state != UTF8_ACCEPT) ?
268 (byte & 0x3fu) | (codepoint << 6) :
269 (0xff >> type) & (byte);
270
271 state = utf8d[256 + state*16 + type];
272
273 if (state == UTF8_REJECT)
274 return false;
275 }
276
277 return true;
278}
279
288VuoText VuoText_makeFromData(const unsigned char *data, const unsigned long size)
289{
290 if (!size || !data)
291 return NULL;
292
293 if (!VuoText_isValidUtf8(data, size))
294 return NULL;
295
296 return VuoText_makeWithMaxLength(data, size);
297}
298
306VuoText VuoText_makeFromUtf32(const uint32_t* data, size_t size)
307{
308 CFStringRef cf_str = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*) data, size * sizeof(uint32_t), kCFStringEncodingUTF32LE, false);
309
310 if(cf_str != NULL)
311 {
312 CFIndex str_len = CFStringGetLength(cf_str);
313 CFRange range = CFRangeMake(0, str_len);
314 CFIndex usedBufLen;
315 CFIndex length = CFStringGetBytes(cf_str, range, kCFStringEncodingUTF8, '?', false, NULL, str_len, &usedBufLen );
316
317 if(length > 0)
318 {
319 char* buffer = (char*) malloc(sizeof(char) * usedBufLen + 1);
320 CFIndex used;
321 CFStringGetBytes(cf_str, range, kCFStringEncodingUTF8, '?', false, (uint8_t*) buffer, usedBufLen + 1, &used);
322 buffer[used] = '\0';
323 VuoText text = VuoText_makeWithoutCopying(buffer);
324 CFRelease(cf_str);
325 return text;
326 }
327
328 CFRelease(cf_str);
329 }
330
331 return VuoText_make("");
332}
333
338{
339 if (!string)
340 return NULL;
341
342 size_t len = strlen(string);
343 CFStringRef cf = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)string, len, kCFStringEncodingMacRoman, false);
344 if (!cf)
345 return NULL;
346
348
349 CFRelease(cf);
350 return t;
351}
352
357size_t VuoText_length(const VuoText text)
358{
359 if (! text)
360 return 0;
361
362 CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8);
363 if (!s)
364 return 0;
365
366 size_t length = CFStringGetLength(s);
367 CFRelease(s);
368 return length;
369}
370
374size_t VuoText_byteCount(const VuoText text)
375{
376 if (! text)
377 return 0;
378
379 return strlen(text);
380}
381
385bool VuoText_isEmpty(const VuoText text)
386{
387 return !text || text[0] == 0;
388}
389
394{
395 return !VuoText_isEmpty(text);
396}
397
404bool VuoText_areEqual(const VuoText text1, const VuoText text2)
405{
406 if (text1 == text2)
407 return true;
408
409 if (! text1 || ! text2)
410 return (! text1 && ! text2);
411
412 CFStringRef s1 = CFStringCreateWithCString(kCFAllocatorDefault, text1, kCFStringEncodingUTF8);
413 if (!s1)
414 return false;
415
416 CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, text2, kCFStringEncodingUTF8);
417 if (!s2)
418 {
419 CFRelease(s1);
420 return false;
421 }
422
423 CFComparisonResult result = CFStringCompare(s1, s2, kCFCompareNonliteral | kCFCompareWidthInsensitive);
424
425 CFRelease(s1);
426 CFRelease(s2);
427 return (result == kCFCompareEqualTo);
428}
429
433static bool isLessThan(const VuoText text1, const VuoText text2, CFStringCompareFlags flags)
434{
435 // Treat null text as greater than non-null text,
436 // so the more useful non-null text sorts to the beginning of the list.
437 if (! text1 || ! text2)
438 return text1 && !text2;
439
440 CFStringRef s1 = CFStringCreateWithCString(kCFAllocatorDefault, text1, kCFStringEncodingUTF8);
441 if (!s1)
442 return false;
443
444 CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, text2, kCFStringEncodingUTF8);
445 if (!s2)
446 {
447 CFRelease(s1);
448 return false;
449 }
450
451 CFComparisonResult result = CFStringCompare(s1, s2, kCFCompareNonliteral | kCFCompareWidthInsensitive | flags);
452
453 CFRelease(s1);
454 CFRelease(s2);
455 return (result == kCFCompareLessThan);
456}
457
463bool VuoText_isLessThan(const VuoText text1, const VuoText text2)
464{
465 return isLessThan(text1, text2, 0);
466}
467
474{
475 return isLessThan(text1, text2, kCFCompareCaseInsensitive);
476}
477
482bool VuoText_isLessThanNumeric(const VuoText text1, const VuoText text2)
483{
485}
486
494bool VuoText_compare(VuoText text1, VuoTextComparison comparison, VuoText text2)
495{
496 if (! comparison.isCaseSensitive)
497 {
500 }
501
502 bool match = false;
503 if (comparison.type == VuoTextComparison_Equals)
504 {
505 match = VuoText_areEqual(text1, text2);
506 }
507 else if (comparison.type == VuoTextComparison_Contains)
508 {
509 match = (VuoText_findFirstOccurrence(text1, text2, 1) > 0);
510 }
511 else if (comparison.type == VuoTextComparison_BeginsWith || comparison.type == VuoTextComparison_EndsWith)
512 {
513 size_t aLength = VuoText_length(text1);
514 size_t bLength = VuoText_length(text2);
515
516 if (bLength == 0)
517 {
518 match = true;
519 }
520 else
521 {
522 int startIndex = (comparison.type == VuoTextComparison_BeginsWith ? 1 : aLength - bLength + 1);
523 VuoText aSub = VuoText_substring(text1, startIndex, bLength);
524 match = VuoText_areEqual(aSub, text2);
525
526 VuoRetain(aSub);
527 VuoRelease(aSub);
528 }
529 }
530
531 else if (comparison.type == VuoTextComparison_MatchesWildcard)
532 {
533 bool t1empty = VuoText_isEmpty(text1);
534 bool t2empty = VuoText_isEmpty(text2);
535 if (t2empty)
536 return t1empty == t2empty;
537
538 locale_t locale = newlocale(LC_ALL_MASK, "en_US.UTF-8", NULL);
539 locale_t oldLocale = uselocale(locale);
540 if (oldLocale != LC_GLOBAL_LOCALE)
541 freelocale(oldLocale);
542
543 match = fnmatch(text2, text1, 0) != FNM_NOMATCH;
544 }
545
546 else if (comparison.type == VuoTextComparison_MatchesRegEx)
547 {
548 bool t1empty = VuoText_isEmpty(text1);
549 bool t2empty = VuoText_isEmpty(text2);
550 if (t2empty)
551 return t1empty == t2empty;
552
553 regex_t re;
554 int ret = regcomp(&re, text2, REG_EXTENDED);
555 if (ret)
556 {
557 char errstr[256];
558 regerror(ret, &re, errstr, sizeof(errstr));
559 VUserLog("Error compiling regular expression: %s", errstr);
560 return false;
561 }
562
563 ret = regexec(&re, text1, 0, NULL, 0);
564 if (ret == 0)
565 match = true;
566 else if (ret == REG_NOMATCH)
567 match = false;
568 else
569 {
570 char errstr[256];
571 regerror(ret, &re, errstr, sizeof(errstr));
572 VUserLog("Error executing regular expression: %s", errstr);
573 }
574 regfree(&re);
575 }
576
577 if (! comparison.isCaseSensitive)
578 {
579 VuoRetain(text1);
580 VuoRelease(text1);
581 VuoRetain(text2);
582 VuoRelease(text2);
583 }
584
585 return match;
586}
587
596size_t VuoText_findFirstOccurrence(const VuoText string, const VuoText substring, const size_t startIndex)
597{
598 if (VuoText_isEmpty(substring))
599 return 1;
600
601 if (! string)
602 return 0;
603
604 size_t stringLength = VuoText_length(string);
605 size_t substringLength = VuoText_length(substring);
606 if (stringLength < substringLength)
607 return 0;
608
609 for (size_t i = startIndex; i <= stringLength - substringLength + 1; ++i)
610 {
611 VuoText currSubstring = VuoText_substring(string, i, substringLength);
612 bool found = VuoText_areEqual(substring, currSubstring);
613 VuoRetain(currSubstring);
614 VuoRelease(currSubstring);
615 if (found)
616 return i;
617 }
618
619 return 0;
620}
621
630size_t VuoText_findLastOccurrence(const VuoText string, const VuoText substring)
631{
632 if (VuoText_isEmpty(substring))
633 {
634 if (string)
635 return VuoText_length(string) + 1;
636 else
637 return 1;
638 }
639
640 if (! string)
641 return 0;
642
643 size_t foundIndex = 0;
644
645 size_t stringLength = VuoText_length(string);
646 size_t substringLength = VuoText_length(substring);
647 if (stringLength < substringLength)
648 return 0;
649
650 for (size_t i = 1; i <= stringLength - substringLength + 1; ++i)
651 {
652 VuoText currSubstring = VuoText_substring(string, i, substringLength);
653 if (VuoText_areEqual(substring, currSubstring))
654 foundIndex = i;
655 VuoRetain(currSubstring);
656 VuoRelease(currSubstring);
657 }
658
659 return foundIndex;
660}
661
669{
670 if (VuoText_isEmpty(string) || VuoText_isEmpty(substring))
671 return NULL;
672
673 size_t stringLength = VuoText_length(string);
674 size_t substringLength = VuoText_length(substring);
675 if (stringLength < substringLength)
676 return 0;
677
679 for (size_t i = 1; i <= stringLength - substringLength + 1; ++i)
680 {
681 VuoText currSubstring = VuoText_substring(string, i, substringLength);
682 VuoLocal(currSubstring);
683 if (VuoText_areEqual(substring, currSubstring))
685 }
686
687 return found;
688}
689
704VuoText VuoText_substring(const VuoText string, int startIndex, int length)
705{
706 if (! string)
707 return VuoText_make("");
708
709 int originalLength = VuoText_length(string);
710 if (startIndex > originalLength)
711 return VuoText_make("");
712
713 if (startIndex < 1)
714 {
715 length -= 1 - startIndex;
716 startIndex = 1;
717 }
718
719 if (length < 0)
720 return VuoText_make("");
721
722 if (startIndex + length - 1 > originalLength)
723 length = originalLength - startIndex + 1;
724
725 size_t startIndexFromZero = startIndex - 1;
726
727 CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, string, kCFStringEncodingUTF8);
728 if (!s)
729 return VuoText_make("");
730
731 CFStringRef ss = CFStringCreateWithSubstring(kCFAllocatorDefault, s, CFRangeMake(startIndexFromZero, length));
732 if (!ss)
733 return VuoText_make("");
734
735 VuoText substring = VuoText_makeFromCFString(ss);
736
737 CFRelease(s);
738 CFRelease(ss);
739 return substring;
740}
741
746VuoText VuoText_append(VuoText *texts, size_t textsCount)
747{
748 if (!textsCount)
749 return NULL;
750
751 CFMutableArrayRef a = CFArrayCreateMutable(kCFAllocatorDefault, textsCount, &kCFTypeArrayCallBacks);
752 for (size_t i = 0; i < textsCount; ++i)
753 {
754 if (texts[i])
755 {
756 CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, texts[i], kCFStringEncodingUTF8);
757 if (!s)
758 continue;
759 CFArrayAppendValue(a, s);
760 CFRelease(s);
761 }
762 }
763
764 CFStringRef s = CFStringCreateByCombiningStrings(kCFAllocatorDefault, a, CFSTR(""));
765 VuoText compositeString = VuoText_makeFromCFString(s);
766
767 CFRelease(s);
768 CFRelease(a);
769 return compositeString;
770}
771
776VuoText VuoText_appendWithSeparator(VuoList_VuoText texts, VuoText separator, bool includeEmptyParts)
777{
778 unsigned long textsCount = VuoListGetCount_VuoText(texts);
779 if (!textsCount)
780 return NULL;
781
782 VuoText *textsArray = (VuoText *)malloc((textsCount * 2 - 1) * sizeof(VuoText));
783 unsigned long outputIndex = 0;
784 bool previousText = false;
785 for (unsigned long inputIndex = 1; inputIndex <= textsCount; ++inputIndex)
786 {
787 VuoText t = VuoListGetValue_VuoText(texts, inputIndex);
788 if (includeEmptyParts)
789 {
790 textsArray[outputIndex++] = t;
791 if (inputIndex < textsCount)
792 textsArray[outputIndex++] = separator;
793 }
794 else if (VuoText_isPopulated(t))
795 {
796 if (previousText && inputIndex <= textsCount)
797 textsArray[outputIndex++] = separator;
798 textsArray[outputIndex++] = t;
799 previousText = true;
800 }
801 }
802
803 VuoText compositeText = VuoText_append(textsArray, outputIndex);
804
805 free(textsArray);
806
807 return compositeText;
808}
809
814VuoText * VuoText_split(VuoText text, VuoText separator, bool includeEmptyParts, size_t *partsCount)
815{
816 if (!text || !separator)
817 return NULL;
818
819 CFMutableArrayRef splitTexts = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
820
821 CFStringRef textCF = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8);
822 if (!textCF)
823 return NULL;
824 VuoDefer(^{ CFRelease(textCF); });
825
826 size_t textLength = CFStringGetLength(textCF);
827
828 CFStringRef separatorCF = CFStringCreateWithCString(kCFAllocatorDefault, separator, kCFStringEncodingUTF8);
829 if (!separatorCF)
830 return NULL;
831 VuoDefer(^{ CFRelease(separatorCF); });
832
833 size_t separatorLength = CFStringGetLength(separatorCF);
834
835 if (separatorLength > 0)
836 {
837 size_t startIndex = 1;
838 size_t separatorIndex = 0;
839
840 while (startIndex <= textLength)
841 {
842 CFRange rangeToSearch = CFRangeMake(startIndex - 1, textLength - (startIndex - 1));
843 CFRange foundRange;
844 Boolean found = CFStringFindWithOptions(textCF, separatorCF, rangeToSearch, 0, &foundRange);
845 separatorIndex = foundRange.location + 1;
846 if (!found)
847 separatorIndex = textLength + 1;
848
849 if (separatorIndex > startIndex || includeEmptyParts)
850 {
851 CFStringRef partStr = CFStringCreateWithSubstring(kCFAllocatorDefault, textCF, CFRangeMake(startIndex - 1, separatorIndex - startIndex));
852 if (partStr)
853 {
854 CFArrayAppendValue(splitTexts, partStr);
855 CFRelease(partStr);
856 }
857 }
858
859 startIndex = separatorIndex + separatorLength;
860 }
861
862 if (includeEmptyParts && textLength > 0 && separatorIndex + separatorLength - 1 == textLength)
863 {
864 CFStringRef emptyPartStr = CFStringCreateWithCString(kCFAllocatorDefault, "", kCFStringEncodingUTF8);
865 if (emptyPartStr)
866 {
867 CFArrayAppendValue(splitTexts, emptyPartStr);
868 CFRelease(emptyPartStr);
869 }
870 }
871 }
872 else
873 {
874 for (size_t i = 1; i <= textLength; ++i)
875 {
876 CFStringRef partStr = CFStringCreateWithSubstring(kCFAllocatorDefault, textCF, CFRangeMake(i - 1, 1));
877 if (partStr)
878 {
879 CFArrayAppendValue(splitTexts, partStr);
880 CFRelease(partStr);
881 }
882 }
883 }
884
885 *partsCount = CFArrayGetCount(splitTexts);
886 VuoText *splitTextsArr = (VuoText *)malloc(*partsCount * sizeof(VuoText));
887 for (size_t i = 0; i < *partsCount; ++i)
888 {
889 CFStringRef part = CFArrayGetValueAtIndex(splitTexts, i);
890 splitTextsArr[i] = VuoText_makeFromCFString(part);
891 }
892 CFRelease(splitTexts);
893
894 return splitTextsArr;
895}
896
902VuoText VuoText_replace(VuoText subject, VuoText stringToFind, VuoText replacement)
903{
904 if (!subject)
905 return NULL;
906 if (!stringToFind)
907 return subject;
908
909 CFMutableStringRef subjectCF = CFStringCreateMutable(NULL, 0);
910 CFStringAppendCString(subjectCF, subject, kCFStringEncodingUTF8);
911
912 CFStringRef stringToFindCF = CFStringCreateWithCString(NULL, stringToFind, kCFStringEncodingUTF8);
913 if (!stringToFindCF)
914 {
915 CFRelease(subjectCF);
916 return subject;
917 }
918
919 CFStringRef replacementCF = nil;
920 if (replacement)
921 {
922 replacementCF = CFStringCreateWithCString(NULL, replacement, kCFStringEncodingUTF8);
923 if (!replacementCF)
924 {
925 CFRelease(stringToFindCF);
926 CFRelease(subjectCF);
927 return subject;
928 }
929 }
930
931 CFStringFindAndReplace(subjectCF, stringToFindCF, replacementCF, CFRangeMake(0,CFStringGetLength(subjectCF)), kCFCompareNonliteral);
932
933 VuoText replacedSubject = VuoText_makeFromCFString(subjectCF);
934 if (replacementCF)
935 CFRelease(replacementCF);
936 CFRelease(stringToFindCF);
937 CFRelease(subjectCF);
938
939 return replacedSubject;
940}
941
948VuoText VuoText_insert(const VuoText string, int startIndex, const VuoText newText)
949{
950 if (!newText)
951 return string;
952 if (!string)
953 return newText;
954
955 int len = VuoText_length(string);
956
957 if(startIndex > len) {
958 const char *append[2] = { string, newText };
959 return VuoText_append(append, 2);
960 } else if(startIndex <= 1) {
961 const char *append[2] = { newText, string };
962 return VuoText_append(append, 2);
963 }
964
965 VuoText left = VuoText_substring(string, 1, startIndex - 1);
966 VuoText right = VuoText_substring(string, startIndex, (len + 1) - startIndex);
967
968 VuoLocal(left);
969 VuoLocal(right);
970
971 const char *append[3] = { left, newText, right };
972 return VuoText_append(append, 3);
973}
974
980VuoText VuoText_removeAt(const VuoText string, int startIndex, int length)
981{
982 int len = VuoText_length(string);
983
984 if(startIndex < 1)
985 {
986 length -= (1 - startIndex);
987 startIndex = 1;
988 }
989
990 // if start is greater than original length or start + len is the whole array
991 if(startIndex > len || length < 1)
992 return VuoText_make(string);
993
994 VuoText left = VuoText_substring(string, 1, startIndex - 1);
995 VuoText right = VuoText_substring(string, startIndex + length, (len + 1) - startIndex);
996
997 VuoLocal(left);
998 VuoLocal(right);
999
1000 return VuoText_makeWithoutCopying(VuoText_format("%s%s", left, right));
1001}
1002
1011char *VuoText_format(const char *format, ...)
1012{
1013 va_list args;
1014
1015 va_start(args, format);
1016 int size = vsnprintf(NULL, 0, format, args);
1017 va_end(args);
1018
1019 char *formattedString = (char *)malloc(size+1);
1020 va_start(args, format);
1021 vsnprintf(formattedString, size+1, format, args);
1022 va_end(args);
1023
1024 return formattedString;
1025}
1026
1035{
1036 if (!text)
1037 return NULL;
1038
1039 size_t len = strlen(text);
1040 size_t firstNonSpace;
1041 for (firstNonSpace = 0; firstNonSpace < len && isspace(text[firstNonSpace]); ++firstNonSpace);
1042
1043 if (firstNonSpace == len)
1044 return VuoText_make("");
1045
1046 size_t lastNonSpace;
1047 for (lastNonSpace = len-1; lastNonSpace > firstNonSpace && isspace(text[lastNonSpace]); --lastNonSpace);
1048
1049 return VuoText_makeWithMaxLength(text + firstNonSpace, lastNonSpace - firstNonSpace + 1);
1050}
1051
1055static bool VuoText_isASCII7(VuoText text)
1056{
1057 size_t len = strlen(text);
1058 for (size_t i = 0; i < len; ++i)
1059 if (((unsigned char *)text)[i] > 127)
1060 return false;
1061
1062 return true;
1063}
1064
1069{
1070 if (!text)
1071 return NULL;
1072
1073 // Optimized conversion for plain ASCII7 text.
1074 if (VuoText_isASCII7(text))
1075 {
1076 if (textCase == VuoTextCase_LowercaseAll)
1077 {
1078 size_t len = strlen(text);
1079 char *processedString = malloc(len + 1);
1080 for (size_t i = 0; i < len; ++i)
1081 processedString[i] = tolower(text[i]);
1082 processedString[len] = 0;
1083 VuoRegister(processedString, free);
1084 return processedString;
1085 }
1086 }
1087
1088 CFMutableStringRef mutable_str = CFStringCreateMutable(NULL, 0);
1089 CFStringAppendCString(mutable_str, text, kCFStringEncodingUTF8);
1090 CFLocaleRef locale = CFLocaleCopyCurrent();
1091
1092 switch( textCase )
1093 {
1095 CFStringLowercase(mutable_str, locale);
1096 break;
1097
1099 CFStringUppercase(mutable_str, locale);
1100 break;
1101
1103 CFStringCapitalize(mutable_str, locale);
1104 break;
1105
1107 {
1108 // The rest of the string functions lower-case everything by default
1109 CFStringLowercase(mutable_str, locale);
1110
1111 // the sentence tokenizer does a better job when all characters are capitalized
1112 CFStringRef tmp = CFStringCreateWithSubstring(kCFAllocatorDefault, mutable_str, CFRangeMake(0, CFStringGetLength(mutable_str)));
1113 CFMutableStringRef all_upper = CFStringCreateMutableCopy(NULL, 0, tmp);
1114 CFRelease(tmp);
1115 CFStringUppercase(all_upper, locale);
1116
1117 CFStringTokenizerRef tokenizer = CFStringTokenizerCreate( kCFAllocatorDefault,
1118 all_upper,
1119 CFRangeMake(0, CFStringGetLength(all_upper)),
1120 kCFStringTokenizerUnitSentence,
1121 locale);
1122
1123 CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone;
1124
1125 // https://stackoverflow.com/questions/15515128/capitalize-first-letter-of-every-sentence-in-nsstring
1126 while(kCFStringTokenizerTokenNone != (tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)))
1127 {
1128 CFRange tokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer);
1129
1130 if (tokenRange.location != kCFNotFound && tokenRange.length > 0)
1131 {
1132 CFRange firstCharRange = CFRangeMake(tokenRange.location, 1);
1133 CFStringRef firstLetter = CFStringCreateWithSubstring(kCFAllocatorDefault, mutable_str, firstCharRange);
1134 CFMutableStringRef upperFirst = CFStringCreateMutableCopy(NULL, 0, firstLetter);
1135 CFRelease(firstLetter);
1136 CFStringCapitalize(upperFirst, locale);
1137 CFStringReplace(mutable_str, firstCharRange, upperFirst);
1138 CFRelease(upperFirst);
1139 }
1140 }
1141
1142 CFRelease(all_upper);
1143 CFRelease(tokenizer);
1144 }
1145 break;
1146 }
1147
1148 VuoText processedString = VuoText_makeFromCFString(mutable_str);
1149
1150 CFRelease(locale);
1151 CFRelease(mutable_str);
1152
1153 return processedString;
1154}
1155
1165uint32_t* VuoText_getUtf32Values(const VuoText text, size_t* length)
1166{
1167 CFMutableStringRef cf_str = CFStringCreateMutable(NULL, 0);
1168 CFStringAppendCString(cf_str, text, kCFStringEncodingUTF8);
1169
1170 size_t str_len = CFStringGetLength(cf_str);
1171
1172 CFRange range = CFRangeMake(0, str_len);
1173 CFIndex usedBufLen;
1174 *length = (size_t) CFStringGetBytes(cf_str, range, kCFStringEncodingUTF32, '?', false, NULL, str_len, &usedBufLen );
1175
1176 uint32_t* decimal = (uint32_t*) NULL;
1177
1178 if(*length > 0)
1179 {
1180 decimal = (uint32_t*) malloc( sizeof(uint32_t) * usedBufLen );
1181 CFStringGetBytes(cf_str, range, kCFStringEncodingUTF32, '?', false, (uint8_t*) decimal, usedBufLen, NULL);
1182 }
1183
1184 CFRelease(cf_str);
1185
1186 return decimal;
1187}