Vuo  2.3.1
VuoText.c
Go to the documentation of this file.
1 
10 #include "VuoMacOSSDKWorkaround.h"
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 
67 VuoText VuoText_truncateWithEllipsis(const VuoText subject, int maxLength, VuoTextTruncation where)
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 
102 char * 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 
129 VuoText 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 
154 VuoText 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 
222 static 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 
242 static 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 
271 VuoText 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 
289 VuoText 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 
341 size_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 
358 size_t VuoText_byteCount(const VuoText text)
359 {
360  if (! text)
361  return 0;
362 
363  return strlen(text);
364 }
365 
369 bool VuoText_isEmpty(const VuoText text)
370 {
371  return !text || text[0] == 0;
372 }
373 
377 bool VuoText_isPopulated(const VuoText text)
378 {
379  return !VuoText_isEmpty(text);
380 }
381 
388 bool 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 
417 static 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 
447 bool VuoText_isLessThan(const VuoText text1, const VuoText text2)
448 {
449  return isLessThan(text1, text2, 0);
450 }
451 
457 bool VuoText_isLessThanCaseInsensitive(const VuoText text1, const VuoText text2)
458 {
459  return isLessThan(text1, text2, kCFCompareCaseInsensitive);
460 }
461 
466 bool 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 
480 bool 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 
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  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 
762 VuoText 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 
800 VuoText * 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 
888 VuoText 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 
934 VuoText 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 
966 VuoText 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 
994 char *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 
1038 static 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 
1148 uint32_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 }