Vuo  2.2.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  "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 locale = newlocale(LC_ALL_MASK, "en_US.UTF-8", NULL);
524  locale_t oldLocale = uselocale(locale);
525  if (oldLocale != LC_GLOBAL_LOCALE)
526  freelocale(oldLocale);
527 
528  match = fnmatch(text2, text1, 0) != FNM_NOMATCH;
529  }
530 
531  else if (comparison.type == VuoTextComparison_MatchesRegEx)
532  {
533  bool t1empty = VuoText_isEmpty(text1);
534  bool t2empty = VuoText_isEmpty(text2);
535  if (t2empty)
536  return t1empty == t2empty;
537 
538  regex_t re;
539  int ret = regcomp(&re, text2, REG_EXTENDED);
540  if (ret)
541  {
542  char errstr[256];
543  regerror(ret, &re, errstr, sizeof(errstr));
544  VUserLog("Error compiling regular expression: %s", errstr);
545  return false;
546  }
547 
548  ret = regexec(&re, text1, 0, NULL, 0);
549  if (ret == 0)
550  match = true;
551  else if (ret == REG_NOMATCH)
552  match = false;
553  else
554  {
555  char errstr[256];
556  regerror(ret, &re, errstr, sizeof(errstr));
557  VUserLog("Error executing regular expression: %s", errstr);
558  }
559  regfree(&re);
560  }
561 
562  if (! comparison.isCaseSensitive)
563  {
564  VuoRetain(text1);
565  VuoRelease(text1);
566  VuoRetain(text2);
567  VuoRelease(text2);
568  }
569 
570  return match;
571 }
572 
581 size_t VuoText_findFirstOccurrence(const VuoText string, const VuoText substring, const size_t startIndex)
582 {
583  if (VuoText_isEmpty(substring))
584  return 1;
585 
586  if (! string)
587  return 0;
588 
589  size_t stringLength = VuoText_length(string);
590  size_t substringLength = VuoText_length(substring);
591  if (stringLength < substringLength)
592  return 0;
593 
594  for (size_t i = startIndex; i <= stringLength - substringLength + 1; ++i)
595  {
596  VuoText currSubstring = VuoText_substring(string, i, substringLength);
597  bool found = VuoText_areEqual(substring, currSubstring);
598  VuoRetain(currSubstring);
599  VuoRelease(currSubstring);
600  if (found)
601  return i;
602  }
603 
604  return 0;
605 }
606 
615 size_t VuoText_findLastOccurrence(const VuoText string, const VuoText substring)
616 {
617  if (VuoText_isEmpty(substring))
618  {
619  if (string)
620  return VuoText_length(string) + 1;
621  else
622  return 1;
623  }
624 
625  if (! string)
626  return 0;
627 
628  size_t foundIndex = 0;
629 
630  size_t stringLength = VuoText_length(string);
631  size_t substringLength = VuoText_length(substring);
632  if (stringLength < substringLength)
633  return 0;
634 
635  for (size_t i = 1; i <= stringLength - substringLength + 1; ++i)
636  {
637  VuoText currSubstring = VuoText_substring(string, i, substringLength);
638  if (VuoText_areEqual(substring, currSubstring))
639  foundIndex = i;
640  VuoRetain(currSubstring);
641  VuoRelease(currSubstring);
642  }
643 
644  return foundIndex;
645 }
646 
654 {
655  if (VuoText_isEmpty(string) || VuoText_isEmpty(substring))
656  return NULL;
657 
658  size_t stringLength = VuoText_length(string);
659  size_t substringLength = VuoText_length(substring);
660  if (stringLength < substringLength)
661  return 0;
662 
664  for (size_t i = 1; i <= stringLength - substringLength + 1; ++i)
665  {
666  VuoText currSubstring = VuoText_substring(string, i, substringLength);
667  VuoLocal(currSubstring);
668  if (VuoText_areEqual(substring, currSubstring))
670  }
671 
672  return found;
673 }
674 
689 VuoText VuoText_substring(const VuoText string, int startIndex, int length)
690 {
691  if (! string)
692  return VuoText_make("");
693 
694  int originalLength = VuoText_length(string);
695  if (startIndex > originalLength)
696  return VuoText_make("");
697 
698  if (startIndex < 1)
699  {
700  length -= 1 - startIndex;
701  startIndex = 1;
702  }
703 
704  if (length < 0)
705  return VuoText_make("");
706 
707  if (startIndex + length - 1 > originalLength)
708  length = originalLength - startIndex + 1;
709 
710  size_t startIndexFromZero = startIndex - 1;
711 
712  CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, string, kCFStringEncodingUTF8);
713  if (!s)
714  return VuoText_make("");
715 
716  CFStringRef ss = CFStringCreateWithSubstring(kCFAllocatorDefault, s, CFRangeMake(startIndexFromZero, length));
717  if (!ss)
718  return VuoText_make("");
719 
720  VuoText substring = VuoText_makeFromCFString(ss);
721 
722  CFRelease(s);
723  CFRelease(ss);
724  return substring;
725 }
726 
731 VuoText VuoText_append(VuoText *texts, size_t textsCount)
732 {
733  if (!textsCount)
734  return NULL;
735 
736  CFMutableArrayRef a = CFArrayCreateMutable(kCFAllocatorDefault, textsCount, &kCFTypeArrayCallBacks);
737  for (size_t i = 0; i < textsCount; ++i)
738  {
739  if (texts[i])
740  {
741  CFStringRef s = CFStringCreateWithCString(kCFAllocatorDefault, texts[i], kCFStringEncodingUTF8);
742  if (!s)
743  continue;
744  CFArrayAppendValue(a, s);
745  CFRelease(s);
746  }
747  }
748 
749  CFStringRef s = CFStringCreateByCombiningStrings(kCFAllocatorDefault, a, CFSTR(""));
750  VuoText compositeString = VuoText_makeFromCFString(s);
751 
752  CFRelease(s);
753  CFRelease(a);
754  return compositeString;
755 }
756 
761 VuoText VuoText_appendWithSeparator(VuoList_VuoText texts, VuoText separator, bool includeEmptyParts)
762 {
763  unsigned long textsCount = VuoListGetCount_VuoText(texts);
764  if (!textsCount)
765  return NULL;
766 
767  VuoText *textsArray = (VuoText *)malloc((textsCount * 2 - 1) * sizeof(VuoText));
768  unsigned long outputIndex = 0;
769  bool previousText = false;
770  for (unsigned long inputIndex = 1; inputIndex <= textsCount; ++inputIndex)
771  {
772  VuoText t = VuoListGetValue_VuoText(texts, inputIndex);
773  if (includeEmptyParts)
774  {
775  textsArray[outputIndex++] = t;
776  if (inputIndex < textsCount)
777  textsArray[outputIndex++] = separator;
778  }
779  else if (VuoText_isPopulated(t))
780  {
781  if (previousText && inputIndex <= textsCount)
782  textsArray[outputIndex++] = separator;
783  textsArray[outputIndex++] = t;
784  previousText = true;
785  }
786  }
787 
788  VuoText compositeText = VuoText_append(textsArray, outputIndex);
789 
790  free(textsArray);
791 
792  return compositeText;
793 }
794 
799 VuoText * VuoText_split(VuoText text, VuoText separator, bool includeEmptyParts, size_t *partsCount)
800 {
801  if (!text || !separator)
802  return NULL;
803 
804  CFMutableArrayRef splitTexts = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
805 
806  CFStringRef textCF = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8);
807  if (!textCF)
808  return NULL;
809  VuoDefer(^{ CFRelease(textCF); });
810 
811  size_t textLength = CFStringGetLength(textCF);
812 
813  CFStringRef separatorCF = CFStringCreateWithCString(kCFAllocatorDefault, separator, kCFStringEncodingUTF8);
814  if (!separatorCF)
815  return NULL;
816  VuoDefer(^{ CFRelease(separatorCF); });
817 
818  size_t separatorLength = CFStringGetLength(separatorCF);
819 
820  if (separatorLength > 0)
821  {
822  size_t startIndex = 1;
823  size_t separatorIndex = 0;
824 
825  while (startIndex <= textLength)
826  {
827  CFRange rangeToSearch = CFRangeMake(startIndex - 1, textLength - (startIndex - 1));
828  CFRange foundRange;
829  Boolean found = CFStringFindWithOptions(textCF, separatorCF, rangeToSearch, 0, &foundRange);
830  separatorIndex = foundRange.location + 1;
831  if (!found)
832  separatorIndex = textLength + 1;
833 
834  if (separatorIndex > startIndex || includeEmptyParts)
835  {
836  CFStringRef partStr = CFStringCreateWithSubstring(kCFAllocatorDefault, textCF, CFRangeMake(startIndex - 1, separatorIndex - startIndex));
837  if (partStr)
838  {
839  CFArrayAppendValue(splitTexts, partStr);
840  CFRelease(partStr);
841  }
842  }
843 
844  startIndex = separatorIndex + separatorLength;
845  }
846 
847  if (includeEmptyParts && textLength > 0 && separatorIndex + separatorLength - 1 == textLength)
848  {
849  CFStringRef emptyPartStr = CFStringCreateWithCString(kCFAllocatorDefault, "", kCFStringEncodingUTF8);
850  if (emptyPartStr)
851  {
852  CFArrayAppendValue(splitTexts, emptyPartStr);
853  CFRelease(emptyPartStr);
854  }
855  }
856  }
857  else
858  {
859  for (size_t i = 1; i <= textLength; ++i)
860  {
861  CFStringRef partStr = CFStringCreateWithSubstring(kCFAllocatorDefault, textCF, CFRangeMake(i - 1, 1));
862  if (partStr)
863  {
864  CFArrayAppendValue(splitTexts, partStr);
865  CFRelease(partStr);
866  }
867  }
868  }
869 
870  *partsCount = CFArrayGetCount(splitTexts);
871  VuoText *splitTextsArr = (VuoText *)malloc(*partsCount * sizeof(VuoText));
872  for (size_t i = 0; i < *partsCount; ++i)
873  {
874  CFStringRef part = CFArrayGetValueAtIndex(splitTexts, i);
875  splitTextsArr[i] = VuoText_makeFromCFString(part);
876  }
877  CFRelease(splitTexts);
878 
879  return splitTextsArr;
880 }
881 
887 VuoText VuoText_replace(VuoText subject, VuoText stringToFind, VuoText replacement)
888 {
889  if (!subject)
890  return NULL;
891  if (!stringToFind)
892  return subject;
893 
894  CFMutableStringRef subjectCF = CFStringCreateMutable(NULL, 0);
895  CFStringAppendCString(subjectCF, subject, kCFStringEncodingUTF8);
896 
897  CFStringRef stringToFindCF = CFStringCreateWithCString(NULL, stringToFind, kCFStringEncodingUTF8);
898  if (!stringToFindCF)
899  {
900  CFRelease(subjectCF);
901  return subject;
902  }
903 
904  CFStringRef replacementCF = nil;
905  if (replacement)
906  {
907  replacementCF = CFStringCreateWithCString(NULL, replacement, kCFStringEncodingUTF8);
908  if (!replacementCF)
909  {
910  CFRelease(stringToFindCF);
911  CFRelease(subjectCF);
912  return subject;
913  }
914  }
915 
916  CFStringFindAndReplace(subjectCF, stringToFindCF, replacementCF, CFRangeMake(0,CFStringGetLength(subjectCF)), kCFCompareNonliteral);
917 
918  VuoText replacedSubject = VuoText_makeFromCFString(subjectCF);
919  if (replacementCF)
920  CFRelease(replacementCF);
921  CFRelease(stringToFindCF);
922  CFRelease(subjectCF);
923 
924  return replacedSubject;
925 }
926 
933 VuoText VuoText_insert(const VuoText string, int startIndex, const VuoText newText)
934 {
935  if (!newText)
936  return string;
937  if (!string)
938  return newText;
939 
940  int len = VuoText_length(string);
941 
942  if(startIndex > len) {
943  const char *append[2] = { string, newText };
944  return VuoText_append(append, 2);
945  } else if(startIndex <= 1) {
946  const char *append[2] = { newText, string };
947  return VuoText_append(append, 2);
948  }
949 
950  VuoText left = VuoText_substring(string, 1, startIndex - 1);
951  VuoText right = VuoText_substring(string, startIndex, (len + 1) - startIndex);
952 
953  VuoLocal(left);
954  VuoLocal(right);
955 
956  const char *append[3] = { left, newText, right };
957  return VuoText_append(append, 3);
958 }
959 
965 VuoText VuoText_removeAt(const VuoText string, int startIndex, int length)
966 {
967  int len = VuoText_length(string);
968 
969  if(startIndex < 1)
970  {
971  length -= (1 - startIndex);
972  startIndex = 1;
973  }
974 
975  // if start is greater than original length or start + len is the whole array
976  if(startIndex > len || length < 1)
977  return VuoText_make(string);
978 
979  VuoText left = VuoText_substring(string, 1, startIndex - 1);
980  VuoText right = VuoText_substring(string, startIndex + length, (len + 1) - startIndex);
981 
982  VuoLocal(left);
983  VuoLocal(right);
984 
985  return VuoText_make(VuoText_format("%s%s", left, right));
986 }
987 
993 char *VuoText_format(const char *format, ...)
994 {
995  va_list args;
996 
997  va_start(args, format);
998  int size = vsnprintf(NULL, 0, format, args);
999  va_end(args);
1000 
1001  char *formattedString = (char *)malloc(size+1);
1002  va_start(args, format);
1003  vsnprintf(formattedString, size+1, format, args);
1004  va_end(args);
1005 
1006  return formattedString;
1007 }
1008 
1017 {
1018  if (!text)
1019  return NULL;
1020 
1021  size_t len = strlen(text);
1022  size_t firstNonSpace;
1023  for (firstNonSpace = 0; firstNonSpace < len && isspace(text[firstNonSpace]); ++firstNonSpace);
1024 
1025  if (firstNonSpace == len)
1026  return VuoText_make("");
1027 
1028  size_t lastNonSpace;
1029  for (lastNonSpace = len-1; lastNonSpace > firstNonSpace && isspace(text[lastNonSpace]); --lastNonSpace);
1030 
1031  return VuoText_makeWithMaxLength(text + firstNonSpace, lastNonSpace - firstNonSpace + 1);
1032 }
1033 
1037 static bool VuoText_isASCII7(VuoText text)
1038 {
1039  size_t len = strlen(text);
1040  for (size_t i = 0; i < len; ++i)
1041  if (((unsigned char *)text)[i] > 127)
1042  return false;
1043 
1044  return true;
1045 }
1046 
1051 {
1052  if (!text)
1053  return NULL;
1054 
1055  // Optimized conversion for plain ASCII7 text.
1056  if (VuoText_isASCII7(text))
1057  {
1058  if (textCase == VuoTextCase_LowercaseAll)
1059  {
1060  size_t len = strlen(text);
1061  char *processedString = malloc(len + 1);
1062  for (size_t i = 0; i < len; ++i)
1063  processedString[i] = tolower(text[i]);
1064  processedString[len] = 0;
1065  VuoRegister(processedString, free);
1066  return processedString;
1067  }
1068  }
1069 
1070  CFMutableStringRef mutable_str = CFStringCreateMutable(NULL, 0);
1071  CFStringAppendCString(mutable_str, text, kCFStringEncodingUTF8);
1072  CFLocaleRef locale = CFLocaleCopyCurrent();
1073 
1074  switch( textCase )
1075  {
1077  CFStringLowercase(mutable_str, locale);
1078  break;
1079 
1081  CFStringUppercase(mutable_str, locale);
1082  break;
1083 
1085  CFStringCapitalize(mutable_str, locale);
1086  break;
1087 
1089  {
1090  // The rest of the string functions lower-case everything by default
1091  CFStringLowercase(mutable_str, locale);
1092 
1093  // the sentence tokenizer does a better job when all characters are capitalized
1094  CFStringRef tmp = CFStringCreateWithSubstring(kCFAllocatorDefault, mutable_str, CFRangeMake(0, CFStringGetLength(mutable_str)));
1095  CFMutableStringRef all_upper = CFStringCreateMutableCopy(NULL, 0, tmp);
1096  CFRelease(tmp);
1097  CFStringUppercase(all_upper, locale);
1098 
1099  CFStringTokenizerRef tokenizer = CFStringTokenizerCreate( kCFAllocatorDefault,
1100  all_upper,
1101  CFRangeMake(0, CFStringGetLength(all_upper)),
1102  kCFStringTokenizerUnitSentence,
1103  locale);
1104 
1105  CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone;
1106 
1107  // https://stackoverflow.com/questions/15515128/capitalize-first-letter-of-every-sentence-in-nsstring
1108  while(kCFStringTokenizerTokenNone != (tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)))
1109  {
1110  CFRange tokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer);
1111 
1112  if (tokenRange.location != kCFNotFound && tokenRange.length > 0)
1113  {
1114  CFRange firstCharRange = CFRangeMake(tokenRange.location, 1);
1115  CFStringRef firstLetter = CFStringCreateWithSubstring(kCFAllocatorDefault, mutable_str, firstCharRange);
1116  CFMutableStringRef upperFirst = CFStringCreateMutableCopy(NULL, 0, firstLetter);
1117  CFRelease(firstLetter);
1118  CFStringCapitalize(upperFirst, locale);
1119  CFStringReplace(mutable_str, firstCharRange, upperFirst);
1120  CFRelease(upperFirst);
1121  }
1122  }
1123 
1124  CFRelease(all_upper);
1125  CFRelease(tokenizer);
1126  }
1127  break;
1128  }
1129 
1130  VuoText processedString = VuoText_makeFromCFString(mutable_str);
1131 
1132  CFRelease(locale);
1133  CFRelease(mutable_str);
1134 
1135  return processedString;
1136 }
1137 
1147 uint32_t* VuoText_getUtf32Values(const VuoText text, size_t* length)
1148 {
1149  CFMutableStringRef cf_str = CFStringCreateMutable(NULL, 0);
1150  CFStringAppendCString(cf_str, text, kCFStringEncodingUTF8);
1151 
1152  size_t str_len = CFStringGetLength(cf_str);
1153 
1154  CFRange range = CFRangeMake(0, str_len);
1155  CFIndex usedBufLen;
1156  *length = (size_t) CFStringGetBytes(cf_str, range, kCFStringEncodingUTF32, '?', false, NULL, str_len, &usedBufLen );
1157 
1158  uint32_t* decimal = (uint32_t*) NULL;
1159 
1160  if(*length > 0)
1161  {
1162  decimal = (uint32_t*) malloc( sizeof(uint32_t) * usedBufLen );
1163  CFStringGetBytes(cf_str, range, kCFStringEncodingUTF32, '?', false, (uint8_t*) decimal, usedBufLen, NULL);
1164  }
1165 
1166  CFRelease(cf_str);
1167 
1168  return decimal;
1169 }