Vuo  2.0.0
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 
189  const char *useUTF8StringPtr = NULL;
190  char *freeUTF8StringPtr = NULL;
191 
192  if ((useUTF8StringPtr = CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8)) == NULL)
193  {
194  CFIndex stringLength = CFStringGetLength(cfString);
195  CFIndex maxBytes = 4 * stringLength + 1;
196  freeUTF8StringPtr = malloc(maxBytes);
197  CFStringGetCString(cfString, freeUTF8StringPtr, maxBytes, kCFStringEncodingUTF8);
198  useUTF8StringPtr = freeUTF8StringPtr;
199  }
200 
201  VuoText text = VuoText_make(useUTF8StringPtr);
202 
203  if (freeUTF8StringPtr != NULL)
204  free(freeUTF8StringPtr);
205 
206  return text;
207 }
208 
222 #define UTF8_ACCEPT 0
223 #define UTF8_REJECT 1
224 
225 
228 static const uint8_t utf8d[] = {
229  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
230  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
231  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
232  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
233  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
234  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
235  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
236  0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
237  0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
238  0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
239  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
240  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
241  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
242  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
243 };
244 
248 static bool VuoText_isValidUtf8(const unsigned char *data, unsigned long size)
249 {
250  // Faster than CFStringCreateFromExternalRepresentation.
251  uint32_t codepoint;
252  for (uint32_t pos = 0, state = 0; *data && pos++ < size; ++data)
253  {
254  uint32_t byte = *data;
255  uint32_t type = utf8d[byte];
256 
257  codepoint = (state != UTF8_ACCEPT) ?
258  (byte & 0x3fu) | (codepoint << 6) :
259  (0xff >> type) & (byte);
260 
261  state = utf8d[256 + state*16 + type];
262 
263  if (state == UTF8_REJECT)
264  return false;
265  }
266 
267  return true;
268 }
269 
277 VuoText VuoText_makeFromData(const unsigned char *data, const unsigned long size)
278 {
279  if (!size || !data)
280  return NULL;
281 
282  if (!VuoText_isValidUtf8(data, size))
283  return NULL;
284 
285  return VuoText_makeWithMaxLength(data, size);
286 }
287 
295 VuoText VuoText_makeFromUtf32(const uint32_t* data, size_t size)
296 {
297  CFStringRef cf_str = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*) data, size * sizeof(uint32_t), kCFStringEncodingUTF32LE, false);
298 
299  if(cf_str != NULL)
300  {
301  CFIndex str_len = CFStringGetLength(cf_str);
302  CFRange range = CFRangeMake(0, str_len);
303  CFIndex usedBufLen;
304  CFIndex length = CFStringGetBytes(cf_str, range, kCFStringEncodingUTF8, '?', false, NULL, str_len, &usedBufLen );
305 
306  if(length > 0)
307  {
308  char* buffer = (char*) malloc(sizeof(char) * usedBufLen + 1);
309  CFIndex used;
310  CFStringGetBytes(cf_str, range, kCFStringEncodingUTF8, '?', false, (uint8_t*) buffer, usedBufLen + 1, &used);
311  buffer[used] = '\0';
312  VuoText text = VuoText_make(buffer);
313  free(buffer);
314  CFRelease(cf_str);
315  return text;
316  }
317 
318  CFRelease(cf_str);
319  }
320 
321  return VuoText_make("");
322 }
323 
328 {
329  if (!string)
330  return NULL;
331 
332  size_t len = strlen(string);
333  CFStringRef cf = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)string, len, kCFStringEncodingMacRoman, false);
334  if (!cf)
335  return NULL;
336 
338 
339  CFRelease(cf);
340  return t;
341 }
342 
347 size_t VuoText_length(const VuoText text)
348 {
349  if (! text)
350  return 0;
351 
352  CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8);
353  if (!s)
354  return 0;
355 
356  size_t length = CFStringGetLength(s);
357  CFRelease(s);
358  return length;
359 }
360 
364 size_t VuoText_byteCount(const VuoText text)
365 {
366  if (! text)
367  return 0;
368 
369  return strlen(text);
370 }
371 
375 bool VuoText_isEmpty(const VuoText text)
376 {
377  return !text || text[0] == 0;
378 }
379 
383 bool VuoText_isPopulated(const VuoText text)
384 {
385  return !VuoText_isEmpty(text);
386 }
387 
394 bool VuoText_areEqual(const VuoText text1, const VuoText text2)
395 {
396  if (text1 == text2)
397  return true;
398 
399  if (! text1 || ! text2)
400  return (! text1 && ! text2);
401 
402  CFStringRef s1 = CFStringCreateWithCString(kCFAllocatorDefault, text1, kCFStringEncodingUTF8);
403  if (!s1)
404  return false;
405 
406  CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, text2, kCFStringEncodingUTF8);
407  if (!s2)
408  {
409  CFRelease(s1);
410  return false;
411  }
412 
413  CFComparisonResult result = CFStringCompare(s1, s2, kCFCompareNonliteral | kCFCompareWidthInsensitive);
414 
415  CFRelease(s1);
416  CFRelease(s2);
417  return (result == kCFCompareEqualTo);
418 }
419 
423 static bool isLessThan(const VuoText text1, const VuoText text2, CFStringCompareFlags flags)
424 {
425  // Treat null text as greater than non-null text,
426  // so the more useful non-null text sorts to the beginning of the list.
427  if (! text1 || ! text2)
428  return text1 && !text2;
429 
430  CFStringRef s1 = CFStringCreateWithCString(kCFAllocatorDefault, text1, kCFStringEncodingUTF8);
431  if (!s1)
432  return false;
433 
434  CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, text2, kCFStringEncodingUTF8);
435  if (!s2)
436  {
437  CFRelease(s1);
438  return false;
439  }
440 
441  CFComparisonResult result = CFStringCompare(s1, s2, kCFCompareNonliteral | kCFCompareWidthInsensitive | flags);
442 
443  CFRelease(s1);
444  CFRelease(s2);
445  return (result == kCFCompareLessThan);
446 }
447 
453 bool VuoText_isLessThan(const VuoText text1, const VuoText text2)
454 {
455  return isLessThan(text1, text2, 0);
456 }
457 
463 bool VuoText_isLessThanCaseInsensitive(const VuoText text1, const VuoText text2)
464 {
465  return isLessThan(text1, text2, kCFCompareCaseInsensitive);
466 }
467 
472 bool VuoText_isLessThanNumeric(const VuoText text1, const VuoText text2)
473 {
474  VuoReal real1 = (text1 ? VuoReal_makeFromString(text1) : 0);
475  VuoReal real2 = (text2 ? VuoReal_makeFromString(text2) : 0);
476  return real1 < real2;
477 }
478 
486 bool VuoText_compare(VuoText text1, VuoTextComparison comparison, VuoText text2)
487 {
488  if (! comparison.isCaseSensitive)
489  {
492  }
493 
494  bool match = false;
495  if (comparison.type == VuoTextComparison_Equals)
496  {
497  match = VuoText_areEqual(text1, text2);
498  }
499  else if (comparison.type == VuoTextComparison_Contains)
500  {
501  match = (VuoText_findFirstOccurrence(text1, text2, 1) > 0);
502  }
503  else if (comparison.type == VuoTextComparison_BeginsWith || comparison.type == VuoTextComparison_EndsWith)
504  {
505  size_t aLength = VuoText_length(text1);
506  size_t bLength = VuoText_length(text2);
507 
508  if (bLength == 0)
509  {
510  match = true;
511  }
512  else
513  {
514  int startIndex = (comparison.type == VuoTextComparison_BeginsWith ? 1 : aLength - bLength + 1);
515  VuoText aSub = VuoText_substring(text1, startIndex, bLength);
516  match = VuoText_areEqual(aSub, text2);
517 
518  VuoRetain(aSub);
519  VuoRelease(aSub);
520  }
521  }
522 
523  else if (comparison.type == VuoTextComparison_MatchesWildcard)
524  {
525  bool t1empty = VuoText_isEmpty(text1);
526  bool t2empty = VuoText_isEmpty(text2);
527  if (t2empty)
528  return t1empty == t2empty;
529 
530  locale_t oldLocale = uselocale(NULL);
531  locale_t locale = newlocale(LC_ALL_MASK, "en_US.UTF-8", NULL);
532  uselocale(locale);
533 
534  match = fnmatch(text2, text1, 0) != FNM_NOMATCH;
535 
536  uselocale(oldLocale);
537  freelocale(locale);
538  }
539 
540  else if (comparison.type == VuoTextComparison_MatchesRegEx)
541  {
542  bool t1empty = VuoText_isEmpty(text1);
543  bool t2empty = VuoText_isEmpty(text2);
544  if (t2empty)
545  return t1empty == t2empty;
546 
547  regex_t re;
548  int ret = regcomp(&re, text2, REG_EXTENDED);
549  if (ret)
550  {
551  char errstr[256];
552  regerror(ret, &re, errstr, sizeof(errstr));
553  VUserLog("Error compiling regular expression: %s", errstr);
554  return false;
555  }
556 
557  ret = regexec(&re, text1, 0, NULL, 0);
558  if (ret == 0)
559  match = true;
560  else if (ret == REG_NOMATCH)
561  match = false;
562  else
563  {
564  char errstr[256];
565  regerror(ret, &re, errstr, sizeof(errstr));
566  VUserLog("Error executing regular expression: %s", errstr);
567  }
568  regfree(&re);
569  }
570 
571  if (! comparison.isCaseSensitive)
572  {
573  VuoRetain(text1);
574  VuoRelease(text1);
575  VuoRetain(text2);
576  VuoRelease(text2);
577  }
578 
579  return match;
580 }
581 
590 size_t VuoText_findFirstOccurrence(const VuoText string, const VuoText substring, const size_t startIndex)
591 {
592  if (VuoText_isEmpty(substring))
593  return 1;
594 
595  if (! string)
596  return 0;
597 
598  size_t stringLength = VuoText_length(string);
599  size_t substringLength = VuoText_length(substring);
600  if (stringLength < substringLength)
601  return 0;
602 
603  for (size_t i = startIndex; i <= stringLength - substringLength + 1; ++i)
604  {
605  VuoText currSubstring = VuoText_substring(string, i, substringLength);
606  bool found = VuoText_areEqual(substring, currSubstring);
607  VuoRetain(currSubstring);
608  VuoRelease(currSubstring);
609  if (found)
610  return i;
611  }
612 
613  return 0;
614 }
615 
624 size_t VuoText_findLastOccurrence(const VuoText string, const VuoText substring)
625 {
626  if (VuoText_isEmpty(substring))
627  {
628  if (string)
629  return VuoText_length(string) + 1;
630  else
631  return 1;
632  }
633 
634  if (! string)
635  return 0;
636 
637  size_t foundIndex = 0;
638 
639  size_t stringLength = VuoText_length(string);
640  size_t substringLength = VuoText_length(substring);
641  if (stringLength < substringLength)
642  return 0;
643 
644  for (size_t i = 1; i <= stringLength - substringLength + 1; ++i)
645  {
646  VuoText currSubstring = VuoText_substring(string, i, substringLength);
647  if (VuoText_areEqual(substring, currSubstring))
648  foundIndex = i;
649  VuoRetain(currSubstring);
650  VuoRelease(currSubstring);
651  }
652 
653  return foundIndex;
654 }
655 
663 {
664  if (VuoText_isEmpty(string) || VuoText_isEmpty(substring))
665  return NULL;
666 
667  size_t stringLength = VuoText_length(string);
668  size_t substringLength = VuoText_length(substring);
669  if (stringLength < substringLength)
670  return 0;
671 
673  for (size_t i = 1; i <= stringLength - substringLength + 1; ++i)
674  {
675  VuoText currSubstring = VuoText_substring(string, i, substringLength);
676  VuoLocal(currSubstring);
677  if (VuoText_areEqual(substring, currSubstring))
679  }
680 
681  return found;
682 }
683 
698 VuoText VuoText_substring(const VuoText string, int startIndex, int length)
699 {
700  if (! string)
701  return VuoText_make("");
702 
703  int originalLength = VuoText_length(string);
704  if (startIndex > originalLength)
705  return VuoText_make("");
706 
707  if (startIndex < 1)
708  {
709  length -= 1 - startIndex;
710  startIndex = 1;
711  }
712 
713  if (length < 0)
714  return VuoText_make("");
715 
716  if (startIndex + length - 1 > originalLength)
717  length = originalLength - startIndex + 1;
718 
719  size_t startIndexFromZero = startIndex - 1;
720 
721  CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, string, kCFStringEncodingUTF8);
722  if (!s)
723  return VuoText_make("");
724 
725  CFStringRef ss = CFStringCreateWithSubstring(kCFAllocatorDefault, s, CFRangeMake(startIndexFromZero, length));
726  if (!ss)
727  return VuoText_make("");
728 
729  VuoText substring = VuoText_makeFromCFString(ss);
730 
731  CFRelease(s);
732  CFRelease(ss);
733  return substring;
734 }
735 
740 VuoText VuoText_append(VuoText *texts, size_t textsCount)
741 {
742  CFMutableArrayRef a = CFArrayCreateMutable(kCFAllocatorDefault, textsCount, &kCFTypeArrayCallBacks);
743  for (size_t i = 0; i < textsCount; ++i)
744  {
745  if (texts[i])
746  {
747  CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, texts[i], kCFStringEncodingUTF8);
748  if (!s)
749  continue;
750  CFArrayAppendValue(a, s);
751  CFRelease(s);
752  }
753  }
754 
755  CFStringRef s = CFStringCreateByCombiningStrings(kCFAllocatorDefault, a, CFSTR(""));
756  VuoText compositeString = VuoText_makeFromCFString(s);
757 
758  CFRelease(s);
759  CFRelease(a);
760  return compositeString;
761 }
762 
767 VuoText * VuoText_split(VuoText text, VuoText separator, bool includeEmptyParts, size_t *partsCount)
768 {
769  if (!text || !separator)
770  return NULL;
771 
772  CFMutableArrayRef splitTexts = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
773 
774  CFStringRef textCF = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8);
775  if (!textCF)
776  return NULL;
777  VuoDefer(^{ CFRelease(textCF); });
778 
779  size_t textLength = CFStringGetLength(textCF);
780 
781  CFStringRef separatorCF = CFStringCreateWithCString(kCFAllocatorDefault, separator, kCFStringEncodingUTF8);
782  if (!separatorCF)
783  return NULL;
784  VuoDefer(^{ CFRelease(separatorCF); });
785 
786  size_t separatorLength = CFStringGetLength(separatorCF);
787 
788  if (separatorLength > 0)
789  {
790  size_t startIndex = 1;
791  size_t separatorIndex = 0;
792 
793  while (startIndex <= textLength)
794  {
795  CFRange rangeToSearch = CFRangeMake(startIndex - 1, textLength - (startIndex - 1));
796  CFRange foundRange;
797  Boolean found = CFStringFindWithOptions(textCF, separatorCF, rangeToSearch, 0, &foundRange);
798  separatorIndex = foundRange.location + 1;
799  if (!found)
800  separatorIndex = textLength + 1;
801 
802  if (separatorIndex > startIndex || includeEmptyParts)
803  {
804  CFStringRef partStr = CFStringCreateWithSubstring(kCFAllocatorDefault, textCF, CFRangeMake(startIndex - 1, separatorIndex - startIndex));
805  if (partStr)
806  {
807  CFArrayAppendValue(splitTexts, partStr);
808  CFRelease(partStr);
809  }
810  }
811 
812  startIndex = separatorIndex + separatorLength;
813  }
814 
815  if (includeEmptyParts && textLength > 0 && separatorIndex + separatorLength - 1 == textLength)
816  {
817  CFStringRef emptyPartStr = CFStringCreateWithCString(kCFAllocatorDefault, "", kCFStringEncodingUTF8);
818  if (emptyPartStr)
819  {
820  CFArrayAppendValue(splitTexts, emptyPartStr);
821  CFRelease(emptyPartStr);
822  }
823  }
824  }
825  else
826  {
827  for (size_t i = 1; i <= textLength; ++i)
828  {
829  CFStringRef partStr = CFStringCreateWithSubstring(kCFAllocatorDefault, textCF, CFRangeMake(i - 1, 1));
830  if (partStr)
831  {
832  CFArrayAppendValue(splitTexts, partStr);
833  CFRelease(partStr);
834  }
835  }
836  }
837 
838  *partsCount = CFArrayGetCount(splitTexts);
839  VuoText *splitTextsArr = (VuoText *)malloc(*partsCount * sizeof(VuoText));
840  for (size_t i = 0; i < *partsCount; ++i)
841  {
842  CFStringRef part = CFArrayGetValueAtIndex(splitTexts, i);
843  splitTextsArr[i] = VuoText_makeFromCFString(part);
844  }
845  CFRelease(splitTexts);
846 
847  return splitTextsArr;
848 }
849 
855 VuoText VuoText_replace(VuoText subject, VuoText stringToFind, VuoText replacement)
856 {
857  if (!subject)
858  return NULL;
859  if (!stringToFind)
860  return subject;
861 
862  CFMutableStringRef subjectCF = CFStringCreateMutable(NULL, 0);
863  CFStringAppendCString(subjectCF, subject, kCFStringEncodingUTF8);
864 
865  CFStringRef stringToFindCF = CFStringCreateWithCString(NULL, stringToFind, kCFStringEncodingUTF8);
866  if (!stringToFindCF)
867  {
868  CFRelease(subjectCF);
869  return subject;
870  }
871 
872  CFStringRef replacementCF = nil;
873  if (replacement)
874  {
875  replacementCF = CFStringCreateWithCString(NULL, replacement, kCFStringEncodingUTF8);
876  if (!replacementCF)
877  {
878  CFRelease(stringToFindCF);
879  CFRelease(subjectCF);
880  return subject;
881  }
882  }
883 
884  CFStringFindAndReplace(subjectCF, stringToFindCF, replacementCF, CFRangeMake(0,CFStringGetLength(subjectCF)), kCFCompareNonliteral);
885 
886  VuoText replacedSubject = VuoText_makeFromCFString(subjectCF);
887  if (replacementCF)
888  CFRelease(replacementCF);
889  CFRelease(stringToFindCF);
890  CFRelease(subjectCF);
891 
892  return replacedSubject;
893 }
894 
901 VuoText VuoText_insert(const VuoText string, int startIndex, const VuoText newText)
902 {
903  if (!newText)
904  return string;
905  if (!string)
906  return newText;
907 
908  int len = VuoText_length(string);
909 
910  if(startIndex > len) {
911  const char *append[2] = { string, newText };
912  return VuoText_append(append, 2);
913  } else if(startIndex <= 1) {
914  const char *append[2] = { newText, string };
915  return VuoText_append(append, 2);
916  }
917 
918  VuoText left = VuoText_substring(string, 1, startIndex - 1);
919  VuoText right = VuoText_substring(string, startIndex, (len + 1) - startIndex);
920 
921  VuoLocal(left);
922  VuoLocal(right);
923 
924  const char *append[3] = { left, newText, right };
925  return VuoText_append(append, 3);
926 }
927 
933 VuoText VuoText_removeAt(const VuoText string, int startIndex, int length)
934 {
935  int len = VuoText_length(string);
936 
937  if(startIndex < 1)
938  {
939  length -= (1 - startIndex);
940  startIndex = 1;
941  }
942 
943  // if start is greater than original length or start + len is the whole array
944  if(startIndex > len || length < 1)
945  return VuoText_make(string);
946 
947  VuoText left = VuoText_substring(string, 1, startIndex - 1);
948  VuoText right = VuoText_substring(string, startIndex + length, (len + 1) - startIndex);
949 
950  VuoLocal(left);
951  VuoLocal(right);
952 
953  return VuoText_make(VuoText_format("%s%s", left, right));
954 }
955 
961 char *VuoText_format(const char *format, ...)
962 {
963  va_list args;
964 
965  va_start(args, format);
966  int size = vsnprintf(NULL, 0, format, args);
967  va_end(args);
968 
969  char *formattedString = (char *)malloc(size+1);
970  va_start(args, format);
971  vsnprintf(formattedString, size+1, format, args);
972  va_end(args);
973 
974  return formattedString;
975 }
976 
985 {
986  if (!text)
987  return NULL;
988 
989  size_t len = strlen(text);
990  size_t firstNonSpace;
991  for (firstNonSpace = 0; firstNonSpace < len && isspace(text[firstNonSpace]); ++firstNonSpace);
992 
993  if (firstNonSpace == len)
994  return VuoText_make("");
995 
996  size_t lastNonSpace;
997  for (lastNonSpace = len-1; lastNonSpace > firstNonSpace && isspace(text[lastNonSpace]); --lastNonSpace);
998 
999  return VuoText_makeWithMaxLength(text + firstNonSpace, lastNonSpace - firstNonSpace + 1);
1000 }
1001 
1005 static bool VuoText_isASCII7(VuoText text)
1006 {
1007  size_t len = strlen(text);
1008  for (size_t i = 0; i < len; ++i)
1009  if (((unsigned char *)text)[i] > 127)
1010  return false;
1011 
1012  return true;
1013 }
1014 
1019 {
1020  if (!text)
1021  return NULL;
1022 
1023  // Optimized conversion for plain ASCII7 text.
1024  if (VuoText_isASCII7(text))
1025  {
1026  if (textCase == VuoTextCase_LowercaseAll)
1027  {
1028  size_t len = strlen(text);
1029  char *processedString = malloc(len + 1);
1030  for (size_t i = 0; i < len; ++i)
1031  processedString[i] = tolower(text[i]);
1032  processedString[len] = 0;
1033  VuoRegister(processedString, free);
1034  return processedString;
1035  }
1036  }
1037 
1038  CFMutableStringRef mutable_str = CFStringCreateMutable(NULL, 0);
1039  CFStringAppendCString(mutable_str, text, kCFStringEncodingUTF8);
1040  CFLocaleRef locale = CFLocaleCopyCurrent();
1041 
1042  switch( textCase )
1043  {
1045  CFStringLowercase(mutable_str, locale);
1046  break;
1047 
1049  CFStringUppercase(mutable_str, locale);
1050  break;
1051 
1053  CFStringCapitalize(mutable_str, locale);
1054  break;
1055 
1057  {
1058  // The rest of the string functions lower-case everything by default
1059  CFStringLowercase(mutable_str, locale);
1060 
1061  // the sentence tokenizer does a better job when all characters are capitalized
1062  CFStringRef tmp = CFStringCreateWithSubstring(kCFAllocatorDefault, mutable_str, CFRangeMake(0, CFStringGetLength(mutable_str)));
1063  CFMutableStringRef all_upper = CFStringCreateMutableCopy(NULL, 0, tmp);
1064  CFRelease(tmp);
1065  CFStringUppercase(all_upper, locale);
1066 
1067  CFStringTokenizerRef tokenizer = CFStringTokenizerCreate( kCFAllocatorDefault,
1068  all_upper,
1069  CFRangeMake(0, CFStringGetLength(all_upper)),
1070  kCFStringTokenizerUnitSentence,
1071  locale);
1072 
1073  CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone;
1074 
1075  // https://stackoverflow.com/questions/15515128/capitalize-first-letter-of-every-sentence-in-nsstring
1076  while(kCFStringTokenizerTokenNone != (tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)))
1077  {
1078  CFRange tokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer);
1079 
1080  if (tokenRange.location != kCFNotFound && tokenRange.length > 0)
1081  {
1082  CFRange firstCharRange = CFRangeMake(tokenRange.location, 1);
1083  CFStringRef firstLetter = CFStringCreateWithSubstring(kCFAllocatorDefault, mutable_str, firstCharRange);
1084  CFMutableStringRef upperFirst = CFStringCreateMutableCopy(NULL, 0, firstLetter);
1085  CFRelease(firstLetter);
1086  CFStringCapitalize(upperFirst, locale);
1087  CFStringReplace(mutable_str, firstCharRange, upperFirst);
1088  CFRelease(upperFirst);
1089  }
1090  }
1091 
1092  CFRelease(all_upper);
1093  CFRelease(tokenizer);
1094  }
1095  break;
1096  }
1097 
1098  VuoText processedString = VuoText_makeFromCFString(mutable_str);
1099 
1100  CFRelease(locale);
1101  CFRelease(mutable_str);
1102 
1103  return processedString;
1104 }
1105 
1115 uint32_t* VuoText_getUtf32Values(const VuoText text, size_t* length)
1116 {
1117  CFMutableStringRef cf_str = CFStringCreateMutable(NULL, 0);
1118  CFStringAppendCString(cf_str, text, kCFStringEncodingUTF8);
1119 
1120  size_t str_len = CFStringGetLength(cf_str);
1121 
1122  CFRange range = CFRangeMake(0, str_len);
1123  CFIndex usedBufLen;
1124  *length = (size_t) CFStringGetBytes(cf_str, range, kCFStringEncodingUTF32, '?', false, NULL, str_len, &usedBufLen );
1125 
1126  uint32_t* decimal = (uint32_t*) NULL;
1127 
1128  if(*length > 0)
1129  {
1130  decimal = (uint32_t*) malloc( sizeof(uint32_t) * usedBufLen );
1131  CFStringGetBytes(cf_str, range, kCFStringEncodingUTF32, '?', false, (uint8_t*) decimal, usedBufLen, NULL);
1132  }
1133 
1134  CFRelease(cf_str);
1135 
1136  return decimal;
1137 }