Vuo  2.0.1
VuoText.c
Go to the documentation of this file.
1 
10 #include <Carbon/Carbon.h>
11 #include <fnmatch.h>
12 #include <regex.h>
13 #include <string.h>
14 #include <xlocale.h>
15 
16 #include "type.h"
17 
19 #ifdef VUO_COMPILER
21  "title" : "Text",
22  "description" : "A Unicode (UTF-8) text string.",
23  "keywords" : [ "char *", "character" ],
24  "version" : "1.0.0",
25  "dependencies" : [
26  "Carbon.framework",
27  "VuoInteger",
28  "VuoReal",
29  "VuoTextCase",
30  "VuoList_VuoInteger"
31  ]
32  });
33 #endif
34 
41 {
42  const char *textString = NULL;
43  if (json_object_get_type(js) == json_type_string)
44  textString = VuoText_make(json_object_get_string(js));
45  return textString;
46 }
47 
53 {
54  if (!value)
55  return NULL;
56 
57  return json_object_new_string(value);
58 }
59 
65 VuoText VuoText_truncateWithEllipsis(const VuoText subject, int maxLength, VuoTextTruncation where)
66 {
67  if (!subject)
68  return VuoText_make("");
69 
70  size_t length = VuoText_length(subject);
71  if (length <= maxLength)
72  return subject;
73  else
74  {
75  VuoText abbreviation = VuoText_substring(subject, (where == VuoTextTruncation_End ? 1 : 1 + length - maxLength), maxLength);
76  VuoText ellipsis = VuoText_make("…");
77  VuoText summaryParts[2] = { abbreviation, ellipsis };
78  if (where == VuoTextTruncation_Beginning)
79  {
80  summaryParts[0] = ellipsis;
81  summaryParts[1] = abbreviation;
82  }
83  VuoText summaryWhole = VuoText_append(summaryParts, 2);
84 
85  VuoRetain(abbreviation);
86  VuoRelease(abbreviation);
87  VuoRetain(ellipsis);
88  VuoRelease(ellipsis);
89  return summaryWhole;
90  }
91 }
92 
100 char * VuoText_getSummary(const VuoText value)
101 {
102  if (VuoText_isEmpty(value))
103  return strdup("<code>&#0;</code>");
104 
105  VuoText truncatedText = VuoText_truncateWithEllipsis(value, 1024, VuoTextTruncation_End);
106 
107  // VuoText_truncateWithEllipsis() may return the same string passed in.
108  // Only dealloc it if new text was actually created.
109  if (truncatedText != value)
110  VuoRetain(truncatedText);
111 
112  VuoText escapedText = VuoText_replace(truncatedText, "&", "&amp;");
113  if (truncatedText != value)
114  VuoRelease(truncatedText);
115 
116  VuoLocal(escapedText);
117  VuoText escapedText2 = VuoText_replace(escapedText, "<", "&lt;");
118  VuoLocal(escapedText2);
119  char *summary = VuoText_format("<code>%s</code>", escapedText2);
120  return summary;
121 }
122 
127 VuoText VuoText_make(const char * unquotedString)
128 {
129  VuoText text;
130  if (unquotedString)
131  text = strdup(unquotedString);
132  else
133  text = strdup("");
134  VuoRegister(text, free);
135  return text;
136 }
137 
152 VuoText VuoText_makeWithMaxLength(const void *data, const size_t maxLength)
153 {
154  char *text;
155  if (data && ((char *)data)[maxLength-1] == 0)
156  {
157  // Faster than scanning through the array byte-by-byte.
158  text = (char *)calloc(1, maxLength);
159  memcpy(text, data, maxLength);
160  }
161  else if (data)
162  {
163  text = (char *)calloc(1, maxLength+1);
164  for (unsigned int i = 0; i < maxLength; ++i)
165  {
166  text[i] = ((char *)data)[i];
167  if (((char *)data)[i] == 0)
168  break;
169  }
170  }
171  else
172  text = strdup("");
173  VuoRegister(text, free);
174  return text;
175 }
176 
181 {
182  if (!cfs)
183  return NULL;
184 
185  CFStringRef cfString = (CFStringRef)cfs;
186 
187  // https://stackoverflow.com/questions/1609565/whats-the-cfstring-equiv-of-nsstrings-utf8string
188  const char *utf8StringPtr = CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8);
189  if (utf8StringPtr)
190  return VuoText_make(utf8StringPtr);
191  else
192  {
193  CFIndex maxBytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfString), kCFStringEncodingUTF8) + 1;
194  char *t = calloc(1, maxBytes);
195  CFStringGetCString(cfString, t, maxBytes, kCFStringEncodingUTF8);
196  VuoRegister(t, free);
197  return t;
198  }
199 }
200 
214 #define UTF8_ACCEPT 0
215 #define UTF8_REJECT 1
216 
217 
220 static const uint8_t utf8d[] = {
221  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
222  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
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, // 40..5f
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, // 60..7f
225  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
226  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
227  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
228  0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
229  0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
230  0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
231  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
232  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
233  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
234  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
235 };
236 
240 static bool VuoText_isValidUtf8(const unsigned char *data, unsigned long size)
241 {
242  // Faster than CFStringCreateFromExternalRepresentation.
243  uint32_t codepoint;
244  for (uint32_t pos = 0, state = 0; *data && pos++ < size; ++data)
245  {
246  uint32_t byte = *data;
247  uint32_t type = utf8d[byte];
248 
249  codepoint = (state != UTF8_ACCEPT) ?
250  (byte & 0x3fu) | (codepoint << 6) :
251  (0xff >> type) & (byte);
252 
253  state = utf8d[256 + state*16 + type];
254 
255  if (state == UTF8_REJECT)
256  return false;
257  }
258 
259  return true;
260 }
261 
269 VuoText VuoText_makeFromData(const unsigned char *data, const unsigned long size)
270 {
271  if (!size || !data)
272  return NULL;
273 
274  if (!VuoText_isValidUtf8(data, size))
275  return NULL;
276 
277  return VuoText_makeWithMaxLength(data, size);
278 }
279 
287 VuoText VuoText_makeFromUtf32(const uint32_t* data, size_t size)
288 {
289  CFStringRef cf_str = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*) data, size * sizeof(uint32_t), kCFStringEncodingUTF32LE, false);
290 
291  if(cf_str != NULL)
292  {
293  CFIndex str_len = CFStringGetLength(cf_str);
294  CFRange range = CFRangeMake(0, str_len);
295  CFIndex usedBufLen;
296  CFIndex length = CFStringGetBytes(cf_str, range, kCFStringEncodingUTF8, '?', false, NULL, str_len, &usedBufLen );
297 
298  if(length > 0)
299  {
300  char* buffer = (char*) malloc(sizeof(char) * usedBufLen + 1);
301  CFIndex used;
302  CFStringGetBytes(cf_str, range, kCFStringEncodingUTF8, '?', false, (uint8_t*) buffer, usedBufLen + 1, &used);
303  buffer[used] = '\0';
304  VuoText text = VuoText_make(buffer);
305  free(buffer);
306  CFRelease(cf_str);
307  return text;
308  }
309 
310  CFRelease(cf_str);
311  }
312 
313  return VuoText_make("");
314 }
315 
320 {
321  if (!string)
322  return NULL;
323 
324  size_t len = strlen(string);
325  CFStringRef cf = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)string, len, kCFStringEncodingMacRoman, false);
326  if (!cf)
327  return NULL;
328 
330 
331  CFRelease(cf);
332  return t;
333 }
334 
339 size_t VuoText_length(const VuoText text)
340 {
341  if (! text)
342  return 0;
343 
344  CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8);
345  if (!s)
346  return 0;
347 
348  size_t length = CFStringGetLength(s);
349  CFRelease(s);
350  return length;
351 }
352 
356 size_t VuoText_byteCount(const VuoText text)
357 {
358  if (! text)
359  return 0;
360 
361  return strlen(text);
362 }
363 
367 bool VuoText_isEmpty(const VuoText text)
368 {
369  return !text || text[0] == 0;
370 }
371 
375 bool VuoText_isPopulated(const VuoText text)
376 {
377  return !VuoText_isEmpty(text);
378 }
379 
386 bool VuoText_areEqual(const VuoText text1, const VuoText text2)
387 {
388  if (text1 == text2)
389  return true;
390 
391  if (! text1 || ! text2)
392  return (! text1 && ! text2);
393 
394  CFStringRef s1 = CFStringCreateWithCString(kCFAllocatorDefault, text1, kCFStringEncodingUTF8);
395  if (!s1)
396  return false;
397 
398  CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, text2, kCFStringEncodingUTF8);
399  if (!s2)
400  {
401  CFRelease(s1);
402  return false;
403  }
404 
405  CFComparisonResult result = CFStringCompare(s1, s2, kCFCompareNonliteral | kCFCompareWidthInsensitive);
406 
407  CFRelease(s1);
408  CFRelease(s2);
409  return (result == kCFCompareEqualTo);
410 }
411 
415 static bool isLessThan(const VuoText text1, const VuoText text2, CFStringCompareFlags flags)
416 {
417  // Treat null text as greater than non-null text,
418  // so the more useful non-null text sorts to the beginning of the list.
419  if (! text1 || ! text2)
420  return text1 && !text2;
421 
422  CFStringRef s1 = CFStringCreateWithCString(kCFAllocatorDefault, text1, kCFStringEncodingUTF8);
423  if (!s1)
424  return false;
425 
426  CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, text2, kCFStringEncodingUTF8);
427  if (!s2)
428  {
429  CFRelease(s1);
430  return false;
431  }
432 
433  CFComparisonResult result = CFStringCompare(s1, s2, kCFCompareNonliteral | kCFCompareWidthInsensitive | flags);
434 
435  CFRelease(s1);
436  CFRelease(s2);
437  return (result == kCFCompareLessThan);
438 }
439 
445 bool VuoText_isLessThan(const VuoText text1, const VuoText text2)
446 {
447  return isLessThan(text1, text2, 0);
448 }
449 
455 bool VuoText_isLessThanCaseInsensitive(const VuoText text1, const VuoText text2)
456 {
457  return isLessThan(text1, text2, kCFCompareCaseInsensitive);
458 }
459 
464 bool VuoText_isLessThanNumeric(const VuoText text1, const VuoText text2)
465 {
466  VuoReal real1 = (text1 ? VuoReal_makeFromString(text1) : 0);
467  VuoReal real2 = (text2 ? VuoReal_makeFromString(text2) : 0);
468  return real1 < real2;
469 }
470 
478 bool VuoText_compare(VuoText text1, VuoTextComparison comparison, VuoText text2)
479 {
480  if (! comparison.isCaseSensitive)
481  {
484  }
485 
486  bool match = false;
487  if (comparison.type == VuoTextComparison_Equals)
488  {
489  match = VuoText_areEqual(text1, text2);
490  }
491  else if (comparison.type == VuoTextComparison_Contains)
492  {
493  match = (VuoText_findFirstOccurrence(text1, text2, 1) > 0);
494  }
495  else if (comparison.type == VuoTextComparison_BeginsWith || comparison.type == VuoTextComparison_EndsWith)
496  {
497  size_t aLength = VuoText_length(text1);
498  size_t bLength = VuoText_length(text2);
499 
500  if (bLength == 0)
501  {
502  match = true;
503  }
504  else
505  {
506  int startIndex = (comparison.type == VuoTextComparison_BeginsWith ? 1 : aLength - bLength + 1);
507  VuoText aSub = VuoText_substring(text1, startIndex, bLength);
508  match = VuoText_areEqual(aSub, text2);
509 
510  VuoRetain(aSub);
511  VuoRelease(aSub);
512  }
513  }
514 
515  else if (comparison.type == VuoTextComparison_MatchesWildcard)
516  {
517  bool t1empty = VuoText_isEmpty(text1);
518  bool t2empty = VuoText_isEmpty(text2);
519  if (t2empty)
520  return t1empty == t2empty;
521 
522  locale_t oldLocale = uselocale(NULL);
523  locale_t locale = newlocale(LC_ALL_MASK, "en_US.UTF-8", NULL);
524  uselocale(locale);
525 
526  match = fnmatch(text2, text1, 0) != FNM_NOMATCH;
527 
528  uselocale(oldLocale);
529  freelocale(locale);
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 
582 size_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 
616 size_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 
690 VuoText 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 
732 VuoText VuoText_append(VuoText *texts, size_t textsCount)
733 {
734  CFMutableArrayRef a = CFArrayCreateMutable(kCFAllocatorDefault, textsCount, &kCFTypeArrayCallBacks);
735  for (size_t i = 0; i < textsCount; ++i)
736  {
737  if (texts[i])
738  {
739  CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, texts[i], kCFStringEncodingUTF8);
740  if (!s)
741  continue;
742  CFArrayAppendValue(a, s);
743  CFRelease(s);
744  }
745  }
746 
747  CFStringRef s = CFStringCreateByCombiningStrings(kCFAllocatorDefault, a, CFSTR(""));
748  VuoText compositeString = VuoText_makeFromCFString(s);
749 
750  CFRelease(s);
751  CFRelease(a);
752  return compositeString;
753 }
754 
759 VuoText * VuoText_split(VuoText text, VuoText separator, bool includeEmptyParts, size_t *partsCount)
760 {
761  if (!text || !separator)
762  return NULL;
763 
764  CFMutableArrayRef splitTexts = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
765 
766  CFStringRef textCF = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8);
767  if (!textCF)
768  return NULL;
769  VuoDefer(^{ CFRelease(textCF); });
770 
771  size_t textLength = CFStringGetLength(textCF);
772 
773  CFStringRef separatorCF = CFStringCreateWithCString(kCFAllocatorDefault, separator, kCFStringEncodingUTF8);
774  if (!separatorCF)
775  return NULL;
776  VuoDefer(^{ CFRelease(separatorCF); });
777 
778  size_t separatorLength = CFStringGetLength(separatorCF);
779 
780  if (separatorLength > 0)
781  {
782  size_t startIndex = 1;
783  size_t separatorIndex = 0;
784 
785  while (startIndex <= textLength)
786  {
787  CFRange rangeToSearch = CFRangeMake(startIndex - 1, textLength - (startIndex - 1));
788  CFRange foundRange;
789  Boolean found = CFStringFindWithOptions(textCF, separatorCF, rangeToSearch, 0, &foundRange);
790  separatorIndex = foundRange.location + 1;
791  if (!found)
792  separatorIndex = textLength + 1;
793 
794  if (separatorIndex > startIndex || includeEmptyParts)
795  {
796  CFStringRef partStr = CFStringCreateWithSubstring(kCFAllocatorDefault, textCF, CFRangeMake(startIndex - 1, separatorIndex - startIndex));
797  if (partStr)
798  {
799  CFArrayAppendValue(splitTexts, partStr);
800  CFRelease(partStr);
801  }
802  }
803 
804  startIndex = separatorIndex + separatorLength;
805  }
806 
807  if (includeEmptyParts && textLength > 0 && separatorIndex + separatorLength - 1 == textLength)
808  {
809  CFStringRef emptyPartStr = CFStringCreateWithCString(kCFAllocatorDefault, "", kCFStringEncodingUTF8);
810  if (emptyPartStr)
811  {
812  CFArrayAppendValue(splitTexts, emptyPartStr);
813  CFRelease(emptyPartStr);
814  }
815  }
816  }
817  else
818  {
819  for (size_t i = 1; i <= textLength; ++i)
820  {
821  CFStringRef partStr = CFStringCreateWithSubstring(kCFAllocatorDefault, textCF, CFRangeMake(i - 1, 1));
822  if (partStr)
823  {
824  CFArrayAppendValue(splitTexts, partStr);
825  CFRelease(partStr);
826  }
827  }
828  }
829 
830  *partsCount = CFArrayGetCount(splitTexts);
831  VuoText *splitTextsArr = (VuoText *)malloc(*partsCount * sizeof(VuoText));
832  for (size_t i = 0; i < *partsCount; ++i)
833  {
834  CFStringRef part = CFArrayGetValueAtIndex(splitTexts, i);
835  splitTextsArr[i] = VuoText_makeFromCFString(part);
836  }
837  CFRelease(splitTexts);
838 
839  return splitTextsArr;
840 }
841 
847 VuoText VuoText_replace(VuoText subject, VuoText stringToFind, VuoText replacement)
848 {
849  if (!subject)
850  return NULL;
851  if (!stringToFind)
852  return subject;
853 
854  CFMutableStringRef subjectCF = CFStringCreateMutable(NULL, 0);
855  CFStringAppendCString(subjectCF, subject, kCFStringEncodingUTF8);
856 
857  CFStringRef stringToFindCF = CFStringCreateWithCString(NULL, stringToFind, kCFStringEncodingUTF8);
858  if (!stringToFindCF)
859  {
860  CFRelease(subjectCF);
861  return subject;
862  }
863 
864  CFStringRef replacementCF = nil;
865  if (replacement)
866  {
867  replacementCF = CFStringCreateWithCString(NULL, replacement, kCFStringEncodingUTF8);
868  if (!replacementCF)
869  {
870  CFRelease(stringToFindCF);
871  CFRelease(subjectCF);
872  return subject;
873  }
874  }
875 
876  CFStringFindAndReplace(subjectCF, stringToFindCF, replacementCF, CFRangeMake(0,CFStringGetLength(subjectCF)), kCFCompareNonliteral);
877 
878  VuoText replacedSubject = VuoText_makeFromCFString(subjectCF);
879  if (replacementCF)
880  CFRelease(replacementCF);
881  CFRelease(stringToFindCF);
882  CFRelease(subjectCF);
883 
884  return replacedSubject;
885 }
886 
893 VuoText VuoText_insert(const VuoText string, int startIndex, const VuoText newText)
894 {
895  if (!newText)
896  return string;
897  if (!string)
898  return newText;
899 
900  int len = VuoText_length(string);
901 
902  if(startIndex > len) {
903  const char *append[2] = { string, newText };
904  return VuoText_append(append, 2);
905  } else if(startIndex <= 1) {
906  const char *append[2] = { newText, string };
907  return VuoText_append(append, 2);
908  }
909 
910  VuoText left = VuoText_substring(string, 1, startIndex - 1);
911  VuoText right = VuoText_substring(string, startIndex, (len + 1) - startIndex);
912 
913  VuoLocal(left);
914  VuoLocal(right);
915 
916  const char *append[3] = { left, newText, right };
917  return VuoText_append(append, 3);
918 }
919 
925 VuoText VuoText_removeAt(const VuoText string, int startIndex, int length)
926 {
927  int len = VuoText_length(string);
928 
929  if(startIndex < 1)
930  {
931  length -= (1 - startIndex);
932  startIndex = 1;
933  }
934 
935  // if start is greater than original length or start + len is the whole array
936  if(startIndex > len || length < 1)
937  return VuoText_make(string);
938 
939  VuoText left = VuoText_substring(string, 1, startIndex - 1);
940  VuoText right = VuoText_substring(string, startIndex + length, (len + 1) - startIndex);
941 
942  VuoLocal(left);
943  VuoLocal(right);
944 
945  return VuoText_make(VuoText_format("%s%s", left, right));
946 }
947 
953 char *VuoText_format(const char *format, ...)
954 {
955  va_list args;
956 
957  va_start(args, format);
958  int size = vsnprintf(NULL, 0, format, args);
959  va_end(args);
960 
961  char *formattedString = (char *)malloc(size+1);
962  va_start(args, format);
963  vsnprintf(formattedString, size+1, format, args);
964  va_end(args);
965 
966  return formattedString;
967 }
968 
977 {
978  if (!text)
979  return NULL;
980 
981  size_t len = strlen(text);
982  size_t firstNonSpace;
983  for (firstNonSpace = 0; firstNonSpace < len && isspace(text[firstNonSpace]); ++firstNonSpace);
984 
985  if (firstNonSpace == len)
986  return VuoText_make("");
987 
988  size_t lastNonSpace;
989  for (lastNonSpace = len-1; lastNonSpace > firstNonSpace && isspace(text[lastNonSpace]); --lastNonSpace);
990 
991  return VuoText_makeWithMaxLength(text + firstNonSpace, lastNonSpace - firstNonSpace + 1);
992 }
993 
997 static bool VuoText_isASCII7(VuoText text)
998 {
999  size_t len = strlen(text);
1000  for (size_t i = 0; i < len; ++i)
1001  if (((unsigned char *)text)[i] > 127)
1002  return false;
1003 
1004  return true;
1005 }
1006 
1011 {
1012  if (!text)
1013  return NULL;
1014 
1015  // Optimized conversion for plain ASCII7 text.
1016  if (VuoText_isASCII7(text))
1017  {
1018  if (textCase == VuoTextCase_LowercaseAll)
1019  {
1020  size_t len = strlen(text);
1021  char *processedString = malloc(len + 1);
1022  for (size_t i = 0; i < len; ++i)
1023  processedString[i] = tolower(text[i]);
1024  processedString[len] = 0;
1025  VuoRegister(processedString, free);
1026  return processedString;
1027  }
1028  }
1029 
1030  CFMutableStringRef mutable_str = CFStringCreateMutable(NULL, 0);
1031  CFStringAppendCString(mutable_str, text, kCFStringEncodingUTF8);
1032  CFLocaleRef locale = CFLocaleCopyCurrent();
1033 
1034  switch( textCase )
1035  {
1037  CFStringLowercase(mutable_str, locale);
1038  break;
1039 
1041  CFStringUppercase(mutable_str, locale);
1042  break;
1043 
1045  CFStringCapitalize(mutable_str, locale);
1046  break;
1047 
1049  {
1050  // The rest of the string functions lower-case everything by default
1051  CFStringLowercase(mutable_str, locale);
1052 
1053  // the sentence tokenizer does a better job when all characters are capitalized
1054  CFStringRef tmp = CFStringCreateWithSubstring(kCFAllocatorDefault, mutable_str, CFRangeMake(0, CFStringGetLength(mutable_str)));
1055  CFMutableStringRef all_upper = CFStringCreateMutableCopy(NULL, 0, tmp);
1056  CFRelease(tmp);
1057  CFStringUppercase(all_upper, locale);
1058 
1059  CFStringTokenizerRef tokenizer = CFStringTokenizerCreate( kCFAllocatorDefault,
1060  all_upper,
1061  CFRangeMake(0, CFStringGetLength(all_upper)),
1062  kCFStringTokenizerUnitSentence,
1063  locale);
1064 
1065  CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone;
1066 
1067  // https://stackoverflow.com/questions/15515128/capitalize-first-letter-of-every-sentence-in-nsstring
1068  while(kCFStringTokenizerTokenNone != (tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)))
1069  {
1070  CFRange tokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer);
1071 
1072  if (tokenRange.location != kCFNotFound && tokenRange.length > 0)
1073  {
1074  CFRange firstCharRange = CFRangeMake(tokenRange.location, 1);
1075  CFStringRef firstLetter = CFStringCreateWithSubstring(kCFAllocatorDefault, mutable_str, firstCharRange);
1076  CFMutableStringRef upperFirst = CFStringCreateMutableCopy(NULL, 0, firstLetter);
1077  CFRelease(firstLetter);
1078  CFStringCapitalize(upperFirst, locale);
1079  CFStringReplace(mutable_str, firstCharRange, upperFirst);
1080  CFRelease(upperFirst);
1081  }
1082  }
1083 
1084  CFRelease(all_upper);
1085  CFRelease(tokenizer);
1086  }
1087  break;
1088  }
1089 
1090  VuoText processedString = VuoText_makeFromCFString(mutable_str);
1091 
1092  CFRelease(locale);
1093  CFRelease(mutable_str);
1094 
1095  return processedString;
1096 }
1097 
1107 uint32_t* VuoText_getUtf32Values(const VuoText text, size_t* length)
1108 {
1109  CFMutableStringRef cf_str = CFStringCreateMutable(NULL, 0);
1110  CFStringAppendCString(cf_str, text, kCFStringEncodingUTF8);
1111 
1112  size_t str_len = CFStringGetLength(cf_str);
1113 
1114  CFRange range = CFRangeMake(0, str_len);
1115  CFIndex usedBufLen;
1116  *length = (size_t) CFStringGetBytes(cf_str, range, kCFStringEncodingUTF32, '?', false, NULL, str_len, &usedBufLen );
1117 
1118  uint32_t* decimal = (uint32_t*) NULL;
1119 
1120  if(*length > 0)
1121  {
1122  decimal = (uint32_t*) malloc( sizeof(uint32_t) * usedBufLen );
1123  CFStringGetBytes(cf_str, range, kCFStringEncodingUTF32, '?', false, (uint8_t*) decimal, usedBufLen, NULL);
1124  }
1125 
1126  CFRelease(cf_str);
1127 
1128  return decimal;
1129 }