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